From 7622a5acaa041a8ee514183d10b5bd33d7f9f2c3 Mon Sep 17 00:00:00 2001 From: Bryce Harrington Date: Mon, 27 Feb 2017 10:52:35 -0800 Subject: [PATCH 0001/1642] configure.ac: Bump to 2.0.90 for open development --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index a36a5166f..9df85d206 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,11 @@ m4_define([weston_major_version], [2]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [0]) +m4_define([weston_micro_version], [90]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [2]) m4_define([libweston_minor_version], [0]) -m4_define([libweston_patch_version], [0]) +m4_define([libweston_patch_version], [90]) AC_PREREQ([2.64]) AC_INIT([weston], From efa504f4eab805f7b9f07944afcac60d50fe7cac Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 19 Dec 2016 16:48:20 +0000 Subject: [PATCH 0002/1642] compositor-drm: Ignore non-KMS devices Given that we can have render-only devices, or vgem in a class of its own, ignore any non-KMS devices in compositor-drm's device selection. For x86 platforms, this is mostly a non-issue since we look at the udev boot_vga issue, but other architectures which lack this, and have multiple KMS devices present, will hit this. Signed-off-by: Daniel Stone Reviewed-by: Emil Velikov Reported-by: Thierry Reding Reported-by: Daniel Vetter --- libweston/compositor-drm.c | 145 ++++++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 42 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 1d38f0519..a2a07cfef 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1506,36 +1506,15 @@ on_drm_input(int fd, uint32_t mask, void *data) } static int -init_drm(struct drm_backend *b, struct udev_device *device) +init_kms_caps(struct drm_backend *b) { - const char *filename, *sysnum; uint64_t cap; - int fd, ret; + int ret; clockid_t clk_id; - sysnum = udev_device_get_sysnum(device); - if (sysnum) - b->drm.id = atoi(sysnum); - if (!sysnum || b->drm.id < 0) { - weston_log("cannot get device sysnum\n"); - return -1; - } - - filename = udev_device_get_devnode(device); - fd = weston_launcher_open(b->compositor->launcher, filename, O_RDWR); - if (fd < 0) { - /* Probably permissions error */ - weston_log("couldn't open %s, skipping\n", - udev_device_get_devnode(device)); - return -1; - } - - weston_log("using %s\n", filename); - - b->drm.fd = fd; - b->drm.filename = strdup(filename); + weston_log("using %s\n", b->drm.filename); - ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); + ret = drmGetCap(b->drm.fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); if (ret == 0 && cap == 1) clk_id = CLOCK_MONOTONIC; else @@ -1547,13 +1526,13 @@ init_drm(struct drm_backend *b, struct udev_device *device) return -1; } - ret = drmGetCap(fd, DRM_CAP_CURSOR_WIDTH, &cap); + ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_WIDTH, &cap); if (ret == 0) b->cursor_width = cap; else b->cursor_width = 64; - ret = drmGetCap(fd, DRM_CAP_CURSOR_HEIGHT, &cap); + ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_HEIGHT, &cap); if (ret == 0) b->cursor_height = cap; else @@ -2940,12 +2919,68 @@ session_notify(struct wl_listener *listener, void *data) }; } +/** + * Determines whether or not a device is capable of modesetting. If successful, + * sets b->drm.fd and b->drm.filename to the opened device. + */ +static bool +drm_device_is_kms(struct drm_backend *b, struct udev_device *device) +{ + const char *filename = udev_device_get_devnode(device); + const char *sysnum = udev_device_get_sysnum(device); + drmModeRes *res; + int id, fd; + + if (!filename) + return false; + + fd = weston_launcher_open(b->compositor->launcher, filename, O_RDWR); + if (fd < 0) + return false; + + res = drmModeGetResources(fd); + if (!res) + goto out_fd; + + if (res->count_crtcs <= 0 || res->count_connectors <= 0 || + res->count_encoders <= 0) + goto out_res; + + if (sysnum) + id = atoi(sysnum); + if (!sysnum || id < 0) { + weston_log("couldn't get sysnum for device %s\n", filename); + goto out_res; + } + + /* We can be called successfully on multiple devices; if we have, + * clean up old entries. */ + if (b->drm.fd >= 0) + weston_launcher_close(b->compositor->launcher, b->drm.fd); + free(b->drm.filename); + + b->drm.fd = fd; + b->drm.id = id; + b->drm.filename = strdup(filename); + + return true; + +out_res: + drmModeFreeResources(res); +out_fd: + weston_launcher_close(b->compositor->launcher, fd); + return false; +} + /* * Find primary GPU * Some systems may have multiple DRM devices attached to a single seat. This * function loops over all devices and tries to find a PCI device with the * boot_vga sysfs attribute set to 1. * If no such device is found, the first DRM device reported by udev is used. + * Devices are also vetted to make sure they are are capable of modesetting, + * rather than pure render nodes (GPU with no display), or pure + * memory-allocation devices (VGEM). */ static struct udev_device* find_primary_gpu(struct drm_backend *b, const char *seat) @@ -2962,6 +2997,8 @@ find_primary_gpu(struct drm_backend *b, const char *seat) udev_enumerate_scan_devices(e); drm_device = NULL; udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { + bool is_boot_vga = false; + path = udev_list_entry_get_name(entry); device = udev_device_new_from_syspath(b->udev, path); if (!device) @@ -2978,20 +3015,46 @@ find_primary_gpu(struct drm_backend *b, const char *seat) "pci", NULL); if (pci) { id = udev_device_get_sysattr_value(pci, "boot_vga"); - if (id && !strcmp(id, "1")) { - if (drm_device) - udev_device_unref(drm_device); - drm_device = device; - break; - } + if (id && !strcmp(id, "1")) + is_boot_vga = true; } - if (!drm_device) - drm_device = device; - else + /* If we already have a modesetting-capable device, and this + * device isn't our boot-VGA device, we aren't going to use + * it. */ + if (!is_boot_vga && drm_device) { + udev_device_unref(device); + continue; + } + + /* Make sure this device is actually capable of modesetting; + * if this call succeeds, b->drm.{fd,filename} will be set, + * and any old values freed. */ + if (!drm_device_is_kms(b, device)) { udev_device_unref(device); + continue; + } + + /* There can only be one boot_vga device, and we try to use it + * at all costs. */ + if (is_boot_vga) { + if (drm_device) + udev_device_unref(drm_device); + drm_device = device; + break; + } + + /* Per the (!is_boot_vga && drm_device) test above, we only + * trump existing saved devices with boot-VGA devices, so if + * we end up here, this must be the first device we've seen. */ + assert(!drm_device); + drm_device = device; } + /* If we're returning a device to use, we must have an open FD for + * it. */ + assert(!!drm_device == (b->drm.fd >= 0)); + udev_enumerate_unref(e); return drm_device; } @@ -3194,7 +3257,6 @@ drm_backend_create(struct weston_compositor *compositor, struct drm_backend *b; struct udev_device *drm_device; struct wl_event_loop *loop; - const char *path; const char *seat_id = default_seat; int ret; @@ -3204,6 +3266,8 @@ drm_backend_create(struct weston_compositor *compositor, if (b == NULL) return NULL; + b->drm.fd = -1; + /* * KMS support for hardware planes cannot properly synchronize * without nuclear page flip. Without nuclear/atomic, hw plane @@ -3247,9 +3311,8 @@ drm_backend_create(struct weston_compositor *compositor, weston_log("no drm device found\n"); goto err_udev; } - path = udev_device_get_syspath(drm_device); - if (init_drm(b, drm_device) < 0) { + if (init_kms_caps(b) < 0) { weston_log("failed to initialize kms\n"); goto err_udev_dev; } @@ -3284,7 +3347,7 @@ drm_backend_create(struct weston_compositor *compositor, b->connector = config->connector; if (create_outputs(b, drm_device) < 0) { - weston_log("failed to create output for %s\n", path); + weston_log("failed to create output for %s\n", b->drm.filename); goto err_udev_input; } @@ -3293,8 +3356,6 @@ drm_backend_create(struct weston_compositor *compositor, if (!b->cursors_are_broken) compositor->capabilities |= WESTON_CAP_CURSOR_PLANE; - path = NULL; - loop = wl_display_get_event_loop(compositor->wl_display); b->drm_source = wl_event_loop_add_fd(loop, b->drm.fd, From 75487c2560dfca033d8d23649914efb4199356dd Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 16 Jan 2017 14:33:38 +0000 Subject: [PATCH 0003/1642] compositor-drm: Try to preserve existing output routing Previously in picking CRTC -> encoder -> connecting routing, we went for the first triplet we found which claimed to work. Preserving the existing routing means that startup will be faster: on a multi-head system, changing the routing implies disabling both CRTCs, then re-enabling them with a new configuration, which may involve retraining links etc. Furthermore, the existing routing may be set for a reason; each CRTC/encoder is not necessarily as capable as the other, so the routing may be configured to stay within such device limits. Try where possible to respect the routing we pick up, rather than blithely configuring our own. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index a2a07cfef..f94b9a448 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1821,16 +1821,20 @@ find_crtc_for_connector(struct drm_backend *b, drmModeRes *resources, drmModeConnector *connector) { drmModeEncoder *encoder; - uint32_t possible_crtcs; int i, j; + int ret = -1; for (j = 0; j < connector->count_encoders; j++) { + uint32_t possible_crtcs, encoder_id, crtc_id; + encoder = drmModeGetEncoder(b->drm.fd, connector->encoders[j]); if (encoder == NULL) { weston_log("Failed to get encoder.\n"); - return -1; + continue; } + encoder_id = encoder->encoder_id; possible_crtcs = encoder->possible_crtcs; + crtc_id = encoder->crtc_id; drmModeFreeEncoder(encoder); for (i = 0; i < resources->count_crtcs; i++) { @@ -1840,11 +1844,21 @@ find_crtc_for_connector(struct drm_backend *b, if (drm_output_find_by_crtc(b, resources->crtcs[i])) continue; - return i; + /* Try to preserve the existing + * CRTC -> encoder -> connector routing; it makes + * initialisation faster, and also since we have a + * very dumb picking algorithm, may preserve a better + * choice. */ + if (!connector->encoder_id || + (encoder_id == connector->encoder_id && + crtc_id == resources->crtcs[i])) + return i; + + ret = i; } } - return -1; + return ret; } /* Init output state that depends on gl or gbm */ From 445b41b9d5ab337169664e94e091e7047dcc90cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Krezovi=C4=87?= Date: Sun, 9 Oct 2016 23:48:16 +0200 Subject: [PATCH 0004/1642] compositor-drm: Construct mode list in create_output_for_connector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And properly deconstruct it in drm_output_destroy. Might be useful for finding out which modes are supported before even setting them, in case we want to extend the modesetting API. Signed-off-by: Armin Krezović Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 63 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index f94b9a448..b70339cc0 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2347,29 +2347,19 @@ drm_output_set_mode(struct weston_output *base, struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); - struct drm_mode *drm_mode, *next, *current; + struct drm_mode *current; drmModeModeInfo crtc_mode; - int i; output->base.make = "unknown"; output->base.model = "unknown"; output->base.serial_number = "unknown"; - wl_list_init(&output->base.mode_list); - - output->original_crtc = drmModeGetCrtc(b->drm.fd, output->crtc_id); if (connector_get_current_mode(output->connector, b->drm.fd, &crtc_mode) < 0) - goto err_free; - - for (i = 0; i < output->connector->count_modes; i++) { - drm_mode = drm_output_add_mode(output, &output->connector->modes[i]); - if (!drm_mode) - goto err_free; - } + return -1; current = drm_output_choose_initial_mode(b, output, mode, modeline, &crtc_mode); if (!current) - goto err_free; + return -1; output->base.current_mode = ¤t->base; output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT; @@ -2382,18 +2372,6 @@ drm_output_set_mode(struct weston_output *base, output->base.mm_height = output->connector->mmHeight; return 0; - -err_free: - drmModeFreeCrtc(output->original_crtc); - output->original_crtc = NULL; - - wl_list_for_each_safe(drm_mode, next, &output->base.mode_list, - base.link) { - wl_list_remove(&drm_mode->base.link); - free(drm_mode); - } - - return -1; } static void @@ -2515,6 +2493,7 @@ drm_output_destroy(struct weston_output *base) { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); + struct drm_mode *drm_mode, *next; drmModeCrtcPtr origcrtc = output->original_crtc; if (output->page_flip_pending) { @@ -2526,6 +2505,12 @@ drm_output_destroy(struct weston_output *base) if (output->base.enabled) drm_output_deinit(&output->base); + wl_list_for_each_safe(drm_mode, next, &output->base.mode_list, + base.link) { + wl_list_remove(&drm_mode->base.link); + free(drm_mode); + } + if (origcrtc) { /* Restore original CRTC state */ drmModeSetCrtc(b->drm.fd, origcrtc->crtc_id, origcrtc->buffer_id, @@ -2587,17 +2572,18 @@ create_output_for_connector(struct drm_backend *b, struct udev_device *drm_device) { struct drm_output *output; + struct drm_mode *drm_mode; int i; i = find_crtc_for_connector(b, resources, connector); if (i < 0) { weston_log("No usable crtc/encoder pair for connector.\n"); - return -1; + goto err; } output = zalloc(sizeof *output); if (output == NULL) - return -1; + goto err; output->connector = connector; output->crtc_id = resources->crtcs[i]; @@ -2607,6 +2593,8 @@ create_output_for_connector(struct drm_backend *b, output->backlight = backlight_init(drm_device, connector->connector_type); + output->original_crtc = drmModeGetCrtc(b->drm.fd, output->crtc_id); + output->base.enable = drm_output_enable; output->base.destroy = drm_output_destroy; output->base.disable = drm_output_disable; @@ -2614,12 +2602,27 @@ create_output_for_connector(struct drm_backend *b, output->destroy_pending = 0; output->disable_pending = 0; - output->original_crtc = NULL; weston_output_init(&output->base, b->compositor); + + wl_list_init(&output->base.mode_list); + + for (i = 0; i < output->connector->count_modes; i++) { + drm_mode = drm_output_add_mode(output, &output->connector->modes[i]); + if (!drm_mode) { + drm_output_destroy(&output->base); + return -1; + } + } + weston_compositor_add_pending_output(&output->base, b->compositor); return 0; + +err: + drmModeFreeConnector(connector); + + return -1; } static void @@ -2719,10 +2722,8 @@ create_outputs(struct drm_backend *b, struct udev_device *drm_device) (b->connector == 0 || connector->connector_id == b->connector)) { if (create_output_for_connector(b, resources, - connector, drm_device) < 0) { - drmModeFreeConnector(connector); + connector, drm_device) < 0) continue; - } } else { drmModeFreeConnector(connector); } From 906488790ba8583ab5eb471c622d83de60fda185 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 21 Oct 2016 18:08:37 +0100 Subject: [PATCH 0005/1642] compositor-drm: Make scanout view preparation more stringent Don't import buffers which span multiple outputs, short-cut any attempt to import SHM buffers, and ignore buffers with a global alpha set. I'm not convinced all of these conditions entirely make sense, but this at least makes them equally nonsensical. Differential Revision: https://phabricator.freedesktop.org/D1414 Signed-off-by: Daniel Stone Reviewed-by: Derek Foreman --- libweston/compositor-drm.c | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index b70339cc0..7f78699c8 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -499,6 +499,13 @@ drm_output_release_fb(struct drm_output *output, struct drm_fb *fb) } } +static int +drm_view_transform_supported(struct weston_view *ev) +{ + return !ev->transform.enabled || + (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); +} + static uint32_t drm_output_check_scanout_format(struct drm_output *output, struct weston_surface *es, struct gbm_bo *bo) @@ -539,27 +546,40 @@ drm_output_prepare_scanout_view(struct drm_output *output, struct gbm_bo *bo; uint32_t format; + /* Don't import buffers which span multiple outputs. */ + if (ev->output_mask != (1u << output->base.id)) + return NULL; + /* We use GBM to import buffers. */ if (b->gbm == NULL) return NULL; if (buffer == NULL) return NULL; + if (wl_shm_buffer_get(buffer->resource)) + return NULL; /* Make sure our view is exactly compatible with the output. */ if (ev->geometry.x != output->base.x || ev->geometry.y != output->base.y) return NULL; + if (buffer->width != output->base.current_mode->width || + buffer->height != output->base.current_mode->height) + return NULL; + if (ev->transform.enabled) return NULL; if (ev->geometry.scissor_enabled) return NULL; - - if (buffer->width != output->base.current_mode->width || - buffer->height != output->base.current_mode->height) - return NULL; if (viewport->buffer.transform != output->base.transform) return NULL; + if (viewport->buffer.scale != output->base.current_scale) + return NULL; + if (!drm_view_transform_supported(ev)) + return NULL; + + if (ev->alpha != 1.0f) + return NULL; bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, buffer->resource, GBM_BO_USE_SCANOUT); @@ -973,13 +993,6 @@ drm_output_check_sprite_format(struct drm_sprite *s, return 0; } -static int -drm_view_transform_supported(struct weston_view *ev) -{ - return !ev->transform.enabled || - (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); -} - static struct weston_plane * drm_output_prepare_overlay_view(struct drm_output *output, struct weston_view *ev) From 8f7201ec5e75cecf746e02d07bf8c91c4c5c8071 Mon Sep 17 00:00:00 2001 From: Emil Velikov Date: Fri, 10 Feb 2017 20:14:22 +0000 Subject: [PATCH 0006/1642] libweston/launcher: annotate iface(s) as constant data Already considered and handled as such. Signed-off-by: Emil Velikov Reviewed-by: Daniel Stone --- libweston/launcher-direct.c | 2 +- libweston/launcher-impl.h | 8 ++++---- libweston/launcher-logind.c | 2 +- libweston/launcher-util.c | 6 +++--- libweston/launcher-weston-launch.c | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c index 4fc1c3c24..4e878a59e 100644 --- a/libweston/launcher-direct.c +++ b/libweston/launcher-direct.c @@ -319,7 +319,7 @@ launcher_direct_get_vt(struct weston_launcher *base) return minor(s.st_rdev); } -struct launcher_interface launcher_direct_iface = { +const struct launcher_interface launcher_direct_iface = { launcher_direct_connect, launcher_direct_destroy, launcher_direct_open, diff --git a/libweston/launcher-impl.h b/libweston/launcher-impl.h index ba2cd8e4e..404383ad2 100644 --- a/libweston/launcher-impl.h +++ b/libweston/launcher-impl.h @@ -42,9 +42,9 @@ struct launcher_interface { }; struct weston_launcher { - struct launcher_interface *iface; + const struct launcher_interface *iface; }; -extern struct launcher_interface launcher_logind_iface; -extern struct launcher_interface launcher_weston_launch_iface; -extern struct launcher_interface launcher_direct_iface; +extern const struct launcher_interface launcher_logind_iface; +extern const struct launcher_interface launcher_weston_launch_iface; +extern const struct launcher_interface launcher_direct_iface; diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c index 8b984a6ee..c9cd75b7f 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -837,7 +837,7 @@ launcher_logind_get_vt(struct weston_launcher *launcher) return wl->vtnr; } -struct launcher_interface launcher_logind_iface = { +const struct launcher_interface launcher_logind_iface = { launcher_logind_connect, launcher_logind_destroy, launcher_logind_open, diff --git a/libweston/launcher-util.c b/libweston/launcher-util.c index 2b828be8e..fa3ed13b1 100644 --- a/libweston/launcher-util.c +++ b/libweston/launcher-util.c @@ -35,7 +35,7 @@ #include #include -static struct launcher_interface *ifaces[] = { +static const struct launcher_interface *ifaces[] = { #ifdef HAVE_SYSTEMD_LOGIN &launcher_logind_iface, #endif @@ -48,10 +48,10 @@ WL_EXPORT struct weston_launcher * weston_launcher_connect(struct weston_compositor *compositor, int tty, const char *seat_id, bool sync_drm) { - struct launcher_interface **it; + const struct launcher_interface **it; for (it = ifaces; *it != NULL; it++) { - struct launcher_interface *iface = *it; + const struct launcher_interface *iface = *it; struct weston_launcher *launcher; if (iface->connect(&launcher, compositor, tty, seat_id, sync_drm) == 0) diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c index 072d626e5..0383550bf 100644 --- a/libweston/launcher-weston-launch.c +++ b/libweston/launcher-weston-launch.c @@ -290,7 +290,7 @@ launcher_weston_launch_get_vt(struct weston_launcher *base) return minor(s.st_rdev); } -struct launcher_interface launcher_weston_launch_iface = { +const struct launcher_interface launcher_weston_launch_iface = { launcher_weston_launch_connect, launcher_weston_launch_destroy, launcher_weston_launch_open, From 4d6eb17a36c21cad6761d81cb4814f6de4999337 Mon Sep 17 00:00:00 2001 From: Emil Velikov Date: Fri, 10 Feb 2017 20:14:23 +0000 Subject: [PATCH 0007/1642] libweston/launcher: use C99 initializers for the iface(s) Makes the code easier to read and browse through. Signed-off-by: Emil Velikov Reviewed-by: Daniel Stone --- libweston/launcher-direct.c | 14 +++++++------- libweston/launcher-logind.c | 14 +++++++------- libweston/launcher-weston-launch.c | 14 +++++++------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c index 4e878a59e..4195cf651 100644 --- a/libweston/launcher-direct.c +++ b/libweston/launcher-direct.c @@ -320,11 +320,11 @@ launcher_direct_get_vt(struct weston_launcher *base) } const struct launcher_interface launcher_direct_iface = { - launcher_direct_connect, - launcher_direct_destroy, - launcher_direct_open, - launcher_direct_close, - launcher_direct_activate_vt, - launcher_direct_restore, - launcher_direct_get_vt, + .connect = launcher_direct_connect, + .destroy = launcher_direct_destroy, + .open = launcher_direct_open, + .close = launcher_direct_close, + .activate_vt = launcher_direct_activate_vt, + .restore = launcher_direct_restore, + .get_vt = launcher_direct_get_vt, }; diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c index c9cd75b7f..8de1ed110 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -838,11 +838,11 @@ launcher_logind_get_vt(struct weston_launcher *launcher) } const struct launcher_interface launcher_logind_iface = { - launcher_logind_connect, - launcher_logind_destroy, - launcher_logind_open, - launcher_logind_close, - launcher_logind_activate_vt, - launcher_logind_restore, - launcher_logind_get_vt, + .connect = launcher_logind_connect, + .destroy = launcher_logind_destroy, + .open = launcher_logind_open, + .close = launcher_logind_close, + .activate_vt = launcher_logind_activate_vt, + .restore = launcher_logind_restore, + .get_vt = launcher_logind_get_vt, }; diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c index 0383550bf..930f4e0c0 100644 --- a/libweston/launcher-weston-launch.c +++ b/libweston/launcher-weston-launch.c @@ -291,11 +291,11 @@ launcher_weston_launch_get_vt(struct weston_launcher *base) } const struct launcher_interface launcher_weston_launch_iface = { - launcher_weston_launch_connect, - launcher_weston_launch_destroy, - launcher_weston_launch_open, - launcher_weston_launch_close, - launcher_weston_launch_activate_vt, - launcher_weston_launch_restore, - launcher_weston_launch_get_vt, + .connect = launcher_weston_launch_connect, + .destroy = launcher_weston_launch_destroy, + .open = launcher_weston_launch_open, + .close = launcher_weston_launch_close, + .activate_vt = launcher_weston_launch_activate_vt, + .restore = launcher_weston_launch_restore, + .get_vt = launcher_weston_launch_get_vt, }; From 78dc6a9a024169050c5135bb7289cfe080dcb1f3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 13 Feb 2017 16:35:00 +0200 Subject: [PATCH 0008/1642] clients/weston-info: print unknown formats better Don't just dump the raw 32-bit values, try to interpret it as a DRM fourcc too. This prints properly the formats YUYV, NV12 and YU12 supported by Weston. Signed-off-by: Pekka Paalanen Reviewed-by: Eric Engestrom --- clients/weston-info.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/clients/weston-info.c b/clients/weston-info.c index 712346a01..c07134d16 100644 --- a/clients/weston-info.c +++ b/clients/weston-info.c @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include @@ -240,9 +242,33 @@ print_output_info(void *data) } } +static char +bits2graph(uint32_t value, unsigned bitoffset) +{ + int c = (value >> bitoffset) & 0xff; + + if (isgraph(c) || isspace(c)) + return c; + + return '?'; +} + +static void +fourcc2str(uint32_t format, char *str, int len) +{ + int i; + + assert(len >= 5); + + for (i = 0; i < 4; i++) + str[i] = bits2graph(format, i * 8); + str[i] = '\0'; +} + static void print_shm_info(void *data) { + char str[5]; struct shm_info *shm = data; struct shm_format *format; @@ -262,7 +288,8 @@ print_shm_info(void *data) printf(" RGB565"); break; default: - printf(" unknown(%08x)", format->format); + fourcc2str(format->format, str, sizeof(str)); + printf(" '%s'(0x%08x)", str, format->format); break; } From 8a888a5fe52b9573f6caca4aec4a1ea0c336b0b2 Mon Sep 17 00:00:00 2001 From: Eero Tamminen Date: Mon, 20 Feb 2017 15:14:49 +0200 Subject: [PATCH 0009/1642] clients/simple-egl: add delay option This emulates extra drawing work by usleep(). This is an enhancement to reproduce the problem in the bug report. Bug: https://bugs.freedesktop.org/show_bug.cgi?id=98833 [Pekka: reordered the help text] Signed-off-by: Pekka Paalanen Reviewed-by: Eric Engestrom --- clients/simple-egl.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/clients/simple-egl.c b/clients/simple-egl.c index 9b6fa1f2f..c4f72a448 100644 --- a/clients/simple-egl.c +++ b/clients/simple-egl.c @@ -100,7 +100,7 @@ struct window { struct ivi_surface *ivi_surface; EGLSurface egl_surface; struct wl_callback *callback; - int fullscreen, opaque, buffer_size, frame_sync; + int fullscreen, opaque, buffer_size, frame_sync, delay; bool wait_for_configure; }; @@ -548,6 +548,8 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) glDisableVertexAttribArray(window->gl.pos); glDisableVertexAttribArray(window->gl.col); + usleep(window->delay); + if (window->opaque || window->fullscreen) { region = wl_compositor_create_region(window->display->compositor); wl_region_add(region, 0, 0, @@ -846,6 +848,7 @@ static void usage(int error_code) { fprintf(stderr, "Usage: simple-egl [OPTIONS]\n\n" + " -d \tBuffer swap delay in microseconds\n" " -f\tRun in fullscreen mode\n" " -o\tCreate an opaque surface\n" " -s\tUse a 16 bpp EGL config\n" @@ -870,9 +873,12 @@ main(int argc, char **argv) window.window_size = window.geometry; window.buffer_size = 32; window.frame_sync = 1; + window.delay = 0; for (i = 1; i < argc; i++) { - if (strcmp("-f", argv[i]) == 0) + if (strcmp("-d", argv[i]) == 0 && i+1 < argc) + window.delay = atoi(argv[++i]); + else if (strcmp("-f", argv[i]) == 0) window.fullscreen = 1; else if (strcmp("-o", argv[i]) == 0) window.opaque = 1; From 11ae2a3036bc95ff26b12a6d60f12514d8145143 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Tue, 7 Mar 2017 13:27:54 +0000 Subject: [PATCH 0010/1642] compositor-drm: pageflip timeout implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Weston will not repaint until previous update has been acked by a pageflip event coming from the drm driver. However, some buggy drivers won’t return those events or will stop sending them at some point and Weston output repaints will completely freeze. To ease developers’ task in testing their drivers, this patch makes compositor-drm use a timer to detect cases where those pageflip events stop coming. This timeout implementation is software only and includes basic features usually found in a watchdog. We simply exit Weston gracefully with a log message and an exit code when the timout is reached. The timeout value can be set via weston.ini by adding a pageflip-timeout= entry under [core] section. Setting it to 0 disables the timeout feature. v2: - Made sure we would get both the pageflip and the vblank events before stopping the timer. - Reordered the error and success cases in drm_output_pageflip_timer_create() to be more in line with the rest of the code. v3: - Reordered (de)arming of the timer with the code around it to avoid it being rearmed before the current dearming. - Return the proper value for the dispatcher in the pageflip_timeout callback. - Also display the output name in case the timer fires. v4: - Reordered a forgotten timer rearming after its drmModePageFlip(). Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=83884 Signed-off-by: Frederic Plourde Signed-off-by: Emmanuel Gil Peyrot Reviewed-by: Daniel Stone Reviewed-by: Pekka Paalanen --- compositor/main.c | 2 ++ libweston/compositor-drm.c | 65 ++++++++++++++++++++++++++++++++++++++ libweston/compositor-drm.h | 7 ++++ man/weston.ini.man | 5 +++ 4 files changed, 79 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index 72c3cd10a..e870dd4a4 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1235,6 +1235,8 @@ load_drm_backend(struct weston_compositor *c, weston_config_section_get_string(section, "gbm-format", &config.gbm_format, NULL); + weston_config_section_get_uint(section, "pageflip-timeout", + &config.pageflip_timeout, 0); config.base.struct_version = WESTON_DRM_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_drm_backend_config); diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 7f78699c8..006230766 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -119,6 +119,7 @@ struct drm_backend { int32_t cursor_height; uint32_t connector; + uint32_t pageflip_timeout; }; struct drm_mode { @@ -182,6 +183,8 @@ struct drm_output { struct vaapi_recorder *recorder; struct wl_listener recorder_frame_listener; + + struct wl_event_source *pageflip_timer; }; /* @@ -225,6 +228,45 @@ to_drm_backend(struct weston_compositor *base) return container_of(base->backend, struct drm_backend, base); } +static int +pageflip_timeout(void *data) { + /* + * Our timer just went off, that means we're not receiving drm + * page flip events anymore for that output. Let's gracefully exit + * weston with a return value so devs can debug what's going on. + */ + struct drm_output *output = data; + struct weston_compositor *compositor = output->base.compositor; + + weston_log("Pageflip timeout reached on output %s, your " + "driver is probably buggy! Exiting.\n", + output->base.name); + weston_compositor_exit_with_code(compositor, EXIT_FAILURE); + + return 0; +} + +/* Creates the pageflip timer. Note that it isn't armed by default */ +static int +drm_output_pageflip_timer_create(struct drm_output *output) +{ + struct wl_event_loop *loop = NULL; + struct weston_compositor *ec = output->base.compositor; + + loop = wl_display_get_event_loop(ec->wl_display); + assert(loop); + output->pageflip_timer = wl_event_loop_add_timer(loop, + pageflip_timeout, + output); + + if (output->pageflip_timer == NULL) { + weston_log("creating drm pageflip timer failed: %m\n"); + return -1; + } + + return 0; +} + static void drm_output_set_cursor(struct drm_output *output); @@ -758,6 +800,10 @@ drm_output_repaint(struct weston_output *output_base, output->page_flip_pending = 1; + if (output->pageflip_timer) + wl_event_source_timer_update(output->pageflip_timer, + backend->pageflip_timeout); + drm_output_set_cursor(output); /* @@ -878,6 +924,10 @@ drm_output_start_repaint_loop(struct weston_output *output_base) goto finish_frame; } + if (output->pageflip_timer) + wl_event_source_timer_update(output->pageflip_timer, + backend->pageflip_timeout); + return; finish_frame: @@ -916,6 +966,10 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, s->next = NULL; if (!output->page_flip_pending) { + /* Stop the pageflip timer instead of rearming it here */ + if (output->pageflip_timer) + wl_event_source_timer_update(output->pageflip_timer, 0); + ts.tv_sec = sec; ts.tv_nsec = usec * 1000; weston_output_finish_frame(&output->base, &ts, flags); @@ -953,6 +1007,10 @@ page_flip_handler(int fd, unsigned int frame, else if (output->disable_pending) weston_output_disable(&output->base); else if (!output->vblank_pending) { + /* Stop the pageflip timer instead of rearming it here */ + if (output->pageflip_timer) + wl_event_source_timer_update(output->pageflip_timer, 0); + ts.tv_sec = sec; ts.tv_nsec = usec * 1000; weston_output_finish_frame(&output->base, &ts, flags); @@ -2418,6 +2476,9 @@ drm_output_enable(struct weston_output *base) output->dpms_prop = drm_get_prop(b->drm.fd, output->connector, "DPMS"); + if (b->pageflip_timeout) + drm_output_pageflip_timer_create(output); + if (b->use_pixman) { if (drm_output_init_pixman(output, b) < 0) { weston_log("Failed to init output pixman state\n"); @@ -2532,6 +2593,9 @@ drm_output_destroy(struct weston_output *base) drmModeFreeCrtc(origcrtc); } + if (output->pageflip_timer) + wl_event_source_remove(output->pageflip_timer); + weston_output_destroy(&output->base); drmModeFreeConnector(output->connector); @@ -3309,6 +3373,7 @@ drm_backend_create(struct weston_compositor *compositor, b->sprites_are_broken = 1; b->compositor = compositor; b->use_pixman = config->use_pixman; + b->pageflip_timeout = config->pageflip_timeout; if (parse_gbm_format(config->gbm_format, GBM_FORMAT_XRGB8888, &b->gbm_format) < 0) goto err_compositor; diff --git a/libweston/compositor-drm.h b/libweston/compositor-drm.h index 2e2995a2a..087071971 100644 --- a/libweston/compositor-drm.h +++ b/libweston/compositor-drm.h @@ -138,6 +138,13 @@ struct weston_drm_backend_config { */ void (*configure_device)(struct weston_compositor *compositor, struct libinput_device *device); + + /** Maximum duration for a pageflip event to arrive, after which the + * compositor will consider the DRM driver crashed and will try to exit + * cleanly. + * + * It is exprimed in milliseconds, 0 means disabled. */ + uint32_t pageflip_timeout; }; #ifdef __cplusplus diff --git a/man/weston.ini.man b/man/weston.ini.man index 2edb0854c..5ec0e1d13 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -162,6 +162,11 @@ By default, xrgb8888 is used. sets Weston's idle timeout in seconds. This idle timeout is the time after which Weston will enter an "inactive" mode and screen will fade to black. A value of 0 disables the timeout. +.TP 7 +.BI "pageflip-timeout="milliseconds +sets Weston's pageflip timeout in milliseconds. This sets a timer to exit +gracefully with a log message and an exit code of 1 in case the DRM driver is +non-responsive. Setting it to 0 disables this feature. .IR Important : This option may also be set via Weston's '-i' command From 5ae7e84c6bc37436e636b75e53788f478ef78454 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 1 Mar 2017 11:34:00 +0000 Subject: [PATCH 0011/1642] timespec: Add timespec_add_nsec helper Add a (timespec) = (timespec) + (nsec) helper, to save intermediate conversions to nanoseconds in its users. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- Makefile.am | 10 ++++ shared/timespec-util.h | 21 +++++++ tests/timespec-test.c | 124 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 tests/timespec-test.c diff --git a/Makefile.am b/Makefile.am index cdf82ab43..7ee613ba7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1194,6 +1194,7 @@ internal_tests = \ shared_tests = \ config-parser.test \ + timespec.test \ string.test \ vertex-clip.test \ zuctest @@ -1307,6 +1308,15 @@ config_parser_test_CFLAGS = \ $(AM_CFLAGS) \ -I$(top_srcdir)/tools/zunitc/inc +timespec_test_SOURCES = tests/timespec-test.c +timespec_test_LDADD = \ + libshared.la \ + libzunitc.la \ + libzunitcmain.la +timespec_test_CFLAGS = \ + $(AM_CFLAGS) \ + -I$(top_srcdir)/tools/zunitc/inc + string_test_SOURCES = \ tests/string-test.c \ shared/string-helpers.h diff --git a/shared/timespec-util.h b/shared/timespec-util.h index edd4ec143..a1d6881b7 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -49,6 +49,27 @@ timespec_sub(struct timespec *r, } } +/* Add a nanosecond value to a timespec + * + * \param r[out] result: a + b + * \param a[in] base operand as timespec + * \param b[in] operand in nanoseconds + */ +static inline void +timespec_add_nsec(struct timespec *r, const struct timespec *a, int64_t b) +{ + r->tv_sec = a->tv_sec + (b / NSEC_PER_SEC); + r->tv_nsec = a->tv_nsec + (b % NSEC_PER_SEC); + + if (r->tv_nsec >= NSEC_PER_SEC) { + r->tv_sec++; + r->tv_nsec -= NSEC_PER_SEC; + } else if (r->tv_nsec < 0) { + r->tv_sec--; + r->tv_nsec += NSEC_PER_SEC; + } +} + /* Convert timespec to nanoseconds * * \param a timespec diff --git a/tests/timespec-test.c b/tests/timespec-test.c new file mode 100644 index 000000000..91d53f737 --- /dev/null +++ b/tests/timespec-test.c @@ -0,0 +1,124 @@ +/* + * Copyright © 2016 Collabora, Ltd. + * + * 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 "timespec-util.h" + +#include "shared/helpers.h" +#include "zunitc/zunitc.h" + +ZUC_TEST(timespec_test, timespec_sub) +{ + struct timespec a, b, r; + + a.tv_sec = 1; + a.tv_nsec = 1; + b.tv_sec = 0; + b.tv_nsec = 2; + timespec_sub(&r, &a, &b); + ZUC_ASSERT_EQ(r.tv_sec, 0); + ZUC_ASSERT_EQ(r.tv_nsec, NSEC_PER_SEC - 1); +} + +ZUC_TEST(timespec_test, timespec_to_nsec) +{ + struct timespec a; + + a.tv_sec = 4; + a.tv_nsec = 4; + ZUC_ASSERT_EQ(timespec_to_nsec(&a), (NSEC_PER_SEC * 4ULL) + 4); +} + +ZUC_TEST(timespec_test, millihz_to_nsec) +{ + ZUC_ASSERT_EQ(millihz_to_nsec(60000), 16666666); +} + +ZUC_TEST(timespec_test, timespec_add_nsec) +{ + struct timespec a, r; + + a.tv_sec = 0; + a.tv_nsec = NSEC_PER_SEC - 1; + timespec_add_nsec(&r, &a, 1); + ZUC_ASSERT_EQ(1, r.tv_sec); + ZUC_ASSERT_EQ(0, r.tv_nsec); + + timespec_add_nsec(&r, &a, 2); + ZUC_ASSERT_EQ(1, r.tv_sec); + ZUC_ASSERT_EQ(1, r.tv_nsec); + + timespec_add_nsec(&r, &a, (NSEC_PER_SEC * 2ULL)); + ZUC_ASSERT_EQ(2, r.tv_sec); + ZUC_ASSERT_EQ(NSEC_PER_SEC - 1, r.tv_nsec); + + timespec_add_nsec(&r, &a, (NSEC_PER_SEC * 2ULL) + 2); + ZUC_ASSERT_EQ(r.tv_sec, 3); + ZUC_ASSERT_EQ(r.tv_nsec, 1); + + a.tv_sec = 1; + a.tv_nsec = 1; + timespec_add_nsec(&r, &a, -2); + ZUC_ASSERT_EQ(r.tv_sec, 0); + ZUC_ASSERT_EQ(r.tv_nsec, NSEC_PER_SEC - 1); + + a.tv_nsec = 0; + timespec_add_nsec(&r, &a, -NSEC_PER_SEC); + ZUC_ASSERT_EQ(0, r.tv_sec); + ZUC_ASSERT_EQ(0, r.tv_nsec); + + a.tv_nsec = 0; + timespec_add_nsec(&r, &a, -NSEC_PER_SEC + 1); + ZUC_ASSERT_EQ(0, r.tv_sec); + ZUC_ASSERT_EQ(1, r.tv_nsec); + + a.tv_nsec = 50; + timespec_add_nsec(&r, &a, (-NSEC_PER_SEC * 10ULL)); + ZUC_ASSERT_EQ(-9, r.tv_sec); + ZUC_ASSERT_EQ(50, r.tv_nsec); + + r.tv_sec = 4; + r.tv_nsec = 0; + timespec_add_nsec(&r, &r, NSEC_PER_SEC + 10ULL); + ZUC_ASSERT_EQ(5, r.tv_sec); + ZUC_ASSERT_EQ(10, r.tv_nsec); + + timespec_add_nsec(&r, &r, (NSEC_PER_SEC * 3ULL) - 9ULL); + ZUC_ASSERT_EQ(8, r.tv_sec); + ZUC_ASSERT_EQ(1, r.tv_nsec); + + timespec_add_nsec(&r, &r, (NSEC_PER_SEC * 7ULL) + (NSEC_PER_SEC - 1ULL)); + ZUC_ASSERT_EQ(16, r.tv_sec); + ZUC_ASSERT_EQ(0, r.tv_nsec); +} From 61f6d7d372f600de36412a41269909050978bcd6 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 1 Mar 2017 11:34:01 +0000 Subject: [PATCH 0012/1642] timespec: Add timespec_add_msec helper Add a (timespec) = (timespec) + (msec) helper, to save intermediate conversions in its users. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- shared/timespec-util.h | 12 ++++++++++++ tests/timespec-test.c | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/shared/timespec-util.h b/shared/timespec-util.h index a1d6881b7..13948b1a7 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -70,6 +70,18 @@ timespec_add_nsec(struct timespec *r, const struct timespec *a, int64_t b) } } +/* Add a millisecond value to a timespec + * + * \param r[out] result: a + b + * \param a[in] base operand as timespec + * \param b[in] operand in milliseconds + */ +static inline void +timespec_add_msec(struct timespec *r, const struct timespec *a, int64_t b) +{ + return timespec_add_nsec(r, a, b * 1000000); +} + /* Convert timespec to nanoseconds * * \param a timespec diff --git a/tests/timespec-test.c b/tests/timespec-test.c index 91d53f737..cd3b1c1af 100644 --- a/tests/timespec-test.c +++ b/tests/timespec-test.c @@ -122,3 +122,14 @@ ZUC_TEST(timespec_test, timespec_add_nsec) ZUC_ASSERT_EQ(16, r.tv_sec); ZUC_ASSERT_EQ(0, r.tv_nsec); } + +ZUC_TEST(timespec_test, timespec_add_msec) +{ + struct timespec a, r; + + a.tv_sec = 1000; + a.tv_nsec = 1; + timespec_add_msec(&r, &a, 2002); + ZUC_ASSERT_EQ(1002, r.tv_sec); + ZUC_ASSERT_EQ(2000001, r.tv_nsec); +} From 37ad7e3bae090c8791dc638bfc3f952fb5c12acf Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 1 Mar 2017 11:34:02 +0000 Subject: [PATCH 0013/1642] timespec: Add timespec_to_msec helper Paralleling timespec_to_nsec, converts to milliseconds. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen [Pekka: added doc about flooring] Signed-off-by: Pekka Paalanen --- libweston/compositor.c | 2 +- shared/timespec-util.h | 13 +++++++++++++ tests/timespec-test.c | 9 +++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 9ded23f3e..2a9f476d4 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2388,7 +2388,7 @@ weston_output_finish_frame(struct weston_output *output, output->msc, presented_flags); - output->frame_time = stamp->tv_sec * 1000 + stamp->tv_nsec / 1000000; + output->frame_time = timespec_to_msec(stamp); weston_compositor_read_presentation_clock(compositor, &now); timespec_sub(&gone, &now, stamp); diff --git a/shared/timespec-util.h b/shared/timespec-util.h index 13948b1a7..958adb058 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -93,6 +93,19 @@ timespec_to_nsec(const struct timespec *a) return (int64_t)a->tv_sec * NSEC_PER_SEC + a->tv_nsec; } +/* Convert timespec to milliseconds + * + * \param a timespec + * \return milliseconds + * + * Rounding to integer milliseconds happens always down (floor()). + */ +static inline int64_t +timespec_to_msec(const struct timespec *a) +{ + return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; +} + /* Convert milli-Hertz to nanoseconds * * \param mhz frequency in mHz, not zero diff --git a/tests/timespec-test.c b/tests/timespec-test.c index cd3b1c1af..712d1acd9 100644 --- a/tests/timespec-test.c +++ b/tests/timespec-test.c @@ -60,6 +60,15 @@ ZUC_TEST(timespec_test, timespec_to_nsec) ZUC_ASSERT_EQ(timespec_to_nsec(&a), (NSEC_PER_SEC * 4ULL) + 4); } +ZUC_TEST(timespec_test, timespec_to_msec) +{ + struct timespec a; + + a.tv_sec = 4; + a.tv_nsec = 4000000; + ZUC_ASSERT_EQ(timespec_to_msec(&a), (4000ULL) + 4); +} + ZUC_TEST(timespec_test, millihz_to_nsec) { ZUC_ASSERT_EQ(millihz_to_nsec(60000), 16666666); From 839b63546d2e69e3bf863d0de701817524e5c1d1 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 1 Mar 2017 11:34:03 +0000 Subject: [PATCH 0014/1642] timespec: Add timespec subtraction helpers Add helpers to subtract two timespecs, then return the difference in either milliseconds or nanoseconds. These will be used to compare timestamps during the repaint cycle. Signed-off-by: Daniel Stone Suggested-by: Pekka Paalanen Reviewed-by: Pekka Paalanen --- shared/timespec-util.h | 26 ++++++++++++++++++++++++++ tests/timespec-test.c | 22 ++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/shared/timespec-util.h b/shared/timespec-util.h index 958adb058..576b3e8f0 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -93,6 +93,20 @@ timespec_to_nsec(const struct timespec *a) return (int64_t)a->tv_sec * NSEC_PER_SEC + a->tv_nsec; } +/* Subtract timespecs and return result in nanoseconds + * + * \param a[in] operand + * \param b[in] operand + * \return to_nanoseconds(a - b) + */ +static inline int64_t +timespec_sub_to_nsec(const struct timespec *a, const struct timespec *b) +{ + struct timespec r; + timespec_sub(&r, a, b); + return timespec_to_nsec(&r); +} + /* Convert timespec to milliseconds * * \param a timespec @@ -106,6 +120,18 @@ timespec_to_msec(const struct timespec *a) return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; } +/* Subtract timespecs and return result in milliseconds + * + * \param a[in] operand + * \param b[in] operand + * \return to_milliseconds(a - b) + */ +static inline int64_t +timespec_sub_to_msec(const struct timespec *a, const struct timespec *b) +{ + return timespec_sub_to_nsec(a, b) / 1000000; +} + /* Convert milli-Hertz to nanoseconds * * \param mhz frequency in mHz, not zero diff --git a/tests/timespec-test.c b/tests/timespec-test.c index 712d1acd9..a50391108 100644 --- a/tests/timespec-test.c +++ b/tests/timespec-test.c @@ -142,3 +142,25 @@ ZUC_TEST(timespec_test, timespec_add_msec) ZUC_ASSERT_EQ(1002, r.tv_sec); ZUC_ASSERT_EQ(2000001, r.tv_nsec); } + +ZUC_TEST(timespec_test, timespec_sub_to_nsec) +{ + struct timespec a, b; + + a.tv_sec = 1000; + a.tv_nsec = 1; + b.tv_sec = 1; + b.tv_nsec = 2; + ZUC_ASSERT_EQ((999L * NSEC_PER_SEC) - 1, timespec_sub_to_nsec(&a, &b)); +} + +ZUC_TEST(timespec_test, timespec_sub_to_msec) +{ + struct timespec a, b; + + a.tv_sec = 1000; + a.tv_nsec = 2000000L; + b.tv_sec = 2; + b.tv_nsec = 1000000L; + ZUC_ASSERT_EQ((998 * 1000) + 1, timespec_sub_to_msec(&a, &b)); +} From 84aff5c77aff83188256cc176bc65b297a32f9d1 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 1 Mar 2017 11:34:04 +0000 Subject: [PATCH 0015/1642] Calculate next-frame target time in absolute space Rather than determining the time until next-frame repaint in relative space (time until repaint), determine it first in absolute space, and then later convert this to relative. This will later allow us to store these per-output, so we can have a single idle timer which will allow us to aggregate multiple repaints together when timing allows. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 2a9f476d4..e7c3b2df3 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2376,8 +2376,9 @@ weston_output_finish_frame(struct weston_output *output, struct weston_compositor *compositor = output->compositor; int32_t refresh_nsec; struct timespec now; - struct timespec gone; - int msec; + struct timespec next; + struct timespec remain; + int msec_rel = 0; TL_POINT("core_repaint_finished", TLP_OUTPUT(output), TLP_VBLANK(stamp), TLP_END); @@ -2389,34 +2390,35 @@ weston_output_finish_frame(struct weston_output *output, presented_flags); output->frame_time = timespec_to_msec(stamp); - weston_compositor_read_presentation_clock(compositor, &now); - timespec_sub(&gone, &now, stamp); - msec = (refresh_nsec - timespec_to_nsec(&gone)) / 1000000; /* floor */ - msec -= compositor->repaint_msec; - if (msec < -1000 || msec > 1000) { + timespec_add_nsec(&next, stamp, refresh_nsec); + timespec_add_msec(&next, &next, -compositor->repaint_msec); + timespec_sub(&remain, &next, &now); + msec_rel = timespec_to_msec(&remain); + + if (msec_rel < -1000 || msec_rel > 1000) { static bool warned; if (!warned) weston_log("Warning: computed repaint delay is " - "insane: %d msec\n", msec); + "insane: %d msec\n", msec_rel); warned = true; - msec = 0; + msec_rel = 0; } /* Called from restart_repaint_loop and restart happens already after * the deadline given by repaint_msec? In that case we delay until * the deadline of the next frame, to give clients a more predictable * timing of the repaint cycle to lock on. */ - if (presented_flags == WP_PRESENTATION_FEEDBACK_INVALID && msec < 0) - msec += refresh_nsec / 1000000; + if (presented_flags == WP_PRESENTATION_FEEDBACK_INVALID && msec_rel < 0) + msec_rel += refresh_nsec / 1000000; - if (msec < 1) + if (msec_rel < 1) output_repaint_timer_handler(output); else - wl_event_source_timer_update(output->repaint_timer, msec); + wl_event_source_timer_update(output->repaint_timer, msec_rel); } static void From 3615ce195562b89de328d64c754b3b3114123321 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 1 Mar 2017 11:34:05 +0000 Subject: [PATCH 0016/1642] Don't delay initial output paint On startup, we cannot lock on to the repaint timer because it is unknown to us. We deal with this by claiming that the moment of entry into the repaint loop is the moment a frame returned, causing finish_frame to delay our initial repaint to (refresh_time - repaint_delay), typically around 9ms of utterly wasted time. Add an explicit stamp == NULL, to determine that we are just beginning our repaint loop, that the timings are in fact totally invalid, and that it would be beneficial to repaint the output immediately. This will only trigger when the display had previously been disabled or the previous state is unknown, e.g. at startup, or coming back from DPMS off. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 3 +-- libweston/compositor.c | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 006230766..3cb8f7eca 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -932,8 +932,7 @@ drm_output_start_repaint_loop(struct weston_output *output_base) finish_frame: /* if we cannot page-flip, immediately finish frame */ - weston_compositor_read_presentation_clock(output_base->compositor, &ts); - weston_output_finish_frame(output_base, &ts, + weston_output_finish_frame(output_base, NULL, WP_PRESENTATION_FEEDBACK_INVALID); } diff --git a/libweston/compositor.c b/libweston/compositor.c index e7c3b2df3..496261832 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2383,6 +2383,14 @@ weston_output_finish_frame(struct weston_output *output, TL_POINT("core_repaint_finished", TLP_OUTPUT(output), TLP_VBLANK(stamp), TLP_END); + assert(stamp || (presented_flags & WP_PRESENTATION_FEEDBACK_INVALID)); + + /* If we haven't been supplied any timestamp at all, we don't have a + * timebase to work against, so any delay just wastes time. Push a + * repaint as soon as possible so we can get on with it. */ + if (!stamp) + goto out; + refresh_nsec = millihz_to_nsec(output->current_mode->refresh); weston_presentation_feedback_present_list(&output->feedback_list, output, refresh_nsec, stamp, @@ -2415,6 +2423,7 @@ weston_output_finish_frame(struct weston_output *output, if (presented_flags == WP_PRESENTATION_FEEDBACK_INVALID && msec_rel < 0) msec_rel += refresh_nsec / 1000000; +out: if (msec_rel < 1) output_repaint_timer_handler(output); else From 09a97e2402c8188fa8b49075bc5604fe8275fb22 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 1 Mar 2017 11:34:06 +0000 Subject: [PATCH 0017/1642] Change repaint_needed to bool It is only used as a binary value. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 2 +- libweston/compositor-fbdev.c | 2 +- libweston/compositor.c | 4 ++-- libweston/compositor.h | 5 ++++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 3cb8f7eca..72696d56c 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2995,7 +2995,7 @@ session_notify(struct wl_listener *listener, void *data) * pending frame callbacks. */ wl_list_for_each(output, &compositor->output_list, base.link) { - output->base.repaint_needed = 0; + output->base.repaint_needed = false; drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); } diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 44f0cf51c..6f976d17d 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -693,7 +693,7 @@ session_notify(struct wl_listener *listener, void *data) wl_list_for_each(output, &compositor->output_list, link) { - output->repaint_needed = 0; + output->repaint_needed = false; } } } diff --git a/libweston/compositor.c b/libweston/compositor.c index 496261832..80268bcdc 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2310,7 +2310,7 @@ weston_output_repaint(struct weston_output *output) pixman_region32_fini(&output_damage); - output->repaint_needed = 0; + output->repaint_needed = false; weston_compositor_repick(ec); @@ -2549,7 +2549,7 @@ weston_output_schedule_repaint(struct weston_output *output) TL_POINT("core_repaint_req", TLP_OUTPUT(output), TLP_END); loop = wl_display_get_event_loop(compositor->wl_display); - output->repaint_needed = 1; + output->repaint_needed = true; if (output->repaint_scheduled) return; diff --git a/libweston/compositor.h b/libweston/compositor.h index 08e728a94..45dcb6d46 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -170,7 +170,10 @@ struct weston_output { pixman_region32_t region; pixman_region32_t previous_damage; - int repaint_needed; + + /** True if damage has occurred since the last repaint for this output; + * if set, a repaint will eventually occur. */ + bool repaint_needed; int repaint_scheduled; struct wl_event_source *repaint_timer; struct weston_output_zoom zoom; From 05df8c16ec8a9ac124691f85d7d89e0a973fea64 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 3 Mar 2017 16:59:42 +0000 Subject: [PATCH 0018/1642] Change boolean repaint_scheduled to quad-state enum repaint_scheduled is actually cleverly a quad-state, disguised as a boolean. There are four possible conditions for the repaint loop to be in at any time: - loop idle; no repaint will occur until specifically requested, which may be never (repaint_scheduled == 0) - loop schedule to begin: the loop was previously idle, but due to a repaint-schedule request, we will call the start_repaint_loop hook in the next idle task - repaint scheduled: the compositor has definitively scheduled a repaint request for this output, which will occur in fixed time - awaiting repaint completion: the backend has not yet signaled completion of the last repaint request, and the compositor will not schedule another until it does so All but the first condition were previously conflated as repaint_scheduled == 1, but break them out into separate conditions to aid clarity, backed up by some asserts. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 20 +++++++++++++++++--- libweston/compositor.h | 10 +++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 80268bcdc..2665c511b 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2311,6 +2311,8 @@ weston_output_repaint(struct weston_output *output) pixman_region32_fini(&output_damage); output->repaint_needed = false; + if (r == 0) + output->repaint_status = REPAINT_AWAITING_COMPLETION; weston_compositor_repick(ec); @@ -2332,7 +2334,7 @@ weston_output_repaint(struct weston_output *output) static void weston_output_schedule_repaint_reset(struct weston_output *output) { - output->repaint_scheduled = 0; + output->repaint_status = REPAINT_NOT_SCHEDULED; TL_POINT("core_repaint_exit_loop", TLP_OUTPUT(output), TLP_END); } @@ -2343,6 +2345,8 @@ output_repaint_timer_handler(void *data) struct weston_compositor *compositor = output->compositor; int ret; + assert(output->repaint_status == REPAINT_SCHEDULED); + /* If we're sleeping, drop the repaint machinery entirely; we will * explicitly repaint all outputs when we come back. */ if (compositor->state == WESTON_COMPOSITOR_SLEEPING || @@ -2383,6 +2387,7 @@ weston_output_finish_frame(struct weston_output *output, TL_POINT("core_repaint_finished", TLP_OUTPUT(output), TLP_VBLANK(stamp), TLP_END); + assert(output->repaint_status == REPAINT_AWAITING_COMPLETION); assert(stamp || (presented_flags & WP_PRESENTATION_FEEDBACK_INVALID)); /* If we haven't been supplied any timestamp at all, we don't have a @@ -2424,6 +2429,8 @@ weston_output_finish_frame(struct weston_output *output, msec_rel += refresh_nsec / 1000000; out: + output->repaint_status = REPAINT_SCHEDULED; + if (msec_rel < 1) output_repaint_timer_handler(output); else @@ -2435,6 +2442,8 @@ idle_repaint(void *data) { struct weston_output *output = data; + assert(output->repaint_status == REPAINT_BEGIN_FROM_IDLE); + output->repaint_status = REPAINT_AWAITING_COMPLETION; output->start_repaint_loop(output); } @@ -2550,11 +2559,16 @@ weston_output_schedule_repaint(struct weston_output *output) loop = wl_display_get_event_loop(compositor->wl_display); output->repaint_needed = true; - if (output->repaint_scheduled) + + /* If we already have a repaint scheduled for our idle handler, + * no need to set it again. If the repaint has been called but + * not finished, then weston_output_finish_frame() will notice + * that a repaint is needed and schedule one. */ + if (output->repaint_status != REPAINT_NOT_SCHEDULED) return; + output->repaint_status = REPAINT_BEGIN_FROM_IDLE; wl_event_loop_add_idle(loop, idle_repaint, output); - output->repaint_scheduled = 1; TL_POINT("core_repaint_enter_loop", TLP_OUTPUT(output), TLP_END); } diff --git a/libweston/compositor.h b/libweston/compositor.h index 45dcb6d46..5fc25c4ad 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -174,7 +174,15 @@ struct weston_output { /** True if damage has occurred since the last repaint for this output; * if set, a repaint will eventually occur. */ bool repaint_needed; - int repaint_scheduled; + + /** State of the repaint loop */ + enum { + REPAINT_NOT_SCHEDULED = 0, /**< idle; no repaint will occur */ + REPAINT_BEGIN_FROM_IDLE, /**< start_repaint_loop scheduled */ + REPAINT_SCHEDULED, /**< repaint is scheduled to occur */ + REPAINT_AWAITING_COMPLETION, /**< last repaint not yet finished */ + } repaint_status; + struct wl_event_source *repaint_timer; struct weston_output_zoom zoom; int dirty; From 2ef9b1a3c4a303601ee475af13b4c13b3f6f286c Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 13 Mar 2017 16:31:36 +0000 Subject: [PATCH 0019/1642] Fix 'implicit fallthrough' warning with new GCC GCC 7 now warns on case statements falling through without an explicit comment that falling through is OK. Insert some to make it happy. Signed-off-by: Daniel Stone Reviewed-by: Emilio Pozuelo Monfort --- desktop-shell/shell.c | 2 ++ tests/subsurface-test.c | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index f80088f76..c9058795b 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -345,12 +345,14 @@ get_output_work_area(struct desktop_shell *shell, case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: default: area->y += panel_height; + /* fallthrough */ case WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM: area->width = output->width; area->height = output->height - panel_height; break; case WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT: area->x += panel_width; + /* fallthrough */ case WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT: area->width = output->width - panel_width; area->height = output->height; diff --git a/tests/subsurface-test.c b/tests/subsurface-test.c index edb084228..1a24ee351 100644 --- a/tests/subsurface-test.c +++ b/tests/subsurface-test.c @@ -579,22 +579,31 @@ create_subsurface_tree(struct client *client, struct wl_surface **surfs, case 11: SUB_LINK(10, 2); + /* fallthrough */ case 10: SUB_LINK(9, 2); + /* fallthrough */ case 9: SUB_LINK(8, 6); + /* fallthrough */ case 8: SUB_LINK(7, 6); + /* fallthrough */ case 7: SUB_LINK(6, 2); + /* fallthrough */ case 6: SUB_LINK(5, 1); + /* fallthrough */ case 5: SUB_LINK(4, 3); + /* fallthrough */ case 4: SUB_LINK(3, 1); + /* fallthrough */ case 3: SUB_LINK(2, 0); + /* fallthrough */ case 2: SUB_LINK(1, 0); From c4d7f66c12853b9575366dd9f4a7960ec5694934 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 13 Mar 2017 16:32:18 +0000 Subject: [PATCH 0020/1642] launcher: Add sysmacros.h include for major() glibc 2.25 produces a warning when sysmacros.h is not directly included but major() is used, as it is intended to be moved to sysmacros.h and only there. Include it to keep the build happy. Signed-off-by: Daniel Stone Reviewed-by: Emilio Pozuelo Monfort --- libweston/launcher-direct.c | 1 + libweston/launcher-logind.c | 1 + libweston/launcher-weston-launch.c | 1 + 3 files changed, 3 insertions(+) diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c index 4195cf651..3d8f5f678 100644 --- a/libweston/launcher-direct.c +++ b/libweston/launcher-direct.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c index 8de1ed110..f10a28317 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c index 930f4e0c0..a7535ce70 100644 --- a/libweston/launcher-weston-launch.c +++ b/libweston/launcher-weston-launch.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include From 6847b858a3df25794a7a62660bb689860d267a52 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 1 Mar 2017 11:34:08 +0000 Subject: [PATCH 0021/1642] Switch to global output repaint timer In preparation for grouping output repaint together where possible, switch the per-output repaint timer, to a global timer which iterates across all outputs. This is implemented by storing the absolute time for the next repaint for each output locally, and maintaining a global timer which iterates all of them, scheduling the repaint for the first available time. Signed-off-by: Daniel Stone Cc: Mario Kleiner Cc: Pekka Paalanen [Pekka: The comment about 1 ms delay.] Signed-off-by: Pekka Paalanen --- libweston/compositor.c | 118 ++++++++++++++++++++++++++++++----------- libweston/compositor.h | 6 ++- 2 files changed, 93 insertions(+), 31 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 2665c511b..527db2084 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2338,14 +2338,21 @@ weston_output_schedule_repaint_reset(struct weston_output *output) TL_POINT("core_repaint_exit_loop", TLP_OUTPUT(output), TLP_END); } -static int -output_repaint_timer_handler(void *data) +static void +weston_output_maybe_repaint(struct weston_output *output, + struct timespec *now) { - struct weston_output *output = data; struct weston_compositor *compositor = output->compositor; int ret; + int64_t msec_to_repaint; - assert(output->repaint_status == REPAINT_SCHEDULED); + /* We're not ready yet; come back to make a decision later. */ + if (output->repaint_status != REPAINT_SCHEDULED) + return; + + msec_to_repaint = timespec_sub_to_msec(&output->next_repaint, now); + if (msec_to_repaint > 1) + return; /* If we're sleeping, drop the repaint machinery entirely; we will * explicitly repaint all outputs when we come back. */ @@ -2360,15 +2367,72 @@ output_repaint_timer_handler(void *data) /* If repaint fails, we aren't going to get weston_output_finish_frame * to trigger a new repaint, so drop it from repaint and hope - * something schedules a successful repaint later. */ + * something schedules a successful repaint later. As repainting may + * take some time, re-read our clock as a courtesy to the next + * output. */ ret = weston_output_repaint(output); + weston_compositor_read_presentation_clock(compositor, now); if (ret != 0) goto err; - return 0; + return; err: weston_output_schedule_repaint_reset(output); +} + +static void +output_repaint_timer_arm(struct weston_compositor *compositor) +{ + struct weston_output *output; + bool any_should_repaint = false; + struct timespec now; + int64_t msec_to_next; + + weston_compositor_read_presentation_clock(compositor, &now); + + wl_list_for_each(output, &compositor->output_list, link) { + int64_t msec_to_this; + + if (output->repaint_status != REPAINT_SCHEDULED) + continue; + + msec_to_this = timespec_sub_to_msec(&output->next_repaint, + &now); + if (!any_should_repaint || msec_to_this < msec_to_next) + msec_to_next = msec_to_this; + + any_should_repaint = true; + } + + if (!any_should_repaint) + return; + + /* Even if we should repaint immediately, add the minimum 1 ms delay. + * This is a workaround to allow coalescing multiple output repaints + * particularly from weston_output_finish_frame() + * into the same call, which would not happen if we called + * output_repaint_timer_handler() directly. + */ + if (msec_to_next < 1) + msec_to_next = 1; + + wl_event_source_timer_update(compositor->repaint_timer, msec_to_next); +} + +static int +output_repaint_timer_handler(void *data) +{ + struct weston_compositor *compositor = data; + struct weston_output *output; + struct timespec now; + + weston_compositor_read_presentation_clock(compositor, &now); + wl_list_for_each(output, &compositor->output_list, link) + weston_output_maybe_repaint(output, &now); + + output_repaint_timer_arm(compositor); + return 0; } @@ -2380,9 +2444,7 @@ weston_output_finish_frame(struct weston_output *output, struct weston_compositor *compositor = output->compositor; int32_t refresh_nsec; struct timespec now; - struct timespec next; - struct timespec remain; - int msec_rel = 0; + int64_t msec_rel; TL_POINT("core_repaint_finished", TLP_OUTPUT(output), TLP_VBLANK(stamp), TLP_END); @@ -2390,11 +2452,15 @@ weston_output_finish_frame(struct weston_output *output, assert(output->repaint_status == REPAINT_AWAITING_COMPLETION); assert(stamp || (presented_flags & WP_PRESENTATION_FEEDBACK_INVALID)); + weston_compositor_read_presentation_clock(compositor, &now); + /* If we haven't been supplied any timestamp at all, we don't have a * timebase to work against, so any delay just wastes time. Push a * repaint as soon as possible so we can get on with it. */ - if (!stamp) + if (!stamp) { + output->next_repaint = now; goto out; + } refresh_nsec = millihz_to_nsec(output->current_mode->refresh); weston_presentation_feedback_present_list(&output->feedback_list, @@ -2403,22 +2469,21 @@ weston_output_finish_frame(struct weston_output *output, presented_flags); output->frame_time = timespec_to_msec(stamp); - weston_compositor_read_presentation_clock(compositor, &now); - timespec_add_nsec(&next, stamp, refresh_nsec); - timespec_add_msec(&next, &next, -compositor->repaint_msec); - timespec_sub(&remain, &next, &now); - msec_rel = timespec_to_msec(&remain); + timespec_add_nsec(&output->next_repaint, stamp, refresh_nsec); + timespec_add_msec(&output->next_repaint, &output->next_repaint, + -compositor->repaint_msec); + msec_rel = timespec_sub_to_msec(&output->next_repaint, &now); if (msec_rel < -1000 || msec_rel > 1000) { static bool warned; if (!warned) weston_log("Warning: computed repaint delay is " - "insane: %d msec\n", msec_rel); + "insane: %lld msec\n", (long long) msec_rel); warned = true; - msec_rel = 0; + output->next_repaint = now; } /* Called from restart_repaint_loop and restart happens already after @@ -2426,15 +2491,12 @@ weston_output_finish_frame(struct weston_output *output, * the deadline of the next frame, to give clients a more predictable * timing of the repaint cycle to lock on. */ if (presented_flags == WP_PRESENTATION_FEEDBACK_INVALID && msec_rel < 0) - msec_rel += refresh_nsec / 1000000; + timespec_add_nsec(&output->next_repaint, &output->next_repaint, + refresh_nsec); out: output->repaint_status = REPAINT_SCHEDULED; - - if (msec_rel < 1) - output_repaint_timer_handler(output); - else - wl_event_source_timer_update(output->repaint_timer, msec_rel); + output_repaint_timer_arm(compositor); } static void @@ -4425,8 +4487,6 @@ weston_output_transform_coordinate(struct weston_output *output, static void weston_output_enable_undo(struct weston_output *output) { - wl_event_source_remove(output->repaint_timer); - wl_global_destroy(output->global); pixman_region32_fini(&output->region); @@ -4608,7 +4668,6 @@ weston_output_enable(struct weston_output *output) { struct weston_compositor *c = output->compositor; struct weston_output *iterator; - struct wl_event_loop *loop; int x = 0, y = 0; assert(output->enable); @@ -4649,10 +4708,6 @@ weston_output_enable(struct weston_output *output) wl_list_init(&output->feedback_list); wl_list_init(&output->link); - loop = wl_display_get_event_loop(c->wl_display); - output->repaint_timer = wl_event_loop_add_timer(loop, - output_repaint_timer_handler, output); - /* Invert the output id pool and look for the lowest numbered * switch (the least significant bit). Take that bit's position * as our ID, and mark it used in the compositor's output_id_pool. @@ -5154,6 +5209,9 @@ weston_compositor_create(struct wl_display *display, void *user_data) loop = wl_display_get_event_loop(ec->wl_display); ec->idle_source = wl_event_loop_add_timer(loop, idle_handler, ec); + ec->repaint_timer = + wl_event_loop_add_timer(loop, output_repaint_timer_handler, + ec); weston_layer_init(&ec->fade_layer, ec); weston_layer_init(&ec->cursor_layer, ec); diff --git a/libweston/compositor.h b/libweston/compositor.h index 5fc25c4ad..9e7ac99bb 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -183,7 +183,10 @@ struct weston_output { REPAINT_AWAITING_COMPLETION, /**< last repaint not yet finished */ } repaint_status; - struct wl_event_source *repaint_timer; + /** If repaint_status is REPAINT_SCHEDULED, contains the time the + * next repaint should be run */ + struct timespec next_repaint; + struct weston_output_zoom zoom; int dirty; struct wl_signal frame_signal; @@ -856,6 +859,7 @@ struct weston_compositor { struct wl_event_source *idle_source; uint32_t idle_inhibit; int idle_time; /* timeout, s */ + struct wl_event_source *repaint_timer; const struct weston_pointer_grab_interface *default_pointer_grab; From b1f166d71e00d33eb43d9c02786476de2eef16f8 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 1 Mar 2017 11:34:10 +0000 Subject: [PATCH 0022/1642] Allow backends to group repaint flushes Implement new repaint_begin and repaint_flush hooks inside weston_backend, allowing backends to gang together repaints which trigger at the same time. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 5 ++-- libweston/compositor-fbdev.c | 3 ++- libweston/compositor-headless.c | 3 ++- libweston/compositor-rdp.c | 3 ++- libweston/compositor-wayland.c | 6 +++-- libweston/compositor-x11.c | 6 +++-- libweston/compositor.c | 46 +++++++++++++++++++++++---------- libweston/compositor.h | 38 +++++++++++++++++++++++++-- 8 files changed, 86 insertions(+), 24 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 72696d56c..4cb27b1d9 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -760,7 +760,8 @@ drm_waitvblank_pipe(struct drm_output *output) static int drm_output_repaint(struct weston_output *output_base, - pixman_region32_t *damage) + pixman_region32_t *damage, + void *repaint_data) { struct drm_output *output = to_drm_output(output_base); struct drm_backend *backend = @@ -1375,7 +1376,7 @@ drm_output_set_cursor(struct drm_output *output) } static void -drm_assign_planes(struct weston_output *output_base) +drm_assign_planes(struct weston_output *output_base, void *repaint_data) { struct drm_backend *b = to_drm_backend(output_base->compositor); struct drm_output *output = to_drm_output(output_base); diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 6f976d17d..32d71e0fe 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -118,7 +118,8 @@ fbdev_output_start_repaint_loop(struct weston_output *output) } static int -fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage) +fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage, + void *repaint_data) { struct fbdev_output *output = to_fbdev_output(base); struct weston_compositor *ec = output->base.compositor; diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index a1aec6d73..9e42e7f62 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -92,7 +92,8 @@ finish_frame_handler(void *data) static int headless_output_repaint(struct weston_output *output_base, - pixman_region32_t *damage) + pixman_region32_t *damage, + void *repaint_data) { struct headless_output *output = to_headless_output(output_base); struct weston_compositor *ec = output->base.compositor; diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index d9668e868..091472b08 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -355,7 +355,8 @@ rdp_output_start_repaint_loop(struct weston_output *output) } static int -rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage) +rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, + void *repaint_data) { struct rdp_output *output = container_of(output_base, struct rdp_output, base); struct weston_compositor *ec = output->base.compositor; diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 9d35ef778..ebdbd13b9 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -488,7 +488,8 @@ wayland_output_start_repaint_loop(struct weston_output *output_base) #ifdef ENABLE_EGL static int wayland_output_repaint_gl(struct weston_output *output_base, - pixman_region32_t *damage) + pixman_region32_t *damage, + void *repaint_data) { struct wayland_output *output = to_wayland_output(output_base); struct weston_compositor *ec = output->base.compositor; @@ -595,7 +596,8 @@ wayland_shm_buffer_attach(struct wayland_shm_buffer *sb) static int wayland_output_repaint_pixman(struct weston_output *output_base, - pixman_region32_t *damage) + pixman_region32_t *damage, + void *repaint_data) { struct wayland_output *output = to_wayland_output(output_base); struct wayland_backend *b = diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index f9cb46124..02cdf3eaf 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -389,7 +389,8 @@ x11_output_start_repaint_loop(struct weston_output *output) static int x11_output_repaint_gl(struct weston_output *output_base, - pixman_region32_t *damage) + pixman_region32_t *damage, + void *repaint_data) { struct x11_output *output = to_x11_output(output_base); struct weston_compositor *ec = output->base.compositor; @@ -457,7 +458,8 @@ set_clip_for_output(struct weston_output *output_base, pixman_region32_t *region static int x11_output_repaint_shm(struct weston_output *output_base, - pixman_region32_t *damage) + pixman_region32_t *damage, + void *repaint_data) { struct x11_output *output = to_x11_output(output_base); struct weston_compositor *ec = output->base.compositor; diff --git a/libweston/compositor.c b/libweston/compositor.c index 527db2084..fb647daa2 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2254,7 +2254,7 @@ weston_output_take_feedback_list(struct weston_output *output, } static int -weston_output_repaint(struct weston_output *output) +weston_output_repaint(struct weston_output *output, void *repaint_data) { struct weston_compositor *ec = output->compositor; struct weston_view *ev; @@ -2273,7 +2273,7 @@ weston_output_repaint(struct weston_output *output) weston_compositor_build_view_list(ec); if (output->assign_planes && !output->disable_planes) { - output->assign_planes(output); + output->assign_planes(output, repaint_data); } else { wl_list_for_each(ev, &ec->view_list, link) { weston_view_move_to_plane(ev, &ec->primary_plane); @@ -2306,7 +2306,7 @@ weston_output_repaint(struct weston_output *output) if (output->dirty) weston_output_update_matrix(output); - r = output->repaint(output, &output_damage); + r = output->repaint(output, &output_damage, repaint_data); pixman_region32_fini(&output_damage); @@ -2338,21 +2338,21 @@ weston_output_schedule_repaint_reset(struct weston_output *output) TL_POINT("core_repaint_exit_loop", TLP_OUTPUT(output), TLP_END); } -static void -weston_output_maybe_repaint(struct weston_output *output, - struct timespec *now) +static int +weston_output_maybe_repaint(struct weston_output *output, struct timespec *now, + void *repaint_data) { struct weston_compositor *compositor = output->compositor; - int ret; + int ret = 0; int64_t msec_to_repaint; /* We're not ready yet; come back to make a decision later. */ if (output->repaint_status != REPAINT_SCHEDULED) - return; + return ret; msec_to_repaint = timespec_sub_to_msec(&output->next_repaint, now); if (msec_to_repaint > 1) - return; + return ret; /* If we're sleeping, drop the repaint machinery entirely; we will * explicitly repaint all outputs when we come back. */ @@ -2370,15 +2370,16 @@ weston_output_maybe_repaint(struct weston_output *output, * something schedules a successful repaint later. As repainting may * take some time, re-read our clock as a courtesy to the next * output. */ - ret = weston_output_repaint(output); + ret = weston_output_repaint(output, repaint_data); weston_compositor_read_presentation_clock(compositor, now); if (ret != 0) goto err; - return; + return ret; err: weston_output_schedule_repaint_reset(output); + return ret; } static void @@ -2426,10 +2427,29 @@ output_repaint_timer_handler(void *data) struct weston_compositor *compositor = data; struct weston_output *output; struct timespec now; + void *repaint_data = NULL; + int ret; weston_compositor_read_presentation_clock(compositor, &now); - wl_list_for_each(output, &compositor->output_list, link) - weston_output_maybe_repaint(output, &now); + + if (compositor->backend->repaint_begin) + repaint_data = compositor->backend->repaint_begin(compositor); + + wl_list_for_each(output, &compositor->output_list, link) { + ret = weston_output_maybe_repaint(output, &now, repaint_data); + if (ret) + break; + } + + if (ret == 0) { + if (compositor->backend->repaint_flush) + compositor->backend->repaint_flush(compositor, + repaint_data); + } else { + if (compositor->backend->repaint_cancel) + compositor->backend->repaint_cancel(compositor, + repaint_data); + } output_repaint_timer_arm(compositor); diff --git a/libweston/compositor.h b/libweston/compositor.h index 9e7ac99bb..6070c7744 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -212,9 +212,10 @@ struct weston_output { void (*start_repaint_loop)(struct weston_output *output); int (*repaint)(struct weston_output *output, - pixman_region32_t *damage); + pixman_region32_t *damage, + void *repaint_data); void (*destroy)(struct weston_output *output); - void (*assign_planes)(struct weston_output *output); + void (*assign_planes)(struct weston_output *output, void *repaint_data); int (*switch_mode)(struct weston_output *output, struct weston_mode *mode); /* backlight values are on 0-255 range, where higher is brighter */ @@ -804,6 +805,39 @@ struct weston_backend_config { struct weston_backend { void (*destroy)(struct weston_compositor *compositor); void (*restore)(struct weston_compositor *compositor); + + /** Begin a repaint sequence + * + * Provides the backend with explicit markers around repaint + * sequences, which may allow the backend to aggregate state + * application. This call will be bracketed by the repaint_flush (on + * success), or repaint_cancel (when any output in the grouping fails + * repaint). + * + * Returns an opaque pointer, which the backend may use as private + * data referring to the repaint cycle. + */ + void * (*repaint_begin)(struct weston_compositor *compositor); + + /** Cancel a repaint sequence + * + * Cancels a repaint sequence, when an error has occurred during + * one output's repaint; see repaint_begin. + * + * @param repaint_data Data returned by repaint_begin + */ + void (*repaint_cancel)(struct weston_compositor *compositor, + void *repaint_data); + + /** Conclude a repaint sequence + * + * Called on successful completion of a repaint sequence; see + * repaint_begin. + * + * @param repaint_data Data returned by repaint_begin + */ + void (*repaint_flush)(struct weston_compositor *compositor, + void *repaint_data); }; struct weston_desktop_xwayland; From 080f130092d1730beda3023bca368a9fc71e26bd Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 14 Mar 2017 12:14:51 +0200 Subject: [PATCH 0023/1642] configure: bump libweston to 3.0.0 Bump the future release to 3.0.0 due to breaking ABI in libweston. We have merged a few patches already that change libweston/compositor.h. While most of the changes arguably change only things libweston users should not be touching, some change the size of e.g. struct weston_output and struct weston_compositor, possibly moving member offsets. We also haven't separated public and private parts from compositor.h yet. To be on the safe side, bump the major now. I'm sure there will be more changes that make the bump obviously necessary. Cc: Bryce Harrington Cc: Daniel Stone Signed-off-by: Pekka Paalanen Reviewed-by: Emilio Pozuelo Monfort Reviewed-by: Daniel Stone --- configure.ac | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index 9df85d206..6cc9f26e6 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,11 @@ -m4_define([weston_major_version], [2]) -m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [90]) +m4_define([weston_major_version], [2]) +m4_define([weston_minor_version], [99]) +m4_define([weston_micro_version], [90]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) -m4_define([libweston_major_version], [2]) +m4_define([libweston_major_version], [3]) m4_define([libweston_minor_version], [0]) -m4_define([libweston_patch_version], [90]) +m4_define([libweston_patch_version], [0]) AC_PREREQ([2.64]) AC_INIT([weston], From 300bc6efea4e4bd9cb0794788bebde425cc39612 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 10 Mar 2017 14:21:32 -0600 Subject: [PATCH 0024/1642] simple-dmabuf-v4l: Remove incorrect assert According to v4l2 documentation, DQBUF always clears FLAG_DONE, so this assert can be expected to fire 100% of the time. Signed-off-by: Derek Foreman Reviewed-by: Pekka Paalanen --- clients/simple-dmabuf-v4l.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c index b49d62fd9..82b55a99a 100644 --- a/clients/simple-dmabuf-v4l.c +++ b/clients/simple-dmabuf-v4l.c @@ -457,8 +457,6 @@ dequeue(struct display *display) return -1; } - assert(buf.flags & V4L2_BUF_FLAG_DONE); - return buf.index; } From 4933ca5e57ecd3b89b1c29497a193c4a86705038 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 14 Mar 2017 17:24:04 +0000 Subject: [PATCH 0025/1642] libinput: Suppress unhandled-case warning When the wheel tilt source is present, gcc complains that we don't handle all possible enumeration values. We already ensure this cannot happen in its only caller (handle_pointer_axis), but gcc doesn't recognise this. Give it a default value to quiet the warning. Signed-off-by: Daniel Stone Reviewed-by: Peter Hutterer --- libweston/libinput-device.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index f97afcf8d..5e7182da1 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -191,6 +191,8 @@ normalize_scroll(struct libinput_event_pointer *pointer_event, value = libinput_event_pointer_get_axis_value(pointer_event, axis); break; + default: + assert(!"unhandled event source in normalize_scroll"); } return value; From 5a313c2f000659e3c0552668af2754e7639ae0d4 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 14 Mar 2017 17:25:30 +0000 Subject: [PATCH 0026/1642] weston-launch: Add sysmacros.h include for major() Same as with c4d7f66c, but I hadn't done a full-tree rebuild so didn't see this one go by. Signed-off-by: Daniel Stone --- libweston/weston-launch.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index 140fde1db..eecb911a1 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include From 760917891792f18b0a609ff74e7a6ec0dc00735c Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 8 Mar 2017 11:58:20 -0600 Subject: [PATCH 0027/1642] weston-terminal: Add a --maximized command line parameter This is useful for testing compositor response to a client that requests a maximized initial surface. Signed-off-by: Derek Foreman Reviewed-by: Bryce Harrington Reviewed-by: Daniel Stone --- clients/terminal.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clients/terminal.c b/clients/terminal.c index 5c25fa8d2..c55317907 100644 --- a/clients/terminal.c +++ b/clients/terminal.c @@ -49,6 +49,7 @@ #include "window.h" static int option_fullscreen; +static int option_maximize; static char *option_font; static int option_font_size; static char *option_term; @@ -3048,6 +3049,8 @@ terminal_run(struct terminal *terminal, const char *path) if (option_fullscreen) window_set_fullscreen(terminal->window, 1); + else if (option_maximize) + window_set_maximized(terminal->window, 1); else terminal_resize(terminal, 80, 24); @@ -3056,6 +3059,7 @@ terminal_run(struct terminal *terminal, const char *path) static const struct weston_option terminal_options[] = { { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &option_fullscreen }, + { WESTON_OPTION_BOOLEAN, "maximized", 'm', &option_maximize }, { WESTON_OPTION_STRING, "font", 0, &option_font }, { WESTON_OPTION_INTEGER, "font-size", 0, &option_font_size }, { WESTON_OPTION_STRING, "shell", 0, &option_shell }, @@ -3089,6 +3093,7 @@ int main(int argc, char *argv[]) ARRAY_LENGTH(terminal_options), &argc, argv) > 1) { printf("Usage: %s [OPTIONS]\n" " --fullscreen or -f\n" + " --maximized or -m\n" " --font=NAME\n" " --font-size=SIZE\n" " --shell=NAME\n", argv[0]); From 65e57c93caf234235bd64d89ecae033526acddce Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 13 Mar 2017 15:25:42 +0200 Subject: [PATCH 0028/1642] man: move pageflip-timeout later The paragraph about pageflip-timeout was added in between the two paragraphs of idle-time, causing the paragraphs to be associated wrong. Move the pageflip-timeout paragraph to the end. Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=100163 Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- man/weston.ini.man | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/man/weston.ini.man b/man/weston.ini.man index 5ec0e1d13..4cfefc913 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -162,11 +162,6 @@ By default, xrgb8888 is used. sets Weston's idle timeout in seconds. This idle timeout is the time after which Weston will enter an "inactive" mode and screen will fade to black. A value of 0 disables the timeout. -.TP 7 -.BI "pageflip-timeout="milliseconds -sets Weston's pageflip timeout in milliseconds. This sets a timer to exit -gracefully with a log message and an exit code of 1 in case the DRM driver is -non-responsive. Setting it to 0 disables this feature. .IR Important : This option may also be set via Weston's '-i' command @@ -181,6 +176,11 @@ set to 300 seconds. .TP 7 .BI "require-input=" true require an input device for launch +.TP 7 +.BI "pageflip-timeout="milliseconds +sets Weston's pageflip timeout in milliseconds. This sets a timer to exit +gracefully with a log message and an exit code of 1 in case the DRM driver is +non-responsive. Setting it to 0 disables this feature. .SH "LIBINPUT SECTION" The From b4c088630f54f8277690fc4c999a5f315b3cb89e Mon Sep 17 00:00:00 2001 From: Sergi Granell Date: Sat, 18 Mar 2017 13:01:15 +0100 Subject: [PATCH 0029/1642] Fix uninitialized msec_to_next in output_repaint_timer_arm Signed-off-by: Sergi Granell Reviewed-by: Quentin Glidic --- libweston/compositor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index fb647daa2..048b195c1 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2388,7 +2388,7 @@ output_repaint_timer_arm(struct weston_compositor *compositor) struct weston_output *output; bool any_should_repaint = false; struct timespec now; - int64_t msec_to_next; + int64_t msec_to_next = INT64_MAX; weston_compositor_read_presentation_clock(compositor, &now); From 2dcbb8d20d644bebed6307192f21d7c3bcf26845 Mon Sep 17 00:00:00 2001 From: Sergi Granell Date: Fri, 24 Mar 2017 20:48:01 +0100 Subject: [PATCH 0030/1642] compositor-wayland: Refactor struct wayland_output::name usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct wayland_output::name was used but never initialized. Also zxdg_toplevel_v6_set_title was only called for windowed outputs, and some compositors let you see the client's name even when it is fullscreen (GNOME Shell's Activities menu for example). So rename struct wayland_output::name to struct wayland_output::title and precompute it on wayland_output_create_common(), so it can be later used on xdg's set_title and frame_create. v2: Move zxdg_toplevel_v6_set_title() before the wl_surface_commit() as per Quentin Glidic's suggestion. Signed-off-by: Sergi Granell Reviewed-by: Armin Krezović Reviewed-by: Quentin Glidic --- libweston/compositor-wayland.c | 67 +++++++++++++++------------------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index ebdbd13b9..2bbf4cf02 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -111,7 +111,7 @@ struct wayland_output { int keyboard_count; - char *name; + char *title; struct frame *frame; struct { @@ -708,6 +708,7 @@ wayland_output_destroy(struct weston_output *base) if (output->frame_cb) wl_callback_destroy(output->frame_cb); + free(output->title); free(output); } @@ -835,36 +836,17 @@ wayland_output_set_windowed(struct wayland_output *output) { struct wayland_backend *b = to_wayland_backend(output->base.compositor); - int tlen; - char *title; if (output->frame) return 0; - if (output->name) { - tlen = strlen(output->name) + strlen(WINDOW_TITLE " - "); - title = malloc(tlen + 1); - if (!title) - return -1; - - snprintf(title, tlen + 1, WINDOW_TITLE " - %s", output->name); - } else { - title = strdup(WINDOW_TITLE); - } - - if (output->parent.xdg_toplevel) - zxdg_toplevel_v6_set_title(output->parent.xdg_toplevel, title); - if (!b->theme) { b->theme = theme_create(); - if (!b->theme) { - free(title); + if (!b->theme) return -1; - } } output->frame = frame_create(b->theme, 100, 100, - FRAME_BUTTON_CLOSE, title); - free(title); + FRAME_BUTTON_CLOSE, output->title); if (!output->frame) return -1; @@ -1132,6 +1114,8 @@ wayland_backend_create_output_surface(struct wayland_output *output) zxdg_toplevel_v6_add_listener(output->parent.xdg_toplevel, &xdg_toplevel_listener, output); + zxdg_toplevel_v6_set_title(output->parent.xdg_toplevel, output->title); + wl_surface_commit(output->parent.surface); output->parent.wait_for_configure = true; @@ -1239,9 +1223,13 @@ wayland_output_enable(struct weston_output *base) } static struct wayland_output * -wayland_output_create_common(void) +wayland_output_create_common(const char *name) { struct wayland_output *output; + size_t len; + + /* name can't be NULL. */ + assert(name); output = zalloc(sizeof *output); if (output == NULL) { @@ -1252,6 +1240,18 @@ wayland_output_create_common(void) output->base.destroy = wayland_output_destroy; output->base.disable = wayland_output_disable; output->base.enable = wayland_output_enable; + output->base.name = strdup(name); + + /* setup output name/title. */ + len = strlen(WINDOW_TITLE " - ") + strlen(name) + 1; + output->title = zalloc(len); + if (!output->title) { + free(output->base.name); + free(output); + return NULL; + } + + snprintf(output->title, len, WINDOW_TITLE " - %s", name); return output; } @@ -1259,12 +1259,7 @@ wayland_output_create_common(void) static int wayland_output_create(struct weston_compositor *compositor, const char *name) { - struct wayland_output *output = wayland_output_create_common(); - - /* name can't be NULL. */ - assert(name); - - output->base.name = strdup(name); + struct wayland_output *output = wayland_output_create_common(name); weston_output_init(&output->base, compositor); weston_compositor_add_pending_output(&output->base, compositor); @@ -1324,11 +1319,9 @@ static int wayland_output_create_for_parent_output(struct wayland_backend *b, struct wayland_parent_output *poutput) { - struct wayland_output *output = wayland_output_create_common(); + struct wayland_output *output = wayland_output_create_common("wlparent"); struct weston_mode *mode; - output->base.name = strdup("wlparent"); - if (poutput->current_mode) { mode = poutput->current_mode; } else if (poutput->preferred_mode) { @@ -1364,7 +1357,8 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, return 0; out: - free(output->name); + free(output->base.name); + free(output->title); free(output); return -1; @@ -1373,11 +1367,9 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, static int wayland_output_create_fullscreen(struct wayland_backend *b) { - struct wayland_output *output = wayland_output_create_common(); + struct wayland_output *output = wayland_output_create_common("wayland-fullscreen"); int width = 0, height = 0; - output->base.name = strdup("wayland-fullscreen"); - weston_output_init(&output->base, b->compositor); output->base.scale = 1; @@ -1411,7 +1403,8 @@ wayland_output_create_fullscreen(struct wayland_backend *b) err_set_size: wayland_backend_destroy_output_surface(output); err_surface: - free(output->name); + free(output->base.name); + free(output->title); free(output); return -1; From 7fecb43735b71207210d056874e164e057ce8723 Mon Sep 17 00:00:00 2001 From: Sergi Granell Date: Fri, 24 Mar 2017 20:48:02 +0100 Subject: [PATCH 0031/1642] compositor-wayland: Check the return value of wayland_output_create_common MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If wayland_output_create_common returns NULL, it means that the output creation failed. Signed-off-by: Sergi Granell Reviewed-by: Armin Krezović Reviewed-by: Quentin Glidic --- libweston/compositor-wayland.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 2bbf4cf02..27beff624 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1261,6 +1261,9 @@ wayland_output_create(struct weston_compositor *compositor, const char *name) { struct wayland_output *output = wayland_output_create_common(name); + if (!output) + return -1; + weston_output_init(&output->base, compositor); weston_compositor_add_pending_output(&output->base, compositor); @@ -1319,9 +1322,13 @@ static int wayland_output_create_for_parent_output(struct wayland_backend *b, struct wayland_parent_output *poutput) { - struct wayland_output *output = wayland_output_create_common("wlparent"); + struct wayland_output *output; struct weston_mode *mode; + output = wayland_output_create_common("wlparent"); + if (!output) + return -1; + if (poutput->current_mode) { mode = poutput->current_mode; } else if (poutput->preferred_mode) { @@ -1367,9 +1374,13 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, static int wayland_output_create_fullscreen(struct wayland_backend *b) { - struct wayland_output *output = wayland_output_create_common("wayland-fullscreen"); + struct wayland_output *output; int width = 0, height = 0; + output = wayland_output_create_common("wayland-fullscreen"); + if (!output) + return -1; + weston_output_init(&output->base, b->compositor); output->base.scale = 1; From 91d4bce7c33338473f649108e326a6405eeff58b Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 23 Mar 2017 11:59:22 -0500 Subject: [PATCH 0032/1642] os: Check for EINTR on posix_fallocate() posix_fallocate() can return EINTR and need to be restarted - I've hit this when running weston-terminal under gdb. Signed-off-by: Derek Foreman Reviewed-by: Eric Engestrom Reviewed-by: Quentin Glidic --- shared/os-compatibility.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/os-compatibility.c b/shared/os-compatibility.c index 551f2a995..6b2f3770c 100644 --- a/shared/os-compatibility.c +++ b/shared/os-compatibility.c @@ -178,7 +178,9 @@ os_create_anonymous_file(off_t size) return -1; #ifdef HAVE_POSIX_FALLOCATE - ret = posix_fallocate(fd, 0, size); + do { + ret = posix_fallocate(fd, 0, size); + } while (ret == EINTR); if (ret != 0) { close(fd); errno = ret; From 5ef6bd7eeec67b9726b78037a868d9f0975d3522 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 23 Mar 2017 11:59:23 -0500 Subject: [PATCH 0033/1642] os: Check for EINTR on ftruncate() The man page indicates that ftruncate() can set errno to EINTR, so test for this. I have not actually been able to provoke an EINTR error from ftruncate() in testing though. Signed-off-by: Derek Foreman Reviewed-by: Eric Engestrom Reviewed-by: Quentin Glidic --- shared/os-compatibility.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/os-compatibility.c b/shared/os-compatibility.c index 6b2f3770c..e19fb61bd 100644 --- a/shared/os-compatibility.c +++ b/shared/os-compatibility.c @@ -187,7 +187,9 @@ os_create_anonymous_file(off_t size) return -1; } #else - ret = ftruncate(fd, size); + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); if (ret < 0) { close(fd); return -1; From f0d39b2243e56cf81d97b130b3104978c1c70ef4 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 24 Mar 2017 09:41:12 -0500 Subject: [PATCH 0034/1642] weston: Set CLOEXEC on stdin We don't want to leak this into apps launched from the panel. stdout and stderr are left for now because some things launched by weston - such as weston-keyboard - share weston's log by printing to those fds. I'm singling out stdin because it's never needed by a child process and because it's value is 0, which makes it easy to accidentally do bad things to (commit 5c611d933f60f720db98331c9c1c6ed4420f9782) Signed-off-by: Derek Foreman Reviewed-by: Quentin Glidic --- compositor/main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index e870dd4a4..f8a60e976 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1800,6 +1800,11 @@ int main(int argc, char *argv[]) { WESTON_OPTION_STRING, "config", 'c', &config_file }, }; + if (os_fd_set_cloexec(fileno(stdin))) { + printf("Unable to set stdin as close on exec().\n"); + return EXIT_FAILURE; + } + cmdline = copy_command_line(argc, argv); parse_options(core_options, ARRAY_LENGTH(core_options), &argc, argv); From 091c8017057099dd34bbd5c8421fd0a42ac4ee8a Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 24 Mar 2017 09:41:13 -0500 Subject: [PATCH 0035/1642] desktop-shell: launch clients in their own process group. Client applications shouldn't be in the same process group as the display server. Signed-off-by: Derek Foreman Reviewed-by: Quentin Glidic --- clients/desktop-shell.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index b133d86e8..2667e9bb5 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -212,6 +212,10 @@ panel_launcher_activate(struct panel_launcher *widget) return; argv = widget->argv.data; + + if (setsid() == -1) + exit(EXIT_FAILURE); + if (execve(argv[0], argv, widget->envp.data) < 0) { fprintf(stderr, "execl '%s' failed: %m\n", argv[0]); exit(1); From 88353ddad7980420017ce65e37cc2eed0c6126d2 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 24 Mar 2017 16:29:31 -0500 Subject: [PATCH 0036/1642] weston-terminal: Fix race at startup If anything is printed for the terminal window to display before the window has been initially sized we end up with a segfault. This defers the exec() of the shell child process until after the window is sized so this can't happen anymore. Signed-off-by: Derek Foreman Reviewed-by: Quentin Glidic --- clients/terminal.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/clients/terminal.c b/clients/terminal.c index c55317907..274ced095 100644 --- a/clients/terminal.c +++ b/clients/terminal.c @@ -38,6 +38,7 @@ #include #include #include +#include #include @@ -481,6 +482,7 @@ struct terminal { int selection_start_row, selection_start_col; int selection_end_row, selection_end_col; struct wl_list link; + int pace_pipe; }; /* Create default tab stops, every 8 characters */ @@ -860,6 +862,10 @@ resize_handler(struct widget *widget, struct terminal *terminal = data; int32_t columns, rows, m; + if (terminal->pace_pipe >= 0) { + close(terminal->pace_pipe); + terminal->pace_pipe = -1; + } m = 2 * terminal->margin; columns = (width - m) / (int32_t) terminal->average_width; rows = (height - m) / (int32_t) terminal->extents.height; @@ -3027,9 +3033,34 @@ terminal_run(struct terminal *terminal, const char *path) { int master; pid_t pid; + int pipes[2]; + + /* Awkwardness: There's a sticky race condition here. If + * anything prints after the forkpty() but before the window has + * a size then we'll segfault. So we make a pipe and wait on + * it before actually exec()ing the terminal. The resize + * handler closes it in the parent process and the child continues + * on to launch a shell. + * + * The reason we don't just do terminal_run() after the window + * has a size is that we'd prefer to perform the fork() before + * the process opens a wayland connection. + */ + if (pipe(pipes) == -1) { + fprintf(stderr, "Can't create pipe for pacing.\n"); + exit(EXIT_FAILURE); + } pid = forkpty(&master, NULL, NULL, NULL); if (pid == 0) { + int ret; + + close(pipes[1]); + do { + char tmp; + ret = read(pipes[0], &tmp, 1); + } while (ret == -1 && errno == EINTR); + close(pipes[0]); setenv("TERM", option_term, 1); setenv("COLORTERM", option_term, 1); if (execl(path, path, NULL)) { @@ -3041,7 +3072,9 @@ terminal_run(struct terminal *terminal, const char *path) return -1; } + close(pipes[0]); terminal->master = master; + terminal->pace_pipe = pipes[1]; fcntl(master, F_SETFL, O_NONBLOCK); terminal->io_task.run = io_handler; display_watch_fd(terminal->display, terminal->master, From ed016bff2363abe680b6d9db4709917e2e15f906 Mon Sep 17 00:00:00 2001 From: Sergi Granell Date: Fri, 24 Mar 2017 23:45:13 +0100 Subject: [PATCH 0037/1642] compositor-wayland: Call set_window_geometry when using zxdg_shell_v6 This way Wayland compositors will be aware of Weston's "visible bounds" (and ignore its shadows). Signed-off-by: Sergi Granell Reviewed-by: Quentin Glidic --- libweston/compositor-wayland.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 27beff624..1900ab083 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -785,6 +785,14 @@ wayland_output_resize_surface(struct wayland_output *output) wl_surface_set_opaque_region(output->parent.surface, region); wl_region_destroy(region); + if (output->parent.xdg_surface) { + zxdg_surface_v6_set_window_geometry(output->parent.xdg_surface, + ix, + iy, + iwidth, + iheight); + } + width = frame_width(output->frame); height = frame_height(output->frame); } else { @@ -797,6 +805,14 @@ wayland_output_resize_surface(struct wayland_output *output) wl_region_add(region, 0, 0, width, height); wl_surface_set_opaque_region(output->parent.surface, region); wl_region_destroy(region); + + if (output->parent.xdg_surface) { + zxdg_surface_v6_set_window_geometry(output->parent.xdg_surface, + 0, + 0, + width, + height); + } } #ifdef ENABLE_EGL From 07a2b99f7fbf1ece6833053cf9fcdcde4f1823b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Pe=C3=B1acoba?= Date: Sat, 25 Mar 2017 16:42:38 +0100 Subject: [PATCH 0038/1642] desktop-shell: Remove unused variable in panel_create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raúl Peñacoba Reviewed-by: Quentin Glidic --- clients/desktop-shell.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index 2667e9bb5..599295ee3 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -591,7 +591,6 @@ panel_create(struct desktop *desktop) { struct panel *panel; struct weston_config_section *s; - char *clock_format_option = NULL; panel = xzalloc(sizeof *panel); @@ -611,8 +610,6 @@ panel_create(struct desktop *desktop) if (panel->clock_format != CLOCK_FORMAT_NONE) panel_add_clock(panel); - free (clock_format_option); - s = weston_config_get_section(desktop->config, "shell", NULL, NULL); weston_config_section_get_color(s, "panel-color", &panel->color, 0xaa000000); From eaa7358403c8fe5e24f7adc5cff82d0cc145088f Mon Sep 17 00:00:00 2001 From: Sergi Granell Date: Sat, 25 Mar 2017 17:19:36 +0100 Subject: [PATCH 0039/1642] compositor-wayland: Call weston_compositor_exit when receiving an xdg toplevel close event Signed-off-by: Sergi Granell Reviewed-by: Quentin Glidic --- libweston/compositor-wayland.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 1900ab083..a76dd08e6 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1097,6 +1097,9 @@ handle_xdg_toplevel_configure(void *data, struct zxdg_toplevel_v6 *toplevel, static void handle_xdg_toplevel_close(void *data, struct zxdg_toplevel_v6 *xdg_toplevel) { + struct wayland_output *output = data; + + weston_compositor_exit(output->base.compositor); } static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { From ceb5981af0423f5322d0adcc4facb38e6890db5a Mon Sep 17 00:00:00 2001 From: Sergi Granell Date: Tue, 28 Mar 2017 12:44:04 +0200 Subject: [PATCH 0040/1642] compositor-drm: Add missing drmModeFreeResources in drm_device_is_kms Signed-off-by: Sergi Granell Reviewed-by: Quentin Glidic --- libweston/compositor-drm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 4cb27b1d9..3f7e97e68 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3055,6 +3055,8 @@ drm_device_is_kms(struct drm_backend *b, struct udev_device *device) b->drm.id = id; b->drm.filename = strdup(filename); + drmModeFreeResources(res); + return true; out_res: From 5fc8d5eb9de0921801c3116b4a7c5f51b0c48d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Pe=C3=B1acoba?= Date: Tue, 28 Mar 2017 18:17:56 +0200 Subject: [PATCH 0041/1642] gl-renderer: Change 'data' type to 'uint8_t *', since 'void *' arithmetic is undefined MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raúl Peñacoba Reviewed-by: Quentin Glidic --- libweston/gl-renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index c6091af09..0de2803f4 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -1237,7 +1237,7 @@ gl_renderer_flush_damage(struct weston_surface *surface) struct weston_view *view; bool texture_used; pixman_box32_t *rectangles; - void *data; + uint8_t *data; int i, j, n; pixman_region32_union(&gs->texture_damage, From bd8dc0a255d079e6f977f0dc5b0a0b7045c18676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Pe=C3=B1acoba?= Date: Wed, 29 Mar 2017 18:13:36 +0200 Subject: [PATCH 0042/1642] ivi-layout: Add missing free() in ivi_view_create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raúl Peñacoba Reviewed-by: Emre Ucan Reviewed-by: Quentin Glidic --- ivi-shell/ivi-layout.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 64e4ead8e..298e18eaa 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -173,6 +173,7 @@ ivi_view_create(struct ivi_layout_layer *ivilayer, ivi_view->view = weston_view_create(ivisurf->surface); if (ivi_view->view == NULL) { weston_log("fails to allocate memory\n"); + free(ivi_view); return NULL; } From 745e647f18ef7b668731e0c7ebbbb0a15184b36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Pe=C3=B1acoba?= Date: Wed, 29 Mar 2017 18:16:46 +0200 Subject: [PATCH 0043/1642] editor: Add missing free() and display_destroy() in main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raúl Peñacoba Reviewed-by: Quentin Glidic --- clients/editor.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clients/editor.c b/clients/editor.c index a0cc97af0..b63c56284 100644 --- a/clients/editor.c +++ b/clients/editor.c @@ -1607,6 +1607,7 @@ main(int argc, char *argv[]) editor.display = display_create(&argc, argv); if (editor.display == NULL) { fprintf(stderr, "failed to create display: %m\n"); + free(text_buffer); return -1; } @@ -1615,6 +1616,8 @@ main(int argc, char *argv[]) if (editor.text_input_manager == NULL) { fprintf(stderr, "No text input manager global\n"); + display_destroy(editor.display); + free(text_buffer); return -1; } From fec723ef565aa0586ad01ea6574232739b5864f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Pe=C3=B1acoba?= Date: Wed, 29 Mar 2017 22:23:07 +0200 Subject: [PATCH 0044/1642] compositor-wayland: Properly dealloc mmap data using munmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raúl Peñacoba Reviewed-by: Quentin Glidic --- libweston/compositor-wayland.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index a76dd08e6..14f2c8dbd 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -301,7 +301,7 @@ wayland_output_get_shm_buffer(struct wayland_output *output) if (sb == NULL) { weston_log("could not zalloc %zu memory for sb: %m\n", sizeof *sb); close(fd); - free(data); + munmap(data, height * stride); return NULL; } From 597dde5cf6f77842f0a24bd86dbd238b345505cf Mon Sep 17 00:00:00 2001 From: Sergi Granell Date: Wed, 29 Mar 2017 22:41:02 +0200 Subject: [PATCH 0045/1642] wcap: Prevent fd leak in wcap_decoder_create() fail path Signed-off-by: Sergi Granell Reviewed-by: Quentin Glidic --- wcap/wcap-decode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wcap/wcap-decode.c b/wcap/wcap-decode.c index e3b8985f4..7e8c84774 100644 --- a/wcap/wcap-decode.c +++ b/wcap/wcap-decode.c @@ -131,6 +131,7 @@ wcap_decoder_create(const char *filename) PROT_READ, MAP_PRIVATE, decoder->fd, 0); if (decoder->map == MAP_FAILED) { fprintf(stderr, "mmap failed\n"); + close(decoder->fd); free(decoder); return NULL; } @@ -146,6 +147,7 @@ wcap_decoder_create(const char *filename) frame_size = header->width * header->height * 4; decoder->frame = malloc(frame_size); if (decoder->frame == NULL) { + close(decoder->fd); free(decoder); return NULL; } From c394179488e9c84f3f113570cf4079ebd5da8760 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Tue, 4 Apr 2017 18:49:33 +0100 Subject: [PATCH 0046/1642] desktop-shell: Position maximized surfaces on the correct output. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During a maximize event, a surface was previously always put back to the primary output after one frame on the correct output, while keeping its size. This was caused by the shell surface’s last_{width,height} not being reset when it was either fullscreen or maximized, leading to the unmaximize/maximize dance being done at each commit. This was introduced in 8f9d90a84bb2888b074fea93c4a28778bc6439c6. Changes since v1: - Fix the actual issue instead of a symptom. Signed-off-by: Emmanuel Gil Peyrot Reviewed-by: Quentin Glidic --- desktop-shell/shell.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index c9058795b..6b1876d2b 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2545,9 +2545,6 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_TOP) sy = shsurf->last_height - surface->height; - shsurf->last_width = surface->width; - shsurf->last_height = surface->height; - weston_view_to_global_float(shsurf->view, 0, 0, &from_x, &from_y); weston_view_to_global_float(shsurf->view, sx, sy, &to_x, &to_y); x = shsurf->view->geometry.x + to_x - from_x; @@ -2556,6 +2553,9 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, weston_view_set_position(shsurf->view, x, y); } + shsurf->last_width = surface->width; + shsurf->last_height = surface->height; + /* XXX: would a fullscreen surface need the same handling? */ if (surface->output) { wl_list_for_each(view, &surface->views, surface_link) From 2667e9e399fa553cb53c38a4c7a061a81ce4e0f7 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 6 Apr 2017 13:18:59 +0300 Subject: [PATCH 0047/1642] configure: replace HAVE_LIBDRM with BUILD_DRM_COMPOSITOR HAVE_LIBDRM was used as a condition for the launcher infrastructure to call libdrm.so functions. It was set by an independent test for libdrm, which would silently continue if libdrm was not found. It was assumed that if you enabled a feature that used libdrm at runtime, the test for that feature would imply that HAVE_LIBDRM is also set. This was quite subtle. The only feature that actually uses libdrm.so at runtime is the DRM backend. No other backend needs the libdrm calls in the launcher infrastructure. Therefore to simplify things, stop using HAVE_LIBDRM and use BUILD_DRM_COMPOSITOR instead. If you enable the DRM compositor, you automatically also get libdrm support in the launchers. There are still things depending on LIBDRM_CFLAGS and LIBDRM_LIBS, so the test cannot be removed completely. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic --- configure.ac | 3 +-- libweston/launcher-direct.c | 2 +- libweston/launcher-weston-launch.c | 2 +- libweston/weston-launch.c | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index 6cc9f26e6..39c053185 100644 --- a/configure.ac +++ b/configure.ac @@ -177,8 +177,7 @@ if test x$enable_xwayland = xyes; then fi fi -PKG_CHECK_MODULES(LIBDRM, [libdrm], - [AC_DEFINE(HAVE_LIBDRM, 1, [Define if libdrm is available]) have_libdrm=yes], have_libdrm=no) +PKG_CHECK_MODULES(LIBDRM, [libdrm], have_libdrm=yes, have_libdrm=no) AC_ARG_ENABLE(x11-compositor, [ --enable-x11-compositor],, enable_x11_compositor=yes) diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c index 3d8f5f678..a5d3ee532 100644 --- a/libweston/launcher-direct.c +++ b/libweston/launcher-direct.c @@ -47,7 +47,7 @@ #define KDSKBMUTE 0x4B51 #endif -#ifdef HAVE_LIBDRM +#ifdef BUILD_DRM_COMPOSITOR #include diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c index a7535ce70..97da18c53 100644 --- a/libweston/launcher-weston-launch.c +++ b/libweston/launcher-weston-launch.c @@ -55,7 +55,7 @@ #define KDSKBMUTE 0x4B51 #endif -#ifdef HAVE_LIBDRM +#ifdef BUILD_DRM_COMPOSITOR #include diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index eecb911a1..aa7e07118 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -73,7 +73,7 @@ #define MAX_ARGV_SIZE 256 -#ifdef HAVE_LIBDRM +#ifdef BUILD_DRM_COMPOSITOR #include From 4e3522bbd59f97661ab2e8e56166a1fd535ca14c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 6 Apr 2017 13:57:57 +0300 Subject: [PATCH 0048/1642] build: do not link libdrm without DRM backend The pkg-config test for LIBDRM is independent of whether the DRM backend is enabled or not. Therefore it is possible to have libdrm available and found, even though it is not needed. Do not link libdrm.so into the launchers unless it is really needed, that is, DRM compositor is built. Otherwise you end up with fbdev-backend.so and weston-launch depending on libdrm.so. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic --- Makefile.am | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 7ee613ba7..ff927c15f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -219,7 +219,11 @@ libsession_helper_la_SOURCES = \ libweston/launcher-weston-launch.c \ libweston/launcher-direct.c libsession_helper_la_CFLAGS = $(AM_CFLAGS) $(LIBDRM_CFLAGS) $(PIXMAN_CFLAGS) $(COMPOSITOR_CFLAGS) -libsession_helper_la_LIBADD = libweston-@LIBWESTON_MAJOR@.la $(LIBDRM_LIBS) +libsession_helper_la_LIBADD = libweston-@LIBWESTON_MAJOR@.la + +if ENABLE_DRM_COMPOSITOR +libsession_helper_la_LIBADD += $(LIBDRM_LIBS) +endif if ENABLE_DBUS if HAVE_SYSTEMD_LOGIN @@ -252,7 +256,11 @@ weston_launch_CFLAGS= \ $(PAM_CFLAGS) \ $(SYSTEMD_LOGIN_CFLAGS) \ $(LIBDRM_CFLAGS) -weston_launch_LDADD = $(PAM_LIBS) $(SYSTEMD_LOGIN_LIBS) $(LIBDRM_LIBS) +weston_launch_LDADD = $(PAM_LIBS) $(SYSTEMD_LOGIN_LIBS) + +if ENABLE_DRM_COMPOSITOR +weston_launch_LDADD += $(LIBDRM_LIBS) +endif if ENABLE_SETUID_INSTALL install-exec-hook: From b030897b380be4ac8089deecc3db6967dfe7923e Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 6 Apr 2017 14:06:20 +0300 Subject: [PATCH 0049/1642] build: make libdrm a hard build-time dependency Libdrm provides headers that are useful even without libdrm.so itself, particularly drm_fourcc.h. Therefore promote libdrm as a hard build-time dependency of libweston core so that we can always rely on libdrm headers. This does not affect any runtime dependencies. Specifically, no runtime dependency to libdrm.so is added in any build configuration. Currently only gl-renderer is using drm_fourcc.h. Now we can drop the GL_RENDERER check from configure.ac and just use LIBDRM_CFLAGS. Signed-off-by: Pekka Paalanen [Pekka, from Quentin: just drop have_libdrm var completely] Reviewed-by: Quentin Glidic --- Makefile.am | 2 +- configure.ac | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index ff927c15f..8ecc90cd8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -324,7 +324,7 @@ gl_renderer_la_LIBADD = \ gl_renderer_la_CFLAGS = \ $(COMPOSITOR_CFLAGS) \ $(EGL_CFLAGS) \ - $(GL_RENDERER_CFLAGS) \ + $(LIBDRM_CFLAGS) \ $(AM_CFLAGS) gl_renderer_la_SOURCES = \ libweston/gl-renderer.h \ diff --git a/configure.ac b/configure.ac index 39c053185..db757f208 100644 --- a/configure.ac +++ b/configure.ac @@ -105,6 +105,14 @@ AC_CHECK_HEADERS([execinfo.h]) AC_CHECK_FUNCS([mkostemp strchrnul initgroups posix_fallocate]) +# check for libdrm as a build-time dependency only +# libdrm 2.4.30 introduced drm_fourcc.h. +PKG_CHECK_MODULES(LIBDRM, [libdrm >= 2.4.30], [], [AC_MSG_ERROR([ + libdrm is a hard build-time dependency for libweston core, + but a sufficient version was not found. However, libdrm + is not a runtime dependency unless you have features + enabled that require it.])]) + COMPOSITOR_MODULES="wayland-server >= $WAYLAND_PREREQ_VERSION pixman-1 >= 0.25.2" AC_CONFIG_FILES([doc/doxygen/tools.doxygen doc/doxygen/tooldev.doxygen]) @@ -133,7 +141,6 @@ if test x$enable_egl = xyes; then AC_DEFINE([ENABLE_EGL], [1], [Build Weston with EGL support]) PKG_CHECK_MODULES(EGL, [egl glesv2]) PKG_CHECK_MODULES([EGL_TESTS], [egl glesv2 wayland-client wayland-egl]) - PKG_CHECK_MODULES([GL_RENDERER], [libdrm]) fi AC_ARG_ENABLE(xkbcommon, @@ -177,8 +184,6 @@ if test x$enable_xwayland = xyes; then fi fi -PKG_CHECK_MODULES(LIBDRM, [libdrm], have_libdrm=yes, have_libdrm=no) - AC_ARG_ENABLE(x11-compositor, [ --enable-x11-compositor],, enable_x11_compositor=yes) AM_CONDITIONAL(ENABLE_X11_COMPOSITOR, test x$enable_x11_compositor = xyes) From 903721a6215f474787b5daf02761fbcb1d3a0bb5 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:20 +0100 Subject: [PATCH 0050/1642] libweston: Add pixel-format helpers Rather than duplicating knowledge of pixel formats across several components, create a custom central repository. Signed-off-by: Daniel Stone [Pekka: fix include paths and two copy-pastas] Reviewed-by: Pekka Paalanen --- Makefile.am | 5 +- libweston/pixel-formats.c | 430 ++++++++++++++++++++++++++++++++++++++ libweston/pixel-formats.h | 194 +++++++++++++++++ 3 files changed, 628 insertions(+), 1 deletion(-) create mode 100644 libweston/pixel-formats.c create mode 100644 libweston/pixel-formats.h diff --git a/Makefile.am b/Makefile.am index 8ecc90cd8..94b1c1430 100644 --- a/Makefile.am +++ b/Makefile.am @@ -71,7 +71,8 @@ install-libweston_moduleLTLIBRARIES install-moduleLTLIBRARIES: install-libLTLIBR lib_LTLIBRARIES = libweston-@LIBWESTON_MAJOR@.la libweston_@LIBWESTON_MAJOR@_la_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON -libweston_@LIBWESTON_MAJOR@_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBUNWIND_CFLAGS) +libweston_@LIBWESTON_MAJOR@_la_CFLAGS = $(AM_CFLAGS) \ + $(COMPOSITOR_CFLAGS) $(LIBUNWIND_CFLAGS) $(LIBDRM_CFLAGS) libweston_@LIBWESTON_MAJOR@_la_LIBADD = $(COMPOSITOR_LIBS) $(LIBUNWIND_LIBS) \ $(DLOPEN_LIBS) -lm $(CLOCK_GETTIME_LIBS) \ $(LIBINPUT_BACKEND_LIBS) libshared.la @@ -105,6 +106,8 @@ libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ libweston/timeline-object.h \ libweston/linux-dmabuf.c \ libweston/linux-dmabuf.h \ + libweston/pixel-formats.c \ + libweston/pixel-formats.h \ shared/helpers.h \ shared/matrix.c \ shared/matrix.h \ diff --git a/libweston/pixel-formats.c b/libweston/pixel-formats.c new file mode 100644 index 000000000..df84a9f3f --- /dev/null +++ b/libweston/pixel-formats.c @@ -0,0 +1,430 @@ +/* + * Copyright © 2016 Collabora, Ltd. + * + * 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. + * + * Author: Daniel Stone + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "helpers.h" +#include "wayland-util.h" +#include "pixel-formats.h" + +#if ENABLE_EGL +#include +#include +#include +#include +#define GL_FORMAT(fmt) .gl_format = (fmt) +#define GL_TYPE(type) .gl_type = (type) +#define SAMPLER_TYPE(type) .sampler_type = (type) +#else +#define GL_FORMAT(fmt) .gl_format = 0 +#define GL_TYPE(type) .gl_type = 0 +#define SAMPLER_TYPE(type) .sampler_type = 0 +#endif + +#include "weston-egl-ext.h" + +/** + * Table of DRM formats supported by Weston; RGB, ARGB and YUV formats are + * supported. Indexed/greyscale formats, and formats not containing complete + * colour channels, are not supported. + */ +static const struct pixel_format_info pixel_format_table[] = { + { + .format = DRM_FORMAT_XRGB4444, + }, + { + .format = DRM_FORMAT_ARGB4444, + .opaque_substitute = DRM_FORMAT_XRGB4444, + }, + { + .format = DRM_FORMAT_XBGR4444, + }, + { + .format = DRM_FORMAT_ABGR4444, + .opaque_substitute = DRM_FORMAT_XBGR4444, + }, + { + .format = DRM_FORMAT_RGBX4444, +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_SHORT_4_4_4_4), +#endif + }, + { + .format = DRM_FORMAT_RGBA4444, + .opaque_substitute = DRM_FORMAT_RGBX4444, +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_SHORT_4_4_4_4), +#endif + }, + { + .format = DRM_FORMAT_BGRX4444, + }, + { + .format = DRM_FORMAT_BGRA4444, + .opaque_substitute = DRM_FORMAT_BGRX4444, + }, + { + .format = DRM_FORMAT_XRGB1555, + .depth = 15, + .bpp = 16, + }, + { + .format = DRM_FORMAT_ARGB1555, + .opaque_substitute = DRM_FORMAT_XRGB1555, + }, + { + .format = DRM_FORMAT_XBGR1555, + }, + { + .format = DRM_FORMAT_ABGR1555, + .opaque_substitute = DRM_FORMAT_XBGR1555, + }, + { + .format = DRM_FORMAT_RGBX5551, +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_SHORT_5_5_5_1), +#endif + }, + { + .format = DRM_FORMAT_RGBA5551, + .opaque_substitute = DRM_FORMAT_RGBX5551, +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_SHORT_5_5_5_1), +#endif + }, + { + .format = DRM_FORMAT_BGRX5551, + }, + { + .format = DRM_FORMAT_BGRA5551, + .opaque_substitute = DRM_FORMAT_BGRX5551, + }, + { + .format = DRM_FORMAT_RGB565, + .depth = 16, + .bpp = 16, +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGB), + GL_TYPE(GL_UNSIGNED_SHORT_5_6_5), +#endif + }, + { + .format = DRM_FORMAT_BGR565, + }, + { + .format = DRM_FORMAT_RGB888, + }, + { + .format = DRM_FORMAT_BGR888, + GL_FORMAT(GL_RGB), + GL_TYPE(GL_UNSIGNED_BYTE), + }, + { + .format = DRM_FORMAT_XRGB8888, + .depth = 24, + .bpp = 32, + GL_FORMAT(GL_BGRA_EXT), + GL_TYPE(GL_UNSIGNED_BYTE), + }, + { + .format = DRM_FORMAT_ARGB8888, + .opaque_substitute = DRM_FORMAT_XRGB8888, + .depth = 32, + .bpp = 32, + GL_FORMAT(GL_BGRA_EXT), + GL_TYPE(GL_UNSIGNED_BYTE), + }, + { + .format = DRM_FORMAT_XBGR8888, + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_BYTE), + }, + { + .format = DRM_FORMAT_ABGR8888, + .opaque_substitute = DRM_FORMAT_XBGR8888, + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_BYTE), + }, + { + .format = DRM_FORMAT_RGBX8888, + }, + { + .format = DRM_FORMAT_RGBA8888, + .opaque_substitute = DRM_FORMAT_RGBX8888, + }, + { + .format = DRM_FORMAT_BGRX8888, + }, + { + .format = DRM_FORMAT_BGRA8888, + .opaque_substitute = DRM_FORMAT_BGRX8888, + }, + { + .format = DRM_FORMAT_XRGB2101010, + .depth = 30, + .bpp = 32, + }, + { + .format = DRM_FORMAT_ARGB2101010, + .opaque_substitute = DRM_FORMAT_XRGB2101010, + }, + { + .format = DRM_FORMAT_XBGR2101010, +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_INT_2_10_10_10_REV_EXT), +#endif + }, + { + .format = DRM_FORMAT_ABGR2101010, + .opaque_substitute = DRM_FORMAT_XBGR2101010, +# if __BYTE_ORDER == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA), + GL_TYPE(GL_UNSIGNED_INT_2_10_10_10_REV_EXT), +#endif + }, + { + .format = DRM_FORMAT_RGBX1010102, + }, + { + .format = DRM_FORMAT_RGBA1010102, + .opaque_substitute = DRM_FORMAT_RGBX1010102, + }, + { + .format = DRM_FORMAT_BGRX1010102, + }, + { + .format = DRM_FORMAT_BGRA1010102, + .opaque_substitute = DRM_FORMAT_BGRX1010102, + }, + { + .format = DRM_FORMAT_YUYV, + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), + .num_planes = 1, + .hsub = 2, + }, + { + .format = DRM_FORMAT_YVYU, + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), + .num_planes = 1, + .chroma_order = ORDER_VU, + .hsub = 2, + }, + { + .format = DRM_FORMAT_UYVY, + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), + .num_planes = 1, + .luma_chroma_order = ORDER_CHROMA_LUMA, + .hsub = 2, + }, + { + .format = DRM_FORMAT_VYUY, + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), + .num_planes = 1, + .luma_chroma_order = ORDER_CHROMA_LUMA, + .chroma_order = ORDER_VU, + .hsub = 2, + }, + { + .format = DRM_FORMAT_NV12, + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + .hsub = 2, + .vsub = 2, + }, + { + .format = DRM_FORMAT_NV21, + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + .chroma_order = ORDER_VU, + .hsub = 2, + .vsub = 2, + }, + { + .format = DRM_FORMAT_NV16, + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + .hsub = 2, + .vsub = 1, + }, + { + .format = DRM_FORMAT_NV61, + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + .chroma_order = ORDER_VU, + .hsub = 2, + .vsub = 1, + }, + { + .format = DRM_FORMAT_NV24, + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + }, + { + .format = DRM_FORMAT_NV42, + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), + .num_planes = 2, + .chroma_order = ORDER_VU, + }, + { + .format = DRM_FORMAT_YUV410, + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .hsub = 4, + .vsub = 4, + }, + { + .format = DRM_FORMAT_YVU410, + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .chroma_order = ORDER_VU, + .hsub = 4, + .vsub = 4, + }, + { + .format = DRM_FORMAT_YUV411, + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .hsub = 4, + .vsub = 1, + }, + { + .format = DRM_FORMAT_YVU411, + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .chroma_order = ORDER_VU, + .hsub = 4, + .vsub = 1, + }, + { + .format = DRM_FORMAT_YUV420, + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .hsub = 2, + .vsub = 2, + }, + { + .format = DRM_FORMAT_YVU420, + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .chroma_order = ORDER_VU, + .hsub = 2, + .vsub = 2, + }, + { + .format = DRM_FORMAT_YUV422, + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .hsub = 2, + .vsub = 1, + }, + { + .format = DRM_FORMAT_YVU422, + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .chroma_order = ORDER_VU, + .hsub = 2, + .vsub = 1, + }, + { + .format = DRM_FORMAT_YUV444, + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + }, + { + .format = DRM_FORMAT_YVU444, + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), + .num_planes = 3, + .chroma_order = ORDER_VU, + }, +}; + +WL_EXPORT const struct pixel_format_info * +pixel_format_get_info(uint32_t format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(pixel_format_table); i++) { + if (pixel_format_table[i].format == format) + return &pixel_format_table[i]; + } + + return NULL; +} + +WL_EXPORT unsigned int +pixel_format_get_plane_count(const struct pixel_format_info *info) +{ + return info->num_planes ? info->num_planes : 1; +} + +WL_EXPORT bool +pixel_format_is_opaque(const struct pixel_format_info *info) +{ + return !info->opaque_substitute; +} + +WL_EXPORT const struct pixel_format_info * +pixel_format_get_opaque_substitute(const struct pixel_format_info *info) +{ + if (!info->opaque_substitute) + return info; + else + return pixel_format_get_info(info->opaque_substitute); +} + +WL_EXPORT unsigned int +pixel_format_width_for_plane(const struct pixel_format_info *info, + unsigned int plane, + unsigned int width) +{ + /* We don't support any formats where the first plane is subsampled. */ + if (plane == 0 || !info->hsub) + return width; + + return width / info->hsub; +} + +WL_EXPORT unsigned int +pixel_format_height_for_plane(const struct pixel_format_info *info, + unsigned int plane, + unsigned int height) +{ + /* We don't support any formats where the first plane is subsampled. */ + if (plane == 0 || !info->vsub) + return height; + + return height / info->vsub; +} diff --git a/libweston/pixel-formats.h b/libweston/pixel-formats.h new file mode 100644 index 000000000..b16aae323 --- /dev/null +++ b/libweston/pixel-formats.h @@ -0,0 +1,194 @@ +/* + * Copyright © 2016 Collabora, Ltd. + * + * 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. + * + * Author: Daniel Stone + */ + +#include +#include + +/** + * Contains information about pixel formats, mapping format codes from + * wl_shm and drm_fourcc.h (which are deliberately identical, but for the + * special cases of WL_SHM_ARGB8888 and WL_SHM_XRGB8888) into various + * sets of information. Helper functions are provided for dealing with these + * raw structures. + */ +struct pixel_format_info { + /** DRM/wl_shm format code */ + uint32_t format; + + /** If non-zero, number of planes in base (non-modified) format. */ + int num_planes; + + /** If format contains alpha channel, opaque equivalent of format, + * i.e. alpha channel replaced with X. */ + uint32_t opaque_substitute; + + /** How the format should be sampled, expressed in terms of tokens + * from the EGL_WL_bind_wayland_display extension. If not set, + * assumed to be either RGB or RGBA, depending on whether or not + * the format contains an alpha channel. The samplers may still + * return alpha even for opaque formats; users must manually set + * the alpha channel to 1.0 (or ignore it) if the format is + * opaque. */ + uint32_t sampler_type; + + /** GL format, if data can be natively/directly uploaded. Note that + * whilst DRM formats are little-endian unless explicitly specified, + * (i.e. DRM_FORMAT_ARGB8888 is stored BGRA as sequential bytes in + * memory), GL uses the sequential byte order, so that format maps to + * GL_BGRA_EXT plus GL_UNSIGNED_BYTE. To add to the confusion, the + * explicitly-sized types (e.g. GL_UNSIGNED_SHORT_5_5_5_1) read in + * machine-endian order, so for these types, the correspondence + * depends on endianness. */ + int gl_format; + + /** GL data type, if data can be natively/directly uploaded. */ + int gl_type; + + /** If set, this format can be used with the legacy drmModeAddFB() + * function (not AddFB2), using this and the bpp member. */ + int depth; + + /** See 'depth' member above. */ + int bpp; + + /** Horizontal subsampling; if non-zero, divide the width by this + * member to obtain the number of columns in the source buffer for + * secondary planes only. Stride is not affected by horizontal + * subsampling. */ + int hsub; + + /** Vertical subsampling; if non-zero, divide the height by this + * member to obtain the number of rows in the source buffer for + * secondary planes only. */ + int vsub; + + /* Ordering of chroma components. */ + enum { + ORDER_UV = 0, + ORDER_VU, + } chroma_order; + + /* If packed YUV (num_planes == 1), ordering of luma/chroma + * components. */ + enum { + ORDER_LUMA_CHROMA = 0, + ORDER_CHROMA_LUMA, + } luma_chroma_order; +}; + +/** + * Get pixel format information for a DRM format code + * + * Given a DRM format code, return a pixel format info structure describing + * the properties of that format. + * + * @param format DRM format code to get info for + * @returns A pixel format structure (must not be freed), or NULL if the + * format could not be found + */ +const struct pixel_format_info *pixel_format_get_info(uint32_t format); + +/** + * Get number of planes used by a pixel format + * + * Given a pixel format info structure, return the number of planes + * required for a buffer. Note that this is not necessarily identical to + * the number of samplers required to be bound, as two views into a single + * plane are sometimes required. + * + * @param format Pixel format info structure + * @returns Number of planes required for the format + */ +unsigned int +pixel_format_get_plane_count(const struct pixel_format_info *format); + +/** + * Determine if a pixel format is opaque or contains alpha + * + * Returns whether or not the pixel format is opaque, or contains a + * significant alpha channel. Note that the suggested EGL sampler type may + * still sample undefined data into the alpha channel; users must consider + * alpha as 1.0 if the format is opaque, and not rely on the sampler to + * return this when sampling from the alpha channel. + * + * @param format Pixel format info structure + * @returns True if the format is opaque, or false if it has significant alpha + */ +bool pixel_format_is_opaque(const struct pixel_format_info *format); + +/** + * Get compatible opaque equivalent for a format + * + * Given a pixel format info structure, return a format which is wholly + * compatible with the input format, but opaque, ignoring the alpha channel. + * If an alpha format is provided, but the content is known to all be opaque, + * then this can be used as a substitute to avoid blending. + * + * If the input format is opaque, this function will return the input format. + * + * @param format Pixel format info structure + * @returns A pixel format info structure for the compatible opaque substitute + */ +const struct pixel_format_info * +pixel_format_get_opaque_substitute(const struct pixel_format_info *format); + +/** + * Return the effective sampling width for a given plane + * + * When horizontal subsampling is effective, a sampler bound to a secondary + * plane must bind the sampler with a smaller effective width. This function + * returns the effective width to use for the sampler, i.e. dividing by hsub. + * + * If horizontal subsampling is not in effect, this will be equal to the + * width. + * + * @param format Pixel format info structure + * @param plane Zero-indexed plane number + * @param width Width of the buffer + * @returns Effective width for sampling + */ +unsigned int +pixel_format_width_for_plane(const struct pixel_format_info *format, + unsigned int plane, + unsigned int width); + +/** + * Return the effective sampling height for a given plane + * + * When vertical subsampling is in effect, a sampler bound to a secondary + * plane must bind the sampler with a smaller effective height. This function + * returns the effective height to use for the sampler, i.e. dividing by vsub. + * + * If vertical subsampling is not in effect, this will be equal to the height. + * + * @param format Pixel format info structure + * @param plane Zero-indexed plane number + * @param height Height of the buffer + * @returns Effective width for sampling + */ +unsigned int +pixel_format_height_for_plane(const struct pixel_format_info *format, + unsigned int plane, + unsigned int height); From a7cba1d4cd4c9013c3ac6cb074fcb7842fb39283 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:21 +0100 Subject: [PATCH 0051/1642] compositor-drm: Calculate more cursor state up front Make drm_output_set_cursor more deterministic, by calculating more state and performing more plane manipulation, inside drm_output_prepare_cursor_view. Signed-off-by: Daniel Stone Reviewed-by: Derek Foreman Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 39 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 3f7e97e68..884d2e940 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1241,6 +1241,7 @@ drm_output_prepare_cursor_view(struct drm_output *output, struct drm_backend *b = to_drm_backend(output->base.compositor); struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; struct wl_shm_buffer *shmbuf; + float x, y; if (b->cursors_are_broken) return NULL; @@ -1279,6 +1280,9 @@ drm_output_prepare_cursor_view(struct drm_output *output, return NULL; output->cursor_view = ev; + weston_view_to_global_float(ev, 0, 0, &x, &y); + output->cursor_plane.x = x; + output->cursor_plane.y = y; return &output->cursor_plane; } @@ -1324,24 +1328,17 @@ static void drm_output_set_cursor(struct drm_output *output) { struct weston_view *ev = output->cursor_view; - struct weston_buffer *buffer; struct drm_backend *b = to_drm_backend(output->base.compositor); EGLint handle; struct gbm_bo *bo; float x, y; - output->cursor_view = NULL; if (ev == NULL) { drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); - output->cursor_plane.x = INT32_MIN; - output->cursor_plane.y = INT32_MIN; return; } - buffer = ev->surface->buffer_ref.buffer; - - if (buffer && - pixman_region32_not_empty(&output->cursor_plane.damage)) { + if (pixman_region32_not_empty(&output->cursor_plane.damage)) { pixman_region32_fini(&output->cursor_plane.damage); pixman_region32_init(&output->cursor_plane.damage); output->current_cursor ^= 1; @@ -1356,22 +1353,14 @@ drm_output_set_cursor(struct drm_output *output) } } - weston_view_to_global_float(ev, 0, 0, &x, &y); - - /* From global to output space, output transform is guaranteed to be - * NORMAL by drm_output_prepare_cursor_view(). - */ - x = (x - output->base.x) * output->base.current_scale; - y = (y - output->base.y) * output->base.current_scale; + x = (output->cursor_plane.x - output->base.x) * + output->base.current_scale; + y = (output->cursor_plane.y - output->base.y) * + output->base.current_scale; - if (output->cursor_plane.x != x || output->cursor_plane.y != y) { - if (drmModeMoveCursor(b->drm.fd, output->crtc_id, x, y)) { - weston_log("failed to move cursor: %m\n"); - b->cursors_are_broken = 1; - } - - output->cursor_plane.x = x; - output->cursor_plane.y = y; + if (drmModeMoveCursor(b->drm.fd, output->crtc_id, x, y)) { + weston_log("failed to move cursor: %m\n"); + b->cursors_are_broken = 1; } } @@ -1400,6 +1389,10 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) pixman_region32_init(&overlap); primary = &output_base->compositor->primary_plane; + output->cursor_view = NULL; + output->cursor_plane.x = INT32_MIN; + output->cursor_plane.y = INT32_MIN; + wl_list_for_each_safe(ev, next, &output_base->compositor->view_list, link) { struct weston_surface *es = ev->surface; From fc175a7ec8f8064ba764cb1e68ff6d3b9eb256ac Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:22 +0100 Subject: [PATCH 0052/1642] compositor-drm: Add explicit type member to drm_fb Rather than magically trying to infer what the buffer is and what we should do with it when we go to destroy it, add an explicit type instead. In doing so, the test for dumb images (destroying them, but only if they're not the 'live' ones) is removed. This was dead code, as the only path which could cause us to shuffle images is drm_output_switch_mode. This calls drm_output_release_fb before the images are reallocated in drm_output_fini_pixman / drm_output_init_pixman, with the reallocation unconditionally destroying the images, so can never be hit. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 55 +++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 884d2e940..bf8162cc7 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -127,11 +127,19 @@ struct drm_mode { drmModeModeInfo mode_info; }; +enum drm_fb_type { + BUFFER_INVALID = 0, /**< never used */ + BUFFER_CLIENT, /**< directly sourced from client */ + BUFFER_PIXMAN_DUMB, /**< internal Pixman rendering */ + BUFFER_GBM_SURFACE, /**< internal EGL rendering */ +}; + struct drm_fb { + enum drm_fb_type type; + uint32_t fb_id, stride, handle, size; int width, height; int fd; - int is_client_buffer; struct weston_buffer_reference buffer_ref; /* Used by gbm fbs */ @@ -367,6 +375,7 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, if (ret) goto err_fb; + fb->type = BUFFER_PIXMAN_DUMB; fb->handle = create_arg.handle; fb->stride = create_arg.pitch; fb->size = create_arg.size; @@ -429,6 +438,8 @@ drm_fb_destroy_dumb(struct drm_fb *fb) { struct drm_mode_destroy_dumb destroy_arg; + assert(fb->type == BUFFER_PIXMAN_DUMB); + if (!fb->map) return; @@ -447,20 +458,23 @@ drm_fb_destroy_dumb(struct drm_fb *fb) } static struct drm_fb * -drm_fb_get_from_bo(struct gbm_bo *bo, - struct drm_backend *backend, uint32_t format) +drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, + uint32_t format, enum drm_fb_type type) { struct drm_fb *fb = gbm_bo_get_user_data(bo); uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 }; int ret; - if (fb) + if (fb) { + assert(fb->type == type); return fb; + } fb = zalloc(sizeof *fb); if (fb == NULL) return NULL; + fb->type = type; fb->bo = bo; fb->width = gbm_bo_get_width(bo); @@ -517,9 +531,7 @@ static void drm_fb_set_buffer(struct drm_fb *fb, struct weston_buffer *buffer) { assert(fb->buffer_ref.buffer == NULL); - - fb->is_client_buffer = 1; - + assert(fb->type == BUFFER_CLIENT); weston_buffer_reference(&fb->buffer_ref, buffer); } @@ -529,15 +541,19 @@ drm_output_release_fb(struct drm_output *output, struct drm_fb *fb) if (!fb) return; - if (fb->map && - (fb != output->dumb[0] && fb != output->dumb[1])) { - drm_fb_destroy_dumb(fb); - } else if (fb->bo) { - if (fb->is_client_buffer) - gbm_bo_destroy(fb->bo); - else - gbm_surface_release_buffer(output->gbm_surface, - fb->bo); + switch (fb->type) { + case BUFFER_PIXMAN_DUMB: + /* nothing: pixman buffers are destroyed manually */ + break; + case BUFFER_CLIENT: + gbm_bo_destroy(fb->bo); + break; + case BUFFER_GBM_SURFACE: + gbm_surface_release_buffer(output->gbm_surface, fb->bo); + break; + default: + assert(NULL); + break; } } @@ -636,7 +652,7 @@ drm_output_prepare_scanout_view(struct drm_output *output, return NULL; } - output->next = drm_fb_get_from_bo(bo, b, format); + output->next = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); if (!output->next) { gbm_bo_destroy(bo); return NULL; @@ -662,7 +678,8 @@ drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) return; } - output->next = drm_fb_get_from_bo(bo, b, output->gbm_format); + output->next = drm_fb_get_from_bo(bo, b, output->gbm_format, + BUFFER_GBM_SURFACE); if (!output->next) { weston_log("failed to get drm_fb for bo\n"); gbm_surface_release_buffer(output->gbm_surface, bo); @@ -1158,7 +1175,7 @@ drm_output_prepare_overlay_view(struct drm_output *output, return NULL; } - s->next = drm_fb_get_from_bo(bo, b, format); + s->next = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); if (!s->next) { gbm_bo_destroy(bo); return NULL; From 0b70fa4b56068879efc6e388bbffe2a9731a366e Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:23 +0100 Subject: [PATCH 0053/1642] compositor-drm: Store format in drm_fb This uses the new pixel-format helpers, so we can also replace depth/bpp with these. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 44 ++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index bf8162cc7..2f141660c 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -54,6 +54,7 @@ #include "gl-renderer.h" #include "weston-egl-ext.h" #include "pixman-renderer.h" +#include "pixel-formats.h" #include "libbacklight.h" #include "libinput-seat.h" #include "launcher-util.h" @@ -138,6 +139,7 @@ struct drm_fb { enum drm_fb_type type; uint32_t fb_id, stride, handle, size; + const struct pixel_format_info *format; int width, height; int fd; struct weston_buffer_reference buffer_ref; @@ -344,7 +346,6 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, { struct drm_fb *fb; int ret; - uint32_t bpp, depth; struct drm_mode_create_dumb create_arg; struct drm_mode_destroy_dumb destroy_arg; @@ -354,20 +355,21 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, if (!fb) return NULL; - switch (format) { - case GBM_FORMAT_XRGB8888: - bpp = 32; - depth = 24; - break; - case GBM_FORMAT_RGB565: - bpp = depth = 16; - break; - default: - return NULL; + fb->format = pixel_format_get_info(format); + if (!fb->format) { + weston_log("failed to look up format 0x%lx\n", + (unsigned long) format); + goto err_fb; + } + + if (!fb->format->depth || !fb->format->bpp) { + weston_log("format 0x%lx is not compatible with dumb buffers\n", + (unsigned long) format); + goto err_fb; } memset(&create_arg, 0, sizeof create_arg); - create_arg.bpp = bpp; + create_arg.bpp = fb->format->bpp; create_arg.width = width; create_arg.height = height; @@ -393,7 +395,8 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, offsets[0] = 0; ret = drmModeAddFB2(b->drm.fd, width, height, - format, handles, pitches, offsets, + fb->format->format, + handles, pitches, offsets, &fb->fb_id, 0); if (ret) { weston_log("addfb2 failed: %m\n"); @@ -402,7 +405,8 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, } if (ret) { - ret = drmModeAddFB(b->drm.fd, width, height, depth, bpp, + ret = drmModeAddFB(b->drm.fd, width, height, + fb->format->depth, fb->format->bpp, fb->stride, fb->handle, &fb->fb_id); } @@ -481,9 +485,16 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, fb->height = gbm_bo_get_height(bo); fb->stride = gbm_bo_get_stride(bo); fb->handle = gbm_bo_get_handle(bo).u32; + fb->format = pixel_format_get_info(format); fb->size = fb->stride * fb->height; fb->fd = backend->drm.fd; + if (!fb->format) { + weston_log("couldn't look up format 0x%lx\n", + (unsigned long) format); + goto err_free; + } + if (backend->min_width > fb->width || fb->width > backend->max_width || backend->min_height > fb->height || @@ -509,9 +520,10 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, } } - if (ret) + if (ret && fb->format->depth && fb->format->bpp) ret = drmModeAddFB(backend->drm.fd, fb->width, fb->height, - 24, 32, fb->stride, fb->handle, &fb->fb_id); + fb->format->depth, fb->format->bpp, + fb->stride, fb->handle, &fb->fb_id); if (ret) { weston_log("failed to create kms fb: %m\n"); From 576f42effe1af94f1bf546b1b91ef85b309d0945 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Tue, 4 Apr 2017 17:54:24 +0100 Subject: [PATCH 0054/1642] compositor-drm: Refactor destroy drm_fb function The drm_fb destroy callback to mostly the same thing regardless of whether the buffer is a dumb buffer or gbm buffer. This patch refactors the common parts into a new function that can be called for both cases. [daniels: Rebased on top of fb->fd changes, cosmetic changes.] Signed-off-by: Tomohito Esaki Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 60 ++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 2f141660c..8788da730 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -328,16 +328,38 @@ drm_output_find_by_connector(struct drm_backend *b, uint32_t connector_id) } static void -drm_fb_destroy_callback(struct gbm_bo *bo, void *data) +drm_fb_destroy(struct drm_fb *fb) { - struct drm_fb *fb = data; - - if (fb->fb_id) + if (fb->fb_id != 0) drmModeRmFB(fb->fd, fb->fb_id); - weston_buffer_reference(&fb->buffer_ref, NULL); + free(fb); +} + +static void +drm_fb_destroy_dumb(struct drm_fb *fb) +{ + struct drm_mode_destroy_dumb destroy_arg; + + assert(fb->type == BUFFER_PIXMAN_DUMB); + + if (fb->map && fb->size > 0) + munmap(fb->map, fb->size); + + memset(&destroy_arg, 0, sizeof(destroy_arg)); + destroy_arg.handle = fb->handle; + drmIoctl(fb->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); + + drm_fb_destroy(fb); +} + +static void +drm_fb_destroy_gbm(struct gbm_bo *bo, void *data) +{ + struct drm_fb *fb = data; - free(data); + assert(fb->type == BUFFER_GBM_SURFACE || fb->type == BUFFER_CLIENT); + drm_fb_destroy(fb); } static struct drm_fb * @@ -437,30 +459,6 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, return NULL; } -static void -drm_fb_destroy_dumb(struct drm_fb *fb) -{ - struct drm_mode_destroy_dumb destroy_arg; - - assert(fb->type == BUFFER_PIXMAN_DUMB); - - if (!fb->map) - return; - - if (fb->fb_id) - drmModeRmFB(fb->fd, fb->fb_id); - - weston_buffer_reference(&fb->buffer_ref, NULL); - - munmap(fb->map, fb->size); - - memset(&destroy_arg, 0, sizeof(destroy_arg)); - destroy_arg.handle = fb->handle; - drmIoctl(fb->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); - - free(fb); -} - static struct drm_fb * drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, uint32_t format, enum drm_fb_type type) @@ -530,7 +528,7 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, goto err_free; } - gbm_bo_set_user_data(bo, fb, drm_fb_destroy_callback); + gbm_bo_set_user_data(bo, fb, drm_fb_destroy_gbm); return fb; From 05a5ac2b8f5a7e702913f9cf78e79673a819f4ff Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:25 +0100 Subject: [PATCH 0055/1642] compositor-drm: Drop output from release_fb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We only need it for the GBM surface the FB was originally created against; a mismatch here is very bad indeed, so no reason to pass it in explictly every time rather than store it. Following patches change drm_fb to be explicitly reference counted; in order to reduce churn, rename drm_output_release_fb to drm_fb_unref whilst changing its call signature here, even though it does not yet actually perform reference counting. Signed-off-by: Daniel Stone Reviewed-by: Armin Krezović Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 8788da730..e4c91f221 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -146,6 +146,7 @@ struct drm_fb { /* Used by gbm fbs */ struct gbm_bo *bo; + struct gbm_surface *gbm_surface; /* Used by dumb fbs */ void *map; @@ -546,7 +547,7 @@ drm_fb_set_buffer(struct drm_fb *fb, struct weston_buffer *buffer) } static void -drm_output_release_fb(struct drm_output *output, struct drm_fb *fb) +drm_fb_unref(struct drm_fb *fb) { if (!fb) return; @@ -559,7 +560,7 @@ drm_output_release_fb(struct drm_output *output, struct drm_fb *fb) gbm_bo_destroy(fb->bo); break; case BUFFER_GBM_SURFACE: - gbm_surface_release_buffer(output->gbm_surface, fb->bo); + gbm_surface_release_buffer(fb->gbm_surface, fb->bo); break; default: assert(NULL); @@ -695,6 +696,7 @@ drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) gbm_surface_release_buffer(output->gbm_surface, bo); return; } + output->next->gbm_surface = output->gbm_surface; } static void @@ -883,7 +885,7 @@ drm_output_repaint(struct weston_output *output_base, err_pageflip: output->cursor_view = NULL; if (output->next) { - drm_output_release_fb(output, output->next); + drm_fb_unref(output->next); output->next = NULL; } @@ -988,7 +990,7 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, drm_output_update_msc(output, frame); output->vblank_pending = 0; - drm_output_release_fb(output, s->current); + drm_fb_unref(s->current); s->current = s->next; s->next = NULL; @@ -1022,7 +1024,7 @@ page_flip_handler(int fd, unsigned int frame, * we just want to page flip to the current buffer to get an accurate * timestamp */ if (output->page_flip_pending) { - drm_output_release_fb(output, output->current); + drm_fb_unref(output->current); output->current = output->next; output->next = NULL; } @@ -1559,8 +1561,8 @@ drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mo WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; /* reset rendering stuff. */ - drm_output_release_fb(output, output->current); - drm_output_release_fb(output, output->next); + drm_fb_unref(output->current); + drm_fb_unref(output->next); output->current = output->next = NULL; if (b->use_pixman) { @@ -2784,8 +2786,8 @@ destroy_sprites(struct drm_backend *backend) sprite->plane_id, output->crtc_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - drm_output_release_fb(output, sprite->current); - drm_output_release_fb(output, sprite->next); + drm_fb_unref(sprite->current); + drm_fb_unref(sprite->next); weston_plane_release(&sprite->plane); free(sprite); } From 6e7a961d43ca4f8a4708118c2c770f3ec769c082 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:26 +0100 Subject: [PATCH 0056/1642] compositor-drm: Refcount drm_fb Sometimes we need to duplicate an existing drm_fb, e.g. when pageflipping to the same buffer to kickstart the repaint loop. To handle situations like these, and simplify resource management for dumb and cursor buffers, refcount drm_fb. drm_fb_get_from_bo has a path where it may reuse a drm_fb, if the BO has been imported and not released yet. As drm_fb_unref now relies on actual refcounting (backed up by asserts), we add a balancing drm_fb_ref() to the path where we return a reused drm_fb. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index e4c91f221..57ee9c67d 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -138,6 +138,8 @@ enum drm_fb_type { struct drm_fb { enum drm_fb_type type; + int refcnt; + uint32_t fb_id, stride, handle, size; const struct pixel_format_info *format; int width, height; @@ -378,6 +380,8 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, if (!fb) return NULL; + fb->refcnt = 1; + fb->format = pixel_format_get_info(format); if (!fb->format) { weston_log("failed to look up format 0x%lx\n", @@ -460,6 +464,13 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, return NULL; } +static struct drm_fb * +drm_fb_ref(struct drm_fb *fb) +{ + fb->refcnt++; + return fb; +} + static struct drm_fb * drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, uint32_t format, enum drm_fb_type type) @@ -470,7 +481,7 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, if (fb) { assert(fb->type == type); - return fb; + return drm_fb_ref(fb); } fb = zalloc(sizeof *fb); @@ -478,6 +489,7 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, return NULL; fb->type = type; + fb->refcnt = 1; fb->bo = bo; fb->width = gbm_bo_get_width(bo); @@ -552,9 +564,13 @@ drm_fb_unref(struct drm_fb *fb) if (!fb) return; + assert(fb->refcnt > 0); + if (--fb->refcnt > 0) + return; + switch (fb->type) { case BUFFER_PIXMAN_DUMB: - /* nothing: pixman buffers are destroyed manually */ + drm_fb_destroy_dumb(fb); break; case BUFFER_CLIENT: gbm_bo_destroy(fb->bo); @@ -715,7 +731,7 @@ drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) output->current_image ^= 1; - output->next = output->dumb[output->current_image]; + output->next = drm_fb_ref(output->dumb[output->current_image]); pixman_renderer_output_set_buffer(&output->base, output->image[output->current_image]); @@ -2060,7 +2076,7 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) err: for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { if (output->dumb[i]) - drm_fb_destroy_dumb(output->dumb[i]); + drm_fb_unref(output->dumb[i]); if (output->image[i]) pixman_image_unref(output->image[i]); @@ -2080,8 +2096,8 @@ drm_output_fini_pixman(struct drm_output *output) pixman_region32_fini(&output->previous_damage); for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { - drm_fb_destroy_dumb(output->dumb[i]); pixman_image_unref(output->image[i]); + drm_fb_unref(output->dumb[i]); output->dumb[i] = NULL; output->image[i] = NULL; } @@ -2570,6 +2586,12 @@ drm_output_deinit(struct weston_output *base) struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); + /* output->next must not be set here; + * destroy_pending/disable_pending exist to guarantee exactly this. */ + assert(!output->next); + drm_fb_unref(output->current); + output->current = NULL; + if (b->use_pixman) drm_output_fini_pixman(output); else From e42568313c2852c7e58b66ee09dc70fbe66b95f6 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:27 +0100 Subject: [PATCH 0057/1642] compositor-drm: Use drm_fb for cursor buffers Now that we have better types in drm_fb, use it for cursor buffers as well. This gives us easier refcounting for our cursors, as well as a unified buffer-destruction path. Currently this makes no difference, as the KMS legacy cursor update API uses GEM names directly, and never touches DRM FBs. However, the cursor plane becomes a regular KMS plane under atomic, at which point we require DRM FBs. Take the opportunity to move to drm_fb ahead of time. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 70 +++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 57ee9c67d..4089afc41 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -133,6 +133,7 @@ enum drm_fb_type { BUFFER_CLIENT, /**< directly sourced from client */ BUFFER_PIXMAN_DUMB, /**< internal Pixman rendering */ BUFFER_GBM_SURFACE, /**< internal EGL rendering */ + BUFFER_CURSOR, /**< internal cursor buffer */ }; struct drm_fb { @@ -181,7 +182,7 @@ struct drm_output { int disable_pending; struct gbm_surface *gbm_surface; - struct gbm_bo *gbm_cursor_bo[2]; + struct drm_fb *gbm_cursor_fb[2]; struct weston_plane cursor_plane; struct weston_plane fb_plane; struct weston_view *cursor_view; @@ -361,7 +362,8 @@ drm_fb_destroy_gbm(struct gbm_bo *bo, void *data) { struct drm_fb *fb = data; - assert(fb->type == BUFFER_GBM_SURFACE || fb->type == BUFFER_CLIENT); + assert(fb->type == BUFFER_GBM_SURFACE || fb->type == BUFFER_CLIENT || + fb->type == BUFFER_CURSOR); drm_fb_destroy(fb); } @@ -572,6 +574,7 @@ drm_fb_unref(struct drm_fb *fb) case BUFFER_PIXMAN_DUMB: drm_fb_destroy_dumb(fb); break; + case BUFFER_CURSOR: case BUFFER_CLIENT: gbm_bo_destroy(fb->bo); break; @@ -1387,7 +1390,7 @@ drm_output_set_cursor(struct drm_output *output) pixman_region32_fini(&output->cursor_plane.damage); pixman_region32_init(&output->cursor_plane.damage); output->current_cursor ^= 1; - bo = output->gbm_cursor_bo[output->current_cursor]; + bo = output->gbm_cursor_fb[output->current_cursor]->bo; cursor_bo_update(b, bo, ev); handle = gbm_bo_get_handle(bo).s32; @@ -1970,6 +1973,48 @@ find_crtc_for_connector(struct drm_backend *b, return ret; } +static void drm_output_fini_cursor_egl(struct drm_output *output) +{ + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) { + drm_fb_unref(output->gbm_cursor_fb[i]); + output->gbm_cursor_fb[i] = NULL; + } +} + +static int +drm_output_init_cursor_egl(struct drm_output *output, struct drm_backend *b) +{ + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) { + struct gbm_bo *bo; + + bo = gbm_bo_create(b->gbm, b->cursor_width, b->cursor_height, + GBM_FORMAT_ARGB8888, + GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); + if (!bo) + goto err; + + output->gbm_cursor_fb[i] = + drm_fb_get_from_bo(bo, b, GBM_FORMAT_ARGB8888, + BUFFER_CURSOR); + if (!output->gbm_cursor_fb[i]) { + gbm_bo_destroy(bo); + goto err; + } + } + + return 0; + +err: + weston_log("cursor buffers unavailable, using gl cursors\n"); + b->cursors_are_broken = 1; + drm_output_fini_cursor_egl(output); + return -1; +} + /* Init output state that depends on gl or gbm */ static int drm_output_init_egl(struct drm_output *output, struct drm_backend *b) @@ -1978,7 +2023,7 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) output->gbm_format, fallback_format_for(output->gbm_format), }; - int i, flags, n_formats = 1; + int n_formats = 1; output->gbm_surface = gbm_surface_create(b->gbm, output->base.current_mode->width, @@ -2004,21 +2049,7 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) return -1; } - flags = GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE; - - for (i = 0; i < 2; i++) { - if (output->gbm_cursor_bo[i]) - continue; - - output->gbm_cursor_bo[i] = - gbm_bo_create(b->gbm, b->cursor_width, b->cursor_height, - GBM_FORMAT_ARGB8888, flags); - } - - if (output->gbm_cursor_bo[0] == NULL || output->gbm_cursor_bo[1] == NULL) { - weston_log("cursor buffers unavailable, using gl cursors\n"); - b->cursors_are_broken = 1; - } + drm_output_init_cursor_egl(output, b); return 0; } @@ -2028,6 +2059,7 @@ drm_output_fini_egl(struct drm_output *output) { gl_renderer->output_destroy(&output->base); gbm_surface_destroy(output->gbm_surface); + drm_output_fini_cursor_egl(output); } static int From 5bb8f58fd2c4f549fc97c7f857c88484f8c81cb8 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:28 +0100 Subject: [PATCH 0058/1642] compositor-drm: Rename current/next FB members 'next' is used as a framebuffer which has either been rendered but not had a configuration request (pageflip or CRTC set) applied to it, or when for a framebuffer that has had configuration requested but not applied (delayed pageflip where the event has not been applied). 'current' is used as the last framebuffer for which we know configuration has been fully applied, i.e. CRTC set executed or pageflip requested and event received. Rename these members to fb_current and fb_pending, doing some small reordering of drm_output whilst in the vicinity. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 104 +++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 4089afc41..44c2105f4 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -172,23 +172,25 @@ struct drm_output { drmModeCrtcPtr original_crtc; struct drm_edid edid; drmModePropertyPtr dpms_prop; - uint32_t gbm_format; enum dpms_enum dpms; + struct backlight *backlight; int vblank_pending; int page_flip_pending; int destroy_pending; int disable_pending; - struct gbm_surface *gbm_surface; struct drm_fb *gbm_cursor_fb[2]; struct weston_plane cursor_plane; - struct weston_plane fb_plane; struct weston_view *cursor_view; int current_cursor; - struct drm_fb *current, *next; - struct backlight *backlight; + + struct gbm_surface *gbm_surface; + uint32_t gbm_format; + + struct weston_plane fb_plane; + struct drm_fb *fb_current, *fb_pending; struct drm_fb *dumb[2]; pixman_image_t *image[2]; @@ -210,7 +212,7 @@ struct drm_sprite { struct weston_plane plane; - struct drm_fb *current, *next; + struct drm_fb *fb_current, *fb_pending; struct drm_output *output; struct drm_backend *backend; @@ -682,13 +684,13 @@ drm_output_prepare_scanout_view(struct drm_output *output, return NULL; } - output->next = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); - if (!output->next) { + output->fb_pending = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); + if (!output->fb_pending) { gbm_bo_destroy(bo); return NULL; } - drm_fb_set_buffer(output->next, buffer); + drm_fb_set_buffer(output->fb_pending, buffer); return &output->fb_plane; } @@ -708,14 +710,14 @@ drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) return; } - output->next = drm_fb_get_from_bo(bo, b, output->gbm_format, - BUFFER_GBM_SURFACE); - if (!output->next) { + output->fb_pending = drm_fb_get_from_bo(bo, b, output->gbm_format, + BUFFER_GBM_SURFACE); + if (!output->fb_pending) { weston_log("failed to get drm_fb for bo\n"); gbm_surface_release_buffer(output->gbm_surface, bo); return; } - output->next->gbm_surface = output->gbm_surface; + output->fb_pending->gbm_surface = output->gbm_surface; } static void @@ -734,7 +736,7 @@ drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) output->current_image ^= 1; - output->next = drm_fb_ref(output->dumb[output->current_image]); + output->fb_pending = drm_fb_ref(output->dumb[output->current_image]); pixman_renderer_output_set_buffer(&output->base, output->image[output->current_image]); @@ -821,16 +823,16 @@ drm_output_repaint(struct weston_output *output_base, if (output->disable_pending || output->destroy_pending) return -1; - if (!output->next) + if (!output->fb_pending) drm_output_render(output, damage); - if (!output->next) + if (!output->fb_pending) return -1; mode = container_of(output->base.current_mode, struct drm_mode, base); - if (!output->current || - output->current->stride != output->next->stride) { + if (!output->fb_current || + output->fb_current->stride != output->fb_pending->stride) { ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, - output->next->fb_id, 0, 0, + output->fb_pending->fb_id, 0, 0, &output->connector_id, 1, &mode->mode_info); if (ret) { @@ -841,7 +843,7 @@ drm_output_repaint(struct weston_output *output_base, } if (drmModePageFlip(backend->drm.fd, output->crtc_id, - output->next->fb_id, + output->fb_pending->fb_id, DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { weston_log("queueing pageflip failed: %m\n"); goto err_pageflip; @@ -865,12 +867,12 @@ drm_output_repaint(struct weston_output *output_base, .request.sequence = 1, }; - if ((!s->current && !s->next) || + if ((!s->fb_current && !s->fb_pending) || !drm_sprite_crtc_supported(output, s)) continue; - if (s->next && !backend->sprites_hidden) - fb_id = s->next->fb_id; + if (s->fb_pending && !backend->sprites_hidden) + fb_id = s->fb_pending->fb_id; ret = drmModeSetPlane(backend->drm.fd, s->plane_id, output->crtc_id, fb_id, flags, @@ -903,9 +905,9 @@ drm_output_repaint(struct weston_output *output_base, err_pageflip: output->cursor_view = NULL; - if (output->next) { - drm_fb_unref(output->next); - output->next = NULL; + if (output->fb_pending) { + drm_fb_unref(output->fb_pending); + output->fb_pending = NULL; } return -1; @@ -931,7 +933,7 @@ drm_output_start_repaint_loop(struct weston_output *output_base) if (output->disable_pending || output->destroy_pending) return; - if (!output->current) { + if (!output->fb_current) { /* We can't page flip if there's no mode set */ goto finish_frame; } @@ -965,7 +967,7 @@ drm_output_start_repaint_loop(struct weston_output *output_base) /* Immediate query didn't provide valid timestamp. * Use pageflip fallback. */ - fb_id = output->current->fb_id; + fb_id = output->fb_current->fb_id; if (drmModePageFlip(backend->drm.fd, output->crtc_id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { @@ -1009,9 +1011,9 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, drm_output_update_msc(output, frame); output->vblank_pending = 0; - drm_fb_unref(s->current); - s->current = s->next; - s->next = NULL; + drm_fb_unref(s->fb_current); + s->fb_current = s->fb_pending; + s->fb_pending = NULL; if (!output->page_flip_pending) { /* Stop the pageflip timer instead of rearming it here */ @@ -1043,9 +1045,9 @@ page_flip_handler(int fd, unsigned int frame, * we just want to page flip to the current buffer to get an accurate * timestamp */ if (output->page_flip_pending) { - drm_fb_unref(output->current); - output->current = output->next; - output->next = NULL; + drm_fb_unref(output->fb_current); + output->fb_current = output->fb_pending; + output->fb_pending = NULL; } output->page_flip_pending = 0; @@ -1147,7 +1149,7 @@ drm_output_prepare_overlay_view(struct drm_output *output, if (!drm_sprite_crtc_supported(output, s)) continue; - if (!s->next) { + if (!s->fb_pending) { found = 1; break; } @@ -1206,13 +1208,13 @@ drm_output_prepare_overlay_view(struct drm_output *output, return NULL; } - s->next = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); - if (!s->next) { + s->fb_pending = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); + if (!s->fb_pending) { gbm_bo_destroy(bo); return NULL; } - drm_fb_set_buffer(s->next, ev->surface->buffer_ref.buffer); + drm_fb_set_buffer(s->fb_pending, ev->surface->buffer_ref.buffer); box = pixman_region32_extents(&ev->transform.boundingbox); s->plane.x = box->x1; @@ -1580,9 +1582,9 @@ drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mo WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; /* reset rendering stuff. */ - drm_fb_unref(output->current); - drm_fb_unref(output->next); - output->current = output->next = NULL; + drm_fb_unref(output->fb_current); + drm_fb_unref(output->fb_pending); + output->fb_current = output->fb_pending = NULL; if (b->use_pixman) { drm_output_fini_pixman(output); @@ -2618,11 +2620,11 @@ drm_output_deinit(struct weston_output *base) struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); - /* output->next must not be set here; + /* output->fb_pending must not be set here; * destroy_pending/disable_pending exist to guarantee exactly this. */ - assert(!output->next); - drm_fb_unref(output->current); - output->current = NULL; + assert(!output->fb_pending); + drm_fb_unref(output->fb_current); + output->fb_current = NULL; if (b->use_pixman) drm_output_fini_pixman(output); @@ -2809,8 +2811,8 @@ create_sprites(struct drm_backend *b) sprite->possible_crtcs = plane->possible_crtcs; sprite->plane_id = plane->plane_id; - sprite->current = NULL; - sprite->next = NULL; + sprite->fb_current = NULL; + sprite->fb_pending = NULL; sprite->backend = b; sprite->count_formats = plane->count_formats; memcpy(sprite->formats, plane->formats, @@ -2840,8 +2842,8 @@ destroy_sprites(struct drm_backend *backend) sprite->plane_id, output->crtc_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - drm_fb_unref(sprite->current); - drm_fb_unref(sprite->next); + drm_fb_unref(sprite->fb_current); + drm_fb_unref(sprite->fb_pending); weston_plane_release(&sprite->plane); free(sprite); } @@ -3277,7 +3279,7 @@ recorder_frame_notify(struct wl_listener *listener, void *data) if (!output->recorder) return; - ret = drmPrimeHandleToFD(b->drm.fd, output->current->handle, + ret = drmPrimeHandleToFD(b->drm.fd, output->fb_current->handle, DRM_CLOEXEC, &fd); if (ret) { weston_log("[libva recorder] " @@ -3286,7 +3288,7 @@ recorder_frame_notify(struct wl_listener *listener, void *data) } ret = vaapi_recorder_frame(output->recorder, fd, - output->current->stride); + output->fb_current->stride); if (ret < 0) { weston_log("[libva recorder] aborted: %m\n"); recorder_destroy(output); From 4e84f7dd32b54a05eabd23839e1e6ac14459712e Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:29 +0100 Subject: [PATCH 0059/1642] compositor-drm: Reshuffle drm_output_render Call drm_output_render unconditionally, doing an early exit if we're already rendering a client buffer on the primary plane. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 44c2105f4..835c9ca58 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -752,6 +752,11 @@ drm_output_render(struct drm_output *output, pixman_region32_t *damage) struct weston_compositor *c = output->base.compositor; struct drm_backend *b = to_drm_backend(c); + /* If we already have a client buffer promoted to scanout, then we don't + * want to render. */ + if (output->fb_pending) + return; + if (b->use_pixman) drm_output_render_pixman(output, damage); else @@ -823,8 +828,7 @@ drm_output_repaint(struct weston_output *output_base, if (output->disable_pending || output->destroy_pending) return -1; - if (!output->fb_pending) - drm_output_render(output, damage); + drm_output_render(output, damage); if (!output->fb_pending) return -1; From 95d48a2a88688c01d5a804935e84bd8e02f7905e Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:30 +0100 Subject: [PATCH 0060/1642] compositor-drm: Return FB directly from render Instead of setting state members directly in the drm_output_render functions (to paint using Pixman or GL), just return a drm_fb, and let the core function place it in state. This brings damage handling in line with repaint state, so we do not clear damage if repaint fails. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 835c9ca58..e27bc1d43 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -695,11 +695,12 @@ drm_output_prepare_scanout_view(struct drm_output *output, return &output->fb_plane; } -static void +static struct drm_fb * drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) { struct drm_backend *b = to_drm_backend(output->base.compositor); struct gbm_bo *bo; + struct drm_fb *ret; output->base.compositor->renderer->repaint_output(&output->base, damage); @@ -707,20 +708,21 @@ drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) bo = gbm_surface_lock_front_buffer(output->gbm_surface); if (!bo) { weston_log("failed to lock front buffer: %m\n"); - return; + return NULL; } - output->fb_pending = drm_fb_get_from_bo(bo, b, output->gbm_format, - BUFFER_GBM_SURFACE); - if (!output->fb_pending) { + ret = drm_fb_get_from_bo(bo, b, output->gbm_format, BUFFER_GBM_SURFACE); + if (!ret) { weston_log("failed to get drm_fb for bo\n"); gbm_surface_release_buffer(output->gbm_surface, bo); - return; + return NULL; } - output->fb_pending->gbm_surface = output->gbm_surface; + ret->gbm_surface = output->gbm_surface; + + return ret; } -static void +static struct drm_fb * drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) { struct weston_compositor *ec = output->base.compositor; @@ -736,7 +738,6 @@ drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) output->current_image ^= 1; - output->fb_pending = drm_fb_ref(output->dumb[output->current_image]); pixman_renderer_output_set_buffer(&output->base, output->image[output->current_image]); @@ -744,6 +745,8 @@ drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) pixman_region32_fini(&total_damage); pixman_region32_fini(&previous_damage); + + return drm_fb_ref(output->dumb[output->current_image]); } static void @@ -751,6 +754,7 @@ drm_output_render(struct drm_output *output, pixman_region32_t *damage) { struct weston_compositor *c = output->base.compositor; struct drm_backend *b = to_drm_backend(c); + struct drm_fb *fb; /* If we already have a client buffer promoted to scanout, then we don't * want to render. */ @@ -758,9 +762,13 @@ drm_output_render(struct drm_output *output, pixman_region32_t *damage) return; if (b->use_pixman) - drm_output_render_pixman(output, damage); + fb = drm_output_render_pixman(output, damage); else - drm_output_render_gl(output, damage); + fb = drm_output_render_gl(output, damage); + + if (!fb) + return; + output->fb_pending = fb; pixman_region32_subtract(&c->primary_plane.damage, &c->primary_plane.damage, damage); From 863e66b003717f3085e1b16d3b13e674353be772 Mon Sep 17 00:00:00 2001 From: Emil Velikov Date: Tue, 4 Apr 2017 18:07:34 +0100 Subject: [PATCH 0061/1642] compositor-drm: correctly set the version of the drmEventContext We implement v2 so use that instead of the DRM_EVENT_CONTEXT_VERSION macro. The latter defines the version of the drmEventContext struct declared in the header [used in the current build] and can be 2, 3 or even 1000. Signed-off-by: Emil Velikov Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index e27bc1d43..095f427c4 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1623,7 +1623,7 @@ on_drm_input(int fd, uint32_t mask, void *data) drmEventContext evctx; memset(&evctx, 0, sizeof evctx); - evctx.version = DRM_EVENT_CONTEXT_VERSION; + evctx.version = 2; evctx.page_flip_handler = page_flip_handler; evctx.vblank_handler = vblank_handler; drmHandleEvent(fd, &evctx); From f30a18c1c3a12a964561c761a1a3b91ed6864289 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:31 +0100 Subject: [PATCH 0062/1642] compositor-drm: Introduce fb_last member Previously, framebuffers were stored as fb_current and fb_pending. In this scheme, current was the last buffer that the kernel/hardware had acknowledged displaying: a framebuffer would be created, set as fb_pending, and Weston would request the kernel display it. When the kernel signals that the request was completed and the hardware had made the buffer current (i.e. page_flip_handler / vblank_handler), we would unreference the old fb_current, and promote fb_pending to fb_current. In other words, the view is 'which buffer has turned to light?'. This patch changes them to a tristate of fb_last, fb_current and fb_pending, based around the kernel's view of the current state. fb_pending is used purely as a staging area for request construction; when the kernel acknowledges a request (e.g. drmModePageFlip returns 0), the previous buffer is moved to fb_last, and this new buffer to fb_current. When the kernel signals that the request has completed and the hardware has made the buffer current, we simply unreference and clear fb_last, without touching fb_current/fb_pending. The view here is now 'which state is current in the kernel?'. As all state changes are incremental on the last state submitted to the kernel, even if the hardware has not yet been able to make it current, this simplifies state tracking: all state submissions will always be relative to fb_current, rather than the previous (fb_pending) ? fb_pending : fb_current. The use of fb_pending is strictly bounded between a repaint cycle (including a grouped set of repaints) beginning, and those repaints being flushed to the kernel. fb_current will always be valid between an output's first repaint flush, and when a disable/destroy request has been processed. For a plane, it will be valid when a repaint cycle enabling that plane has been flushed, and when a repaint cycle disabling that plane has been flushed. fb_last is only present when a repaint request for the output/plane has been submitted, but not yet completed by the hardware. This is the same set of constructs which will be used for storing plane/output state objects in future patches. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 61 ++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 095f427c4..732b35ce0 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -190,7 +190,15 @@ struct drm_output { uint32_t gbm_format; struct weston_plane fb_plane; - struct drm_fb *fb_current, *fb_pending; + + /* The last framebuffer submitted to the kernel for this CRTC. */ + struct drm_fb *fb_current; + /* The previously-submitted framebuffer, where the hardware has not + * yet acknowledged display of fb_current. */ + struct drm_fb *fb_last; + /* Framebuffer we are going to submit to the kernel when the current + * repaint is flushed. */ + struct drm_fb *fb_pending; struct drm_fb *dumb[2]; pixman_image_t *image[2]; @@ -212,7 +220,6 @@ struct drm_sprite { struct weston_plane plane; - struct drm_fb *fb_current, *fb_pending; struct drm_output *output; struct drm_backend *backend; @@ -220,6 +227,15 @@ struct drm_sprite { uint32_t plane_id; uint32_t count_formats; + /* The last framebuffer submitted to the kernel for this plane. */ + struct drm_fb *fb_current; + /* The previously-submitted framebuffer, where the hardware has not + * yet acknowledged display of fb_current. */ + struct drm_fb *fb_last; + /* Framebuffer we are going to submit to the kernel when the current + * repaint is flushed. */ + struct drm_fb *fb_pending; + int32_t src_x, src_y; uint32_t src_w, src_h; uint32_t dest_x, dest_y; @@ -836,6 +852,8 @@ drm_output_repaint(struct weston_output *output_base, if (output->disable_pending || output->destroy_pending) return -1; + assert(!output->fb_last); + drm_output_render(output, damage); if (!output->fb_pending) return -1; @@ -861,6 +879,10 @@ drm_output_repaint(struct weston_output *output_base, goto err_pageflip; } + output->fb_last = output->fb_current; + output->fb_current = output->fb_pending; + output->fb_pending = NULL; + output->page_flip_pending = 1; if (output->pageflip_timer) @@ -879,6 +901,8 @@ drm_output_repaint(struct weston_output *output_base, .request.sequence = 1, }; + /* XXX: Set output much earlier, so we don't attempt to place + * planes on entirely the wrong output. */ if ((!s->fb_current && !s->fb_pending) || !drm_sprite_crtc_supported(output, s)) continue; @@ -910,6 +934,9 @@ drm_output_repaint(struct weston_output *output_base, } s->output = output; + s->fb_last = s->fb_current; + s->fb_current = s->fb_pending; + s->fb_pending = NULL; output->vblank_pending = 1; } @@ -1023,9 +1050,9 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, drm_output_update_msc(output, frame); output->vblank_pending = 0; - drm_fb_unref(s->fb_current); - s->fb_current = s->fb_pending; - s->fb_pending = NULL; + assert(s->fb_last || s->fb_current); + drm_fb_unref(s->fb_last); + s->fb_last = NULL; if (!output->page_flip_pending) { /* Stop the pageflip timer instead of rearming it here */ @@ -1057,9 +1084,8 @@ page_flip_handler(int fd, unsigned int frame, * we just want to page flip to the current buffer to get an accurate * timestamp */ if (output->page_flip_pending) { - drm_fb_unref(output->fb_current); - output->fb_current = output->fb_pending; - output->fb_pending = NULL; + drm_fb_unref(output->fb_last); + output->fb_last = NULL; } output->page_flip_pending = 0; @@ -1593,10 +1619,16 @@ drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mo output->base.current_mode->flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; - /* reset rendering stuff. */ + /* XXX: This drops our current buffer too early, before we've started + * displaying it. Ideally this should be much more atomic and + * integrated with a full repaint cycle, rather than doing a + * sledgehammer modeswitch first, and only later showing new + * content. + */ drm_fb_unref(output->fb_current); - drm_fb_unref(output->fb_pending); - output->fb_current = output->fb_pending = NULL; + assert(!output->fb_last); + assert(!output->fb_pending); + output->fb_last = output->fb_current = NULL; if (b->use_pixman) { drm_output_fini_pixman(output); @@ -2632,8 +2664,9 @@ drm_output_deinit(struct weston_output *base) struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); - /* output->fb_pending must not be set here; + /* output->fb_last and output->fb_pending must not be set here; * destroy_pending/disable_pending exist to guarantee exactly this. */ + assert(!output->fb_last); assert(!output->fb_pending); drm_fb_unref(output->fb_current); output->fb_current = NULL; @@ -2823,6 +2856,7 @@ create_sprites(struct drm_backend *b) sprite->possible_crtcs = plane->possible_crtcs; sprite->plane_id = plane->plane_id; + sprite->fb_last = NULL; sprite->fb_current = NULL; sprite->fb_pending = NULL; sprite->backend = b; @@ -2854,8 +2888,9 @@ destroy_sprites(struct drm_backend *backend) sprite->plane_id, output->crtc_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + assert(!sprite->fb_last); + assert(!sprite->fb_pending); drm_fb_unref(sprite->fb_current); - drm_fb_unref(sprite->fb_pending); weston_plane_release(&sprite->plane); free(sprite); } From 65d87d071f739fe2580a0c3ecb95686384eb0bf3 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:32 +0100 Subject: [PATCH 0063/1642] compositor-drm: Turn vblank_pending from bool to refcount vblank_pending is currently a bool, which is reset on every vblank requests (i.e. sprite pageflip). This can occur more than once per frame, so turn it into a callback, so we only fire frame-done when we've collected all the events. This fixes unexpected behaviour when multiple views per output have been promoted to DRM planes. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 732b35ce0..887823717 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -937,7 +937,7 @@ drm_output_repaint(struct weston_output *output_base, s->fb_last = s->fb_current; s->fb_current = s->fb_pending; s->fb_pending = NULL; - output->vblank_pending = 1; + output->vblank_pending++; } return 0; @@ -1048,13 +1048,14 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; drm_output_update_msc(output, frame); - output->vblank_pending = 0; + output->vblank_pending--; + assert(output->vblank_pending >= 0); assert(s->fb_last || s->fb_current); drm_fb_unref(s->fb_last); s->fb_last = NULL; - if (!output->page_flip_pending) { + if (!output->page_flip_pending && !output->vblank_pending) { /* Stop the pageflip timer instead of rearming it here */ if (output->pageflip_timer) wl_event_source_timer_update(output->pageflip_timer, 0); From 205c0a018ccf79c59f4b43ebb2b6df32103c32fd Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:33 +0100 Subject: [PATCH 0064/1642] compositor-drm: Clean up page_flip_pending path page_flip_pending is only be set when do a pageflip to a newly-rendered buffer; if the flag is not set, we have landed in the start_repaint_loop path where the vblank query fails, and thus we must pageflip to the same buffer. This test was not sufficient for what it was supposed to guard: releasing framebuffers back. When using client-supplied framebuffers, it is possible to reuse the same buffer multiple times, and we would send a framebuffer-release event too early. However, since we have a properly reference-counted drm_fb now, we can just drop this test, and rely on the reference counting to prevent too-early release of client framebuffers. page_flip_pending now becomes exactly what the name suggests: a flag which indicates whether or not we are expecting a pageflip event. Add asserts here to verify that we never receive a pageflip event we weren't expecting. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 887823717..f9b073749 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -883,6 +883,7 @@ drm_output_repaint(struct weston_output *output_base, output->fb_current = output->fb_pending; output->fb_pending = NULL; + assert(!output->page_flip_pending); output->page_flip_pending = 1; if (output->pageflip_timer) @@ -1008,6 +1009,9 @@ drm_output_start_repaint_loop(struct weston_output *output_base) */ fb_id = output->fb_current->fb_id; + assert(!output->page_flip_pending); + assert(!output->fb_last); + if (drmModePageFlip(backend->drm.fd, output->crtc_id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { weston_log("queueing pageflip failed: %m\n"); @@ -1018,6 +1022,9 @@ drm_output_start_repaint_loop(struct weston_output *output_base) wl_event_source_timer_update(output->pageflip_timer, backend->pageflip_timeout); + output->fb_last = drm_fb_ref(output->fb_current); + output->page_flip_pending = 1; + return; finish_frame: @@ -1081,16 +1088,12 @@ page_flip_handler(int fd, unsigned int frame, drm_output_update_msc(output, frame); - /* We don't set page_flip_pending on start_repaint_loop, in that case - * we just want to page flip to the current buffer to get an accurate - * timestamp */ - if (output->page_flip_pending) { - drm_fb_unref(output->fb_last); - output->fb_last = NULL; - } - + assert(output->page_flip_pending); output->page_flip_pending = 0; + drm_fb_unref(output->fb_last); + output->fb_last = NULL; + if (output->destroy_pending) drm_output_destroy(&output->base); else if (output->disable_pending) From 08d4edf20d196d6dd1205a92f89b9f1e01e33d5d Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 4 Apr 2017 17:54:34 +0100 Subject: [PATCH 0065/1642] compositor-drm: Rename drm_sprite to drm_plane We make the differentiation where planes are an abstract framebuffer with a position within a CRTC/output, and sprites are special cases of planes that are neither the primary (base/framebuffer) nor cursor plane. drm_sprite, OTOH, contains nothing that's actually specific to sprites, and we end up duplicating a lot of code to deal with them, especially when we come to use an entirely plane-based interface with atomic modesetting. Rename drm_sprite to drm_plane, to reflect that it's actually generic. No functional changes. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Reviewed-by: Quentin Glidic [Pekka: dropped the removal of an unrelated comment] Signed-off-by: Pekka Paalanen --- libweston/compositor-drm.c | 201 +++++++++++++++++++------------------ 1 file changed, 106 insertions(+), 95 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index f9b073749..707ef7ab1 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -162,6 +162,50 @@ struct drm_edid { char serial_number[13]; }; +/** + * A plane represents one buffer, positioned within a CRTC, and stacked + * relative to other planes on the same CRTC. + * + * Each CRTC has a 'primary plane', which use used to display the classic + * framebuffer contents, as accessed through the legacy drmModeSetCrtc + * call (which combines setting the CRTC's actual physical mode, and the + * properties of the primary plane). + * + * The cursor plane also has its own alternate legacy API. + * + * Other planes are used opportunistically to display content we do not + * wish to blit into the primary plane. These non-primary/cursor planes + * are referred to as 'sprites'. + */ +struct drm_plane { + struct wl_list link; + + struct weston_plane base; + + struct drm_output *output; + struct drm_backend *backend; + + uint32_t possible_crtcs; + uint32_t plane_id; + uint32_t count_formats; + + /* The last framebuffer submitted to the kernel for this plane. */ + struct drm_fb *fb_current; + /* The previously-submitted framebuffer, where the hardware has not + * yet acknowledged display of fb_current. */ + struct drm_fb *fb_last; + /* Framebuffer we are going to submit to the kernel when the current + * repaint is flushed. */ + struct drm_fb *fb_pending; + + int32_t src_x, src_y; + uint32_t src_w, src_h; + uint32_t dest_x, dest_y; + uint32_t dest_w, dest_h; + + uint32_t formats[]; +}; + struct drm_output { struct weston_output base; drmModeConnector *connector; @@ -211,39 +255,6 @@ struct drm_output { struct wl_event_source *pageflip_timer; }; -/* - * An output has a primary display plane plus zero or more sprites for - * blending display contents. - */ -struct drm_sprite { - struct wl_list link; - - struct weston_plane plane; - - struct drm_output *output; - struct drm_backend *backend; - - uint32_t possible_crtcs; - uint32_t plane_id; - uint32_t count_formats; - - /* The last framebuffer submitted to the kernel for this plane. */ - struct drm_fb *fb_current; - /* The previously-submitted framebuffer, where the hardware has not - * yet acknowledged display of fb_current. */ - struct drm_fb *fb_last; - /* Framebuffer we are going to submit to the kernel when the current - * repaint is flushed. */ - struct drm_fb *fb_pending; - - int32_t src_x, src_y; - uint32_t src_w, src_h; - uint32_t dest_x, dest_y; - uint32_t dest_w, dest_h; - - uint32_t formats[]; -}; - static struct gl_renderer_interface *gl_renderer; static const char default_seat[] = "seat0"; @@ -306,9 +317,9 @@ static void drm_output_update_msc(struct drm_output *output, unsigned int seq); static int -drm_sprite_crtc_supported(struct drm_output *output, struct drm_sprite *sprite) +drm_plane_crtc_supported(struct drm_output *output, struct drm_plane *plane) { - return !!(sprite->possible_crtcs & (1 << output->pipe)); + return !!(plane->possible_crtcs & (1 << output->pipe)); } static struct drm_output * @@ -845,7 +856,7 @@ drm_output_repaint(struct weston_output *output_base, struct drm_output *output = to_drm_output(output_base); struct drm_backend *backend = to_drm_backend(output->base.compositor); - struct drm_sprite *s; + struct drm_plane *s; struct drm_mode *mode; int ret = 0; @@ -905,7 +916,7 @@ drm_output_repaint(struct weston_output *output_base, /* XXX: Set output much earlier, so we don't attempt to place * planes on entirely the wrong output. */ if ((!s->fb_current && !s->fb_pending) || - !drm_sprite_crtc_supported(output, s)) + !drm_plane_crtc_supported(output, s)) continue; if (s->fb_pending && !backend->sprites_hidden) @@ -1048,7 +1059,7 @@ static void vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { - struct drm_sprite *s = (struct drm_sprite *)data; + struct drm_plane *s = (struct drm_plane *)data; struct drm_output *output = s->output; struct timespec ts; uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | @@ -1115,7 +1126,7 @@ page_flip_handler(int fd, unsigned int frame, } static uint32_t -drm_output_check_sprite_format(struct drm_sprite *s, +drm_output_check_plane_format(struct drm_plane *p, struct weston_view *ev, struct gbm_bo *bo) { uint32_t i, format; @@ -1136,8 +1147,8 @@ drm_output_check_sprite_format(struct drm_sprite *s, pixman_region32_fini(&r); } - for (i = 0; i < s->count_formats; i++) - if (s->formats[i] == format) + for (i = 0; i < p->count_formats; i++) + if (p->formats[i] == format) return format; return 0; @@ -1151,7 +1162,7 @@ drm_output_prepare_overlay_view(struct drm_output *output, struct drm_backend *b = to_drm_backend(ec); struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; struct wl_resource *buffer_resource; - struct drm_sprite *s; + struct drm_plane *p; struct linux_dmabuf_buffer *dmabuf; int found = 0; struct gbm_bo *bo; @@ -1187,11 +1198,11 @@ drm_output_prepare_overlay_view(struct drm_output *output, if (ev->alpha != 1.0f) return NULL; - wl_list_for_each(s, &b->sprite_list, link) { - if (!drm_sprite_crtc_supported(output, s)) + wl_list_for_each(p, &b->sprite_list, link) { + if (!drm_plane_crtc_supported(output, p)) continue; - if (!s->fb_pending) { + if (!p->fb_pending) { found = 1; break; } @@ -1244,23 +1255,23 @@ drm_output_prepare_overlay_view(struct drm_output *output, if (!bo) return NULL; - format = drm_output_check_sprite_format(s, ev, bo); + format = drm_output_check_plane_format(p, ev, bo); if (format == 0) { gbm_bo_destroy(bo); return NULL; } - s->fb_pending = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); - if (!s->fb_pending) { + p->fb_pending = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); + if (!p->fb_pending) { gbm_bo_destroy(bo); return NULL; } - drm_fb_set_buffer(s->fb_pending, ev->surface->buffer_ref.buffer); + drm_fb_set_buffer(p->fb_pending, ev->surface->buffer_ref.buffer); box = pixman_region32_extents(&ev->transform.boundingbox); - s->plane.x = box->x1; - s->plane.y = box->y1; + p->base.x = box->x1; + p->base.y = box->y1; /* * Calculate the source & dest rects properly based on actual @@ -1277,10 +1288,10 @@ drm_output_prepare_overlay_view(struct drm_output *output, output->base.transform, output->base.current_scale, *box); - s->dest_x = tbox.x1; - s->dest_y = tbox.y1; - s->dest_w = tbox.x2 - tbox.x1; - s->dest_h = tbox.y2 - tbox.y1; + p->dest_x = tbox.x1; + p->dest_y = tbox.y1; + p->dest_w = tbox.x2 - tbox.x1; + p->dest_h = tbox.y2 - tbox.y1; pixman_region32_fini(&dest_rect); pixman_region32_init(&src_rect); @@ -1317,13 +1328,13 @@ drm_output_prepare_overlay_view(struct drm_output *output, viewport->buffer.scale, tbox); - s->src_x = tbox.x1 << 8; - s->src_y = tbox.y1 << 8; - s->src_w = (tbox.x2 - tbox.x1) << 8; - s->src_h = (tbox.y2 - tbox.y1) << 8; + p->src_x = tbox.x1 << 8; + p->src_y = tbox.y1 << 8; + p->src_w = (tbox.x2 - tbox.x1) << 8; + p->src_h = (tbox.y2 - tbox.y1) << 8; pixman_region32_fini(&src_rect); - return &s->plane; + return &p->base; } static struct weston_plane * @@ -2832,71 +2843,71 @@ create_output_for_connector(struct drm_backend *b, static void create_sprites(struct drm_backend *b) { - struct drm_sprite *sprite; - drmModePlaneRes *plane_res; - drmModePlane *plane; + struct drm_plane *plane; + drmModePlaneRes *kplane_res; + drmModePlane *kplane; uint32_t i; - plane_res = drmModeGetPlaneResources(b->drm.fd); - if (!plane_res) { + kplane_res = drmModeGetPlaneResources(b->drm.fd); + if (!kplane_res) { weston_log("failed to get plane resources: %s\n", strerror(errno)); return; } - for (i = 0; i < plane_res->count_planes; i++) { - plane = drmModeGetPlane(b->drm.fd, plane_res->planes[i]); - if (!plane) + for (i = 0; i < kplane_res->count_planes; i++) { + kplane = drmModeGetPlane(b->drm.fd, kplane_res->planes[i]); + if (!kplane) continue; - sprite = zalloc(sizeof(*sprite) + ((sizeof(uint32_t)) * - plane->count_formats)); - if (!sprite) { + plane = zalloc(sizeof(*plane) + ((sizeof(uint32_t)) * + kplane->count_formats)); + if (!plane) { weston_log("%s: out of memory\n", __func__); - drmModeFreePlane(plane); + drmModeFreePlane(kplane); continue; } - sprite->possible_crtcs = plane->possible_crtcs; - sprite->plane_id = plane->plane_id; - sprite->fb_last = NULL; - sprite->fb_current = NULL; - sprite->fb_pending = NULL; - sprite->backend = b; - sprite->count_formats = plane->count_formats; - memcpy(sprite->formats, plane->formats, - plane->count_formats * sizeof(plane->formats[0])); - drmModeFreePlane(plane); - weston_plane_init(&sprite->plane, b->compositor, 0, 0); - weston_compositor_stack_plane(b->compositor, &sprite->plane, + plane->possible_crtcs = kplane->possible_crtcs; + plane->plane_id = kplane->plane_id; + plane->fb_last = NULL; + plane->fb_current = NULL; + plane->fb_pending = NULL; + plane->backend = b; + plane->count_formats = kplane->count_formats; + memcpy(plane->formats, kplane->formats, + kplane->count_formats * sizeof(kplane->formats[0])); + drmModeFreePlane(kplane); + weston_plane_init(&plane->base, b->compositor, 0, 0); + weston_compositor_stack_plane(b->compositor, &plane->base, &b->compositor->primary_plane); - wl_list_insert(&b->sprite_list, &sprite->link); + wl_list_insert(&b->sprite_list, &plane->link); } - drmModeFreePlaneResources(plane_res); + drmModeFreePlaneResources(kplane_res); } static void destroy_sprites(struct drm_backend *backend) { - struct drm_sprite *sprite, *next; + struct drm_plane *plane, *next; struct drm_output *output; output = container_of(backend->compositor->output_list.next, struct drm_output, base.link); - wl_list_for_each_safe(sprite, next, &backend->sprite_list, link) { + wl_list_for_each_safe(plane, next, &backend->sprite_list, link) { drmModeSetPlane(backend->drm.fd, - sprite->plane_id, + plane->plane_id, output->crtc_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - assert(!sprite->fb_last); - assert(!sprite->fb_pending); - drm_fb_unref(sprite->fb_current); - weston_plane_release(&sprite->plane); - free(sprite); + assert(!plane->fb_last); + assert(!plane->fb_pending); + drm_fb_unref(plane->fb_current); + weston_plane_release(&plane->base); + free(plane); } } @@ -3102,7 +3113,7 @@ session_notify(struct wl_listener *listener, void *data) { struct weston_compositor *compositor = data; struct drm_backend *b = to_drm_backend(compositor); - struct drm_sprite *sprite; + struct drm_plane *sprite; struct drm_output *output; if (compositor->session_active) { From c65df6403ab76ccbb3d0d79d553d0bc23557abbb Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 29 Mar 2017 15:45:46 +0300 Subject: [PATCH 0066/1642] libweston: improve weston_output_disable() comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorder some paragraphs to be more logically ordered. Rewrite the description of the backend-specific disable function to explain the semantics instead of the mechanics. Remove the paragraph about pending_output_list as unnecessary details. Add a big fat comment on why we call output->disable() always instead of only for actually enabled outputs. Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 048b195c1..2bca19cda 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4764,28 +4764,22 @@ weston_output_enable(struct weston_output *output) * * \param output The weston_output object that needs to be disabled. * - * See weston_output_init() for more information on the - * state output is returned to. - * * Calls a backend specific function to disable an output, in case * such function exists. * - * If the output is being used by the compositor, it is first removed + * The backend specific disable function may choose to postpone the disabling + * by returning a negative value, in which case this function returns early. + * In that case the backend will guarantee the output will be disabled soon + * by the backend calling this function again. One must not attempt to re-enable + * the output until that happens. + * + * Otherwise, if the output is being used by the compositor, it is removed * from weston's output_list (see weston_compositor_remove_output()) * and is returned to a state it was before weston_output_enable() * was ran (see weston_output_enable_undo()). * - * Output is added to pending_output_list so it will get destroyed - * if the output does not get configured again when the compositor - * shuts down. If an output is to be used immediately, it needs to - * be manually removed from the list (the compositor specific functions - * for handling pending outputs will take care of that). - * - * If backend specific disable function returns negative value, - * this function will return too. It can be used as an indicator - * that output cannot be disabled at the present time. In that case - * backend needs to make sure the output is disabled when it is - * possible. + * See weston_output_init() for more information on the + * state output is returned to. */ WL_EXPORT void weston_output_disable(struct weston_output *output) @@ -4795,6 +4789,14 @@ weston_output_disable(struct weston_output *output) /* Should we rename this? */ output->destroying = 1; + /* Disable is called unconditionally also for not-enabled outputs, + * because at compositor start-up, if there is an output that is + * already on but the compositor wants to turn it off, we have to + * forward the turn-off to the backend so it knows to do it. + * The backend cannot initially turn off everything, because it + * would cause unnecessary mode-sets for all outputs the compositor + * wants to be on. + */ if (output->disable(output) < 0) return; From 552e4c4309221eb01edadaea1d25b031680a1a33 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Wed, 12 Apr 2017 15:32:09 +0200 Subject: [PATCH 0067/1642] libweston: fix pixel-format helpers compilation on non-X11 EGL platforms Since building libweston includes EGL/egl.h from pixel-formats.c, EGL_CFLAGS must be added to libweston_CFLAGS, as on some platforms that contains -DMESA_EGL_NO_X11_HEADERS, and fails to compile without it: CC libweston/libweston_3_la-pixel-formats.lo In file included from [...]/usr/include/EGL/egl.h:39:0, from libweston/pixel-formats.c:39: [...]/usr/include/EGL/eglplatform.h:119:22: fatal error: X11/Xlib.h: No such file or directory Fixes: 903721a6215f ("libweston: Add pixel-format helpers") Signed-off-by: Philipp Zabel Reviewed-by: Daniel Stone --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 94b1c1430..45df7feab 100644 --- a/Makefile.am +++ b/Makefile.am @@ -72,7 +72,7 @@ install-libweston_moduleLTLIBRARIES install-moduleLTLIBRARIES: install-libLTLIBR lib_LTLIBRARIES = libweston-@LIBWESTON_MAJOR@.la libweston_@LIBWESTON_MAJOR@_la_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON libweston_@LIBWESTON_MAJOR@_la_CFLAGS = $(AM_CFLAGS) \ - $(COMPOSITOR_CFLAGS) $(LIBUNWIND_CFLAGS) $(LIBDRM_CFLAGS) + $(COMPOSITOR_CFLAGS) $(EGL_CFLAGS) $(LIBUNWIND_CFLAGS) $(LIBDRM_CFLAGS) libweston_@LIBWESTON_MAJOR@_la_LIBADD = $(COMPOSITOR_LIBS) $(LIBUNWIND_LIBS) \ $(DLOPEN_LIBS) -lm $(CLOCK_GETTIME_LIBS) \ $(LIBINPUT_BACKEND_LIBS) libshared.la From ad0da4596d6315b18e888af75eee0a9bad1ff44d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 18 Jan 2017 15:37:57 +0200 Subject: [PATCH 0068/1642] xwm: do not draw decor twice on map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Normal windows enter the MapRequest handler, which schedules drawing the decorations. Then Xwayland realizes the window, which ends with a call to xserver_map_shell_surface(). The decorations are already drawn, no need to draw them a second time. However, MapRequest handler could not set the pending state because the weston_surface did not exist at the time, because it gets created only when Xwayland realizes the window, which happens after XWM has forwarded the MapWindow in MapRequest handler. Therefore set the pending state explicitly at the end. Scheduling had it done much later anyway. Now that the pending state is set much earlier, it seems to be more likely that it gets set before Xwayland's first commit is handled. This means that -geometry command line option of X11 apps already takes the geometry (decorations) into account. I do not think it is reliable yet, though. There is still the race between Xwayland committing and XWM setting the pending state assuming the very next commit latches it in appropriately. The race exists not because of Wayland, but because WL_SURFACE_ID comes via X11, and could be processed after wl_compositor.create_surface and wl_surface.commit. That commit/pending race is solved by a following patch. For override-redirect windows weston_wm_window_schedule_repaint() reduced into a call to weston_wm_window_set_pending_state_OR(), so we can just call that directly. It should not matter that the call is moved to the end of the function. Signed-off-by: Pekka Paalanen Reviewed-by: Louis-Francis Ratté-Boulianne Acked-by: Daniel Stone --- xwayland/window-manager.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index d40d56eaf..ac875a339 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2681,8 +2681,6 @@ xserver_map_shell_surface(struct weston_wm_window *window, wl_signal_add(&window->surface->destroy_signal, &window->surface_destroy_listener); - weston_wm_window_schedule_repaint(window); - if (!xwayland_interface) return; @@ -2742,6 +2740,11 @@ xserver_map_shell_surface(struct weston_wm_window *window, xwayland_interface->set_toplevel(window->shsurf); } } + + if (window->frame_id == XCB_WINDOW_NONE) + weston_wm_window_set_pending_state_OR(window); + else + weston_wm_window_set_pending_state(window); } const struct weston_xwayland_surface_api surface_api = { From 7ace831ca6205ea288e49fdbd6b63f53e73fae59 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 18 Jan 2017 15:37:58 +0200 Subject: [PATCH 0069/1642] xwm: use _XWAYLAND_ALLOW_COMMITS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch uses the new feature proposed for Xwayland in the patch series https://patchwork.freedesktop.org/series/16610/ . When the frame window is created, immediately forbid Xwayland commits on it. This prevents commits before the decorations have been drawn and the initial pending state has been set. Commits are enabled right after drawing and setting. This ensures that the decorations are fully drawn when a window is mapped. This also solves the initial commit/pending race, but the race is on again after mapping. If Xwayland does not implement the needed support, we are just setting a window property with no effect. This patch is the final piece for solving T7622, excluding the _NET_WM_SYNC_REQUEST handling. Task: https://phabricator.freedesktop.org/T7622 Signed-off-by: Pekka Paalanen Reviewed-by: Louis-Francis Ratté-Boulianne Acked-by: Daniel Stone --- xwayland/window-manager.c | 40 +++++++++++++++++++++++++++++++++++++-- xwayland/xwayland.h | 1 + 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index ac875a339..260807593 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -873,6 +873,37 @@ weston_wm_window_activate(struct wl_listener *listener, void *data) } +/** Control Xwayland wl_surface.commit behaviour + * + * This function sets the "_XWAYLAND_ALLOW_COMMITS" property of the frame window + * (not the content window!) to \p allow. + * + * If the property is set to \c true, Xwayland will commit whenever it likes. + * If the property is set to \c false, Xwayland will not commit. + * If the property is not set at all, Xwayland assumes it is \c true. + * + * \param window The XWM window to control. + * \param allow Whether Xwayland is allowed to wl_surface.commit for the window. + */ +static void +weston_wm_window_set_allow_commits(struct weston_wm_window *window, bool allow) +{ + struct weston_wm *wm = window->wm; + uint32_t property[1]; + + assert(window->frame_id != XCB_WINDOW_NONE); + + property[0] = allow ? 1 : 0; + + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + window->frame_id, + wm->atom.allow_commits, + XCB_ATOM_CARDINAL, + 32, /* format */ + 1, property); +} + #define ICCCM_WITHDRAWN_STATE 0 #define ICCCM_NORMAL_STATE 1 #define ICCCM_ICONIC_STATE 3 @@ -1048,6 +1079,7 @@ weston_wm_handle_map_request(struct weston_wm *wm, xcb_generic_event_t *event) window->width, window->height, window->map_request_x, window->map_request_y); + weston_wm_window_set_allow_commits(window, false); weston_wm_window_set_wm_state(window, ICCCM_NORMAL_STATE); weston_wm_window_set_net_wm_state(window); weston_wm_window_set_virtual_desktop(window, 0); @@ -2220,6 +2252,7 @@ weston_wm_get_resources(struct weston_wm *wm) { "XdndFinished", F(atom.xdnd_finished) }, { "XdndTypeList", F(atom.xdnd_type_list) }, { "XdndActionCopy", F(atom.xdnd_action_copy) }, + { "_XWAYLAND_ALLOW_COMMITS", F(atom.allow_commits) }, { "WL_SURFACE_ID", F(atom.wl_surface_id) } }; #undef F @@ -2741,10 +2774,13 @@ xserver_map_shell_surface(struct weston_wm_window *window, } } - if (window->frame_id == XCB_WINDOW_NONE) + if (window->frame_id == XCB_WINDOW_NONE) { weston_wm_window_set_pending_state_OR(window); - else + } else { weston_wm_window_set_pending_state(window); + weston_wm_window_set_allow_commits(window, true); + xcb_flush(wm->conn); + } } const struct weston_xwayland_surface_api surface_api = { diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index b1225f5a7..ca75f5b7c 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -154,6 +154,7 @@ struct weston_wm { xcb_atom_t xdnd_type_list; xcb_atom_t xdnd_action_copy; xcb_atom_t wl_surface_id; + xcb_atom_t allow_commits; } atom; }; From 5f93b9f640b456de0406cc1be9685b3d768ce0c0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 18 Jan 2017 15:37:59 +0200 Subject: [PATCH 0070/1642] libweston-desktop/xwayland: react to geometry changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix up the window position whenever the geometry info changes. If the window geometry changes, we want to keep the input-responding content anchored to top-left. It is done by manipulating the dx,dy arguments originating from a wl_surface.attach request. Signed-off-by: Pekka Paalanen Reviewed-by: Louis-Francis Ratté-Boulianne Acked-by: Daniel Stone --- libweston-desktop/xwayland.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index b9843853f..4f4b453fc 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -131,12 +131,19 @@ weston_desktop_xwayland_surface_committed(struct weston_desktop_surface *dsurfac int32_t sx, int32_t sy) { struct weston_desktop_xwayland_surface *surface = user_data; + struct weston_geometry oldgeom; + + assert(dsurface == surface->surface); #ifdef WM_DEBUG weston_log("%s: xwayland surface %p\n", __func__, surface); #endif if (surface->has_next_geometry) { + oldgeom = weston_desktop_surface_get_geometry(surface->surface); + sx -= surface->next_geometry.x - oldgeom.x; + sy -= surface->next_geometry.y - oldgeom.x; + surface->has_next_geometry = false; weston_desktop_surface_set_geometry(surface->surface, surface->next_geometry); From 2cd87fe8d7cdd2bdcbe33d9cff3fce2c1c4259bb Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 13 Apr 2017 13:48:48 -0500 Subject: [PATCH 0071/1642] compositor-drm: Fix disabling cursor plane commit a7cba1d4cd4c9013c3ac6cb074fcb7842fb39283 changed the way the cursor plane is setup. Previously it was pre-emptively set disabled for the next frame, and that would be changed at next frame time if the cursor plane was to be used. It was changed to be disabled at plane assignment time. We disable the use of planes entirely by setting disable_planes to a non-zero value, which bypasses all calls to assign_planes - so if the plane was set-up in the previous frame it will retain its state post-disable. This leads to desktop zoom leaving the cursor plane in place when it sets disable_planes. This patch clears any stale cursor plane state from the redraw handler if disable_planes is set so drm_output_set_cursor() will do the right thing. Reviewed-by: Daniel Stone Reported-by: Emmanuel Gil Peyrot --- libweston/compositor-drm.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 707ef7ab1..10adb463f 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -865,6 +865,16 @@ drm_output_repaint(struct weston_output *output_base, assert(!output->fb_last); + /* If disable_planes is set then assign_planes() wasn't + * called for this render, so we could still have a stale + * cursor plane set up. + */ + if (output->base.disable_planes) { + output->cursor_view = NULL; + output->cursor_plane.x = INT32_MIN; + output->cursor_plane.y = INT32_MIN; + } + drm_output_render(output, damage); if (!output->fb_pending) return -1; From 9ad4de1f7ad411fcb5a62eb85e17cf96ae076a0f Mon Sep 17 00:00:00 2001 From: Bryce Harrington Date: Fri, 9 Sep 2016 13:16:02 -0700 Subject: [PATCH 0072/1642] desktop-shell: Enable per-output fade animations Instead of creating a single global fade surface across all outputs, create a separate surface for each output. This will permit e.g. individual fades for each output (or blocking the fade-outs if inhibiting idling as will come in a later patch.) This also fixes a potential issue if on multihead layout spanning a desktop wider than 8096 (or higher than 8096), the fade animation may not completely cover all surfaces. This assumes the output geometry doesn't change to become larger during the course of the fade animation. Signed-off-by: Bryce Harrington Reviewed-by: Quentin Glidic --- desktop-shell/shell.c | 137 ++++++++++++++++++++++++------------------ desktop-shell/shell.h | 14 ++--- 2 files changed, 84 insertions(+), 67 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 6b1876d2b..f1577c12d 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -202,6 +202,9 @@ surface_rotate(struct shell_surface *surface, struct weston_pointer *pointer); static void shell_fade_startup(struct desktop_shell *shell); +static void +shell_fade(struct desktop_shell *shell, enum fade_type type); + static struct shell_seat * get_shell_seat(struct weston_seat *seat); @@ -2846,9 +2849,6 @@ static const struct weston_desktop_api shell_desktop_api = { /* ************************ * * end of libweston-desktop * * ************************ */ -static void -shell_fade(struct desktop_shell *shell, enum fade_type type); - static void configure_static_view(struct weston_view *ev, struct weston_layer *layer, int x, int y) { @@ -3845,16 +3845,16 @@ unlock(struct desktop_shell *shell) } static void -shell_fade_done(struct weston_view_animation *animation, void *data) +shell_fade_done_for_output(struct weston_view_animation *animation, void *data) { - struct desktop_shell *shell = data; + struct shell_output *shell_output = data; + struct desktop_shell *shell = shell_output->shell; - shell->fade.animation = NULL; - - switch (shell->fade.type) { + shell_output->fade.animation = NULL; + switch (shell_output->fade.type) { case FADE_IN: - weston_surface_destroy(shell->fade.view->surface); - shell->fade.view = NULL; + weston_surface_destroy(shell_output->fade.view->surface); + shell_output->fade.view = NULL; break; case FADE_OUT: lock(shell); @@ -3865,7 +3865,7 @@ shell_fade_done(struct weston_view_animation *animation, void *data) } static struct weston_view * -shell_fade_create_surface(struct desktop_shell *shell) +shell_fade_create_surface_for_output(struct desktop_shell *shell, struct shell_output *shell_output) { struct weston_compositor *compositor = shell->compositor; struct weston_surface *surface; @@ -3881,8 +3881,8 @@ shell_fade_create_surface(struct desktop_shell *shell) return NULL; } - weston_surface_set_size(surface, 8192, 8192); - weston_view_set_position(view, 0, 0); + weston_surface_set_size(surface, shell_output->output->width, shell_output->output->height); + weston_view_set_position(view, shell_output->output->x, shell_output->output->y); weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1.0); weston_layer_entry_insert(&compositor->fade_layer.view_list, &view->layer_link); @@ -3897,6 +3897,7 @@ static void shell_fade(struct desktop_shell *shell, enum fade_type type) { float tint; + struct shell_output *shell_output; switch (type) { case FADE_IN: @@ -3910,32 +3911,35 @@ shell_fade(struct desktop_shell *shell, enum fade_type type) return; } - shell->fade.type = type; + /* Create a separate fade surface for each output */ + wl_list_for_each(shell_output, &shell->output_list, link) { + shell_output->fade.type = type; - if (shell->fade.view == NULL) { - shell->fade.view = shell_fade_create_surface(shell); - if (!shell->fade.view) - return; + if (shell_output->fade.view == NULL) { + shell_output->fade.view = shell_fade_create_surface_for_output(shell, shell_output); + if (!shell_output->fade.view) + continue; - shell->fade.view->alpha = 1.0 - tint; - weston_view_update_transform(shell->fade.view); - } + shell_output->fade.view->alpha = 1.0 - tint; + weston_view_update_transform(shell_output->fade.view); + } - if (shell->fade.view->output == NULL) { - /* If the black view gets a NULL output, we lost the - * last output and we'll just cancel the fade. This - * happens when you close the last window under the - * X11 or Wayland backends. */ - shell->locked = false; - weston_surface_destroy(shell->fade.view->surface); - shell->fade.view = NULL; - } else if (shell->fade.animation) { - weston_fade_update(shell->fade.animation, tint); - } else { - shell->fade.animation = - weston_fade_run(shell->fade.view, - 1.0 - tint, tint, 300.0, - shell_fade_done, shell); + if (shell_output->fade.view->output == NULL) { + /* If the black view gets a NULL output, we lost the + * last output and we'll just cancel the fade. This + * happens when you close the last window under the + * X11 or Wayland backends. */ + shell->locked = false; + weston_surface_destroy(shell_output->fade.view->surface); + shell_output->fade.view = NULL; + } else if (shell_output->fade.animation) { + weston_fade_update(shell_output->fade.animation, tint); + } else { + shell_output->fade.animation = + weston_fade_run(shell_output->fade.view, + 1.0 - tint, tint, 300.0, + shell_fade_done_for_output, shell_output); + } } } @@ -3943,6 +3947,7 @@ static void do_shell_fade_startup(void *data) { struct desktop_shell *shell = data; + struct shell_output *shell_output; if (shell->startup_animation_type == ANIMATION_FADE) { shell_fade(shell, FADE_IN); @@ -3950,8 +3955,10 @@ do_shell_fade_startup(void *data) weston_log("desktop shell: " "unexpected fade-in animation type %d\n", shell->startup_animation_type); - weston_surface_destroy(shell->fade.view->surface); - shell->fade.view = NULL; + wl_list_for_each(shell_output, &shell->output_list, link) { + weston_surface_destroy(shell_output->fade.view->surface); + shell_output->fade.view = NULL; + } } } @@ -3959,15 +3966,22 @@ static void shell_fade_startup(struct desktop_shell *shell) { struct wl_event_loop *loop; + struct shell_output *shell_output; + bool has_fade = false; - if (!shell->fade.startup_timer) - return; + wl_list_for_each(shell_output, &shell->output_list, link) { + if (!shell_output->fade.startup_timer) + continue; - wl_event_source_remove(shell->fade.startup_timer); - shell->fade.startup_timer = NULL; + wl_event_source_remove(shell_output->fade.startup_timer); + shell_output->fade.startup_timer = NULL; + has_fade = true; + } - loop = wl_display_get_event_loop(shell->compositor->wl_display); - wl_event_loop_add_idle(loop, do_shell_fade_startup, shell); + if (has_fade) { + loop = wl_display_get_event_loop(shell->compositor->wl_display); + wl_event_loop_add_idle(loop, do_shell_fade_startup, shell); + } } static int @@ -3988,27 +4002,30 @@ shell_fade_init(struct desktop_shell *shell) */ struct wl_event_loop *loop; - - if (shell->fade.view != NULL) { - weston_log("%s: warning: fade surface already exists\n", - __func__); - return; - } + struct shell_output *shell_output; if (shell->startup_animation_type == ANIMATION_NONE) return; - shell->fade.view = shell_fade_create_surface(shell); - if (!shell->fade.view) - return; + wl_list_for_each(shell_output, &shell->output_list, link) { + if (shell_output->fade.view != NULL) { + weston_log("%s: warning: fade surface already exists\n", + __func__); + continue; + } + + shell_output->fade.view = shell_fade_create_surface_for_output(shell, shell_output); + if (!shell_output->fade.view) + continue; - weston_view_update_transform(shell->fade.view); - weston_surface_damage(shell->fade.view->surface); + weston_view_update_transform(shell_output->fade.view); + weston_surface_damage(shell_output->fade.view->surface); - loop = wl_display_get_event_loop(shell->compositor->wl_display); - shell->fade.startup_timer = - wl_event_loop_add_timer(loop, fade_startup_timeout, shell); - wl_event_source_timer_update(shell->fade.startup_timer, 15000); + loop = wl_display_get_event_loop(shell->compositor->wl_display); + shell_output->fade.startup_timer = + wl_event_loop_add_timer(loop, fade_startup_timeout, shell); + wl_event_source_timer_update(shell_output->fade.startup_timer, 15000); + } } static void @@ -4023,7 +4040,7 @@ idle_handler(struct wl_listener *listener, void *data) weston_seat_break_desktop_grabs(seat); shell_fade(shell, FADE_OUT); - /* lock() is called from shell_fade_done() */ + /* lock() is called from shell_fade_done_for_output() */ } static void diff --git a/desktop-shell/shell.h b/desktop-shell/shell.h index a1cea7505..063641d21 100644 --- a/desktop-shell/shell.h +++ b/desktop-shell/shell.h @@ -122,6 +122,13 @@ struct shell_output { struct weston_surface *background_surface; struct wl_listener background_surface_listener; + + struct { + struct weston_view *view; + struct weston_view_animation *animation; + enum fade_type type; + struct wl_event_source *startup_timer; + } fade; }; struct weston_desktop; @@ -192,13 +199,6 @@ struct desktop_shell { struct wl_list surfaces; } input_panel; - struct { - struct weston_view *view; - struct weston_view_animation *animation; - enum fade_type type; - struct wl_event_source *startup_timer; - } fade; - struct exposay exposay; bool allow_zap; From eca5cca56125052003861529eb2c181b420845aa Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 28 Feb 2017 21:53:51 +0000 Subject: [PATCH 0073/1642] Account for very large repaint window misses At the bottom of weston_output_finish_frame(), code exists to account for flips which have missed the repaint window, by shifting them to lock on to the next repaint window rather than repainting immediately. This code only accounted for flips which missed their target by one repaint window. If they miss by multiples of the repaint window, adjust them until the next repaint timestamp is in the future. This will only happen in fairly extreme situations, such as Weston being scheduled out for a punitively long period of time. Nevertheless, try to help recovery by still aiming for more predictable timings. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 2bca19cda..2a3074dbb 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2510,9 +2510,14 @@ weston_output_finish_frame(struct weston_output *output, * the deadline given by repaint_msec? In that case we delay until * the deadline of the next frame, to give clients a more predictable * timing of the repaint cycle to lock on. */ - if (presented_flags == WP_PRESENTATION_FEEDBACK_INVALID && msec_rel < 0) - timespec_add_nsec(&output->next_repaint, &output->next_repaint, - refresh_nsec); + if (presented_flags == WP_PRESENTATION_FEEDBACK_INVALID && + msec_rel < 0) { + while (timespec_sub_to_nsec(&output->next_repaint, &now) < 0) { + timespec_add_nsec(&output->next_repaint, + &output->next_repaint, + refresh_nsec); + } + } out: output->repaint_status = REPAINT_SCHEDULED; From 9e07d25a1b933007c403df27fa430a82a2327743 Mon Sep 17 00:00:00 2001 From: Olivier Fourdan Date: Mon, 15 May 2017 13:32:01 +0200 Subject: [PATCH 0074/1642] xwm: Don't change focus on focus events from grabs xwm would not let X clients change the focus behind the compositor's back, reverting focus where it's supposed to be when this occurs. However, X11 grab also issue focus in events, on which some clients rely and reverting focus in this case braks the client logic (e.g. combobox menu in gtk+ using the X11 backend). Check if the focus event is actually coming from a grab or ungrab and do not revert focus in this case, to avoid breaking clients logic. Signed-off-by: Olivier Fourdan Tested-by: Pekka Paalanen Acked-by: Pekka Paalanen Acked-by: Daniel Stone Acked-by: Quentin Glidic --- xwayland/window-manager.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 260807593..25008539e 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2055,6 +2055,12 @@ static void weston_wm_handle_focus_in(struct weston_wm *wm, xcb_generic_event_t *event) { xcb_focus_in_event_t *focus = (xcb_focus_in_event_t *) event; + + /* Do not interfere with grabs */ + if (focus->mode == XCB_NOTIFY_MODE_GRAB || + focus->mode == XCB_NOTIFY_MODE_UNGRAB) + return; + /* Do not let X clients change the focus behind the compositor's * back. Reset the focus to the old one if it changed. */ if (!wm->focus_window || focus->event != wm->focus_window->id) From 6156d675f455e9e6c2d16b4bbc7b180b9eaac97b Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 19 May 2017 09:39:06 -0500 Subject: [PATCH 0075/1642] clients: Allow simple-egl to use wl_surface_damage_buffer wl_surface_damage_buffer landed ages ago, but in order for GL to use it the client must bind a wl_compositor version >= 4 (the version where damage_buffer was introduced). This patch updates the bind version and allows eglSwapBuffersWithDamage to actually use the provided damage rectangles instead of performing full surface damage. This log is much longer than the patch. Reviewed-by: Daniel Stone --- clients/simple-egl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clients/simple-egl.c b/clients/simple-egl.c index c4f72a448..dad0f09b3 100644 --- a/clients/simple-egl.c +++ b/clients/simple-egl.c @@ -797,7 +797,8 @@ registry_handle_global(void *data, struct wl_registry *registry, if (strcmp(interface, "wl_compositor") == 0) { d->compositor = wl_registry_bind(registry, name, - &wl_compositor_interface, 1); + &wl_compositor_interface, + MIN(version, 4)); } else if (strcmp(interface, "zxdg_shell_v6") == 0) { d->shell = wl_registry_bind(registry, name, &zxdg_shell_v6_interface, 1); From dbfd248da4ccfffde461de856a4d8504e33d91a9 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 19 May 2017 10:42:07 -0500 Subject: [PATCH 0076/1642] libweston: Allow compositor-wayland to use wl_surface_damage_buffer wl_surface_damage_buffer landed ages ago, but in order for GL to use it the client must bind a wl_compositor version >= 4 (the version where damage_buffer was introduced). This patch updates the bind version and allows eglSwapBuffersWithDamage to actually use the provided damage rectangles instead of performing full surface damage. Reviewed-by: Daniel Stone --- libweston/compositor-wayland.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 14f2c8dbd..77a736896 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -2319,7 +2319,8 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, if (strcmp(interface, "wl_compositor") == 0) { b->parent.compositor = wl_registry_bind(registry, name, - &wl_compositor_interface, 1); + &wl_compositor_interface, + MIN(version, 4)); } else if (strcmp(interface, "zxdg_shell_v6") == 0) { b->parent.xdg_shell = wl_registry_bind(registry, name, From 47bbdc72967faf1cca2ca8b8bbeefab4fee67d95 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Mon, 8 May 2017 12:47:55 -0400 Subject: [PATCH 0077/1642] weston: Add support for "--foo bar" style options A little earlier today I ended up spending a lot of time trying to figure out why weston wasn't managing to launch over SSH and telling me that I did not have a --tty option specified, despite me passing the option strings ["--tty", "3"]. Turns out weston just doesn't support that. So, add support for this kind of format in addition to "--foo=bar" to save others from making the same mistake I did. Changes since v1: - Add comment about unreachable boolean check in long_option_with_arg() - Convert boolean check in long_option_with_arg() to assert Signed-off-by: Lyude Reviewed-by: Eric Engestrom Reviewed-by: Quentin Glidic --- shared/option-parser.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/shared/option-parser.c b/shared/option-parser.c index eee75468e..e8d9b3b72 100644 --- a/shared/option-parser.c +++ b/shared/option-parser.c @@ -86,6 +86,31 @@ long_option(const struct weston_option *options, int count, char *arg) return 0; } +static int +long_option_with_arg(const struct weston_option *options, int count, char *arg, + char *param) +{ + int k, len; + + for (k = 0; k < count; k++) { + if (!options[k].name) + continue; + + len = strlen(options[k].name); + if (strncmp(options[k].name, arg + 2, len) != 0) + continue; + + /* Since long_option() should handle all booleans, we should + * never reach this + */ + assert(options[k].type != WESTON_OPTION_BOOLEAN); + + return handle_option(options + k, param); + } + + return 0; +} + static int short_option(const struct weston_option *options, int count, char *arg) { @@ -148,6 +173,13 @@ parse_options(const struct weston_option *options, if (long_option(options, count, argv[i])) continue; + /* ...also handle --foo bar */ + if (i + 1 < *argc && + long_option_with_arg(options, count, + argv[i], argv[i+1])) { + i++; + continue; + } } else { /* Short option, e.g -f or -f42 */ if (short_option(options, count, argv[i])) From a5066e00e8fdd8775d3c784a1c0cf1a07fa6dacb Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Mon, 17 Apr 2017 11:11:00 +0000 Subject: [PATCH 0078/1642] compositor-fbdev: Instead of less than 1 Hz use default refresh rate I ran Weston on a Nexus 4 mobile phone, with a native GNU/Linux userland, and the latest Android kernel for that device from LineageOS [1]. calculate_refresh_rate() returned 1 (mHz), which gets rounded to 0 Hz later and results in nothing being drawn to the screen. This patch makes sure, that there is at least a refresh rate of 1 Hz, because it returns the default refresh rate of 60 Hz otherwise. [1]: https://github.com/LineageOS/lge-kernel-mako Signed-off-by: Oliver Smith Reviewed-by: Quentin Glidic --- libweston/compositor-fbdev.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 32d71e0fe..e80a5040d 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -253,7 +253,8 @@ calculate_refresh_rate(struct fb_var_screeninfo *vinfo) if (refresh_rate > 200000) refresh_rate = 200000; /* cap at 200 Hz */ - return refresh_rate; + if (refresh_rate >= 1000) /* at least 1 Hz */ + return refresh_rate; } return 60 * 1000; /* default to 60 Hz */ From 65c94b8804bc4556a8c14ea2af53b2a72c59f73d Mon Sep 17 00:00:00 2001 From: Varad Gautam Date: Wed, 26 Apr 2017 19:15:59 +0530 Subject: [PATCH 0079/1642] linux-dmabuf: implement immediate dmabuf import handle create_immed() dmabuf import requests and support zwp_linux_dmabuf_v1_interface version 2. v2: terminate client with INVALID_WL_BUFFER when reason for create_immed failure is unknown. [daniels: Bump wayland-protocols dependency.] Signed-off-by: Varad Gautam Reviewed-by: Daniel Stone --- configure.ac | 2 +- libweston/linux-dmabuf.c | 63 +++++++++++++++++++++++++++++++++------- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac index db757f208..c4f5f2128 100644 --- a/configure.ac +++ b/configure.ac @@ -219,7 +219,7 @@ fi PKG_CHECK_MODULES(LIBINPUT_BACKEND, [libinput >= 0.8.0]) PKG_CHECK_MODULES(COMPOSITOR, [$COMPOSITOR_MODULES]) -PKG_CHECK_MODULES(WAYLAND_PROTOCOLS, [wayland-protocols >= 1.7], +PKG_CHECK_MODULES(WAYLAND_PROTOCOLS, [wayland-protocols >= 1.8], [ac_wayland_protocols_pkgdatadir=`$PKG_CONFIG --variable=pkgdatadir wayland-protocols`]) AC_SUBST(WAYLAND_PROTOCOLS_DATADIR, $ac_wayland_protocols_pkgdatadir) diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index c1627d6ef..9121e59c1 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -143,12 +143,13 @@ destroy_linux_dmabuf_wl_buffer(struct wl_resource *resource) } static void -params_create(struct wl_client *client, - struct wl_resource *params_resource, - int32_t width, - int32_t height, - uint32_t format, - uint32_t flags) +params_create_common(struct wl_client *client, + struct wl_resource *params_resource, + uint32_t buffer_id, + int32_t width, + int32_t height, + uint32_t format, + uint32_t flags) { struct linux_dmabuf_buffer *buffer; int i; @@ -263,7 +264,7 @@ params_create(struct wl_client *client, buffer->buffer_resource = wl_resource_create(client, &wl_buffer_interface, - 1, 0); + 1, buffer_id); if (!buffer->buffer_resource) { wl_resource_post_no_memory(params_resource); goto err_buffer; @@ -273,7 +274,10 @@ params_create(struct wl_client *client, &linux_dmabuf_buffer_implementation, buffer, destroy_linux_dmabuf_wl_buffer); - zwp_linux_buffer_params_v1_send_created(params_resource, + /* send 'created' event when the request is not for an immediate + * import, ie buffer_id is zero */ + if (buffer_id == 0) + zwp_linux_buffer_params_v1_send_created(params_resource, buffer->buffer_resource); return; @@ -283,17 +287,54 @@ params_create(struct wl_client *client, buffer->user_data_destroy_func(buffer); err_failed: - zwp_linux_buffer_params_v1_send_failed(params_resource); + if (buffer_id == 0) + zwp_linux_buffer_params_v1_send_failed(params_resource); + else + /* since the behavior is left implementation defined by the + * protocol in case of create_immed failure due to an unknown cause, + * we choose to treat it as a fatal error and immediately kill the + * client instead of creating an invalid handle and waiting for it + * to be used. + */ + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER, + "importing the supplied dmabufs failed"); err_out: linux_dmabuf_buffer_destroy(buffer); } +static void +params_create(struct wl_client *client, + struct wl_resource *params_resource, + int32_t width, + int32_t height, + uint32_t format, + uint32_t flags) +{ + params_create_common(client, params_resource, 0, width, height, format, + flags); +} + +static void +params_create_immed(struct wl_client *client, + struct wl_resource *params_resource, + uint32_t buffer_id, + int32_t width, + int32_t height, + uint32_t format, + uint32_t flags) +{ + params_create_common(client, params_resource, buffer_id, width, height, + format, flags); +} + static const struct zwp_linux_buffer_params_v1_interface zwp_linux_buffer_params_implementation = { params_destroy, params_add, - params_create + params_create, + params_create_immed }; static void @@ -457,7 +498,7 @@ WL_EXPORT int linux_dmabuf_setup(struct weston_compositor *compositor) { if (!wl_global_create(compositor->wl_display, - &zwp_linux_dmabuf_v1_interface, 1, + &zwp_linux_dmabuf_v1_interface, 2, compositor, bind_linux_dmabuf)) return -1; From 48be0be7a3d4e93136c339904f1fb636d9d7d859 Mon Sep 17 00:00:00 2001 From: Varad Gautam Date: Wed, 26 Apr 2017 19:16:00 +0530 Subject: [PATCH 0080/1642] clients/simple-dmabuf-intel: request no-roundtrip dmabuf import request immediate dmabuf import when run with "immed" arg. Signed-off-by: Varad Gautam Reviewed-by: Daniel Stone --- clients/simple-dmabuf-intel.c | 36 ++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/clients/simple-dmabuf-intel.c b/clients/simple-dmabuf-intel.c index 67850b0bd..0cd0d2ff8 100644 --- a/clients/simple-dmabuf-intel.c +++ b/clients/simple-dmabuf-intel.c @@ -57,6 +57,7 @@ struct display { struct zwp_fullscreen_shell_v1 *fshell; struct zwp_linux_dmabuf_v1 *dmabuf; int xrgb8888_format_found; + int req_dmabuf_immediate; }; struct buffer { @@ -282,7 +283,16 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, modifier >> 32, modifier & 0xffffffff); zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buffer); - zwp_linux_buffer_params_v1_create(params, + if (display->req_dmabuf_immediate) { + buffer->buffer = zwp_linux_buffer_params_v1_create_immed(params, + buffer->width, + buffer->height, + DRM_FORMAT_XRGB8888, + flags); + wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); + } + else + zwp_linux_buffer_params_v1_create(params, buffer->width, buffer->height, DRM_FORMAT_XRGB8888, @@ -506,7 +516,8 @@ registry_handle_global(void *data, struct wl_registry *registry, id, &zwp_fullscreen_shell_v1_interface, 1); } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { d->dmabuf = wl_registry_bind(registry, - id, &zwp_linux_dmabuf_v1_interface, 1); + id, &zwp_linux_dmabuf_v1_interface, + d->req_dmabuf_immediate ? 2 : 1); zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); } } @@ -523,7 +534,7 @@ static const struct wl_registry_listener registry_listener = { }; static struct display * -create_display(void) +create_display(int is_immediate) { struct display *display; @@ -537,6 +548,7 @@ create_display(void) /* XXX: fake, because the compositor does not yet advertise anything */ display->xrgb8888_format_found = 1; + display->req_dmabuf_immediate = is_immediate; display->registry = wl_display_get_registry(display->display); wl_registry_add_listener(display->registry, @@ -590,9 +602,22 @@ main(int argc, char **argv) struct sigaction sigint; struct display *display; struct window *window; + int is_immediate = 0; int ret = 0; - display = create_display(); + if (argc > 1) { + if (!strcmp(argv[1], "immed")) { + is_immediate = 1; + } + else { + fprintf(stderr, "usage:\n\tsimple-dmabuf-intel [options]\n" + "available options:\n\timmed: avoid dmabuf " + "creation roundtrip and import immediately\n"); + return 1; + } + } + + display = create_display(is_immediate); window = create_window(display, 250, 250); if (!window) return 1; @@ -602,7 +627,8 @@ main(int argc, char **argv) sigint.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sigint, NULL); - /* Here we retrieve the linux-dmabuf objects, or error */ + /* Here we retrieve the linux-dmabuf objects if executed without immed, + * or error */ wl_display_roundtrip(display->display); if (!running) From 0775cd116fad810a0fc289ffc193c57c896b8bd5 Mon Sep 17 00:00:00 2001 From: Varad Gautam Date: Wed, 23 Nov 2016 14:03:20 +0530 Subject: [PATCH 0081/1642] gl-renderer: support format and modifier queries EGL_EXT_image_dma_buf_import_modifiers allows querying the formats and modifiers supported by the platform. expose these to the compositor. v2: - change calloc args (Daniel Stone) - check for modifier support before querying formats (Daniel Stone) Signed-off-by: Varad Gautam Reviewed-by: Daniel Stone --- libweston/compositor.h | 7 ++++ libweston/gl-renderer.c | 85 ++++++++++++++++++++++++++++++++++++++++- shared/weston-egl-ext.h | 7 ++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/libweston/compositor.h b/libweston/compositor.h index 6070c7744..50f7420d8 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -747,6 +747,13 @@ struct weston_renderer { /** See weston_compositor_import_dmabuf() */ bool (*import_dmabuf)(struct weston_compositor *ec, struct linux_dmabuf_buffer *buffer); + + bool (*query_dmabuf_formats)(struct weston_compositor *ec, + int **formats, int *num_formats); + + bool (*query_dmabuf_modifiers)(struct weston_compositor *ec, + int format, uint64_t **modifiers, + int *num_modifiers); }; enum weston_capability { diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 0de2803f4..9e56a200a 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -227,6 +227,10 @@ struct gl_renderer { struct wl_signal destroy_signal; struct wl_listener output_destroy_listener; + + int has_dmabuf_import_modifiers; + PFNEGLQUERYDMABUFFORMATSEXTPROC query_dmabuf_formats; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dmabuf_modifiers; }; static PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = NULL; @@ -1865,6 +1869,70 @@ import_dmabuf(struct gl_renderer *gr, return image; } +static bool +gl_renderer_query_dmabuf_formats(struct weston_compositor *wc, + int **formats, int *num_formats) +{ + struct gl_renderer *gr = get_renderer(wc); + EGLint num; + + assert(gr->has_dmabuf_import); + + if (!gr->has_dmabuf_import_modifiers || + !gr->query_dmabuf_formats(gr->egl_display, 0, NULL, &num)) { + *num_formats = 0; + return false; + } + + *formats = calloc(num, sizeof(int)); + if (*formats == NULL) { + *num_formats = 0; + return false; + } + if (!gr->query_dmabuf_formats(gr->egl_display, num, *formats, + (EGLint*) &num)) { + *num_formats = 0; + free(*formats); + return false; + } + + *num_formats = num; + return true; +} + +static bool +gl_renderer_query_dmabuf_modifiers(struct weston_compositor *wc, int format, + uint64_t **modifiers, + int *num_modifiers) +{ + struct gl_renderer *gr = get_renderer(wc); + int num; + + assert(gr->has_dmabuf_import); + + if (!gr->has_dmabuf_import_modifiers || + !gr->query_dmabuf_modifiers(gr->egl_display, format, 0, NULL, + NULL, &num)) { + *num_modifiers = 0; + return false; + } + + *modifiers = calloc(num, sizeof(uint64_t)); + if (*modifiers == NULL) { + *num_modifiers = 0; + return false; + } + if (!gr->query_dmabuf_modifiers(gr->egl_display, format, + num, *modifiers, NULL, &num)) { + *num_modifiers = 0; + free(*modifiers); + return false; + } + + *num_modifiers = num; + return true; +} + static bool gl_renderer_import_dmabuf(struct weston_compositor *ec, struct linux_dmabuf_buffer *dmabuf) @@ -2855,6 +2923,7 @@ gl_renderer_setup_egl_extensions(struct weston_compositor *ec) gr->create_image = (void *) eglGetProcAddress("eglCreateImageKHR"); gr->destroy_image = (void *) eglGetProcAddress("eglDestroyImageKHR"); + gr->bind_display = (void *) eglGetProcAddress("eglBindWaylandDisplayWL"); gr->unbind_display = @@ -2908,6 +2977,15 @@ gl_renderer_setup_egl_extensions(struct weston_compositor *ec) if (weston_check_egl_extension(extensions, "EGL_EXT_image_dma_buf_import")) gr->has_dmabuf_import = 1; + if (weston_check_egl_extension(extensions, + "EGL_EXT_image_dma_buf_import_modifiers")) { + gr->query_dmabuf_formats = + (void *) eglGetProcAddress("eglQueryDmaBufFormatsEXT"); + gr->query_dmabuf_modifiers = + (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + gr->has_dmabuf_import_modifiers = 1; + } + if (weston_check_egl_extension(extensions, "GL_EXT_texture_rg")) gr->has_gl_texture_rg = 1; @@ -3146,8 +3224,13 @@ gl_renderer_display_create(struct weston_compositor *ec, EGLenum platform, goto fail_with_error; wl_list_init(&gr->dmabuf_images); - if (gr->has_dmabuf_import) + if (gr->has_dmabuf_import) { gr->base.import_dmabuf = gl_renderer_import_dmabuf; + gr->base.query_dmabuf_formats = + gl_renderer_query_dmabuf_formats; + gr->base.query_dmabuf_modifiers = + gl_renderer_query_dmabuf_modifiers; + } if (gr->has_surfaceless_context) { weston_log("EGL_KHR_surfaceless_context available\n"); diff --git a/shared/weston-egl-ext.h b/shared/weston-egl-ext.h index f3e6dcea2..c7a34302b 100644 --- a/shared/weston-egl-ext.h +++ b/shared/weston-egl-ext.h @@ -125,6 +125,13 @@ typedef struct wl_buffer * (EGLAPIENTRYP PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL) ( #define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A #endif +/* Define tokens from EGL_EXT_image_dma_buf_import_modifiers */ +#ifndef EGL_EXT_image_dma_buf_import_modifiers +#define EGL_EXT_image_dma_buf_import_modifiers 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFFORMATSEXTPROC) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFMODIFIERSEXTPROC) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); +#endif + #ifndef EGL_EXT_swap_buffers_with_damage #define EGL_EXT_swap_buffers_with_damage 1 typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC) (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects); From 41b4b8f492a1e3c3b31e516465807124f2a1e390 Mon Sep 17 00:00:00 2001 From: Varad Gautam Date: Wed, 26 Apr 2017 19:17:17 +0530 Subject: [PATCH 0082/1642] linux-dmabuf: advertise supported formats and modifiers implement 'modifier' event to communicate available formats and modifiers to the client and support zwp_linux_dmabuf_v1 interface version 3. v2: handle zero modifiers case, deprecate 'format' event. Signed-off-by: Varad Gautam Reviewed-by: Daniel Stone --- libweston/linux-dmabuf.c | 39 +++++++++++++++++++++++++++++++++++---- libweston/linux-dmabuf.h | 3 +++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index 9121e59c1..4f153b1c9 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -467,6 +467,11 @@ bind_linux_dmabuf(struct wl_client *client, { struct weston_compositor *compositor = data; struct wl_resource *resource; + int *formats = NULL; + uint64_t *modifiers = NULL; + int num_formats, num_modifiers; + uint64_t modifier_invalid = DRM_FORMAT_MOD_INVALID; + int i, j; resource = wl_resource_create(client, &zwp_linux_dmabuf_v1_interface, version, id); @@ -478,9 +483,35 @@ bind_linux_dmabuf(struct wl_client *client, wl_resource_set_implementation(resource, &linux_dmabuf_implementation, compositor, NULL); - /* EGL_EXT_image_dma_buf_import does not provide a way to query the - * supported pixel formats. */ - /* XXX: send formats */ + /* + * Use EGL_EXT_image_dma_buf_import_modifiers to query and advertise + * format/modifier codes. + */ + compositor->renderer->query_dmabuf_formats(compositor, &formats, + &num_formats); + + for (i = 0; i < num_formats; i++) { + compositor->renderer->query_dmabuf_modifiers(compositor, + formats[i], + &modifiers, + &num_modifiers); + + /* send DRM_FORMAT_MOD_INVALID token when no modifiers are supported + * for this format */ + if (num_modifiers == 0) { + num_modifiers = 1; + modifiers = &modifier_invalid; + } + for (j = 0; j < num_modifiers; j++) { + uint32_t modifier_lo = modifiers[j] & 0xFFFFFFFF; + uint32_t modifier_hi = modifiers[j] >> 32; + zwp_linux_dmabuf_v1_send_modifier(resource, formats[i], + modifier_hi, + modifier_lo); + } + free(modifiers); + } + free(formats); } /** Advertise linux_dmabuf support @@ -498,7 +529,7 @@ WL_EXPORT int linux_dmabuf_setup(struct weston_compositor *compositor) { if (!wl_global_create(compositor->wl_display, - &zwp_linux_dmabuf_v1_interface, 2, + &zwp_linux_dmabuf_v1_interface, 3, compositor, bind_linux_dmabuf)) return -1; diff --git a/libweston/linux-dmabuf.h b/libweston/linux-dmabuf.h index 64f43e597..f4ab52cbd 100644 --- a/libweston/linux-dmabuf.h +++ b/libweston/linux-dmabuf.h @@ -29,6 +29,9 @@ #include #define MAX_DMABUF_PLANES 4 +#ifndef DRM_FORMAT_MOD_INVALID +#define DRM_FORMAT_MOD_INVALID ((1ULL<<56) - 1) +#endif struct linux_dmabuf_buffer; typedef void (*dmabuf_user_data_destroy_func)( From f7da8b3139ad609d7ab75f3652f658e4dd4574ac Mon Sep 17 00:00:00 2001 From: Varad Gautam Date: Wed, 23 Nov 2016 14:03:18 +0530 Subject: [PATCH 0083/1642] gl-renderer: allow importing dmabufs with format modifiers pass over the modifier attributes to EGL. v2: ensure same modifier is passed for all planes (Daniel Stone) Signed-off-by: Varad Gautam Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 29 ++++++++++++++++++++++++++--- shared/weston-egl-ext.h | 6 ++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 9e56a200a..f5e5371ee 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -1577,7 +1577,7 @@ import_simple_dmabuf(struct gl_renderer *gr, struct dmabuf_attributes *attributes) { struct egl_image *image; - EGLint attribs[30]; + EGLint attribs[40]; int atti = 0; /* This requires the Mesa commit in @@ -1594,7 +1594,6 @@ import_simple_dmabuf(struct gl_renderer *gr, attribs[atti++] = attributes->height; attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; attribs[atti++] = attributes->format; - /* XXX: Add modifier here when supported */ if (attributes->n_planes > 0) { attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT; @@ -1603,6 +1602,12 @@ import_simple_dmabuf(struct gl_renderer *gr, attribs[atti++] = attributes->offset[0]; attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; attribs[atti++] = attributes->stride[0]; + if (gr->has_dmabuf_import_modifiers) { + attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; + attribs[atti++] = attributes->modifier[0] & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; + attribs[atti++] = attributes->modifier[0] >> 32; + } } if (attributes->n_planes > 1) { @@ -1612,6 +1617,12 @@ import_simple_dmabuf(struct gl_renderer *gr, attribs[atti++] = attributes->offset[1]; attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT; attribs[atti++] = attributes->stride[1]; + if (gr->has_dmabuf_import_modifiers) { + attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT; + attribs[atti++] = attributes->modifier[1] & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT; + attribs[atti++] = attributes->modifier[1] >> 32; + } } if (attributes->n_planes > 2) { @@ -1621,6 +1632,12 @@ import_simple_dmabuf(struct gl_renderer *gr, attribs[atti++] = attributes->offset[2]; attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT; attribs[atti++] = attributes->stride[2]; + if (gr->has_dmabuf_import_modifiers) { + attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT; + attribs[atti++] = attributes->modifier[2] & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT; + attribs[atti++] = attributes->modifier[2] >> 32; + } } attribs[atti++] = EGL_NONE; @@ -1944,8 +1961,14 @@ gl_renderer_import_dmabuf(struct weston_compositor *ec, assert(gr->has_dmabuf_import); for (i = 0; i < dmabuf->attributes.n_planes; i++) { - /* EGL import does not have modifiers */ + /* return if EGL doesn't support import modifiers */ if (dmabuf->attributes.modifier[i] != 0) + if (!gr->has_dmabuf_import_modifiers) + return false; + + /* return if modifiers passed are unequal */ + if (dmabuf->attributes.modifier[i] != + dmabuf->attributes.modifier[0]) return false; } diff --git a/shared/weston-egl-ext.h b/shared/weston-egl-ext.h index c7a34302b..05eca3175 100644 --- a/shared/weston-egl-ext.h +++ b/shared/weston-egl-ext.h @@ -128,6 +128,12 @@ typedef struct wl_buffer * (EGLAPIENTRYP PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL) ( /* Define tokens from EGL_EXT_image_dma_buf_import_modifiers */ #ifndef EGL_EXT_image_dma_buf_import_modifiers #define EGL_EXT_image_dma_buf_import_modifiers 1 +#define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 +#define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 +#define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 +#define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 +#define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 +#define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFFORMATSEXTPROC) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFMODIFIERSEXTPROC) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); #endif From c32e05bbf3bdef6007156146a8dab01770674e38 Mon Sep 17 00:00:00 2001 From: Varad Gautam Date: Wed, 23 Nov 2016 14:03:19 +0530 Subject: [PATCH 0084/1642] gl-renderer: allow importing fourth dmabuf plane EGL_EXT_image_dma_buf_import_modifiers supports importing upto four dmabuf planes into an EGLImage. v2: correct PLANE3_PITCH token (Daniel Stone) Signed-off-by: Varad Gautam Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 17 ++++++++++++++++- shared/weston-egl-ext.h | 5 +++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index f5e5371ee..a1301ffbb 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -1577,7 +1577,7 @@ import_simple_dmabuf(struct gl_renderer *gr, struct dmabuf_attributes *attributes) { struct egl_image *image; - EGLint attribs[40]; + EGLint attribs[50]; int atti = 0; /* This requires the Mesa commit in @@ -1640,6 +1640,21 @@ import_simple_dmabuf(struct gl_renderer *gr, } } + if (gr->has_dmabuf_import_modifiers) { + if (attributes->n_planes > 3) { + attribs[atti++] = EGL_DMA_BUF_PLANE3_FD_EXT; + attribs[atti++] = attributes->fd[3]; + attribs[atti++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT; + attribs[atti++] = attributes->offset[3]; + attribs[atti++] = EGL_DMA_BUF_PLANE3_PITCH_EXT; + attribs[atti++] = attributes->stride[3]; + attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT; + attribs[atti++] = attributes->modifier[3] & 0xFFFFFFFF; + attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT; + attribs[atti++] = attributes->modifier[3] >> 32; + } + } + attribs[atti++] = EGL_NONE; image = egl_image_create(gr, EGL_LINUX_DMA_BUF_EXT, NULL, diff --git a/shared/weston-egl-ext.h b/shared/weston-egl-ext.h index 05eca3175..ffea438b2 100644 --- a/shared/weston-egl-ext.h +++ b/shared/weston-egl-ext.h @@ -128,12 +128,17 @@ typedef struct wl_buffer * (EGLAPIENTRYP PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL) ( /* Define tokens from EGL_EXT_image_dma_buf_import_modifiers */ #ifndef EGL_EXT_image_dma_buf_import_modifiers #define EGL_EXT_image_dma_buf_import_modifiers 1 +#define EGL_DMA_BUF_PLANE3_FD_EXT 0x3440 +#define EGL_DMA_BUF_PLANE3_OFFSET_EXT 0x3441 +#define EGL_DMA_BUF_PLANE3_PITCH_EXT 0x3442 #define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 #define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 #define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 #define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 #define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 #define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 +#define EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT 0x3449 +#define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFFORMATSEXTPROC) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFMODIFIERSEXTPROC) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); #endif From f9dec67990a54afe14d4d2db694bf696ae418bcd Mon Sep 17 00:00:00 2001 From: Varad Gautam Date: Thu, 17 Nov 2016 17:25:59 +0530 Subject: [PATCH 0085/1642] clients/simple-dmabuf-intel: rename to simple-dmabuf-drm this will allow adding other drm backends later. Signed-off-by: Varad Gautam Reviewed-by: Daniel Stone --- Makefile.am | 12 +++++----- ...ple-dmabuf-intel.c => simple-dmabuf-drm.c} | 0 configure.ac | 22 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) rename clients/{simple-dmabuf-intel.c => simple-dmabuf-drm.c} (100%) diff --git a/Makefile.am b/Makefile.am index 45df7feab..79bd744ed 100644 --- a/Makefile.am +++ b/Makefile.am @@ -623,18 +623,18 @@ weston_simple_egl_CFLAGS = $(AM_CFLAGS) $(SIMPLE_EGL_CLIENT_CFLAGS) weston_simple_egl_LDADD = $(SIMPLE_EGL_CLIENT_LIBS) -lm endif -if BUILD_SIMPLE_DMABUF_INTEL_CLIENT -demo_clients += weston-simple-dmabuf-intel -weston_simple_dmabuf_intel_SOURCES = clients/simple-dmabuf-intel.c -nodist_weston_simple_dmabuf_intel_SOURCES = \ +if BUILD_SIMPLE_DMABUF_DRM_CLIENT +demo_clients += weston-simple-dmabuf-drm +weston_simple_dmabuf_drm_SOURCES = clients/simple-dmabuf-drm.c +nodist_weston_simple_dmabuf_drm_SOURCES = \ protocol/xdg-shell-unstable-v6-protocol.c \ protocol/xdg-shell-unstable-v6-client-protocol.h \ protocol/fullscreen-shell-unstable-v1-protocol.c \ protocol/fullscreen-shell-unstable-v1-client-protocol.h \ protocol/linux-dmabuf-unstable-v1-protocol.c \ protocol/linux-dmabuf-unstable-v1-client-protocol.h -weston_simple_dmabuf_intel_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_INTEL_CLIENT_CFLAGS) -weston_simple_dmabuf_intel_LDADD = $(SIMPLE_DMABUF_INTEL_CLIENT_LIBS) libshared.la +weston_simple_dmabuf_drm_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_DRM_CLIENT_CFLAGS) +weston_simple_dmabuf_drm_LDADD = $(SIMPLE_DMABUF_DRM_CLIENT_LIBS) libshared.la BUILT_SOURCES += protocol/linux-dmabuf-unstable-v1-client-protocol.h endif diff --git a/clients/simple-dmabuf-intel.c b/clients/simple-dmabuf-drm.c similarity index 100% rename from clients/simple-dmabuf-intel.c rename to clients/simple-dmabuf-drm.c diff --git a/configure.ac b/configure.ac index c4f5f2128..bb3375332 100644 --- a/configure.ac +++ b/configure.ac @@ -380,19 +380,19 @@ if test x$enable_simple_egl_clients = xyes; then [egl glesv2 wayland-client wayland-egl wayland-cursor]) fi -AC_ARG_ENABLE(simple-dmabuf-intel-client, - AS_HELP_STRING([--disable-simple-dmabuf-intel-client], - [do not build the simple dmabuf intel client]),, - enable_simple_dmabuf_intel_client="auto") -if ! test "x$enable_simple_dmabuf_intel_client" = "xno"; then - PKG_CHECK_MODULES(SIMPLE_DMABUF_INTEL_CLIENT, [wayland-client libdrm libdrm_intel], - have_simple_dmabuf_intel_client=yes, have_simple_dmabuf_intel_client=no) - if test "x$have_simple_dmabuf_intel_client" = "xno" -a "x$enable_simple_dmabuf_intel_client" = "xyes"; then - AC_MSG_ERROR([Intel dmabuf client explicitly enabled, but libdrm_intel couldn't be found]) +AC_ARG_ENABLE(simple-dmabuf-drm-client, + AS_HELP_STRING([--disable-simple-dmabuf-drm-client], + [do not build the simple dmabuf drm client]),, + enable_simple_dmabuf_drm_client="auto") +if ! test "x$enable_simple_dmabuf_drm_client" = "xno"; then + PKG_CHECK_MODULES(SIMPLE_DMABUF_DRM_CLIENT, [wayland-client libdrm libdrm_intel], + have_simple_dmabuf_drm_client=yes, have_simple_dmabuf_drm_client=no) + if test "x$have_simple_dmabuf_drm_client" = "xno" -a "x$enable_simple_dmabuf_drm_client" = "xyes"; then + AC_MSG_ERROR([DRM dmabuf client explicitly enabled, but libdrm_intel couldn't be found]) fi - enable_simple_dmabuf_intel_client="$have_simple_dmabuf_intel_client" + enable_simple_dmabuf_drm_client="$have_simple_dmabuf_drm_client" fi -AM_CONDITIONAL(BUILD_SIMPLE_DMABUF_INTEL_CLIENT, test "x$enable_simple_dmabuf_intel_client" = "xyes") +AM_CONDITIONAL(BUILD_SIMPLE_DMABUF_DRM_CLIENT, test "x$enable_simple_dmabuf_drm_client" = "xyes") AC_ARG_ENABLE(simple-dmabuf-v4l-client, AS_HELP_STRING([--disable-simple-dmabuf-v4l-client], From f7b3a396253b175703952588692266897af22843 Mon Sep 17 00:00:00 2001 From: Varad Gautam Date: Wed, 26 Apr 2017 19:17:18 +0530 Subject: [PATCH 0086/1642] clients/simple-dmabuf-drm: add freedreno support alongside intel abstract drm specific bits to struct drm_device and support running on freedreno. introduce 'modifier' event. v2: rebase to master, deprecate 'format' event. Signed-off-by: Varad Gautam Reviewed-by: Daniel Stone --- Makefile.am | 2 +- clients/simple-dmabuf-drm.c | 218 ++++++++++++++++++++++++++++++------ configure.ac | 19 +++- 3 files changed, 197 insertions(+), 42 deletions(-) diff --git a/Makefile.am b/Makefile.am index 79bd744ed..2a285480f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -634,7 +634,7 @@ nodist_weston_simple_dmabuf_drm_SOURCES = \ protocol/linux-dmabuf-unstable-v1-protocol.c \ protocol/linux-dmabuf-unstable-v1-client-protocol.h weston_simple_dmabuf_drm_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_DRM_CLIENT_CFLAGS) -weston_simple_dmabuf_drm_LDADD = $(SIMPLE_DMABUF_DRM_CLIENT_LIBS) libshared.la +weston_simple_dmabuf_drm_LDADD = $(SIMPLE_DMABUF_DRM_CLIENT_LIBS) $(LIBDRM_PLATFORM_LIBS) libshared.la BUILT_SOURCES += protocol/linux-dmabuf-unstable-v1-client-protocol.h endif diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 0cd0d2ff8..0acb30fb2 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -39,8 +39,13 @@ #include #include + +#ifdef HAVE_LIBDRM_INTEL #include #include +#elif HAVE_LIBDRM_FREEDRENO +#include +#endif #include #include @@ -49,6 +54,8 @@ #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" +struct buffer; + struct display { struct wl_display *display; struct wl_registry *registry; @@ -60,14 +67,31 @@ struct display { int req_dmabuf_immediate; }; +struct drm_device { + int fd; + char *name; + + int (*alloc_bo)(struct buffer *buf); + void (*free_bo)(struct buffer *buf); + int (*export_bo_to_prime)(struct buffer *buf); + int (*map_bo)(struct buffer *buf); + void (*unmap_bo)(struct buffer *buf); +}; + struct buffer { struct wl_buffer *buffer; int busy; + struct drm_device *dev; int drm_fd; +#ifdef HAVE_LIBDRM_INTEL drm_intel_bufmgr *bufmgr; drm_intel_bo *bo; +#elif HAVE_LIBDRM_FREEDRENO + struct fd_device *fd_dev; + struct fd_bo *bo; +#endif /* HAVE_LIBDRM_FREEDRENO */ uint32_t gem_handle; int dmabuf_fd; @@ -111,31 +135,10 @@ static const struct wl_buffer_listener buffer_listener = { buffer_release }; -static int -drm_connect(struct buffer *my_buf) -{ - /* This won't work with card0 as we need to be authenticated; instead, - * boot with drm.rnodes=1 and use that. */ - my_buf->drm_fd = open("/dev/dri/renderD128", O_RDWR); - if (my_buf->drm_fd < 0) - return 0; - - my_buf->bufmgr = drm_intel_bufmgr_gem_init(my_buf->drm_fd, 32); - if (!my_buf->bufmgr) - return 0; - - return 1; -} - -static void -drm_shutdown(struct buffer *my_buf) -{ - drm_intel_bufmgr_destroy(my_buf->bufmgr); - close(my_buf->drm_fd); -} +#ifdef HAVE_LIBDRM_INTEL static int -alloc_bo(struct buffer *my_buf) +intel_alloc_bo(struct buffer *my_buf) { /* XXX: try different tiling modes for testing FB modifiers. */ uint32_t tiling = I915_TILING_NONE; @@ -160,13 +163,13 @@ alloc_bo(struct buffer *my_buf) } static void -free_bo(struct buffer *my_buf) +intel_free_bo(struct buffer *my_buf) { drm_intel_bo_unreference(my_buf->bo); } static int -map_bo(struct buffer *my_buf) +intel_map_bo(struct buffer *my_buf) { if (drm_intel_gem_bo_map_gtt(my_buf->bo) != 0) return 0; @@ -176,6 +179,68 @@ map_bo(struct buffer *my_buf) return 1; } +static int +intel_bo_export_to_prime(struct buffer *buffer) +{ + return drm_intel_bo_gem_export_to_prime(buffer->bo, &buffer->dmabuf_fd); +} + +static void +intel_unmap_bo(struct buffer *my_buf) +{ + drm_intel_gem_bo_unmap_gtt(my_buf->bo); +} +#elif HAVE_LIBDRM_FREEDRENO +#define ALIGN(v, a) ((v + a - 1) & ~(a - 1)) + +static +int fd_alloc_bo(struct buffer *buf) +{ + int flags = DRM_FREEDRENO_GEM_CACHE_WCOMBINE; + int size = buf->width * buf->height * buf->bpp / 8; + buf->fd_dev = fd_device_new(buf->drm_fd); + + buf->bo = fd_bo_new(buf->fd_dev, size, flags); + + if (!buf->bo) + return 0; + buf->stride = ALIGN(buf->width, 32) * buf->bpp / 8; + return 1; +} + +static +void fd_free_bo(struct buffer *buf) +{ + fd_bo_del(buf->bo); +} + +static +int fd_bo_export_to_prime(struct buffer *buf) +{ + buf->dmabuf_fd = fd_bo_dmabuf(buf->bo); + if (buf->dmabuf_fd > 0) + return 0; + + return 1; +} + +static +int fd_map_bo(struct buffer *buf) +{ + buf->mmap = fd_bo_map(buf->bo); + + if (buf->mmap != NULL) + return 1; + + return 0; +} + +static +void fd_unmap_bo(struct buffer *buf) +{ +} +#endif + static void fill_content(struct buffer *my_buf) { @@ -194,11 +259,78 @@ fill_content(struct buffer *my_buf) } static void -unmap_bo(struct buffer *my_buf) +drm_device_destroy(struct buffer *buf) { - drm_intel_gem_bo_unmap_gtt(my_buf->bo); +#ifdef HAVE_LIBDRM_INTEL + drm_intel_bufmgr_destroy(buf->bufmgr); +#elif HAVE_LIBDRM_FREEDRENO + fd_device_del(buf->fd_dev); +#endif + + close(buf->drm_fd); +} + +static int +drm_device_init(struct buffer *buf) +{ + struct drm_device *dev = calloc(1, sizeof(struct drm_device)); + + drmVersionPtr version = drmGetVersion(buf->drm_fd); + + dev->fd = buf->drm_fd; + dev->name = strdup(version->name); + if (0) { + /* nothing */ + } +#ifdef HAVE_LIBDRM_INTEL + else if (!strcmp(dev->name, "i915")) { + buf->bufmgr = drm_intel_bufmgr_gem_init(buf->drm_fd, 32); + if (!buf->bufmgr) + return 0; + dev->alloc_bo = intel_alloc_bo; + dev->free_bo = intel_free_bo; + dev->export_bo_to_prime = intel_bo_export_to_prime; + dev->map_bo = intel_map_bo; + dev->unmap_bo = intel_unmap_bo; + } +#elif HAVE_LIBDRM_FREEDRENO + else if (!strcmp(dev->name, "msm")) { + dev->alloc_bo = fd_alloc_bo; + dev->free_bo = fd_free_bo; + dev->export_bo_to_prime = fd_bo_export_to_prime; + dev->map_bo = fd_map_bo; + dev->unmap_bo = fd_unmap_bo; + } +#endif + else { + fprintf(stderr, "Error: drm device %s unsupported.\n", + dev->name); + free(dev); + return 0; + } + buf->dev = dev; + return 1; } +static int +drm_connect(struct buffer *my_buf) +{ + /* This won't work with card0 as we need to be authenticated; instead, + * boot with drm.rnodes=1 and use that. */ + my_buf->drm_fd = open("/dev/dri/renderD128", O_RDWR); + if (my_buf->drm_fd < 0) + return 0; + + return drm_device_init(my_buf); +} + +static void +drm_shutdown(struct buffer *my_buf) +{ + drm_device_destroy(my_buf); +} + + static void create_succeeded(void *data, struct zwp_linux_buffer_params_v1 *params, @@ -237,30 +369,32 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, struct zwp_linux_buffer_params_v1 *params; uint64_t modifier; uint32_t flags; + struct drm_device *drm_dev; if (!drm_connect(buffer)) { fprintf(stderr, "drm_connect failed\n"); goto error; } + drm_dev = buffer->dev; buffer->width = width; buffer->height = height; buffer->bpp = 32; /* hardcoded XRGB8888 format */ - if (!alloc_bo(buffer)) { + if (!drm_dev->alloc_bo(buffer)) { fprintf(stderr, "alloc_bo failed\n"); goto error1; } - if (!map_bo(buffer)) { + if (!drm_dev->map_bo(buffer)) { fprintf(stderr, "map_bo failed\n"); goto error2; } fill_content(buffer); - unmap_bo(buffer); + drm_dev->unmap_bo(buffer); - if (drm_intel_bo_gem_export_to_prime(buffer->bo, &buffer->dmabuf_fd) != 0) { - fprintf(stderr, "drm_intel_bo_gem_export_to_prime failed\n"); + if (drm_dev->export_bo_to_prime(buffer) != 0) { + fprintf(stderr, "gem_export_to_prime failed\n"); goto error2; } if (buffer->dmabuf_fd < 0) { @@ -301,7 +435,7 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, return 0; error2: - free_bo(buffer); + drm_dev->free_bo(buffer); error1: drm_shutdown(buffer); error: @@ -405,6 +539,7 @@ create_window(struct display *display, int width, int height) static void destroy_window(struct window *window) { + struct drm_device* dev; int i; if (window->callback) @@ -415,7 +550,8 @@ destroy_window(struct window *window) continue; wl_buffer_destroy(window->buffers[i].buffer); - free_bo(&window->buffers[i]); + dev = window->buffers[i].dev; + dev->free_bo(&window->buffers[i]); close(window->buffers[i].dmabuf_fd); drm_shutdown(&window->buffers[i]); } @@ -475,16 +611,26 @@ static const struct wl_callback_listener frame_listener = { }; static void -dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) +dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, + uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { struct display *d = data; if (format == DRM_FORMAT_XRGB8888) d->xrgb8888_format_found = 1; + + /* XXX: do something useful with modifiers */ +} + +static void +dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) +{ + /* XXX: will be deprecated. */ } static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { - dmabuf_format + dmabuf_format, + dmabuf_modifiers }; static void diff --git a/configure.ac b/configure.ac index bb3375332..14c742fda 100644 --- a/configure.ac +++ b/configure.ac @@ -385,12 +385,21 @@ AC_ARG_ENABLE(simple-dmabuf-drm-client, [do not build the simple dmabuf drm client]),, enable_simple_dmabuf_drm_client="auto") if ! test "x$enable_simple_dmabuf_drm_client" = "xno"; then - PKG_CHECK_MODULES(SIMPLE_DMABUF_DRM_CLIENT, [wayland-client libdrm libdrm_intel], - have_simple_dmabuf_drm_client=yes, have_simple_dmabuf_drm_client=no) - if test "x$have_simple_dmabuf_drm_client" = "xno" -a "x$enable_simple_dmabuf_drm_client" = "xyes"; then - AC_MSG_ERROR([DRM dmabuf client explicitly enabled, but libdrm_intel couldn't be found]) + PKG_CHECK_MODULES(SIMPLE_DMABUF_DRM_CLIENT, [wayland-client libdrm], + [PKG_CHECK_MODULES(LIBDRM_PLATFORM, [libdrm_freedreno], + AC_DEFINE([HAVE_LIBDRM_FREEDRENO], [1], [Build freedreno dmabuf client]) have_simple_dmabuf_drm_client=freedreno, + [PKG_CHECK_MODULES(LIBDRM_PLATFORM, [libdrm_intel], + AC_DEFINE([HAVE_LIBDRM_INTEL], [1], [Build intel dmabuf client]) have_simple_dmabuf_drm_client=intel, + have_simple_dmabuf_drm_client=unsupported)])], + have_simple_dmabuf_drm_client=unsupported) + + if test "x$have_simple_dmabuf_drm_client" = "xunsupported" -a "x$enable_simple_dmabuf_drm_client" = "xyes"; then + AC_MSG_ERROR([DRM dmabuf client explicitly enabled, but libdrm_intel or libdrm_freedreno not found]) + fi + + if test "x$have_simple_dmabuf_drm_client" = "xfreedreno" -o "x$have_simple_dmabuf_drm_client" = "xintel"; then + enable_simple_dmabuf_drm_client="yes" fi - enable_simple_dmabuf_drm_client="$have_simple_dmabuf_drm_client" fi AM_CONDITIONAL(BUILD_SIMPLE_DMABUF_DRM_CLIENT, test "x$enable_simple_dmabuf_drm_client" = "xyes") From ee58911912d88caacf340a5cb6a9e25fcba24996 Mon Sep 17 00:00:00 2001 From: Varad Gautam Date: Wed, 23 Nov 2016 14:03:21 +0530 Subject: [PATCH 0087/1642] clients/simple-dmabuf-drm: import with dmabuf modifiers mesa's freedreno driver supports importing dmabufs with format DRM_FORMAT_NV12 and DRM_FORMAT_MOD_SAMSUNG_64_32_TILE modifier. demonstrate weston modifier advertising and import path using this combination when run with --import-format=NV12. v2: - hard code format if platform doesn't implement EGL_EXT_image_dma_buf_import_modifiers and cannot advertise format/modifier support. - squash using valid frame data to fill dmabuf planes Signed-off-by: Varad Gautam Reviewed-by: Daniel Stone --- Makefile.am | 4 +- clients/simple-dmabuf-drm-data.h | 3074 ++++++++++++++++++++++++++++++ clients/simple-dmabuf-drm.c | 187 +- configure.ac | 2 +- 4 files changed, 3227 insertions(+), 40 deletions(-) create mode 100644 clients/simple-dmabuf-drm-data.h diff --git a/Makefile.am b/Makefile.am index 2a285480f..e9679e685 100644 --- a/Makefile.am +++ b/Makefile.am @@ -625,7 +625,9 @@ endif if BUILD_SIMPLE_DMABUF_DRM_CLIENT demo_clients += weston-simple-dmabuf-drm -weston_simple_dmabuf_drm_SOURCES = clients/simple-dmabuf-drm.c +weston_simple_dmabuf_drm_SOURCES = \ + clients/simple-dmabuf-drm.c \ + clients/simple-dmabuf-drm-data.h nodist_weston_simple_dmabuf_drm_SOURCES = \ protocol/xdg-shell-unstable-v6-protocol.c \ protocol/xdg-shell-unstable-v6-client-protocol.h \ diff --git a/clients/simple-dmabuf-drm-data.h b/clients/simple-dmabuf-drm-data.h new file mode 100644 index 000000000..5c42aadb7 --- /dev/null +++ b/clients/simple-dmabuf-drm-data.h @@ -0,0 +1,3074 @@ +/* 256x256 tiled nv12 frame data */ +const unsigned nv12_tiled[] = {0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, 0xd2d2d2d2, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x29292929, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0xd2d2d2d2, 0xd2d2d2d2, 0xaaaaaad2, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x919191aa, 0x91919191, 0x91919191, 0x91919191, 0x91919191, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x6a6a6a10, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x1010106a, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xaaaa1010, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, +0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x1010aaaa, 0x10101010, 0x10101010, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0xeb101010, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x91919191, 0x91919191, 0x91919191, 0x91919191, 0x6a6a9191, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, +0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x6a6a6a6a, 0x51516a6a, 0x51515151, 0x51515151, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x29515151, 0x29292929, +0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, 0x29292929, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0xebeb1010, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0xebebebeb, 0x101010eb, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, 0x10101010, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0xdedbb1d5, 0x499e953c, 0x80665146, 0x3c61618e, 0x7d65cf3d, 0x33ea645b, 0x4056c7d3, 0x63a613a3, +0x3ae4d468, 0xebaaa076, 0x8fe2835c, 0x91da4bd2, 0xaf4e844d, 0x9f57cabf, 0x9fbe794b, 0x2480dc56, +0x87ab495d, 0xb5352a50, 0x4a22881e, 0xd59a87c2, 0xcb5e56d7, 0xa95266a5, 0x10c35bd1, 0xb83f66e0, +0x9328a6e3, 0x99825968, 0x37b33f99, 0x3ee2841f, 0x77902452, 0x69665fa9, 0xcf5e4bde, 0x7d4fb34b, +0xdc311273, 0xa9409e81, 0xd5d76044, 0x9b638f64, 0x36dca5a7, 0xcf1e268e, 0xc96426a3, 0x63375711, +0x31be2118, 0x1fb1685b, 0x1487ca2c, 0x48601678, 0x8a5fdd4d, 0x37bde217, 0x81cea74e, 0x80617ad0, +0xc912b1de, 0xc887df95, 0x30967c82, 0x3462411c, 0x3ecbaa50, 0x6e194bbc, 0x7ab9cf10, 0xe7568a93, +0x94b7c362, 0x24259616, 0xcd243399, 0x75d281ca, 0xb060c3e2, 0xac4d6489, 0x5c1f7a19, 0xd07ed4d3, +0x1616378c, 0xdbd2b656, 0xb7e0a8a1, 0x445f448c, 0xabd02c79, 0x2be69d1c, 0xa7af649a, 0x5265c876, +0xacba9fb0, 0x6f5fad3e, 0x7231dbcb, 0x914bb1dd, 0xb25c7a22, 0xb5936612, 0x28ac6ae7, 0x5c71b01c, +0x45e32421, 0xacebc86b, 0x7ea2ab6b, 0x94225ea3, 0x6cb4ade9, 0xaa98e62e, 0x3c106853, 0x272dd961, +0x408d5aca, 0xcb4a769e, 0x8376d08c, 0x634b727b, 0x5b1ba56b, 0x1c7fb131, 0x8a8a3ec7, 0xa4ddb22b, +0x438b4244, 0xe09cde9a, 0x28814d82, 0x12525c29, 0x25403e8f, 0xd9af12bc, 0xb85ca5bb, 0x2f54881a, +0xd1d69778, 0x248e9521, 0xcabdb780, 0x919d664a, 0x4d4333aa, 0x61b431b1, 0x6f5a9e5f, 0x95b1a5cb, +0xb58d5abc, 0x6388c2ad, 0x5b4636b2, 0x3eb7dea0, 0x7a1a61eb, 0x81197eb1, 0x3080e2bf, 0x32a27949, +0x70844462, 0xd716d449, 0x35ce5c6f, 0xe32b5812, 0x549ca5a6, 0x711f687f, 0x7cc37453, 0xd38f4fe8, +0x87b33576, 0xb77a3c6e, 0xde962cc3, 0xe03ed618, 0x5be5dde9, 0x47553cb6, 0x2144e828, 0xd6e074b5, +0x9f39e34e, 0x19621fdf, 0x44736125, 0x48791076, 0x3713e82b, 0x1643443e, 0x7bb3656d, 0x4d44574c, +0x5fc67719, 0xca38cc81, 0xa05ed67d, 0xe868e513, 0x69b4a154, 0xd051cb90, 0x56508177, 0x2bd8444c, +0x4a9c85e2, 0x4862d5d0, 0xbf508f44, 0x3f723267, 0x9a2bc3b7, 0xd0e76893, 0x34cd3d75, 0x83718676, +0xe28fea48, 0x626b85b0, 0x46444587, 0x1c22d658, 0x702b50ab, 0x2cb33ee6, 0x986a5199, 0x8e528c8e, +0x1699aa31, 0x50b928c0, 0x942fb198, 0x92bb8a87, 0x6eabde5e, 0xb11d9d6d, 0x4cdb9e36, 0x62e2a356, +0x21b37bcc, 0x25d9e7c2, 0x74ead5a7, 0xdb1173b3, 0x38ca91dc, 0xba6716a4, 0xaf5d2357, 0x11cf3a69, +0xa9d63e94, 0xd5557779, 0x68b58ee7, 0x2e43de9f, 0x5481249f, 0x5c41ae70, 0x8ca57652, 0x8e817a8d, +0x791fab94, 0xba70de82, 0x186497aa, 0x58da5fea, 0x67a550b1, 0x89131b70, 0x65ce9956, 0x3561d082, +0x133fc41f, 0xc5b6af9f, 0xe0cecd1f, 0x93871d7a, 0x17521667, 0x79426265, 0xbdd16b94, 0xd1d3ade7, +0xd6536744, 0x30b13694, 0xb1323156, 0x178e63c9, 0xc35e31cd, 0x3f5a1713, 0x5fa8a360, 0xc2875da1, +0xb1564c53, 0xc6c99921, 0xe943599f, 0xaf79a3bc, 0x5cc27e5c, 0xcfc26015, 0xa94c44c5, 0x19c22b50, +0x263e537f, 0xe4896dc2, 0x531d4750, 0xbaaf4a17, 0x3d9bdb1f, 0x812bad31, 0x6592e43d, 0xa5316391, +0x94e2581c, 0xa058d87f, 0x4c96b475, 0x4bbb8152, 0xebbe4946, 0x4ab16e25, 0x1897a9ae, 0xc218744a, +0xc6833935, 0xe89d70d4, 0x7ccaa262, 0x5428b755, 0x534a5d25, 0x3d29cc93, 0x42544a91, 0x84ddab19, +0x62d2b01f, 0x1ce35ca5, 0x8ab3cb48, 0xe83a5cbd, 0xb1111aca, 0x6eb2595e, 0x8a9e8417, 0xdc56779c, +0xc710bee5, 0xe2b6e793, 0x1b4a3056, 0x647b9870, 0xd0ea3aac, 0x161f3d26, 0x9993564a, 0x279f23ba, +0xbfca4125, 0xbb7ba45d, 0x906467bc, 0x9678dae8, 0x553a99d6, 0x258fc38a, 0x132b7a81, 0x3268d635, +0xce66cfa1, 0x999cbca4, 0xb11f71cf, 0xb6969131, 0x9f8e399f, 0xb1b17f6e, 0x563dd0ea, 0x34194a86, +0x99948dd3, 0x8ea39e4c, 0x264e2dbe, 0xd762e6a0, 0x7a27b133, 0x16119970, 0x31bf57db, 0x44188194, +0xa37234ea, 0xb11a95d0, 0x4eeb53bb, 0x34416c3f, 0x67b625a1, 0x21cc8153, 0x22d1a581, 0x74cdea22, +0x93d57f3b, 0x3b45123b, 0xcf38c219, 0x989b4b87, 0xe87c2b62, 0xe6b781b6, 0xae6bbdeb, 0xdae71aa8, +0x11d96fad, 0x1ab03ce2, 0x74c37abf, 0x6120ce61, 0xcb4fa17f, 0xc13ae77e, 0x9f627c91, 0xafcd1432, +0x7979beb8, 0x65e482ce, 0x9aca374a, 0x7e138968, 0x9027894e, 0xa596444a, 0x77b09b7d, 0xe2e64839, +0x97876e8e, 0x546e549f, 0xea49b0a4, 0x28daa460, 0xb9da9cdb, 0xa47a9fb6, 0xba701ce1, 0xaebd2580, +0x1125375e, 0xd691db18, 0x2e13e842, 0x51498de6, 0x13a8b79e, 0xd45087ab, 0x327a9534, 0x26b9d3c9, +0x4624d357, 0x4ad6a8b0, 0x9f23bd32, 0x317f92e1, 0xd768baa3, 0x6d7b4cc4, 0x3ec42b5f, 0x3b66e6d5, +0xdd7e68ab, 0x7ccbe474, 0x2e70e7a4, 0xb72b2113, 0x1812803e, 0x608a38c7, 0x836e74b6, 0x252ba967, +0xa1508dad, 0xa5d624dc, 0x38196910, 0x428f62d2, 0x117ac37c, 0xc2e29673, 0xa94c718c, 0xd66e681d, +0x1372438d, 0xd78269d0, 0x64d0da5d, 0x9d5cc44c, 0x68bf87b1, 0xcb32d668, 0x5757dd13, 0xaabb6e95, +0x93d9427c, 0x4e364910, 0x5ad35ce1, 0x25d6b8d7, 0xc3da822f, 0x909a6766, 0xeb88b856, 0x919d2cb6, +0x10a568ab, 0xd459e918, 0x991d8713, 0xa7a98a7e, 0xcac394e1, 0x25c97231, 0x53d9bda9, 0xa07bc841, +0xc3a9946b, 0xc776dfa9, 0xcc5f5db7, 0x803e83ba, 0x24749b62, 0x7b4a6666, 0xcb66c960, 0x11bdd6b1, +0x9be0a4ca, 0x3c1976aa, 0x97b69769, 0x9fd71571, 0x2ec17499, 0xcb5f8ec8, 0x41e0b988, 0xae10a4eb, +0x1c677441, 0x2384d5b8, 0x7d3f3637, 0x3f496643, 0x90c8d6dc, 0x4e955c3d, 0x13896d74, 0xd6957ab2, +0xa311bdb7, 0x90689097, 0x25abd04f, 0x4fb1e7cc, 0x1382c87d, 0xd076183e, 0xc4369b56, 0x9b1fa5a5, +0x1fb683a4, 0xbd2acfe3, 0x123c8be2, 0xe3bf2b18, 0x18c5238a, 0xac6a108f, 0x44bc485c, 0x15cc9583, +0xcc947e38, 0xbd312584, 0xc5a21f68, 0x59dc367a, 0x44ac7e57, 0xd6d78f15, 0xcb5c2bb7, 0x32709612, +0x97838d80, 0xa6e79617, 0x2e1f49c9, 0x7e9076dc, 0x3d33dc13, 0x846d294a, 0x4ac723bc, 0x2b4f17c9, +0xe07b8bd0, 0x8d1cb63a, 0x8863e37c, 0x88445d22, 0x872c3dcb, 0xa76ee115, 0x1e3ee999, 0x16ce40b4, +0x6f75587a, 0x85cdd0af, 0x9d8d1590, 0x8f5e36a3, 0xc793371b, 0x7a686f13, 0xca74a581, 0xe29d8070, +0xa66aad8e, 0xa3cc535d, 0x13945057, 0xa56a4b68, 0xa683a7c7, 0xe9bed6e2, 0x3e82e8a1, 0x1224479c, +0x4e548d5d, 0x208168be, 0x4a7495dd, 0xe2d0e9eb, 0xa5d2906b, 0x5622ab8d, 0xb366b850, 0x4ca5bd1f, +0xc52bae19, 0xca527db7, 0xe722c39b, 0x7a3da538, 0x6c9dab13, 0x89b1398f, 0x1916cd98, 0xcad07470, +0xd7c415cd, 0x1fa8e0e8, 0xb676b69e, 0x61d0a3a4, 0x7bb9d6cc, 0xbd42a7a8, 0xa86828ce, 0xc3309678, +0x2a625616, 0xca312336, 0x388d4c38, 0xaa5f5638, 0x7b441231, 0x2b138a9b, 0x52795f46, 0x4ee291d4, +0x1cb374b7, 0x4a2f4a1a, 0xce603f75, 0x8d2be370, 0x1312d12b, 0x9e8f2b2b, 0x744274e8, 0x5abdb16a, +0x54d74a4c, 0x6b2fc358, 0x65c36eac, 0x229be031, 0x9f1d5ce9, 0x7464b5d1, 0xb3984450, 0x202567b5, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10101000, 0x10101010, 0x10101010, +0x10101010, 0x10101010, 0x20201010, 0x20202020, 0x20202020, 0x20202020, 0x20202020, 0x20202020, +0x6713cd10, 0x29a52ba2, 0xa4d5cb5d, 0x20622a27, 0xe96d7870, 0xa625eb1e, 0xa4132962, 0x351229ab, +0x4273774c, 0xa562d0e0, 0xc35c17ad, 0xbe1e41dc, 0xbfab904e, 0xeb1f3386, 0x3864ac15, 0x3ee644d0, +0x328f50e9, 0x4a7a18bd, 0x916112aa, 0x6ed9aa62, 0x7f37c6a4, 0xd0a66a6e, 0x965184d6, 0x3ec4d613, +0xbd623660, 0xe378e2d9, 0x8eda9983, 0x1f64d431, 0xaabe1bc7, 0x6b6b2276, 0x8ab06942, 0x4c1774cc, +0xaac1e198, 0xcec3e519, 0x62e5b169, 0xc4e02fe7, 0xe88425e8, 0xabb0845f, 0x959e3b40, 0x7a1e4427, +0x7dc877e4, 0x1fe56ed2, 0x8e7c338d, 0xb91fbe90, 0xdf5dc15b, 0xc92523c6, 0x5e13b125, 0xdf67937c, +0xb8718f9b, 0xa16ba434, 0xbb74b840, 0x12403ba5, 0x3643142b, 0xdae82693, 0x69c2cf22, 0xb17ad0b0, +0x27922ac8, 0xb3971994, 0x8dc56592, 0x9bd0a6e8, 0x4b2e9288, 0x16ceda64, 0x5ceb7067, 0xe4c26b86, +0x2846dcbd, 0x6919d4d8, 0x898ecfd5, 0xb27b9640, 0xc6185ec9, 0x4a3cd2d6, 0xb8885046, 0xaec88c9a, +0x6263cdae, 0xb3328ac3, 0x55a21c5c, 0x6b875527, 0x70d6333e, 0xb07659d0, 0x4a6e4acd, 0x2417a1b1, +0x7ae3b5c8, 0xca706317, 0x721fbd37, 0xb77c2f5e, 0xa9a8cbd2, 0x7e2b9813, 0x6f943f74, 0x38ce1a8c, +0x18e27261, 0xc35cac4e, 0x88b7d892, 0xcec56ea8, 0x3b644a1f, 0xa69f211a, 0xcf1ae746, 0x20c120c8, +0x995bbe83, 0xb35ed44d, 0x3eab4bc3, 0xc4ea85c7, 0x84e04810, 0x62811da8, 0x32b06774, 0xd056234c, +0xca209b85, 0xaddd49b4, 0x16cfab1f, 0xb078bd7d, 0x505ca0d3, 0xa037d7a1, 0xd6975552, 0xa1d26c93, +0x512cc294, 0xc7883468, 0x933f1f8c, 0xa5b18668, 0x208956e1, 0x9a28e2a5, 0xa7a48f11, 0x87e26d81, +0x8d7934e2, 0x38a3e1e2, 0x7fb03b83, 0xb7442b4b, 0x79852168, 0x65bc8c74, 0x2ad016bb, 0x941370b7, +0x462387c3, 0xcfb7e32f, 0x5d60dd35, 0x2075d6c3, 0x2349bd78, 0x14a21eaf, 0xbb3981ca, 0xde82c219, +0xdddbbe68, 0xe75186a4, 0xb0252bad, 0xacd03db7, 0x7ea95062, 0x9a41c03b, 0x4a8fd06e, 0x9fe1d121, +0x41e2b6db, 0x95b5154a, 0x42d39764, 0xb7dd63c5, 0x54c36c57, 0x52dc2b91, 0x3714e1b2, 0xc3b8ea92, +0xab314b95, 0xca8fb89b, 0x71ae4566, 0x572bb8af, 0x28901787, 0x2c476298, 0x267992eb, 0x88707d52, +0xe77a38a3, 0xbd49277e, 0xe688cce2, 0x7cd6ae36, 0x7de6e348, 0x3ea0d411, 0x9bdb9d6e, 0xdc70b0dc, +0x99da5b58, 0x86486592, 0x695c5252, 0x5fb18dd6, 0x1ee11ca3, 0x9e971578, 0x6056df44, 0x1b431838, +0x486e91c1, 0x3619e298, 0x5a236c9d, 0x1326a497, 0x687a3312, 0x819229b2, 0xd59e5aa0, 0x134fb7bd, +0x73e7b755, 0xbf213252, 0x60b11b3a, 0x8b775f19, 0x26872a7c, 0xda1d7f82, 0xc3cda3d6, 0xd8214876, +0xc286ab21, 0x7fca575f, 0x204a1938, 0x2251e8d6, 0xb824b95b, 0xe17b8785, 0xadddbc38, 0xc71fcadd, +0xdb1f4c55, 0x64c0c380, 0x1e9a44c3, 0xa51fd3d7, 0xe826e3ba, 0xaa5c8d7e, 0x3ac982b1, 0xd0b0d5d7, +0x658a5345, 0x866ce478, 0xdcc37a58, 0x70446bbb, 0x5c888610, 0x4b25dc2e, 0xca88b1b7, 0x2c83b127, +0xbfe29c21, 0x19352ae4, 0x4abc99dc, 0x754dd644, 0x73675b47, 0xb43ee335, 0x4913487a, 0xa8dcac47, +0xb4442b1a, 0x138299ac, 0x9f7e7bc8, 0xc8a2abeb, 0xd3963eb2, 0x42331353, 0x1642dd2c, 0xa06a351e, +0xc562935f, 0x84dec36d, 0xabdedf4b, 0xa1cd31bc, 0x483310a6, 0xc06a6f4c, 0xb52e97d8, 0x29707448, +0x9a5ad844, 0xa7d41ac4, 0x101fc373, 0x16599554, 0x30148853, 0x8d6f8cbd, 0x14af2f19, 0x3379d688, +0xb622d6d6, 0x90cba09a, 0x2fcce193, 0x178a6751, 0x3131aac3, 0x98aab38c, 0x6ee25db6, 0xd410cfa1, +0xbd936c8e, 0x554fe98b, 0xad4a3cdc, 0xb7eacf76, 0xcf855072, 0x1aa35657, 0x8dbe2549, 0x6878a754, +0x59c75376, 0xe4a16659, 0x574d68a0, 0x562a6285, 0xd32b3868, 0xe0c49abd, 0x193e40b9, 0xba60ab40, +0xe7b868bf, 0x9b733ca3, 0x89ca6812, 0xbd67453f, 0xc0d43cd9, 0x6a9815a5, 0x6e388b81, 0x29304c27, +0x55618abd, 0x6b2bb84f, 0x10e2193b, 0x4a51c744, 0x3ec33b3a, 0x8684caab, 0x5aa3e4ab, 0x804eafa7, +0xde9495a0, 0x682f6ed6, 0x498c3672, 0xcb507a53, 0xabcb1375, 0x6a147092, 0x5c9c4c8d, 0x1d4669a5, +0x72716899, 0xa8e78744, 0xdb9d99e6, 0x9cc98631, 0xd0e87ddd, 0x2d8d301c, 0x1d1f5a56, 0xcb7fa2c2, +0x6ecbbcd8, 0x62266e12, 0x913248eb, 0xac6d597b, 0x55347ac7, 0xbf9c54c3, 0x1fd9ea35, 0xe884c9bd, +0x7ac092b1, 0x87e874de, 0xc922d6b1, 0x367e4119, 0x9962c662, 0x7ca94d93, 0xe532235c, 0x87bc4d5a, +0x3b4aa552, 0x5023e1b3, 0x4e646868, 0x29888881, 0x668f61bc, 0x563e6526, 0x5db7751e, 0x9bb15736, +0x343cd1ca, 0xadd24a2e, 0x7f179742, 0x99169f9e, 0x3eb4e04b, 0x60a0e91f, 0xc2a9e487, 0x5ccd5614, +0xe8911a90, 0xd7c9b1ca, 0x25ea616c, 0xc16bd02e, 0xa6ca691d, 0xb07d6c3c, 0xdfdc6f11, 0xb9999592, +0x476513b1, 0xe26eac92, 0xa144813d, 0xb4368cad, 0x66c99361, 0x7d3d1642, 0x5a6eaea2, 0x107c8595, +0x8a8e9c7f, 0xe52b8721, 0xbdb1d6be, 0x87ba1e25, 0xdeab5f6b, 0xb84533cd, 0x9133a1b3, 0x2cdf71bd, +0x7b29b42b, 0x16638b18, 0x1e735f6a, 0x4ea5ac11, 0xd86b866c, 0x9c2331c4, 0x50262674, 0x496ea4ca, +0x9fe73bc3, 0x43a42938, 0x255eb44c, 0x1f62cd31, 0xd7dbe593, 0x3b3c5ec7, 0x193fd415, 0x786f8e81, +0x9a2cc6bd, 0xa4788844, 0x756dd595, 0xe854cb4a, 0x813e7f35, 0x871b2795, 0x6e77cea3, 0xd06d7a7d, +0x14877a4a, 0x71463dd8, 0xb996a098, 0x2ee3151b, 0x5a44e95e, 0xb729b6ce, 0x1bc8d094, 0x87cdb682, +0x6c381176, 0x9c756edc, 0x95d6d085, 0x95c0b044, 0xe3e84962, 0xe2cb9e57, 0x7f4fb61b, 0x913fb052, +0x6c134495, 0x5c928d13, 0x8d46648d, 0x7d505187, 0xe8693471, 0x3eb229ab, 0x62bc5e42, 0x25e3b28b, +0x961415d7, 0xa526e2d0, 0x459a18df, 0xd5d723a6, 0xeb7a88bb, 0x994480b1, 0x4950835e, 0x338c2f12, +0x92323f91, 0xab75e044, 0x4413a4d0, 0xd6297062, 0x93364795, 0x51c53729, 0xb5be259f, 0xd07c4b62, +0xe2689ece, 0x862ed0c4, 0x2c622f90, 0x9464a75c, 0x61742b2e, 0x55e3748e, 0x7246da58, 0x5a3f7962, +0x50ac33e2, 0x4a94245b, 0x82a24e50, 0x25383852, 0xda50eb93, 0xb82aa9c7, 0xdd99c898, 0x9c3d26af, +0x3b1e9622, 0xe656de81, 0x12cfde3e, 0x78e64ce6, 0xc79fb03b, 0xb7b84494, 0xc3d5a8b0, 0xcfdb7c2f, +0x4a6ec776, 0xb7b8b71e, 0x38e0e28c, 0xc744511f, 0xab803133, 0x62626d96, 0xa51558d1, 0x2bc9e85d, +0x25b8a456, 0xb168d5ab, 0x9ad0368d, 0x4a6e9356, 0x2dc62aab, 0xad6a938d, 0x2ae89350, 0x7f6edc1f, +0x4b1ce6ce, 0xe4aaa833, 0xe2967172, 0xcd17826f, 0xab8f771c, 0xcf5b263c, 0xb0b35739, 0x2533a312, +0x66499056, 0x8c2c7d31, 0xb52d946b, 0xae7f6a2d, 0x15b0196d, 0xdc9b4f41, 0x264b62bc, 0xea7d87d6, +0x1b5c5ad6, 0x97339e64, 0x94697da7, 0xdc56962e, 0xa446a816, 0xebb87b5d, 0xc7858e50, 0x50d7d673, +0xc95023c7, 0x3f497b92, 0x2968e57b, 0x92df7434, 0x47264824, 0x333b1d54, 0x827ebbde, 0x23cd2264, +0x5e1ca458, 0x75d4617a, 0xd022ce19, 0xbeca53de, 0x5d4a8ec6, 0x816859c3, 0xb42fc6df, 0x77c76e6c, +0x3996df96, 0x728599bb, 0x786d1689, 0x9a7d7d34, 0x8dab7d74, 0x31caa191, 0x2a6e8c81, 0x864e504c, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, 0x92109210, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x6ef06ef0, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x6ef06ef0, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x6ef06ef0, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x6ef06ef0, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x6ef06ef0, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x6ef06ef0, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x6ef06ef0, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x6ef06ef0, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x6ef06ef0, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x6ef06ef0, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x6ef06ef0, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x92109210, 0x92109210, 0x10a6515b, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x2236196e, 0x22362236, 0x22362236, 0x22362236, 0x22362236, +0x80808080, 0x80808080, 0xdecaafa5, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0x8080afa5, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0xdecaafa5, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0x8080afa5, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0xdecaafa5, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0x8080afa5, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0xdecaafa5, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0x8080afa5, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0xdecaafa5, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0x8080afa5, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0xdecaafa5, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0x8080afa5, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0xdecaafa5, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0x8080afa5, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0xdecaafa5, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0x8080afa5, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0xdecaafa5, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0x8080afa5, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0xdecaafa5, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0x8080afa5, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0xdecaafa5, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0x8080afa5, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, 0x15c615c6, +0x15c615c6, 0x15c615c6, 0x808015c6, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0xc6eba3b6, 0xc6ebc6eb, 0xc6ebc6eb, +0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, 0xc6ebc6eb, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x22362236, 0x22362236, 0x22362236, 0x22362236, 0xdeca2236, 0xdecadeca, 0xdecadeca, 0xdecadeca, +0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xdecadeca, 0xf05adeca, 0xf05af05a, 0xf05af05a, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x10a68080, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x808010a6, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x10a68080, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x808010a6, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x10a68080, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x808010a6, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x10a68080, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x808010a6, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x10a68080, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x808010a6, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x10a68080, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x808010a6, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x10a68080, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x808010a6, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x10a68080, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x808010a6, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x10a68080, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x808010a6, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x10a68080, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x808010a6, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x10a68080, 0x10a610a6, 0x10a610a6, 0x10a610a6, +0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x10a610a6, 0x808010a6, 0x80808080, 0x80808080, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xf05af05a, 0xafa5f05a, 0x6ef06ef0, +0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, 0x6ef06ef0, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, +0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080, 0x80808080 +}; diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 0acb30fb2..14d716de1 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -26,6 +26,7 @@ */ #include "config.h" +#include "simple-dmabuf-drm-data.h" #include #include @@ -50,10 +51,12 @@ #include #include "shared/zalloc.h" +#include "shared/platform.h" #include "xdg-shell-unstable-v6-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" +extern const unsigned nv12_tiled[]; struct buffer; struct display { @@ -64,7 +67,10 @@ struct display { struct zwp_fullscreen_shell_v1 *fshell; struct zwp_linux_dmabuf_v1 *dmabuf; int xrgb8888_format_found; + int nv12_format_found; + int nv12_modifier_found; int req_dmabuf_immediate; + int req_dmabuf_modifiers; }; struct drm_device { @@ -101,6 +107,7 @@ struct buffer { int height; int bpp; unsigned long stride; + int format; }; #define NUM_BUFFERS 3 @@ -249,11 +256,19 @@ fill_content(struct buffer *my_buf) assert(my_buf->mmap); - for (y = 0; y < my_buf->height; y++) { - pix = (uint32_t *)(my_buf->mmap + y * my_buf->stride); - for (x = 0; x < my_buf->width; x++) { - *pix++ = (0xff << 24) | ((x % 256) << 16) | - ((y % 256) << 8) | 0xf0; + if (my_buf->format == DRM_FORMAT_NV12) { + pix = (uint32_t *) my_buf->mmap; + for (y = 0; y < my_buf->height; y++) + memcpy(&pix[y * my_buf->width / 4], + &nv12_tiled[my_buf->width * y / 4], + my_buf->width); + } + else { + for (y = 0; y < my_buf->height; y++) { + pix = (uint32_t *)(my_buf->mmap + y * my_buf->stride); + for (x = 0; x < my_buf->width; x++) + *pix++ = (0xff << 24) | ((x % 256) << 16) | + ((y % 256) << 8) | 0xf0; } } } @@ -364,10 +379,10 @@ static const struct zwp_linux_buffer_params_v1_listener params_listener = { static int create_dmabuf_buffer(struct display *display, struct buffer *buffer, - int width, int height) + int width, int height, int format) { struct zwp_linux_buffer_params_v1 *params; - uint64_t modifier; + uint64_t modifier = 0; uint32_t flags; struct drm_device *drm_dev; @@ -378,8 +393,18 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, drm_dev = buffer->dev; buffer->width = width; - buffer->height = height; - buffer->bpp = 32; /* hardcoded XRGB8888 format */ + switch (format) { + case DRM_FORMAT_NV12: + /* adjust height for allocation of NV12 Y and UV planes */ + buffer->height = height * 3 / 2; + buffer->bpp = 8; + modifier = DRM_FORMAT_MOD_SAMSUNG_64_32_TILE; + break; + default: + buffer->height = height; + buffer->bpp = 32; + } + buffer->format = format; if (!drm_dev->alloc_bo(buffer)) { fprintf(stderr, "alloc_bo failed\n"); @@ -402,10 +427,13 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, goto error2; } - /* We now have a dmabuf! It should contain 2x2 tiles (i.e. each tile - * is 256x256) of misc colours, and be mappable, either as ARGB8888, or - * XRGB8888. */ - modifier = 0; + /* We now have a dmabuf! For format XRGB8888, it should contain 2x2 + * tiles (i.e. each tile is 256x256) of misc colours, and be mappable, + * either as ARGB8888, or XRGB8888. For format NV12, it should contain + * the Y and UV components, and needs to be re-adjusted for passing the + * correct height to the compositor. + */ + buffer->height = height; flags = 0; params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); @@ -416,12 +444,23 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, buffer->stride, modifier >> 32, modifier & 0xffffffff); + + if (format == DRM_FORMAT_NV12) { + /* add the second plane params */ + zwp_linux_buffer_params_v1_add(params, + buffer->dmabuf_fd, + 1, + buffer->width * buffer->height, + buffer->stride, + modifier >> 32, + modifier & 0xffffffff); + } zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buffer); if (display->req_dmabuf_immediate) { buffer->buffer = zwp_linux_buffer_params_v1_create_immed(params, buffer->width, buffer->height, - DRM_FORMAT_XRGB8888, + format, flags); wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); } @@ -429,7 +468,7 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, zwp_linux_buffer_params_v1_create(params, buffer->width, buffer->height, - DRM_FORMAT_XRGB8888, + format, flags); return 0; @@ -478,7 +517,7 @@ static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { }; static struct window * -create_window(struct display *display, int width, int height) +create_window(struct display *display, int width, int height, int format) { struct window *window; int i; @@ -527,7 +566,7 @@ create_window(struct display *display, int width, int height) for (i = 0; i < NUM_BUFFERS; ++i) { ret = create_dmabuf_buffer(display, &window->buffers[i], - width, height); + width, height, format); if (ret < 0) return NULL; @@ -615,17 +654,26 @@ dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { struct display *d = data; + uint64_t modifier = ((uint64_t) modifier_hi << 32) | modifier_lo; - if (format == DRM_FORMAT_XRGB8888) + switch (format) { + case DRM_FORMAT_XRGB8888: d->xrgb8888_format_found = 1; - - /* XXX: do something useful with modifiers */ + break; + case DRM_FORMAT_NV12: + d->nv12_format_found = 1; + if (modifier == DRM_FORMAT_MOD_SAMSUNG_64_32_TILE) + d->nv12_modifier_found = 1; + break; + default: + break; + } } static void dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) { - /* XXX: will be deprecated. */ + /* XXX: deprecated */ } static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { @@ -661,9 +709,16 @@ registry_handle_global(void *data, struct wl_registry *registry, d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { + int ver; + if (d->req_dmabuf_modifiers) + ver = 3; + else if (d->req_dmabuf_immediate) + ver = 2; + else + ver = 1; d->dmabuf = wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, - d->req_dmabuf_immediate ? 2 : 1); + ver); zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); } } @@ -680,9 +735,10 @@ static const struct wl_registry_listener registry_listener = { }; static struct display * -create_display(int is_immediate) +create_display(int is_immediate, int format) { struct display *display; + const char *extensions; display = malloc(sizeof *display); if (display == NULL) { @@ -692,9 +748,17 @@ create_display(int is_immediate) display->display = wl_display_connect(NULL); assert(display->display); - /* XXX: fake, because the compositor does not yet advertise anything */ - display->xrgb8888_format_found = 1; display->req_dmabuf_immediate = is_immediate; + display->req_dmabuf_modifiers = (format == DRM_FORMAT_NV12); + + /* + * hard code format if the platform egl doesn't support format + * querying / advertising. + */ + extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (extensions && !weston_check_egl_extension(extensions, + "EGL_EXT_image_dma_buf_import_modifiers")) + display->xrgb8888_format_found = 1; display->registry = wl_display_get_registry(display->display); wl_registry_add_listener(display->registry, @@ -707,8 +771,10 @@ create_display(int is_immediate) wl_display_roundtrip(display->display); - if (!display->xrgb8888_format_found) { - fprintf(stderr, "DRM_FORMAT_XRGB8888 not available\n"); + if ((format == DRM_FORMAT_XRGB8888 && !display->xrgb8888_format_found) || + (format == DRM_FORMAT_NV12 && (!display->nv12_format_found || + !display->nv12_modifier_found))) { + fprintf(stderr, "requested format is not available\n"); exit(1); } @@ -742,6 +808,43 @@ signal_int(int signum) running = 0; } +static void +print_usage_and_exit(void) +{ + printf("usage flags:\n" + "\t'--import-immediate=<>'\n\t\t0 to import dmabuf via roundtrip," + "\n\t\t1 to enable import without roundtrip\n" + "\t'--import-format=<>'\n\t\tXRGB to import dmabuf as XRGB8888," + "\n\t\tNV12 to import as multi plane NV12 with tiling modifier\n"); + exit(0); +} + +static int +is_import_mode_immediate(const char* c) +{ + if (!strcmp(c, "1")) + return 1; + else if (!strcmp(c, "0")) + return 0; + else + print_usage_and_exit(); + + return 0; +} + +static int +parse_import_format(const char* c) +{ + if (!strcmp(c, "NV12")) + return DRM_FORMAT_NV12; + else if (!strcmp(c, "XRGB")) + return DRM_FORMAT_XRGB8888; + else + print_usage_and_exit(); + + return 0; +} + int main(int argc, char **argv) { @@ -749,22 +852,30 @@ main(int argc, char **argv) struct display *display; struct window *window; int is_immediate = 0; - int ret = 0; + int import_format = DRM_FORMAT_XRGB8888; + int ret = 0, i = 0; if (argc > 1) { - if (!strcmp(argv[1], "immed")) { - is_immediate = 1; - } - else { - fprintf(stderr, "usage:\n\tsimple-dmabuf-intel [options]\n" - "available options:\n\timmed: avoid dmabuf " - "creation roundtrip and import immediately\n"); - return 1; + static const char import_mode[] = "--import-immediate="; + static const char format[] = "--import-format="; + for (i = 1; i < argc; i++) { + if (!strncmp(argv[i], import_mode, + sizeof(import_mode) - 1)) { + is_immediate = is_import_mode_immediate(argv[i] + + sizeof(import_mode) - 1); + } + else if (!strncmp(argv[i], format, sizeof(format) - 1)) { + import_format = parse_import_format(argv[i] + + sizeof(format) - 1); + } + else { + print_usage_and_exit(); + } } } - display = create_display(is_immediate); - window = create_window(display, 250, 250); + display = create_display(is_immediate, import_format); + window = create_window(display, 256, 256, import_format); if (!window) return 1; diff --git a/configure.ac b/configure.ac index 14c742fda..b4ef1a213 100644 --- a/configure.ac +++ b/configure.ac @@ -385,7 +385,7 @@ AC_ARG_ENABLE(simple-dmabuf-drm-client, [do not build the simple dmabuf drm client]),, enable_simple_dmabuf_drm_client="auto") if ! test "x$enable_simple_dmabuf_drm_client" = "xno"; then - PKG_CHECK_MODULES(SIMPLE_DMABUF_DRM_CLIENT, [wayland-client libdrm], + PKG_CHECK_MODULES(SIMPLE_DMABUF_DRM_CLIENT, [wayland-client libdrm egl], [PKG_CHECK_MODULES(LIBDRM_PLATFORM, [libdrm_freedreno], AC_DEFINE([HAVE_LIBDRM_FREEDRENO], [1], [Build freedreno dmabuf client]) have_simple_dmabuf_drm_client=freedreno, [PKG_CHECK_MODULES(LIBDRM_PLATFORM, [libdrm_intel], From c84423baeaf669d80a87121d1aceb4cade85ce8f Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Fri, 10 Mar 2017 11:50:41 +0100 Subject: [PATCH 0088/1642] libweston-desktop/xdg_shell_v6: Send error on wrongly-sized buffer Signed-off-by: Quentin Glidic Reviewed-by: Daniel Stone --- libweston-desktop/xdg-shell-v6.c | 36 ++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index 7d0bd8e46..600723ebf 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -625,7 +625,6 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev { struct weston_surface *wsurface = weston_desktop_surface_get_surface(toplevel->base.desktop_surface); - bool reconfigure = false; if (!wsurface->buffer_ref.buffer && !toplevel->added) { weston_desktop_xdg_toplevel_ensure_added(toplevel); @@ -634,22 +633,27 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev if (!wsurface->buffer_ref.buffer) return; - if (toplevel->next_state.maximized || toplevel->next_state.fullscreen) - reconfigure = - ( ( toplevel->requested_size.width != wsurface->width ) || - ( toplevel->requested_size.height != wsurface->height ) ); - - if (reconfigure) { - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); - } else { - toplevel->state = toplevel->next_state; - toplevel->min_size = toplevel->next_min_size; - toplevel->max_size = toplevel->next_max_size; - - weston_desktop_api_committed(toplevel->base.desktop, - toplevel->base.desktop_surface, - sx, sy); + if ((toplevel->next_state.maximized || toplevel->next_state.fullscreen) && + (toplevel->requested_size.width != wsurface->width || + toplevel->requested_size.height != wsurface->height)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(toplevel->base.desktop_surface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + ZXDG_SHELL_V6_ERROR_INVALID_SURFACE_STATE, + "xdg_surface buffer does not match the configured state"); + return; } + + toplevel->state = toplevel->next_state; + toplevel->min_size = toplevel->next_min_size; + toplevel->max_size = toplevel->next_max_size; + + weston_desktop_api_committed(toplevel->base.desktop, + toplevel->base.desktop_surface, + sx, sy); } static void From d51f826c0bb9f1c350bef499b0ffe0b70733d114 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Thu, 13 Apr 2017 20:25:27 +0200 Subject: [PATCH 0089/1642] libweston-desktop/xdg-shell: Consolidate configure event sending When switching a state twice in a row, we were overwriting the old value without setting it back, sending a wrong state to the client. Now we update our requested state, then check if we need to schedule a configure event, if we have one scheduled already or even if we can cancel it. Signed-off-by: Quentin Glidic Reviewed-by: Daniel Stone --- libweston-desktop/xdg-shell-v5.c | 81 +++++++++++++++++---------- libweston-desktop/xdg-shell-v6.c | 95 +++++++++++++++++++++----------- 2 files changed, 113 insertions(+), 63 deletions(-) diff --git a/libweston-desktop/xdg-shell-v5.c b/libweston-desktop/xdg-shell-v5.c index 08cf71ee8..1ec796e1e 100644 --- a/libweston-desktop/xdg-shell-v5.c +++ b/libweston-desktop/xdg-shell-v5.c @@ -118,18 +118,56 @@ weston_desktop_xdg_surface_send_configure(void *data) wl_array_release(&states); }; +static bool +weston_desktop_xdg_surface_state_compare(struct weston_desktop_xdg_surface *surface) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(surface->surface); + + if (surface->requested_state.activated != surface->state.activated) + return false; + if (surface->requested_state.fullscreen != surface->state.fullscreen) + return false; + if (surface->requested_state.maximized != surface->state.maximized) + return false; + if (surface->requested_state.resizing != surface->state.resizing) + return false; + + if (wsurface->width == surface->requested_size.width && + wsurface->height == surface->requested_size.height) + return true; + + if (surface->requested_size.width == 0 && + surface->requested_size.height == 0) + return true; + + return false; +} + static void -weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface) +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface, + bool force) { struct wl_display *display = weston_desktop_get_display(surface->desktop); struct wl_event_loop *loop = wl_display_get_event_loop(display); + bool requested_same = + !force && weston_desktop_xdg_surface_state_compare(surface); - if (surface->configure_idle != NULL) - return; - surface->configure_idle = - wl_event_loop_add_idle(loop, - weston_desktop_xdg_surface_send_configure, - surface); + if (surface->configure_idle != NULL) { + if (!requested_same) + return; + + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + } else { + if (requested_same) + return; + + surface->configure_idle = + wl_event_loop_add_idle(loop, + weston_desktop_xdg_surface_send_configure, + surface); + } } static void @@ -138,11 +176,8 @@ weston_desktop_xdg_surface_set_maximized(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_surface *surface = user_data; - if (surface->state.maximized == maximized) - return; - surface->requested_state.maximized = maximized; - weston_desktop_xdg_surface_schedule_configure(surface); + weston_desktop_xdg_surface_schedule_configure(surface, false); } static void @@ -151,11 +186,8 @@ weston_desktop_xdg_surface_set_fullscreen(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_surface *surface = user_data; - if (surface->state.fullscreen == fullscreen) - return; - surface->requested_state.fullscreen = fullscreen; - weston_desktop_xdg_surface_schedule_configure(surface); + weston_desktop_xdg_surface_schedule_configure(surface, false); } static void @@ -164,11 +196,8 @@ weston_desktop_xdg_surface_set_resizing(struct weston_desktop_surface *dsurface, { struct weston_desktop_xdg_surface *surface = user_data; - if (surface->state.resizing == resizing) - return; - surface->requested_state.resizing = resizing; - weston_desktop_xdg_surface_schedule_configure(surface); + weston_desktop_xdg_surface_schedule_configure(surface, false); } static void @@ -177,11 +206,8 @@ weston_desktop_xdg_surface_set_activated(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_surface *surface = user_data; - if (surface->state.activated == activated) - return; - surface->requested_state.activated = activated; - weston_desktop_xdg_surface_schedule_configure(surface); + weston_desktop_xdg_surface_schedule_configure(surface, false); } static void @@ -190,16 +216,11 @@ weston_desktop_xdg_surface_set_size(struct weston_desktop_surface *dsurface, int32_t width, int32_t height) { struct weston_desktop_xdg_surface *surface = user_data; - struct weston_surface *wsurface = weston_desktop_surface_get_surface(surface->surface); surface->requested_size.width = width; surface->requested_size.height = height; - if ((wsurface->width == width && wsurface->height == height) || - (width == 0 && height == 0)) - return; - - weston_desktop_xdg_surface_schedule_configure(surface); + weston_desktop_xdg_surface_schedule_configure(surface, false); } static void @@ -217,7 +238,7 @@ weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, surface->requested_size.height != wsurface->height; if (reconfigure) { - weston_desktop_xdg_surface_schedule_configure(surface); + weston_desktop_xdg_surface_schedule_configure(surface, true); } else { surface->state = surface->next_state; if (surface->has_next_geometry) { diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index 600723ebf..9dec1fe9e 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -281,7 +281,8 @@ static const struct zxdg_positioner_v6_interface weston_desktop_xdg_positioner_i }; static void -weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface); +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface, + bool force); static void weston_desktop_xdg_toplevel_ensure_added(struct weston_desktop_xdg_toplevel *toplevel) @@ -291,7 +292,7 @@ weston_desktop_xdg_toplevel_ensure_added(struct weston_desktop_xdg_toplevel *top weston_desktop_api_surface_added(toplevel->base.desktop, toplevel->base.desktop_surface); - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base, true); toplevel->added = true; } @@ -554,11 +555,8 @@ weston_desktop_xdg_toplevel_set_maximized(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_toplevel *toplevel = user_data; - if (toplevel->state.maximized == maximized) - return; - toplevel->requested_state.maximized = maximized; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } static void @@ -567,11 +565,8 @@ weston_desktop_xdg_toplevel_set_fullscreen(struct weston_desktop_surface *dsurfa { struct weston_desktop_xdg_toplevel *toplevel = user_data; - if (toplevel->state.fullscreen == fullscreen) - return; - toplevel->requested_state.fullscreen = fullscreen; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } static void @@ -580,11 +575,8 @@ weston_desktop_xdg_toplevel_set_resizing(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_toplevel *toplevel = user_data; - if (toplevel->state.resizing == resizing) - return; - toplevel->requested_state.resizing = resizing; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } static void @@ -593,11 +585,8 @@ weston_desktop_xdg_toplevel_set_activated(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_toplevel *toplevel = user_data; - if (toplevel->state.activated == activated) - return; - toplevel->requested_state.activated = activated; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } static void @@ -606,17 +595,11 @@ weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, int32_t width, int32_t height) { struct weston_desktop_xdg_toplevel *toplevel = user_data; - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(toplevel->base.desktop_surface); toplevel->requested_size.width = width; toplevel->requested_size.height = height; - if ((wsurface->width == width && wsurface->height == height) || - (width == 0 && height == 0)) - return; - - weston_desktop_xdg_surface_schedule_configure(&toplevel->base); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } static void @@ -793,7 +776,7 @@ static void weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) { if (!popup->committed) - weston_desktop_xdg_surface_schedule_configure(&popup->base); + weston_desktop_xdg_surface_schedule_configure(&popup->base, true); popup->committed = true; weston_desktop_xdg_popup_update_position(popup->base.desktop_surface, popup); @@ -874,18 +857,64 @@ weston_desktop_xdg_surface_send_configure(void *user_data) zxdg_surface_v6_send_configure(surface->resource, surface->configure_serial); } +static bool +weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) +{ + if (toplevel->requested_state.activated != toplevel->state.activated) + return false; + if (toplevel->requested_state.fullscreen != toplevel->state.fullscreen) + return false; + if (toplevel->requested_state.maximized != toplevel->state.maximized) + return false; + if (toplevel->requested_state.resizing != toplevel->state.resizing) + return false; + + if (toplevel->base.surface->width == toplevel->requested_size.width && + toplevel->base.surface->height == toplevel->requested_size.height) + return true; + + if (toplevel->requested_size.width == 0 && + toplevel->requested_size.height == 0) + return true; + + return false; +} + static void -weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface) +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface, + bool force) { struct wl_display *display = weston_desktop_get_display(surface->desktop); struct wl_event_loop *loop = wl_display_get_event_loop(display); + bool requested_same = !force; - if (surface->configure_idle != NULL) - return; - surface->configure_idle = - wl_event_loop_add_idle(loop, - weston_desktop_xdg_surface_send_configure, - surface); + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + requested_same = requested_same && + weston_desktop_xdg_toplevel_state_compare((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + break; + } + + if (surface->configure_idle != NULL) { + if (!requested_same) + return; + + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + } else { + if (requested_same) + return; + + surface->configure_idle = + wl_event_loop_add_idle(loop, + weston_desktop_xdg_surface_send_configure, + surface); + } } static void From 185d1585ebc7b593bec7921551e9655338dd0a48 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 28 Jun 2017 11:17:23 -0500 Subject: [PATCH 0090/1642] input: Remove --disable-xkbcommon It looks like there are some code paths where this has been forgotten, so it likely doesn't work as is. It's probable that nobody has actually used this in a very long time, so it's not worth the maintenance burden of keeping xkbcommon vs raw keyboard code anymore. Signed-off-by: Derek Foreman Reviewed-by: Daniel Stone --- configure.ac | 21 ++----- libweston/compositor.h | 3 - libweston/input.c | 134 +++++++++-------------------------------- 3 files changed, 35 insertions(+), 123 deletions(-) diff --git a/configure.ac b/configure.ac index b4ef1a213..d8fe8489e 100644 --- a/configure.ac +++ b/configure.ac @@ -143,22 +143,11 @@ if test x$enable_egl = xyes; then PKG_CHECK_MODULES([EGL_TESTS], [egl glesv2 wayland-client wayland-egl]) fi -AC_ARG_ENABLE(xkbcommon, - AS_HELP_STRING([--disable-xkbcommon], [Disable libxkbcommon - support: This is only useful in environments - where you do not have a hardware keyboard. If - libxkbcommon support is disabled clients will not - be sent a keymap and must know how to interpret - the keycode sent for any key event.]),, - enable_xkbcommon=yes) -if test x$enable_xkbcommon = xyes; then - AC_DEFINE(ENABLE_XKBCOMMON, [1], [Build Weston with libxkbcommon support]) - COMPOSITOR_MODULES="$COMPOSITOR_MODULES xkbcommon >= 0.3.0" - PKG_CHECK_MODULES(XKBCOMMON_COMPOSE, [xkbcommon >= 0.5.0], - [AC_DEFINE(HAVE_XKBCOMMON_COMPOSE, 1, - [Define if xkbcommon is 0.5.0 or newer])], - true) -fi +COMPOSITOR_MODULES="$COMPOSITOR_MODULES xkbcommon >= 0.3.0" + +PKG_CHECK_MODULES(XKBCOMMON_COMPOSE, [xkbcommon >= 0.5.0], + [AC_DEFINE(HAVE_XKBCOMMON_COMPOSE, 1, + [Define if xkbcommon is 0.5.0 or newer])],true) AC_ARG_ENABLE(setuid-install, [ --enable-setuid-install],, enable_setuid_install=yes) diff --git a/libweston/compositor.h b/libweston/compositor.h index 50f7420d8..21c4046de 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -924,9 +924,6 @@ struct weston_compositor { struct xkb_context *xkb_context; struct weston_xkb_info *xkb_info; - /* Raw keyboard processing (no libxkbcommon initialization or handling) */ - int use_xkbcommon; - int32_t kb_repeat_rate; int32_t kb_repeat_delay; diff --git a/libweston/input.c b/libweston/input.c index 4fedc558c..81a94a929 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -1123,14 +1123,10 @@ weston_keyboard_destroy(struct weston_keyboard *keyboard) { /* XXX: What about keyboard->resource_list? */ -#ifdef ENABLE_XKBCOMMON - if (keyboard->seat->compositor->use_xkbcommon) { - xkb_state_unref(keyboard->xkb_state.state); - if (keyboard->xkb_info) - weston_xkb_info_destroy(keyboard->xkb_info); - xkb_keymap_unref(keyboard->pending_keymap); - } -#endif + xkb_state_unref(keyboard->xkb_state.state); + if (keyboard->xkb_info) + weston_xkb_info_destroy(keyboard->xkb_info); + xkb_keymap_unref(keyboard->pending_keymap); wl_array_release(&keyboard->keys); wl_list_remove(&keyboard->focus_resource_listener.link); @@ -1722,7 +1718,6 @@ WL_EXPORT int weston_keyboard_set_locks(struct weston_keyboard *keyboard, uint32_t mask, uint32_t value) { -#ifdef ENABLE_XKBCOMMON uint32_t serial; xkb_mod_mask_t mods_depressed, mods_latched, mods_locked, group; xkb_mod_mask_t num, caps; @@ -1765,12 +1760,8 @@ weston_keyboard_set_locks(struct weston_keyboard *keyboard, notify_modifiers(keyboard->seat, serial); return 0; -#else - return -1; -#endif } -#ifdef ENABLE_XKBCOMMON WL_EXPORT void notify_modifiers(struct weston_seat *seat, uint32_t serial) { @@ -1849,10 +1840,6 @@ update_modifier_state(struct weston_seat *seat, uint32_t serial, uint32_t key, struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); enum xkb_key_direction direction; - /* Keyboard modifiers don't exist in raw keyboard mode */ - if (!seat->compositor->use_xkbcommon) - return; - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) direction = XKB_KEY_DOWN; else @@ -1945,23 +1932,6 @@ update_keymap(struct weston_seat *seat) wl_resource_for_each(resource, &keyboard->focus_resource_list) send_modifiers(resource, wl_display_get_serial(seat->compositor->wl_display), keyboard); } -#else -WL_EXPORT void -notify_modifiers(struct weston_seat *seat, uint32_t serial) -{ -} - -static void -update_modifier_state(struct weston_seat *seat, uint32_t serial, uint32_t key, - enum wl_keyboard_key_state state) -{ -} - -static void -update_keymap(struct weston_seat *seat) -{ -} -#endif WL_EXPORT void notify_key(struct weston_seat *seat, uint32_t time, uint32_t key, @@ -2490,17 +2460,9 @@ seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, seat->compositor->kb_repeat_delay); } - if (seat->compositor->use_xkbcommon) { - wl_keyboard_send_keymap(cr, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, - keyboard->xkb_info->keymap_fd, - keyboard->xkb_info->keymap_size); - } else { - int null_fd = open("/dev/null", O_RDONLY); - wl_keyboard_send_keymap(cr, WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP, - null_fd, - 0); - close(null_fd); - } + wl_keyboard_send_keymap(cr, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, + keyboard->xkb_info->keymap_fd, + keyboard->xkb_info->keymap_size); if (should_send_modifiers_to_client(seat, client)) { send_modifiers_to_resource(keyboard, @@ -2685,13 +2647,10 @@ bind_relative_pointer_manager(struct wl_client *client, void *data, NULL); } -#ifdef ENABLE_XKBCOMMON WL_EXPORT int weston_compositor_set_xkb_rule_names(struct weston_compositor *ec, struct xkb_rule_names *names) { - ec->use_xkbcommon = 1; - if (ec->xkb_context == NULL) { ec->xkb_context = xkb_context_new(0); if (ec->xkb_context == NULL) { @@ -2730,13 +2689,6 @@ weston_xkb_info_destroy(struct weston_xkb_info *xkb_info) void weston_compositor_xkb_destroy(struct weston_compositor *ec) { - /* - * If we're operating in raw keyboard mode, we never initialized - * libxkbcommon so there's no cleanup to do either. - */ - if (!ec->use_xkbcommon) - return; - free((char *) ec->xkb_names.rules); free((char *) ec->xkb_names.model); free((char *) ec->xkb_names.layout); @@ -2850,19 +2802,6 @@ weston_compositor_build_global_keymap(struct weston_compositor *ec) return 0; } -#else -WL_EXPORT int -weston_compositor_set_xkb_rule_names(struct weston_compositor *ec, - struct xkb_rule_names *names) -{ - return 0; -} - -void -weston_compositor_xkb_destroy(struct weston_compositor *ec) -{ -} -#endif WL_EXPORT void weston_seat_update_keymap(struct weston_seat *seat, struct xkb_keymap *keymap) @@ -2872,16 +2811,11 @@ weston_seat_update_keymap(struct weston_seat *seat, struct xkb_keymap *keymap) if (!keyboard || !keymap) return; -#ifdef ENABLE_XKBCOMMON - if (!seat->compositor->use_xkbcommon) - return; - xkb_keymap_unref(keyboard->pending_keymap); keyboard->pending_keymap = xkb_keymap_ref(keymap); if (keyboard->keys.size == 0) update_keymap(seat); -#endif } WL_EXPORT int @@ -2902,28 +2836,24 @@ weston_seat_init_keyboard(struct weston_seat *seat, struct xkb_keymap *keymap) return -1; } -#ifdef ENABLE_XKBCOMMON - if (seat->compositor->use_xkbcommon) { - if (keymap != NULL) { - keyboard->xkb_info = weston_xkb_info_create(keymap); - if (keyboard->xkb_info == NULL) - goto err; - } else { - if (weston_compositor_build_global_keymap(seat->compositor) < 0) - goto err; - keyboard->xkb_info = seat->compositor->xkb_info; - keyboard->xkb_info->ref_count++; - } - - keyboard->xkb_state.state = xkb_state_new(keyboard->xkb_info->keymap); - if (keyboard->xkb_state.state == NULL) { - weston_log("failed to initialise XKB state\n"); + if (keymap != NULL) { + keyboard->xkb_info = weston_xkb_info_create(keymap); + if (keyboard->xkb_info == NULL) goto err; - } + } else { + if (weston_compositor_build_global_keymap(seat->compositor) < 0) + goto err; + keyboard->xkb_info = seat->compositor->xkb_info; + keyboard->xkb_info->ref_count++; + } - keyboard->xkb_state.leds = 0; + keyboard->xkb_state.state = xkb_state_new(keyboard->xkb_info->keymap); + if (keyboard->xkb_state.state == NULL) { + weston_log("failed to initialise XKB state\n"); + goto err; } -#endif + + keyboard->xkb_state.leds = 0; seat->keyboard_state = keyboard; seat->keyboard_device_count = 1; @@ -2947,19 +2877,15 @@ weston_keyboard_reset_state(struct weston_keyboard *keyboard) struct weston_seat *seat = keyboard->seat; struct xkb_state *state; -#ifdef ENABLE_XKBCOMMON - if (seat->compositor->use_xkbcommon) { - state = xkb_state_new(keyboard->xkb_info->keymap); - if (!state) { - weston_log("failed to reset XKB state\n"); - return; - } - xkb_state_unref(keyboard->xkb_state.state); - keyboard->xkb_state.state = state; - - keyboard->xkb_state.leds = 0; + state = xkb_state_new(keyboard->xkb_info->keymap); + if (!state) { + weston_log("failed to reset XKB state\n"); + return; } -#endif + xkb_state_unref(keyboard->xkb_state.state); + keyboard->xkb_state.state = state; + + keyboard->xkb_state.leds = 0; seat->modifier_state = 0; } From 12968e37567df01f777988ffdeee9cd241493778 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 26 Jun 2017 14:42:44 -0500 Subject: [PATCH 0091/1642] gl-renderer: Fix some missing newlines in log messages Some log messages weren't terminated with a newline. Signed-off-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index a1301ffbb..da29b0725 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -1802,13 +1802,13 @@ import_yuv_dmabuf(struct gl_renderer *gr, if (!format) { weston_log("Error during import, and no known conversion for format " - "%.4s in the renderer", + "%.4s in the renderer\n", dump_format(attributes->format, fmt)); return false; } if (attributes->n_planes != format->input_planes) { - weston_log("%.4s dmabuf must contain %d plane%s (%d provided)", + weston_log("%.4s dmabuf must contain %d plane%s (%d provided)\n", dump_format(format->format, fmt), format->input_planes, (format->input_planes > 1) ? "s" : "", @@ -3165,7 +3165,7 @@ gl_renderer_create_pbuffer_surface(struct gl_renderer *gr) { }; if (egl_choose_config(gr, pbuffer_config_attribs, NULL, 0, &pbuffer_config) < 0) { - weston_log("failed to choose EGL config for PbufferSurface"); + weston_log("failed to choose EGL config for PbufferSurface\n"); return -1; } From cd052a6214fc5d773f6c3b9c9828f916dfaaf1ad Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 26 Jun 2017 14:44:54 -0500 Subject: [PATCH 0092/1642] linux-dmabuf: Fix crash with no valid modifiers We shouldn't free &modifier_invalid because it wasn't allocated with malloc() Signed-off-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/linux-dmabuf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index 4f153b1c9..ac219ac36 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -509,7 +509,8 @@ bind_linux_dmabuf(struct wl_client *client, modifier_hi, modifier_lo); } - free(modifiers); + if (modifiers != &modifier_invalid) + free(modifiers); } free(formats); } From bbc206e948352b79e240a6898f3ada0c46d0b02a Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 26 Jun 2017 15:55:44 -0500 Subject: [PATCH 0093/1642] dmabuf: Don't crash clients by sending version inappropriate events We need to make sure the client bound dmabuf with a high enough version to receive modifier events before sending them or the client will crash. Signed-off-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/linux-dmabuf.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index ac219ac36..d81b63d54 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -483,6 +483,8 @@ bind_linux_dmabuf(struct wl_client *client, wl_resource_set_implementation(resource, &linux_dmabuf_implementation, compositor, NULL); + if (version < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) + return; /* * Use EGL_EXT_image_dma_buf_import_modifiers to query and advertise * format/modifier codes. From 65d3464ec6c3c4362ffba8eea3c551cb46c9cb4d Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 3 Jul 2017 14:36:50 -0500 Subject: [PATCH 0094/1642] configure: Stop printing libxkbcommon in configure results Cosmetic leftovers I missed when removing the configure option to --disable-xkbcommon Reviewed-by: Daniel Stone --- configure.ac | 1 - 1 file changed, 1 deletion(-) diff --git a/configure.ac b/configure.ac index d8fe8489e..53faee345 100644 --- a/configure.ac +++ b/configure.ac @@ -689,7 +689,6 @@ AC_MSG_RESULT([ Cairo Renderer ${with_cairo} EGL ${enable_egl} - libxkbcommon ${enable_xkbcommon} xcb_xkb ${have_xcb_xkb} XWayland ${enable_xwayland} dbus ${enable_dbus} From 1170c663057834fdc8f601b917d61f2d98710d42 Mon Sep 17 00:00:00 2001 From: Bryce Harrington Date: Tue, 11 Jul 2017 19:16:40 -0700 Subject: [PATCH 0095/1642] configure.ac: bump to version 2.99.91 for the alpha release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 53faee345..67fd9c9a8 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [2]) m4_define([weston_minor_version], [99]) -m4_define([weston_micro_version], [90]) +m4_define([weston_micro_version], [91]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [3]) From 218126d992e01225fd48555c9261415a91cf2ac5 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Tue, 11 Jul 2017 13:31:36 +0200 Subject: [PATCH 0096/1642] libweston-desktop/xdg-shell: Rename requested_ to pending_ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Quentin Glidic Reviewed-by: Jonas Ådahl Tested-by: Emmanuel Gil Peyrot --- libweston-desktop/xdg-shell-v5.c | 56 +++++++++++++++--------------- libweston-desktop/xdg-shell-v6.c | 58 ++++++++++++++++---------------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/libweston-desktop/xdg-shell-v5.c b/libweston-desktop/xdg-shell-v5.c index 1ec796e1e..161a4891d 100644 --- a/libweston-desktop/xdg-shell-v5.c +++ b/libweston-desktop/xdg-shell-v5.c @@ -47,13 +47,13 @@ struct weston_desktop_xdg_surface { struct wl_event_source *add_idle; struct wl_event_source *configure_idle; uint32_t configure_serial; - struct weston_size requested_size; + struct weston_size pending_size; struct { bool maximized; bool fullscreen; bool resizing; bool activated; - } requested_state, next_state, state; + } pending_state, next_state, state; bool has_next_geometry; struct weston_geometry next_geometry; }; @@ -92,26 +92,26 @@ weston_desktop_xdg_surface_send_configure(void *data) wl_display_next_serial(weston_desktop_get_display(surface->desktop)); wl_array_init(&states); - if (surface->requested_state.maximized) { + if (surface->pending_state.maximized) { s = wl_array_add(&states, sizeof(uint32_t)); *s = XDG_SURFACE_STATE_MAXIMIZED; } - if (surface->requested_state.fullscreen) { + if (surface->pending_state.fullscreen) { s = wl_array_add(&states, sizeof(uint32_t)); *s = XDG_SURFACE_STATE_FULLSCREEN; } - if (surface->requested_state.resizing) { + if (surface->pending_state.resizing) { s = wl_array_add(&states, sizeof(uint32_t)); *s = XDG_SURFACE_STATE_RESIZING; } - if (surface->requested_state.activated) { + if (surface->pending_state.activated) { s = wl_array_add(&states, sizeof(uint32_t)); *s = XDG_SURFACE_STATE_ACTIVATED; } xdg_surface_send_configure(surface->resource, - surface->requested_size.width, - surface->requested_size.height, + surface->pending_size.width, + surface->pending_size.height, &states, surface->configure_serial); @@ -124,21 +124,21 @@ weston_desktop_xdg_surface_state_compare(struct weston_desktop_xdg_surface *surf struct weston_surface *wsurface = weston_desktop_surface_get_surface(surface->surface); - if (surface->requested_state.activated != surface->state.activated) + if (surface->pending_state.activated != surface->state.activated) return false; - if (surface->requested_state.fullscreen != surface->state.fullscreen) + if (surface->pending_state.fullscreen != surface->state.fullscreen) return false; - if (surface->requested_state.maximized != surface->state.maximized) + if (surface->pending_state.maximized != surface->state.maximized) return false; - if (surface->requested_state.resizing != surface->state.resizing) + if (surface->pending_state.resizing != surface->state.resizing) return false; - if (wsurface->width == surface->requested_size.width && - wsurface->height == surface->requested_size.height) + if (wsurface->width == surface->pending_size.width && + wsurface->height == surface->pending_size.height) return true; - if (surface->requested_size.width == 0 && - surface->requested_size.height == 0) + if (surface->pending_size.width == 0 && + surface->pending_size.height == 0) return true; return false; @@ -150,17 +150,17 @@ weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface { struct wl_display *display = weston_desktop_get_display(surface->desktop); struct wl_event_loop *loop = wl_display_get_event_loop(display); - bool requested_same = + bool pending_same = !force && weston_desktop_xdg_surface_state_compare(surface); if (surface->configure_idle != NULL) { - if (!requested_same) + if (!pending_same) return; wl_event_source_remove(surface->configure_idle); surface->configure_idle = NULL; } else { - if (requested_same) + if (pending_same) return; surface->configure_idle = @@ -176,7 +176,7 @@ weston_desktop_xdg_surface_set_maximized(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_surface *surface = user_data; - surface->requested_state.maximized = maximized; + surface->pending_state.maximized = maximized; weston_desktop_xdg_surface_schedule_configure(surface, false); } @@ -186,7 +186,7 @@ weston_desktop_xdg_surface_set_fullscreen(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_surface *surface = user_data; - surface->requested_state.fullscreen = fullscreen; + surface->pending_state.fullscreen = fullscreen; weston_desktop_xdg_surface_schedule_configure(surface, false); } @@ -196,7 +196,7 @@ weston_desktop_xdg_surface_set_resizing(struct weston_desktop_surface *dsurface, { struct weston_desktop_xdg_surface *surface = user_data; - surface->requested_state.resizing = resizing; + surface->pending_state.resizing = resizing; weston_desktop_xdg_surface_schedule_configure(surface, false); } @@ -206,7 +206,7 @@ weston_desktop_xdg_surface_set_activated(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_surface *surface = user_data; - surface->requested_state.activated = activated; + surface->pending_state.activated = activated; weston_desktop_xdg_surface_schedule_configure(surface, false); } @@ -217,8 +217,8 @@ weston_desktop_xdg_surface_set_size(struct weston_desktop_surface *dsurface, { struct weston_desktop_xdg_surface *surface = user_data; - surface->requested_size.width = width; - surface->requested_size.height = height; + surface->pending_size.width = width; + surface->pending_size.height = height; weston_desktop_xdg_surface_schedule_configure(surface, false); } @@ -234,8 +234,8 @@ weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, bool reconfigure = false; if (surface->next_state.maximized || surface->next_state.fullscreen) - reconfigure = surface->requested_size.width != wsurface->width || - surface->requested_size.height != wsurface->height; + reconfigure = surface->pending_size.width != wsurface->width || + surface->pending_size.height != wsurface->height; if (reconfigure) { weston_desktop_xdg_surface_schedule_configure(surface, true); @@ -436,7 +436,7 @@ weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, if (surface->configure_serial != serial) return; - surface->next_state = surface->requested_state; + surface->next_state = surface->pending_state; } static void diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index 9dec1fe9e..52f89c045 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -82,13 +82,13 @@ struct weston_desktop_xdg_toplevel { struct wl_resource *resource; bool added; - struct weston_size requested_size; + struct weston_size pending_size; struct { bool maximized; bool fullscreen; bool resizing; bool activated; - } requested_state, next_state, state; + } pending_state, next_state, state; struct weston_size next_max_size, max_size, next_min_size, min_size; @@ -414,7 +414,7 @@ weston_desktop_xdg_toplevel_protocol_resize(struct wl_client *wl_client, static void weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel) { - toplevel->next_state = toplevel->requested_state; + toplevel->next_state = toplevel->pending_state; } static void @@ -524,26 +524,26 @@ weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *t struct wl_array states; wl_array_init(&states); - if (toplevel->requested_state.maximized) { + if (toplevel->pending_state.maximized) { s = wl_array_add(&states, sizeof(uint32_t)); *s = ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED; } - if (toplevel->requested_state.fullscreen) { + if (toplevel->pending_state.fullscreen) { s = wl_array_add(&states, sizeof(uint32_t)); *s = ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN; } - if (toplevel->requested_state.resizing) { + if (toplevel->pending_state.resizing) { s = wl_array_add(&states, sizeof(uint32_t)); *s = ZXDG_TOPLEVEL_V6_STATE_RESIZING; } - if (toplevel->requested_state.activated) { + if (toplevel->pending_state.activated) { s = wl_array_add(&states, sizeof(uint32_t)); *s = ZXDG_TOPLEVEL_V6_STATE_ACTIVATED; } zxdg_toplevel_v6_send_configure(toplevel->resource, - toplevel->requested_size.width, - toplevel->requested_size.height, + toplevel->pending_size.width, + toplevel->pending_size.height, &states); wl_array_release(&states); @@ -555,7 +555,7 @@ weston_desktop_xdg_toplevel_set_maximized(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_toplevel *toplevel = user_data; - toplevel->requested_state.maximized = maximized; + toplevel->pending_state.maximized = maximized; weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } @@ -565,7 +565,7 @@ weston_desktop_xdg_toplevel_set_fullscreen(struct weston_desktop_surface *dsurfa { struct weston_desktop_xdg_toplevel *toplevel = user_data; - toplevel->requested_state.fullscreen = fullscreen; + toplevel->pending_state.fullscreen = fullscreen; weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } @@ -575,7 +575,7 @@ weston_desktop_xdg_toplevel_set_resizing(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_toplevel *toplevel = user_data; - toplevel->requested_state.resizing = resizing; + toplevel->pending_state.resizing = resizing; weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } @@ -585,7 +585,7 @@ weston_desktop_xdg_toplevel_set_activated(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_toplevel *toplevel = user_data; - toplevel->requested_state.activated = activated; + toplevel->pending_state.activated = activated; weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } @@ -596,8 +596,8 @@ weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, { struct weston_desktop_xdg_toplevel *toplevel = user_data; - toplevel->requested_size.width = width; - toplevel->requested_size.height = height; + toplevel->pending_size.width = width; + toplevel->pending_size.height = height; weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } @@ -617,8 +617,8 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev return; if ((toplevel->next_state.maximized || toplevel->next_state.fullscreen) && - (toplevel->requested_size.width != wsurface->width || - toplevel->requested_size.height != wsurface->height)) { + (toplevel->pending_size.width != wsurface->width || + toplevel->pending_size.height != wsurface->height)) { struct weston_desktop_client *client = weston_desktop_surface_get_client(toplevel->base.desktop_surface); struct wl_resource *client_resource = @@ -860,21 +860,21 @@ weston_desktop_xdg_surface_send_configure(void *user_data) static bool weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) { - if (toplevel->requested_state.activated != toplevel->state.activated) + if (toplevel->pending_state.activated != toplevel->state.activated) return false; - if (toplevel->requested_state.fullscreen != toplevel->state.fullscreen) + if (toplevel->pending_state.fullscreen != toplevel->state.fullscreen) return false; - if (toplevel->requested_state.maximized != toplevel->state.maximized) + if (toplevel->pending_state.maximized != toplevel->state.maximized) return false; - if (toplevel->requested_state.resizing != toplevel->state.resizing) + if (toplevel->pending_state.resizing != toplevel->state.resizing) return false; - if (toplevel->base.surface->width == toplevel->requested_size.width && - toplevel->base.surface->height == toplevel->requested_size.height) + if (toplevel->base.surface->width == toplevel->pending_size.width && + toplevel->base.surface->height == toplevel->pending_size.height) return true; - if (toplevel->requested_size.width == 0 && - toplevel->requested_size.height == 0) + if (toplevel->pending_size.width == 0 && + toplevel->pending_size.height == 0) return true; return false; @@ -886,14 +886,14 @@ weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface { struct wl_display *display = weston_desktop_get_display(surface->desktop); struct wl_event_loop *loop = wl_display_get_event_loop(display); - bool requested_same = !force; + bool pending_same = !force; switch (surface->role) { case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: assert(0 && "not reached"); break; case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - requested_same = requested_same && + pending_same = pending_same && weston_desktop_xdg_toplevel_state_compare((struct weston_desktop_xdg_toplevel *) surface); break; case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: @@ -901,13 +901,13 @@ weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface } if (surface->configure_idle != NULL) { - if (!requested_same) + if (!pending_same) return; wl_event_source_remove(surface->configure_idle); surface->configure_idle = NULL; } else { - if (requested_same) + if (pending_same) return; surface->configure_idle = From 19d1f6effe1d28a8f905ad87d6fdde0b7393a363 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Wed, 12 Jul 2017 09:42:57 +0200 Subject: [PATCH 0097/1642] libweston-desktop/xdg-shell: Add pending/next/current structs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Quentin Glidic Reviewed-by: Jonas Ådahl Tested-by: Emmanuel Gil Peyrot --- libweston-desktop/xdg-shell-v5.c | 80 ++++++++++++++------------ libweston-desktop/xdg-shell-v6.c | 97 +++++++++++++++++--------------- 2 files changed, 98 insertions(+), 79 deletions(-) diff --git a/libweston-desktop/xdg-shell-v5.c b/libweston-desktop/xdg-shell-v5.c index 161a4891d..0fb067abf 100644 --- a/libweston-desktop/xdg-shell-v5.c +++ b/libweston-desktop/xdg-shell-v5.c @@ -39,6 +39,13 @@ #define WD_XDG_SHELL_PROTOCOL_VERSION 1 +struct weston_desktop_xdg_surface_state { + bool maximized; + bool fullscreen; + bool resizing; + bool activated; +}; + struct weston_desktop_xdg_surface { struct wl_resource *resource; struct weston_desktop_surface *surface; @@ -47,13 +54,16 @@ struct weston_desktop_xdg_surface { struct wl_event_source *add_idle; struct wl_event_source *configure_idle; uint32_t configure_serial; - struct weston_size pending_size; struct { - bool maximized; - bool fullscreen; - bool resizing; - bool activated; - } pending_state, next_state, state; + struct weston_desktop_xdg_surface_state state; + struct weston_size size; + } pending; + struct { + struct weston_desktop_xdg_surface_state state; + } next; + struct { + struct weston_desktop_xdg_surface_state state; + } current; bool has_next_geometry; struct weston_geometry next_geometry; }; @@ -92,26 +102,26 @@ weston_desktop_xdg_surface_send_configure(void *data) wl_display_next_serial(weston_desktop_get_display(surface->desktop)); wl_array_init(&states); - if (surface->pending_state.maximized) { + if (surface->pending.state.maximized) { s = wl_array_add(&states, sizeof(uint32_t)); *s = XDG_SURFACE_STATE_MAXIMIZED; } - if (surface->pending_state.fullscreen) { + if (surface->pending.state.fullscreen) { s = wl_array_add(&states, sizeof(uint32_t)); *s = XDG_SURFACE_STATE_FULLSCREEN; } - if (surface->pending_state.resizing) { + if (surface->pending.state.resizing) { s = wl_array_add(&states, sizeof(uint32_t)); *s = XDG_SURFACE_STATE_RESIZING; } - if (surface->pending_state.activated) { + if (surface->pending.state.activated) { s = wl_array_add(&states, sizeof(uint32_t)); *s = XDG_SURFACE_STATE_ACTIVATED; } xdg_surface_send_configure(surface->resource, - surface->pending_size.width, - surface->pending_size.height, + surface->pending.size.width, + surface->pending.size.height, &states, surface->configure_serial); @@ -124,21 +134,21 @@ weston_desktop_xdg_surface_state_compare(struct weston_desktop_xdg_surface *surf struct weston_surface *wsurface = weston_desktop_surface_get_surface(surface->surface); - if (surface->pending_state.activated != surface->state.activated) + if (surface->pending.state.activated != surface->current.state.activated) return false; - if (surface->pending_state.fullscreen != surface->state.fullscreen) + if (surface->pending.state.fullscreen != surface->current.state.fullscreen) return false; - if (surface->pending_state.maximized != surface->state.maximized) + if (surface->pending.state.maximized != surface->current.state.maximized) return false; - if (surface->pending_state.resizing != surface->state.resizing) + if (surface->pending.state.resizing != surface->current.state.resizing) return false; - if (wsurface->width == surface->pending_size.width && - wsurface->height == surface->pending_size.height) + if (wsurface->width == surface->pending.size.width && + wsurface->height == surface->pending.size.height) return true; - if (surface->pending_size.width == 0 && - surface->pending_size.height == 0) + if (surface->pending.size.width == 0 && + surface->pending.size.height == 0) return true; return false; @@ -176,7 +186,7 @@ weston_desktop_xdg_surface_set_maximized(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_surface *surface = user_data; - surface->pending_state.maximized = maximized; + surface->pending.state.maximized = maximized; weston_desktop_xdg_surface_schedule_configure(surface, false); } @@ -186,7 +196,7 @@ weston_desktop_xdg_surface_set_fullscreen(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_surface *surface = user_data; - surface->pending_state.fullscreen = fullscreen; + surface->pending.state.fullscreen = fullscreen; weston_desktop_xdg_surface_schedule_configure(surface, false); } @@ -196,7 +206,7 @@ weston_desktop_xdg_surface_set_resizing(struct weston_desktop_surface *dsurface, { struct weston_desktop_xdg_surface *surface = user_data; - surface->pending_state.resizing = resizing; + surface->pending.state.resizing = resizing; weston_desktop_xdg_surface_schedule_configure(surface, false); } @@ -206,7 +216,7 @@ weston_desktop_xdg_surface_set_activated(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_surface *surface = user_data; - surface->pending_state.activated = activated; + surface->pending.state.activated = activated; weston_desktop_xdg_surface_schedule_configure(surface, false); } @@ -217,8 +227,8 @@ weston_desktop_xdg_surface_set_size(struct weston_desktop_surface *dsurface, { struct weston_desktop_xdg_surface *surface = user_data; - surface->pending_size.width = width; - surface->pending_size.height = height; + surface->pending.size.width = width; + surface->pending.size.height = height; weston_desktop_xdg_surface_schedule_configure(surface, false); } @@ -233,14 +243,14 @@ weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, weston_desktop_surface_get_surface(surface->surface); bool reconfigure = false; - if (surface->next_state.maximized || surface->next_state.fullscreen) - reconfigure = surface->pending_size.width != wsurface->width || - surface->pending_size.height != wsurface->height; + if (surface->next.state.maximized || surface->next.state.fullscreen) + reconfigure = surface->pending.size.width != wsurface->width || + surface->pending.size.height != wsurface->height; if (reconfigure) { weston_desktop_xdg_surface_schedule_configure(surface, true); } else { - surface->state = surface->next_state; + surface->current.state = surface->next.state; if (surface->has_next_geometry) { surface->has_next_geometry = false; weston_desktop_surface_set_geometry(surface->surface, @@ -279,7 +289,7 @@ weston_desktop_xdg_surface_get_maximized(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_surface *surface = user_data; - return surface->state.maximized; + return surface->current.state.maximized; } static bool @@ -288,7 +298,7 @@ weston_desktop_xdg_surface_get_fullscreen(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_surface *surface = user_data; - return surface->state.fullscreen; + return surface->current.state.fullscreen; } static bool @@ -297,7 +307,7 @@ weston_desktop_xdg_surface_get_resizing(struct weston_desktop_surface *dsurface, { struct weston_desktop_xdg_surface *surface = user_data; - return surface->state.resizing; + return surface->current.state.resizing; } static bool @@ -306,7 +316,7 @@ weston_desktop_xdg_surface_get_activated(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_surface *surface = user_data; - return surface->state.activated; + return surface->current.state.activated; } static void @@ -436,7 +446,7 @@ weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, if (surface->configure_serial != serial) return; - surface->next_state = surface->pending_state; + surface->next.state = surface->pending.state; } static void diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index 52f89c045..db894d4ab 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -77,21 +77,30 @@ struct weston_desktop_xdg_surface { enum weston_desktop_xdg_surface_role role; }; +struct weston_desktop_xdg_toplevel_state { + bool maximized; + bool fullscreen; + bool resizing; + bool activated; +}; + struct weston_desktop_xdg_toplevel { struct weston_desktop_xdg_surface base; struct wl_resource *resource; bool added; - struct weston_size pending_size; struct { - bool maximized; - bool fullscreen; - bool resizing; - bool activated; - } pending_state, next_state, state; - struct weston_size - next_max_size, max_size, - next_min_size, min_size; + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } pending; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size min_size, max_size; + } next; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size min_size, max_size; + } current; }; struct weston_desktop_xdg_popup { @@ -414,7 +423,7 @@ weston_desktop_xdg_toplevel_protocol_resize(struct wl_client *wl_client, static void weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel) { - toplevel->next_state = toplevel->pending_state; + toplevel->next.state = toplevel->pending.state; } static void @@ -427,8 +436,8 @@ weston_desktop_xdg_toplevel_protocol_set_min_size(struct wl_client *wl_client, struct weston_desktop_xdg_toplevel *toplevel = weston_desktop_surface_get_implementation_data(dsurface); - toplevel->next_min_size.width = width; - toplevel->next_min_size.height = height; + toplevel->next.min_size.width = width; + toplevel->next.min_size.height = height; } static void @@ -441,8 +450,8 @@ weston_desktop_xdg_toplevel_protocol_set_max_size(struct wl_client *wl_client, struct weston_desktop_xdg_toplevel *toplevel = weston_desktop_surface_get_implementation_data(dsurface); - toplevel->next_max_size.width = width; - toplevel->next_max_size.height = height; + toplevel->next.max_size.width = width; + toplevel->next.max_size.height = height; } static void @@ -524,26 +533,26 @@ weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *t struct wl_array states; wl_array_init(&states); - if (toplevel->pending_state.maximized) { + if (toplevel->pending.state.maximized) { s = wl_array_add(&states, sizeof(uint32_t)); *s = ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED; } - if (toplevel->pending_state.fullscreen) { + if (toplevel->pending.state.fullscreen) { s = wl_array_add(&states, sizeof(uint32_t)); *s = ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN; } - if (toplevel->pending_state.resizing) { + if (toplevel->pending.state.resizing) { s = wl_array_add(&states, sizeof(uint32_t)); *s = ZXDG_TOPLEVEL_V6_STATE_RESIZING; } - if (toplevel->pending_state.activated) { + if (toplevel->pending.state.activated) { s = wl_array_add(&states, sizeof(uint32_t)); *s = ZXDG_TOPLEVEL_V6_STATE_ACTIVATED; } zxdg_toplevel_v6_send_configure(toplevel->resource, - toplevel->pending_size.width, - toplevel->pending_size.height, + toplevel->pending.size.width, + toplevel->pending.size.height, &states); wl_array_release(&states); @@ -555,7 +564,7 @@ weston_desktop_xdg_toplevel_set_maximized(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_toplevel *toplevel = user_data; - toplevel->pending_state.maximized = maximized; + toplevel->pending.state.maximized = maximized; weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } @@ -565,7 +574,7 @@ weston_desktop_xdg_toplevel_set_fullscreen(struct weston_desktop_surface *dsurfa { struct weston_desktop_xdg_toplevel *toplevel = user_data; - toplevel->pending_state.fullscreen = fullscreen; + toplevel->pending.state.fullscreen = fullscreen; weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } @@ -575,7 +584,7 @@ weston_desktop_xdg_toplevel_set_resizing(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_toplevel *toplevel = user_data; - toplevel->pending_state.resizing = resizing; + toplevel->pending.state.resizing = resizing; weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } @@ -585,7 +594,7 @@ weston_desktop_xdg_toplevel_set_activated(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_toplevel *toplevel = user_data; - toplevel->pending_state.activated = activated; + toplevel->pending.state.activated = activated; weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } @@ -596,8 +605,8 @@ weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, { struct weston_desktop_xdg_toplevel *toplevel = user_data; - toplevel->pending_size.width = width; - toplevel->pending_size.height = height; + toplevel->pending.size.width = width; + toplevel->pending.size.height = height; weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); } @@ -616,9 +625,9 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev if (!wsurface->buffer_ref.buffer) return; - if ((toplevel->next_state.maximized || toplevel->next_state.fullscreen) && - (toplevel->pending_size.width != wsurface->width || - toplevel->pending_size.height != wsurface->height)) { + if ((toplevel->next.state.maximized || toplevel->next.state.fullscreen) && + (toplevel->pending.size.width != wsurface->width || + toplevel->pending.size.height != wsurface->height)) { struct weston_desktop_client *client = weston_desktop_surface_get_client(toplevel->base.desktop_surface); struct wl_resource *client_resource = @@ -630,9 +639,9 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev return; } - toplevel->state = toplevel->next_state; - toplevel->min_size = toplevel->next_min_size; - toplevel->max_size = toplevel->next_max_size; + toplevel->current.state = toplevel->next.state; + toplevel->current.min_size = toplevel->next.min_size; + toplevel->current.max_size = toplevel->next.max_size; weston_desktop_api_committed(toplevel->base.desktop, toplevel->base.desktop_surface, @@ -651,7 +660,7 @@ weston_desktop_xdg_toplevel_get_maximized(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_toplevel *toplevel = user_data; - return toplevel->state.maximized; + return toplevel->current.state.maximized; } static bool @@ -660,7 +669,7 @@ weston_desktop_xdg_toplevel_get_fullscreen(struct weston_desktop_surface *dsurfa { struct weston_desktop_xdg_toplevel *toplevel = user_data; - return toplevel->state.fullscreen; + return toplevel->current.state.fullscreen; } static bool @@ -669,7 +678,7 @@ weston_desktop_xdg_toplevel_get_resizing(struct weston_desktop_surface *dsurface { struct weston_desktop_xdg_toplevel *toplevel = user_data; - return toplevel->state.resizing; + return toplevel->current.state.resizing; } static bool @@ -678,7 +687,7 @@ weston_desktop_xdg_toplevel_get_activated(struct weston_desktop_surface *dsurfac { struct weston_desktop_xdg_toplevel *toplevel = user_data; - return toplevel->state.activated; + return toplevel->current.state.activated; } static void @@ -860,21 +869,21 @@ weston_desktop_xdg_surface_send_configure(void *user_data) static bool weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) { - if (toplevel->pending_state.activated != toplevel->state.activated) + if (toplevel->pending.state.activated != toplevel->current.state.activated) return false; - if (toplevel->pending_state.fullscreen != toplevel->state.fullscreen) + if (toplevel->pending.state.fullscreen != toplevel->current.state.fullscreen) return false; - if (toplevel->pending_state.maximized != toplevel->state.maximized) + if (toplevel->pending.state.maximized != toplevel->current.state.maximized) return false; - if (toplevel->pending_state.resizing != toplevel->state.resizing) + if (toplevel->pending.state.resizing != toplevel->current.state.resizing) return false; - if (toplevel->base.surface->width == toplevel->pending_size.width && - toplevel->base.surface->height == toplevel->pending_size.height) + if (toplevel->base.surface->width == toplevel->pending.size.width && + toplevel->base.surface->height == toplevel->pending.size.height) return true; - if (toplevel->pending_size.width == 0 && - toplevel->pending_size.height == 0) + if (toplevel->pending.size.width == 0 && + toplevel->pending.size.height == 0) return true; return false; From ac394a10bcecd1aec499ebeaec4aea0430657308 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Wed, 12 Jul 2017 09:45:43 +0200 Subject: [PATCH 0098/1642] libweston-desktop/xdg-shell: Check surface size against acknowledged size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We were checking against the pending size, which lead some clients (simple-egl) to crash because they sent a buffer before acknowledging the latest configure event. Signed-off-by: Quentin Glidic Tested-by: Emmanuel Gil Peyrot Reviewed-by: Jonas Ådahl --- libweston-desktop/xdg-shell-v5.c | 6 ++++-- libweston-desktop/xdg-shell-v6.c | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libweston-desktop/xdg-shell-v5.c b/libweston-desktop/xdg-shell-v5.c index 0fb067abf..b32b78121 100644 --- a/libweston-desktop/xdg-shell-v5.c +++ b/libweston-desktop/xdg-shell-v5.c @@ -60,6 +60,7 @@ struct weston_desktop_xdg_surface { } pending; struct { struct weston_desktop_xdg_surface_state state; + struct weston_size size; } next; struct { struct weston_desktop_xdg_surface_state state; @@ -244,8 +245,8 @@ weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, bool reconfigure = false; if (surface->next.state.maximized || surface->next.state.fullscreen) - reconfigure = surface->pending.size.width != wsurface->width || - surface->pending.size.height != wsurface->height; + reconfigure = surface->next.size.width != wsurface->width || + surface->next.size.height != wsurface->height; if (reconfigure) { weston_desktop_xdg_surface_schedule_configure(surface, true); @@ -447,6 +448,7 @@ weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, return; surface->next.state = surface->pending.state; + surface->next.size = surface->pending.size; } static void diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index db894d4ab..f5e46daa3 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -95,6 +95,7 @@ struct weston_desktop_xdg_toplevel { } pending; struct { struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; struct weston_size min_size, max_size; } next; struct { @@ -424,6 +425,7 @@ static void weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel) { toplevel->next.state = toplevel->pending.state; + toplevel->next.size = toplevel->pending.size; } static void @@ -626,8 +628,8 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev return; if ((toplevel->next.state.maximized || toplevel->next.state.fullscreen) && - (toplevel->pending.size.width != wsurface->width || - toplevel->pending.size.height != wsurface->height)) { + (toplevel->next.size.width != wsurface->width || + toplevel->next.size.height != wsurface->height)) { struct weston_desktop_client *client = weston_desktop_surface_get_client(toplevel->base.desktop_surface); struct wl_resource *client_resource = From 749637a8a306588964885fe6b25fda6087a84ccd Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Tue, 18 Jul 2017 12:59:14 +0200 Subject: [PATCH 0099/1642] libweston-desktop/xdg-shell: Properly handle ack_configure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now we keep track of serial->state association and we discard the states that the client ignored. Signed-off-by: Quentin Glidic Reviewed-by: Jonas Ådahl --- libweston-desktop/xdg-shell-v5.c | 60 +++++++++++++++++++++--- libweston-desktop/xdg-shell-v6.c | 79 ++++++++++++++++++++++++++++---- 2 files changed, 122 insertions(+), 17 deletions(-) diff --git a/libweston-desktop/xdg-shell-v5.c b/libweston-desktop/xdg-shell-v5.c index b32b78121..c91c25905 100644 --- a/libweston-desktop/xdg-shell-v5.c +++ b/libweston-desktop/xdg-shell-v5.c @@ -46,6 +46,13 @@ struct weston_desktop_xdg_surface_state { bool activated; }; +struct weston_desktop_xdg_surface_configure { + struct wl_list link; /* weston_desktop_xdg_surface::configure_list */ + uint32_t serial; + struct weston_desktop_xdg_surface_state state; + struct weston_size size; +}; + struct weston_desktop_xdg_surface { struct wl_resource *resource; struct weston_desktop_surface *surface; @@ -53,7 +60,7 @@ struct weston_desktop_xdg_surface { bool added; struct wl_event_source *add_idle; struct wl_event_source *configure_idle; - uint32_t configure_serial; + struct wl_list configure_list; /* weston_desktop_xdg_surface_configure::link */ struct { struct weston_desktop_xdg_surface_state state; struct weston_size size; @@ -94,13 +101,26 @@ static void weston_desktop_xdg_surface_send_configure(void *data) { struct weston_desktop_xdg_surface *surface = data; + struct weston_desktop_xdg_surface_configure *configure; uint32_t *s; struct wl_array states; surface->configure_idle = NULL; - surface->configure_serial = + configure = zalloc(sizeof(struct weston_desktop_xdg_surface_configure)); + if (configure == NULL) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(surface->surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + wl_client_post_no_memory(wl_client); + return; + } + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->serial = wl_display_next_serial(weston_desktop_get_display(surface->desktop)); + configure->state = surface->pending.state; + configure->size = surface->pending.size; wl_array_init(&states); if (surface->pending.state.maximized) { @@ -124,7 +144,7 @@ weston_desktop_xdg_surface_send_configure(void *data) surface->pending.size.width, surface->pending.size.height, &states, - surface->configure_serial); + configure->serial); wl_array_release(&states); }; @@ -325,6 +345,7 @@ weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, void *user_data) { struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure, *temp; if (surface->added) weston_desktop_api_surface_removed(surface->desktop, @@ -336,6 +357,9 @@ weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, if (surface->configure_idle != NULL) wl_event_source_remove(surface->configure_idle); + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) + free(configure); + free(surface); } @@ -443,12 +467,34 @@ weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, wl_resource_get_user_data(resource); struct weston_desktop_xdg_surface *surface = weston_desktop_surface_get_implementation_data(dsurface); - - if (surface->configure_serial != serial) + struct weston_desktop_xdg_surface_configure *configure, *temp; + bool found = false; + + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) { + if (configure->serial < serial) { + wl_list_remove(&configure->link); + free(configure); + } else if (configure->serial == serial) { + wl_list_remove(&configure->link); + found = true; + } + break; + } + if (!found) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + wl_resource_post_error(client_resource, + XDG_SHELL_ERROR_DEFUNCT_SURFACES, + "Wrong configure serial: %u", serial); return; + } + + surface->next.state = configure->state; + surface->next.size = configure->size; - surface->next.state = surface->pending.state; - surface->next.size = surface->pending.size; + free(configure); } static void diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index f5e46daa3..de5d3e058 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -69,7 +69,7 @@ struct weston_desktop_xdg_surface { struct weston_desktop_surface *desktop_surface; bool configured; struct wl_event_source *configure_idle; - uint32_t configure_serial; + struct wl_list configure_list; /* weston_desktop_xdg_surface_configure::link */ bool has_next_geometry; struct weston_geometry next_geometry; @@ -77,6 +77,11 @@ struct weston_desktop_xdg_surface { enum weston_desktop_xdg_surface_role role; }; +struct weston_desktop_xdg_surface_configure { + struct wl_list link; /* weston_desktop_xdg_surface::configure_list */ + uint32_t serial; +}; + struct weston_desktop_xdg_toplevel_state { bool maximized; bool fullscreen; @@ -84,6 +89,12 @@ struct weston_desktop_xdg_toplevel_state { bool activated; }; +struct weston_desktop_xdg_toplevel_configure { + struct weston_desktop_xdg_surface_configure base; + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; +}; + struct weston_desktop_xdg_toplevel { struct weston_desktop_xdg_surface base; @@ -115,6 +126,7 @@ struct weston_desktop_xdg_popup { }; #define weston_desktop_surface_role_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) +#define weston_desktop_surface_configure_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) static struct weston_geometry @@ -422,10 +434,11 @@ weston_desktop_xdg_toplevel_protocol_resize(struct wl_client *wl_client, } static void -weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel) +weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) { - toplevel->next.state = toplevel->pending.state; - toplevel->next.size = toplevel->pending.size; + toplevel->next.state = configure->state; + toplevel->next.size = configure->size; } static void @@ -529,11 +542,15 @@ weston_desktop_xdg_toplevel_protocol_set_minimized(struct wl_client *wl_client, } static void -weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *toplevel) +weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) { uint32_t *s; struct wl_array states; + configure->state = toplevel->pending.state; + configure->size = toplevel->pending.size; + wl_array_init(&states); if (toplevel->pending.state.maximized) { s = wl_array_add(&states, sizeof(uint32_t)); @@ -848,9 +865,21 @@ static void weston_desktop_xdg_surface_send_configure(void *user_data) { struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure; surface->configure_idle = NULL; - surface->configure_serial = + + configure = zalloc(weston_desktop_surface_configure_biggest_size); + if (configure == NULL) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(surface->desktop_surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + wl_client_post_no_memory(wl_client); + return; + } + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->serial = wl_display_next_serial(weston_desktop_get_display(surface->desktop)); switch (surface->role) { @@ -858,14 +887,15 @@ weston_desktop_xdg_surface_send_configure(void *user_data) assert(0 && "not reached"); break; case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_send_configure((struct weston_desktop_xdg_toplevel *) surface); + weston_desktop_xdg_toplevel_send_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); break; case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: weston_desktop_xdg_popup_send_configure((struct weston_desktop_xdg_popup *) surface); break; } - zxdg_surface_v6_send_configure(surface->resource, surface->configure_serial); + zxdg_surface_v6_send_configure(surface->resource, configure->serial); } static bool @@ -1060,12 +1090,32 @@ weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, wl_resource_get_user_data(resource); struct weston_desktop_xdg_surface *surface = weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_xdg_surface_configure *configure, *temp; + bool found = false; if (!weston_desktop_xdg_surface_check_role(surface)) return; - if (surface->configure_serial != serial) + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) { + if (configure->serial < serial) { + wl_list_remove(&configure->link); + free(configure); + } else if (configure->serial == serial) { + wl_list_remove(&configure->link); + found = true; + } + break; + } + if (!found) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + wl_resource_post_error(client_resource, + ZXDG_SHELL_V6_ERROR_INVALID_SURFACE_STATE, + "Wrong configure serial: %u", serial); return; + } surface->configured = true; @@ -1074,11 +1124,14 @@ weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, assert(0 && "not reached"); break; case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - weston_desktop_xdg_toplevel_ack_configure((struct weston_desktop_xdg_toplevel *) surface); + weston_desktop_xdg_toplevel_ack_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); break; case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: break; } + + free(configure); } static void @@ -1153,6 +1206,7 @@ weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, void *user_data) { struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure, *temp; switch (surface->role) { case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: @@ -1168,6 +1222,9 @@ weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, if (surface->configure_idle != NULL) wl_event_source_remove(surface->configure_idle); + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) + free(configure); + free(surface); } @@ -1290,6 +1347,8 @@ weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, "xdg_surface must not have a buffer at creation"); return; } + + wl_list_init(&surface->configure_list); } static void From 3e5303daf4212334f1859a025a3de7449efa2b49 Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Wed, 28 Jun 2017 00:08:40 +0300 Subject: [PATCH 0100/1642] xwm: update override-redirect surface's position upon configure_notify When we receive configure_notify we should update the surface's position by calling xwayland_api->set_xwayland(). Otherwise some surfaces like dnd surfaces from xwayland views are "stuck" at one place. When setting XWAYLAND state though we should always call view_set_position(), not just the first time we set this state. Signed-off-by: Ilia Bozhinov Reviewed-by: Quentin Glidic --- libweston-desktop/xwayland.c | 2 +- xwayland/window-manager.c | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index 4f4b453fc..002e2523c 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -112,7 +112,6 @@ weston_desktop_xwayland_surface_change_state(struct weston_desktop_xwayland_surf weston_desktop_surface_create_view(surface->surface); weston_layer_entry_insert(&surface->xwayland->layer.view_list, &surface->view->layer_link); - weston_view_set_position(surface->view, x, y); surface->view->is_mapped = true; wsurface->is_mapped = true; } @@ -316,6 +315,7 @@ set_xwayland(struct weston_desktop_xwayland_surface *surface, int x, int y) { weston_desktop_xwayland_surface_change_state(surface, XWAYLAND, NULL, x, y); + weston_view_set_position(surface->view, x, y); } static int diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 25008539e..3e8c4c7c5 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -739,6 +739,8 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve { xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *) event; + const struct weston_desktop_xwayland_interface *xwayland_api = + wm->server->compositor->xwayland_interface; struct weston_wm_window *window; wm_log("XCB_CONFIGURE_NOTIFY (window %d) %d,%d @ %dx%d%s\n", @@ -760,6 +762,13 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve if (window->frame) frame_resize_inside(window->frame, window->width, window->height); + + /* We should check if shsurf has been created because sometimes + * there are races + * (configure_notify is sent before xserver_map_surface) */ + if (window->shsurf) + xwayland_api->set_xwayland(window->shsurf, + window->x, window->y); } } From deee858b0b199d8cfa8033a46d7078f30b23725e Mon Sep 17 00:00:00 2001 From: "Ucan, Emre (ADITG/SW1)" Date: Thu, 2 Mar 2017 08:47:33 +0000 Subject: [PATCH 0101/1642] ivi-shell: add_screen_remove_layer API It is analagous to layer_remove_surface API. The API removes a layer from the render order of the screen. v3: add the new vfunc at the end of the ivi_layout_interface struct. Signed-off-by: Emre Ucan Reviewed-by: Eugen Friedrich Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout-export.h | 10 ++++++++++ ivi-shell/ivi-layout.c | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index 2317d6e9b..39ffde131 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -578,6 +578,16 @@ struct ivi_layout_interface { */ struct ivi_layout_surface * (*get_surface)(struct weston_surface *surface); + + /** + * \brief Remove a ivi_layer to a weston_output which is currently managed + * by the service + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*screen_remove_layer)(struct weston_output *output, + struct ivi_layout_layer *removelayer); }; #ifdef __cplusplus diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 298e18eaa..8e4280ba7 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -1663,6 +1663,27 @@ ivi_layout_screen_add_layer(struct weston_output *output, return IVI_SUCCEEDED; } +static int32_t +ivi_layout_screen_remove_layer(struct weston_output *output, + struct ivi_layout_layer *removelayer) +{ + struct ivi_layout_screen *iviscrn; + + if (output == NULL || removelayer == NULL) { + weston_log("ivi_layout_screen_remove_layer: invalid argument\n"); + return IVI_FAILED; + } + + iviscrn = get_screen_from_output(output); + + wl_list_remove(&removelayer->pending.link); + wl_list_init(&removelayer->pending.link); + + iviscrn->order.dirty = 1; + + return IVI_SUCCEEDED; +} + static int32_t ivi_layout_screen_set_render_order(struct weston_output *output, struct ivi_layout_layer **pLayer, @@ -2088,6 +2109,7 @@ static struct ivi_layout_interface ivi_layout_interface = { */ .get_screens_under_layer = ivi_layout_get_screens_under_layer, .screen_add_layer = ivi_layout_screen_add_layer, + .screen_remove_layer = ivi_layout_screen_remove_layer, .screen_set_render_order = ivi_layout_screen_set_render_order, /** From 9337197f8223897c7bbd17a33ad9d2a55ef89ddb Mon Sep 17 00:00:00 2001 From: "Ucan, Emre (ADITG/SW1)" Date: Fri, 3 Mar 2017 14:21:28 +0000 Subject: [PATCH 0102/1642] ivi-shell: remove surface_set_orientation API This API is used to rotate the contents of application's buffer. But it is not needed because an application can rotate its buffers with set_buffer_transform request of wl_surface interface. Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout-export.h | 9 --------- ivi-shell/ivi-layout.c | 23 --------------------- tests/ivi_layout-internal-test.c | 9 --------- tests/ivi_layout-test-plugin.c | 34 -------------------------------- tests/ivi_layout-test.c | 2 -- 5 files changed, 77 deletions(-) diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index 39ffde131..f8802544d 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -275,15 +275,6 @@ struct ivi_layout_interface { int32_t x, int32_t y, int32_t width, int32_t height); - /** - * \brief Sets the orientation of a ivi_surface. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*surface_set_orientation)(struct ivi_layout_surface *ivisurf, - enum wl_output_transform orientation); - /** * \brief add a listener to listen property changes of ivi_surface * diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 8e4280ba7..3a0d3198c 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -1620,28 +1620,6 @@ ivi_layout_surface_set_destination_rectangle(struct ivi_layout_surface *ivisurf, return IVI_SUCCEEDED; } -static int32_t -ivi_layout_surface_set_orientation(struct ivi_layout_surface *ivisurf, - enum wl_output_transform orientation) -{ - struct ivi_layout_surface_properties *prop = NULL; - - if (ivisurf == NULL) { - weston_log("ivi_layout_surface_set_orientation: invalid argument\n"); - return IVI_FAILED; - } - - prop = &ivisurf->pending.prop; - prop->orientation = orientation; - - if (ivisurf->prop.orientation != orientation) - prop->event_mask |= IVI_NOTIFICATION_ORIENTATION; - else - prop->event_mask &= ~IVI_NOTIFICATION_ORIENTATION; - - return IVI_SUCCEEDED; -} - static int32_t ivi_layout_screen_add_layer(struct weston_output *output, struct ivi_layout_layer *addlayer) @@ -2074,7 +2052,6 @@ static struct ivi_layout_interface ivi_layout_interface = { .surface_set_opacity = ivi_layout_surface_set_opacity, .surface_set_source_rectangle = ivi_layout_surface_set_source_rectangle, .surface_set_destination_rectangle = ivi_layout_surface_set_destination_rectangle, - .surface_set_orientation = ivi_layout_surface_set_orientation, .surface_add_listener = ivi_layout_surface_add_listener, .surface_get_weston_surface = ivi_layout_surface_get_weston_surface, .surface_set_transition = ivi_layout_surface_set_transition, diff --git a/tests/ivi_layout-internal-test.c b/tests/ivi_layout-internal-test.c index 37a235634..f5e2763f9 100644 --- a/tests/ivi_layout-internal-test.c +++ b/tests/ivi_layout-internal-test.c @@ -88,14 +88,6 @@ test_surface_bad_destination_rectangle(struct test_context *ctx) iassert(lyt->surface_set_destination_rectangle(NULL, 20, 30, 200, 300) == IVI_FAILED); } -static void -test_surface_bad_orientation(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->surface_set_orientation(NULL, WL_OUTPUT_TRANSFORM_90) == IVI_FAILED); -} - static void test_surface_bad_source_rectangle(struct test_context *ctx) { @@ -939,7 +931,6 @@ run_internal_tests(void *data) test_surface_bad_visibility(ctx); test_surface_bad_destination_rectangle(ctx); - test_surface_bad_orientation(ctx); test_surface_bad_source_rectangle(ctx); test_surface_bad_properties(ctx); diff --git a/tests/ivi_layout-test-plugin.c b/tests/ivi_layout-test-plugin.c index 609c71ffc..19eab81a0 100644 --- a/tests/ivi_layout-test-plugin.c +++ b/tests/ivi_layout-test-plugin.c @@ -400,29 +400,6 @@ RUNNER_TEST(surface_opacity) runner_assert(prop->opacity == wl_fixed_from_double(0.5)); } -RUNNER_TEST(surface_orientation) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - const struct ivi_layout_surface_properties *prop; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - - prop = lyt->get_properties_of_surface(ivisurf); - runner_assert_or_return(prop); - runner_assert(prop->orientation == WL_OUTPUT_TRANSFORM_NORMAL); - - runner_assert(lyt->surface_set_orientation( - ivisurf, WL_OUTPUT_TRANSFORM_90) == IVI_SUCCEEDED); - - runner_assert(prop->orientation == WL_OUTPUT_TRANSFORM_NORMAL); - - lyt->commit_changes(); - - runner_assert(prop->orientation == WL_OUTPUT_TRANSFORM_90); -} - RUNNER_TEST(surface_dimension) { const struct ivi_layout_interface *lyt = ctx->layout_interface; @@ -663,17 +640,6 @@ RUNNER_TEST(commit_changes_after_opacity_set_surface_destroy) ivisurf, wl_fixed_from_double(0.5)) == IVI_SUCCEEDED); } -RUNNER_TEST(commit_changes_after_orientation_set_surface_destroy) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - runner_assert(lyt->surface_set_orientation( - ivisurf, WL_OUTPUT_TRANSFORM_90) == IVI_SUCCEEDED); -} - RUNNER_TEST(commit_changes_after_source_rectangle_set_surface_destroy) { const struct ivi_layout_interface *lyt = ctx->layout_interface; diff --git a/tests/ivi_layout-test.c b/tests/ivi_layout-test.c index 86e63b133..d2f4c9353 100644 --- a/tests/ivi_layout-test.c +++ b/tests/ivi_layout-test.c @@ -192,7 +192,6 @@ ivi_window_destroy(struct ivi_window *wnd) const char * const basic_test_names[] = { "surface_visibility", "surface_opacity", - "surface_orientation", "surface_dimension", "surface_position", "surface_destination_rectangle", @@ -206,7 +205,6 @@ const char * const basic_test_names[] = { const char * const surface_property_commit_changes_test_names[] = { "commit_changes_after_visibility_set_surface_destroy", "commit_changes_after_opacity_set_surface_destroy", - "commit_changes_after_orientation_set_surface_destroy", "commit_changes_after_source_rectangle_set_surface_destroy", "commit_changes_after_destination_rectangle_set_surface_destroy", }; From 6e423ed99672f5fc0c672e3601528c2c0d668ec8 Mon Sep 17 00:00:00 2001 From: "Ucan, Emre (ADITG/SW1)" Date: Fri, 3 Mar 2017 14:21:31 +0000 Subject: [PATCH 0103/1642] ivi-shell: remove layer_set_orientation API This API is used to rotate the contents of application's buffer, which are in the render order list of the layer. But this API is not needed because an application can rotate its buffers with set_buffer_transform request of wl_surface interface Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout-export.h | 9 ------ ivi-shell/ivi-layout.c | 23 -------------- tests/ivi_layout-internal-test.c | 54 -------------------------------- 3 files changed, 86 deletions(-) diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index f8802544d..afbb11011 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -452,15 +452,6 @@ struct ivi_layout_interface { int32_t x, int32_t y, int32_t width, int32_t height); - /** - * \brief Sets the orientation of a ivi_layer. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed - */ - int32_t (*layer_set_orientation)(struct ivi_layout_layer *ivilayer, - enum wl_output_transform orientation); - /** * \brief Add a ivi_surface to a ivi_layer which is currently managed by the service * diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 3a0d3198c..60e381c3e 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -1489,28 +1489,6 @@ ivi_layout_layer_set_destination_rectangle(struct ivi_layout_layer *ivilayer, return IVI_SUCCEEDED; } -static int32_t -ivi_layout_layer_set_orientation(struct ivi_layout_layer *ivilayer, - enum wl_output_transform orientation) -{ - struct ivi_layout_layer_properties *prop = NULL; - - if (ivilayer == NULL) { - weston_log("ivi_layout_layer_set_orientation: invalid argument\n"); - return IVI_FAILED; - } - - prop = &ivilayer->pending.prop; - prop->orientation = orientation; - - if (ivilayer->prop.orientation != orientation) - prop->event_mask |= IVI_NOTIFICATION_ORIENTATION; - else - prop->event_mask &= ~IVI_NOTIFICATION_ORIENTATION; - - return IVI_SUCCEEDED; -} - int32_t ivi_layout_layer_set_render_order(struct ivi_layout_layer *ivilayer, struct ivi_layout_surface **pSurface, @@ -2074,7 +2052,6 @@ static struct ivi_layout_interface ivi_layout_interface = { .layer_set_opacity = ivi_layout_layer_set_opacity, .layer_set_source_rectangle = ivi_layout_layer_set_source_rectangle, .layer_set_destination_rectangle = ivi_layout_layer_set_destination_rectangle, - .layer_set_orientation = ivi_layout_layer_set_orientation, .layer_add_surface = ivi_layout_layer_add_surface, .layer_remove_surface = ivi_layout_layer_remove_surface, .layer_set_render_order = ivi_layout_layer_set_render_order, diff --git a/tests/ivi_layout-internal-test.c b/tests/ivi_layout-internal-test.c index f5e2763f9..e56eb1250 100644 --- a/tests/ivi_layout-internal-test.c +++ b/tests/ivi_layout-internal-test.c @@ -179,31 +179,6 @@ test_layer_opacity(struct test_context *ctx) lyt->layer_destroy(ivilayer); } -static void -test_layer_orientation(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - const struct ivi_layout_layer_properties *prop; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - prop = lyt->get_properties_of_layer(ivilayer); - iassert(prop->orientation == WL_OUTPUT_TRANSFORM_NORMAL); - - iassert(lyt->layer_set_orientation( - ivilayer, WL_OUTPUT_TRANSFORM_90) == IVI_SUCCEEDED); - - iassert(prop->orientation == WL_OUTPUT_TRANSFORM_NORMAL); - - lyt->commit_changes(); - - iassert(prop->orientation == WL_OUTPUT_TRANSFORM_90); - - lyt->layer_destroy(ivilayer); -} - static void test_layer_dimension(struct test_context *ctx) { @@ -397,17 +372,6 @@ test_layer_bad_destination_rectangle(struct test_context *ctx) NULL, 20, 30, 200, 300) == IVI_FAILED); } -static void -test_layer_bad_orientation(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->layer_set_orientation( - NULL, WL_OUTPUT_TRANSFORM_90) == IVI_FAILED); - - lyt->commit_changes(); -} - static void test_layer_bad_source_rectangle(struct test_context *ctx) { @@ -454,21 +418,6 @@ test_commit_changes_after_opacity_set_layer_destroy(struct test_context *ctx) lyt->commit_changes(); } -static void -test_commit_changes_after_orientation_set_layer_destroy(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - iassert(lyt->layer_set_orientation( - ivilayer, WL_OUTPUT_TRANSFORM_90) == IVI_SUCCEEDED); - lyt->layer_destroy(ivilayer); - lyt->commit_changes(); -} - static void test_commit_changes_after_source_rectangle_set_layer_destroy(struct test_context *ctx) { @@ -937,7 +886,6 @@ run_internal_tests(void *data) test_layer_create(ctx); test_layer_visibility(ctx); test_layer_opacity(ctx); - test_layer_orientation(ctx); test_layer_dimension(ctx); test_layer_position(ctx); test_layer_destination_rectangle(ctx); @@ -946,12 +894,10 @@ run_internal_tests(void *data) test_layer_bad_visibility(ctx); test_layer_bad_opacity(ctx); test_layer_bad_destination_rectangle(ctx); - test_layer_bad_orientation(ctx); test_layer_bad_source_rectangle(ctx); test_layer_bad_properties(ctx); test_commit_changes_after_visibility_set_layer_destroy(ctx); test_commit_changes_after_opacity_set_layer_destroy(ctx); - test_commit_changes_after_orientation_set_layer_destroy(ctx); test_commit_changes_after_source_rectangle_set_layer_destroy(ctx); test_commit_changes_after_destination_rectangle_set_layer_destroy(ctx); test_layer_create_duplicate(ctx); From 57ac260c5d64041005f0547c0da7522f3aeac202 Mon Sep 17 00:00:00 2001 From: "Ucan, Emre (ADITG/SW1)" Date: Fri, 3 Mar 2017 14:21:32 +0000 Subject: [PATCH 0104/1642] ivi-shell: remove orientation calculation Dead code as orientation cannot be changed anymore, see patches: ivi-shell: remove layer_set_orientation API ivi-shell: remove surface_set_orientation API Signed-off-by: Emre Ucan [Pekka: added commit message] Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout.c | 80 +++--------------------------------------- 1 file changed, 4 insertions(+), 76 deletions(-) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 60e381c3e..87adde326 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -321,72 +321,13 @@ update_opacity(struct ivi_layout_layer *ivilayer, view->alpha = layer_alpha * surf_alpha; } -static void -get_rotate_values(enum wl_output_transform orientation, - float *v_sin, - float *v_cos) -{ - switch (orientation) { - case WL_OUTPUT_TRANSFORM_90: - *v_sin = 1.0f; - *v_cos = 0.0f; - break; - case WL_OUTPUT_TRANSFORM_180: - *v_sin = 0.0f; - *v_cos = -1.0f; - break; - case WL_OUTPUT_TRANSFORM_270: - *v_sin = -1.0f; - *v_cos = 0.0f; - break; - case WL_OUTPUT_TRANSFORM_NORMAL: - default: - *v_sin = 0.0f; - *v_cos = 1.0f; - break; - } -} - -static void -get_scale(enum wl_output_transform orientation, - float dest_width, - float dest_height, - float source_width, - float source_height, - float *scale_x, - float *scale_y) -{ - switch (orientation) { - case WL_OUTPUT_TRANSFORM_90: - *scale_x = dest_width / source_height; - *scale_y = dest_height / source_width; - break; - case WL_OUTPUT_TRANSFORM_180: - *scale_x = dest_width / source_width; - *scale_y = dest_height / source_height; - break; - case WL_OUTPUT_TRANSFORM_270: - *scale_x = dest_width / source_height; - *scale_y = dest_height / source_width; - break; - case WL_OUTPUT_TRANSFORM_NORMAL: - default: - *scale_x = dest_width / source_width; - *scale_y = dest_height / source_height; - break; - } -} - static void calc_transformation_matrix(struct ivi_rectangle *source_rect, struct ivi_rectangle *dest_rect, - enum wl_output_transform orientation, struct weston_matrix *m) { float source_center_x; float source_center_y; - float vsin; - float vcos; float scale_x; float scale_y; float translate_x; @@ -396,16 +337,8 @@ calc_transformation_matrix(struct ivi_rectangle *source_rect, source_center_y = source_rect->y + source_rect->height * 0.5f; weston_matrix_translate(m, -source_center_x, -source_center_y, 0.0f); - get_rotate_values(orientation, &vsin, &vcos); - weston_matrix_rotate_xy(m, vcos, vsin); - - get_scale(orientation, - dest_rect->width, - dest_rect->height, - source_rect->width, - source_rect->height, - &scale_x, - &scale_y); + scale_x = (float) dest_rect->width / (float) source_rect->width; + scale_y = (float) dest_rect->height / (float) source_rect->height; weston_matrix_scale(m, scale_x, scale_y, 1.0f); translate_x = dest_rect->width * 0.5f + dest_rect->x; @@ -582,13 +515,8 @@ calc_surface_to_global_matrix_and_mask_to_weston_surface( * - single screen-local coordinates to multi-screen coordinates, * which are global coordinates. */ - calc_transformation_matrix(&surface_source_rect, - &surface_dest_rect, - sp->orientation, m); - - calc_transformation_matrix(&layer_source_rect, - &layer_dest_rect, - lp->orientation, m); + calc_transformation_matrix(&surface_source_rect, &surface_dest_rect, m); + calc_transformation_matrix(&layer_source_rect, &layer_dest_rect, m); weston_matrix_translate(m, output->x, output->y, 0.0f); From d2535d761468f179e85043f75be0ea644f46c49a Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Tue, 25 Jul 2017 11:34:33 +0200 Subject: [PATCH 0105/1642] ivi-shell: Fixed broken link to wiki page in ivi-layout header file Signed-off-by: Michael Teyfel Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout-export.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index afbb11011..277ac59e8 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -45,7 +45,7 @@ * application surfaces grouped to the layer all together. * * This API and ABI follow following specifications. - * http://projects.genivi.org/wayland-ivi-extension/layer-manager-apis + * https://at.projects.genivi.org/wiki/display/PROJ/Wayland+IVI+Extension+Design */ #ifndef _IVI_LAYOUT_EXPORT_H_ From e6ac4fcbc93a5e754c5d64de7e0b939374a09715 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 29 Mar 2017 16:07:34 +0300 Subject: [PATCH 0106/1642] libweston: untangle weston_compositor_remove_output doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trying to make it more readable. Things that happen in the same step are kept in the same paragraph. v2: talk about "list of enabled outputs" Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 2a3074dbb..65b3de20f 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4521,19 +4521,25 @@ weston_output_enable_undo(struct weston_output *output) output->enabled = false; } -/** Removes output from compositor's output list +/** Removes output from compositor's list of enabled outputs * * \param output The weston_output object that is being removed. * - * Presentation feedback is discarded. - * Compositor is notified that outputs were changed and - * applies the necessary changes. - * All views assigned to the weston_output object are - * moved to a new output. - * Signal is emitted to notify all users of the weston_output - * object that the output is being destroyed. - * wl_output protocol objects referencing this weston_output - * are made inert. + * The following happens: + * + * - The output assignments of all views in the current scenegraph are + * recomputed. + * + * - Presentation feedback is discarded. + * + * - Compositor is notified that outputs were changed and + * applies the necessary changes to re-layout outputs. + * + * - Signal is emitted to notify all users of the weston_output + * object that the output is being destroyed. + * + * - wl_output protocol objects referencing this weston_output + * are made inert. */ static void weston_compositor_remove_output(struct weston_output *output) From bccda71c788d8bb73c5ba2a987e71a83f31c731a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 29 Mar 2017 16:16:04 +0300 Subject: [PATCH 0107/1642] libweston: use helper var in weston_compositor_remove_output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To shorten lines. Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 65b3de20f..5d51e1989 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4544,22 +4544,23 @@ weston_output_enable_undo(struct weston_output *output) static void weston_compositor_remove_output(struct weston_output *output) { + struct weston_compositor *compositor = output->compositor; struct wl_resource *resource; struct weston_view *view; assert(output->destroying); - wl_list_for_each(view, &output->compositor->view_list, link) { + wl_list_for_each(view, &compositor->view_list, link) { if (view->output_mask & (1u << output->id)) weston_view_assign_output(view); } weston_presentation_feedback_discard_list(&output->feedback_list); - weston_compositor_reflow_outputs(output->compositor, output, output->width); + weston_compositor_reflow_outputs(compositor, output, output->width); wl_list_remove(&output->link); - wl_signal_emit(&output->compositor->output_destroyed_signal, output); + wl_signal_emit(&compositor->output_destroyed_signal, output); wl_signal_emit(&output->destroy_signal, output); wl_resource_for_each(resource, &output->resource_list) { From f0ca79639e965139f613cf82adf917acc8c0bac5 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 29 Mar 2017 16:20:19 +0300 Subject: [PATCH 0108/1642] libweston: let add/remove_output handle the lists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A weston_output available to the compositor should always be either in the list of pending outputs or the list of enabled outputs. Let weston_compositor_add_output() and weston_compositor_remove_output() handle the moves between the lists. This way weston_output_enable() does not need to remove and oops-it-failed-add-it-back. weston_output_disable() does not need to manually re-add the output back to the pending list. To make everything nicely symmetric and fix any unbalancing caused by this: - weston_output_destroy() explicitly wl_list_remove()s - weston_compositor_add_pending_output() first removes then inserts, as we have the assumption that the link is always valid, even if empty. Update the documentations, too. v2: - talk about "list of enabled outputs" - keep wl_list_remove in weston_compositor_remove_output in its old place Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 5d51e1989..98813d7b0 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4464,8 +4464,10 @@ weston_output_move(struct weston_output *output, int x, int y) } } -/** Adds an output to the compositor's output list and - * send the compositor's output_created signal. +/** Signal that a pending output is taken into use. + * + * Removes the output from the pending list and adds it to the compositor's + * list of enabled outputs. The output created signal is emitted. * * \param compositor The compositor instance. * \param output The output to be added. @@ -4476,7 +4478,9 @@ weston_compositor_add_output(struct weston_compositor *compositor, { struct weston_view *view, *next; + wl_list_remove(&output->link); wl_list_insert(compositor->output_list.prev, &output->link); + wl_signal_emit(&compositor->output_created_signal, output); wl_list_for_each_safe(view, next, &compositor->view_list, link) @@ -4535,6 +4539,8 @@ weston_output_enable_undo(struct weston_output *output) * - Compositor is notified that outputs were changed and * applies the necessary changes to re-layout outputs. * + * - The output is put back in the pending outputs list. + * * - Signal is emitted to notify all users of the weston_output * object that the output is being destroyed. * @@ -4558,7 +4564,9 @@ weston_compositor_remove_output(struct weston_output *output) weston_presentation_feedback_discard_list(&output->feedback_list); weston_compositor_reflow_outputs(compositor, output, output->width); + wl_list_remove(&output->link); + wl_list_insert(compositor->pending_output_list.prev, &output->link); wl_signal_emit(&compositor->output_destroyed_signal, output); wl_signal_emit(&output->destroy_signal, output); @@ -4654,11 +4662,14 @@ weston_output_init(struct weston_output *output, * * Also notifies the compositor that an output is pending for * configuration. + * + * The opposite of this operation is built into weston_output_destroy(). */ WL_EXPORT void weston_compositor_add_pending_output(struct weston_output *output, struct weston_compositor *compositor) { + wl_list_remove(&output->link); wl_list_insert(compositor->pending_output_list.prev, &output->link); wl_signal_emit(&compositor->output_pending_signal, output); } @@ -4716,9 +4727,6 @@ weston_output_enable(struct weston_output *output) /* Make sure we have a transform set */ assert(output->transform != UINT32_MAX); - /* Remove it from pending/disabled output list */ - wl_list_remove(&output->link); - /* Verify we haven't reached the limit of 32 available output IDs */ assert(ffs(~c->output_id_pool) > 0); @@ -4738,7 +4746,6 @@ weston_output_enable(struct weston_output *output) wl_list_init(&output->animation_list); wl_list_init(&output->resource_list); wl_list_init(&output->feedback_list); - wl_list_init(&output->link); /* Invert the output id pool and look for the lowest numbered * switch (the least significant bit). Take that bit's position @@ -4761,8 +4768,6 @@ weston_output_enable(struct weston_output *output) weston_log("Enabling output \"%s\" failed.\n", output->name); weston_output_enable_undo(output); - wl_list_insert(output->compositor->pending_output_list.prev, - &output->link); return -1; } @@ -4815,10 +4820,6 @@ weston_output_disable(struct weston_output *output) if (output->enabled) { weston_compositor_remove_output(output); weston_output_enable_undo(output); - - /* We need to preserve it somewhere so it can be destroyed on shutdown - if nobody wants to configure it again */ - wl_list_insert(output->compositor->pending_output_list.prev, &output->link); } output->destroying = 0; @@ -4847,6 +4848,7 @@ weston_output_destroy(struct weston_output *output) weston_output_enable_undo(output); } + wl_list_remove(&output->link); free(output->name); } From ee16ea95bcacf7f1b38cc172d188d65d225d7b5b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 29 Mar 2017 16:53:50 +0300 Subject: [PATCH 0109/1642] libweston: two more weston_output docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document two more functions of the weston_output API. Exported functions marked internal are meant for backends only. Exported functions not marked internal are meant for libweston users. v2: talk about "list of enabled outputs". Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index 98813d7b0..c66193a45 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4471,6 +4471,8 @@ weston_output_move(struct weston_output *output, int x, int y) * * \param compositor The compositor instance. * \param output The output to be added. + * + * \internal */ WL_EXPORT void weston_compositor_add_output(struct weston_compositor *compositor, @@ -4487,6 +4489,20 @@ weston_compositor_add_output(struct weston_compositor *compositor, weston_view_geometry_dirty(view); } +/** Transform device coordinates into global coordinates + * + * \param device_x[in] X coordinate in device units. + * \param device_y[in] Y coordinate in device units. + * \param x[out] X coordinate in the global space. + * \param y[out] Y coordinate in the global space. + * + * Transforms coordinates from the device coordinate space + * (physical pixel units) to the global coordinate space (logical pixel units). + * This takes into account output transform and scale. + * + * \memberof weston_output + * \internal + */ WL_EXPORT void weston_output_transform_coordinate(struct weston_output *output, double device_x, double device_y, @@ -4546,6 +4562,9 @@ weston_output_enable_undo(struct weston_output *output) * * - wl_output protocol objects referencing this weston_output * are made inert. + * + * \memberof weston_output + * \internal */ static void weston_compositor_remove_output(struct weston_output *output) @@ -4583,6 +4602,8 @@ weston_compositor_remove_output(struct weston_output *output) * * It only supports setting scale for an output that * is not enabled and it can only be ran once. + * + * \memberof weston_output */ WL_EXPORT void weston_output_set_scale(struct weston_output *output, @@ -4608,6 +4629,8 @@ weston_output_set_scale(struct weston_output *output, * Refer to wl_output::transform section located at * https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_output * for list of values that can be passed to this function. + * + * \memberof weston_output */ WL_EXPORT void weston_output_set_transform(struct weston_output *output, @@ -4630,6 +4653,9 @@ weston_output_set_transform(struct weston_output *output, * * Sets initial values for fields that are expected to be * configured either by compositors or backends. + * + * \memberof weston_output + * \internal */ WL_EXPORT void weston_output_init(struct weston_output *output, @@ -4664,6 +4690,9 @@ weston_output_init(struct weston_output *output, * configuration. * * The opposite of this operation is built into weston_output_destroy(). + * + * \memberof weston_output + * \internal */ WL_EXPORT void weston_compositor_add_pending_output(struct weston_output *output, @@ -4838,6 +4867,18 @@ weston_pending_output_coldplug(struct weston_compositor *compositor) wl_signal_emit(&compositor->output_pending_signal, output); } +/** Uninitialize an output + * + * Removes the output from the list of enabled outputs if necessary, but + * does not call the backend's output disable function. The output will no + * longer be in the list of pending outputs either. + * + * All fields of weston_output become uninitialized, i.e. should not be used + * anymore. The caller can free the memory after this. + * + * \memberof weston_output + * \internal + */ WL_EXPORT void weston_output_destroy(struct weston_output *output) { From f9681b564dac97ed5bdd5565e57158116b844611 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 29 Mar 2017 16:58:48 +0300 Subject: [PATCH 0110/1642] libweston: unexport weston_compositor_add_output() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only used by weston_output_enable(). Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 2 +- libweston/compositor.h | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index c66193a45..c8bcbb086 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4474,7 +4474,7 @@ weston_output_move(struct weston_output *output, int x, int y) * * \internal */ -WL_EXPORT void +static void weston_compositor_add_output(struct weston_compositor *compositor, struct weston_output *output) { diff --git a/libweston/compositor.h b/libweston/compositor.h index 21c4046de..e90bfe84b 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1718,9 +1718,6 @@ weston_output_update_matrix(struct weston_output *output); void weston_output_move(struct weston_output *output, int x, int y); -void -weston_compositor_add_output(struct weston_compositor *compositor, - struct weston_output *output); void weston_output_destroy(struct weston_output *output); void From d72bad2f53b10e160820725d647b9b96762b47b8 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 29 Mar 2017 17:01:41 +0300 Subject: [PATCH 0111/1642] libweston: unexport weston_output_update_matrix() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only used internally in core. Needs to happen automatically when something changes, so there should no need to call it from outside. Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 5 ++++- libweston/compositor.h | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index c8bcbb086..0c6d1eb93 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -66,6 +66,9 @@ #define DEFAULT_REPAINT_WINDOW 7 /* milliseconds */ +static void +weston_output_update_matrix(struct weston_output *output); + static void weston_output_transform_scale_init(struct weston_output *output, uint32_t transform, uint32_t scale); @@ -4344,7 +4347,7 @@ weston_compositor_reflow_outputs(struct weston_compositor *compositor, } } -WL_EXPORT void +static void weston_output_update_matrix(struct weston_output *output) { float magnification; diff --git a/libweston/compositor.h b/libweston/compositor.h index e90bfe84b..4e01f056b 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1714,8 +1714,6 @@ void weston_output_activate_zoom(struct weston_output *output, struct weston_seat *seat); void -weston_output_update_matrix(struct weston_output *output); -void weston_output_move(struct weston_output *output, int x, int y); void From e952a01c3b42c7c870091e71488e9469bd897153 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 29 Mar 2017 17:14:00 +0300 Subject: [PATCH 0112/1642] libweston: move asserts to add_pending_output() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit weston_compositor_add_pending_output() is the point through which all backends must go when creating a new output. The enable and disable vfuns are essential for anything to be done with the output, so it makes sense to check them here, rather than when actually enabling or disabling. Particularly the disable vfunc is rarely called, so this gets the check better excercised. Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 0c6d1eb93..97ade223b 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4701,6 +4701,9 @@ WL_EXPORT void weston_compositor_add_pending_output(struct weston_output *output, struct weston_compositor *compositor) { + assert(output->disable); + assert(output->enable); + wl_list_remove(&output->link); wl_list_insert(compositor->pending_output_list.prev, &output->link); wl_signal_emit(&compositor->output_pending_signal, output); @@ -4745,8 +4748,6 @@ weston_output_enable(struct weston_output *output) struct weston_output *iterator; int x = 0, y = 0; - assert(output->enable); - iterator = container_of(c->output_list.prev, struct weston_output, link); @@ -4833,8 +4834,6 @@ weston_output_enable(struct weston_output *output) WL_EXPORT void weston_output_disable(struct weston_output *output) { - assert(output->disable); - /* Should we rename this? */ output->destroying = 1; From 7f340ff89595c397153d0e485cbddd8562dc8a0e Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 30 Mar 2017 14:56:22 +0300 Subject: [PATCH 0113/1642] libweston: specify weston_output::enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was ambiguous what this flag meant - it did not mean whether the backend is considering this output to be enabled, because weston_output_destroy() unsets it while deliberately not calling the backend disable() vfunc. Perhaps the most clear definition is with respect to the output's assignment in the pending vs. enabled output lists. There is also a whole bunch of variables that are allocated only when enabled is true. Since the flag is related to the list membership, set and clear the flag only when manipulating the lists. Assert that weston_compositor_add_output() and weston_compositor_remove_output() are not called in a wrong state. v2: - talk about "list of enabled outputs" - clear 'enabled' in weston_compositor_remove_output() earlier Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 9 ++++----- libweston/compositor.h | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 97ade223b..6d3dc1cc0 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4483,8 +4483,10 @@ weston_compositor_add_output(struct weston_compositor *compositor, { struct weston_view *view, *next; + assert(!output->enabled); wl_list_remove(&output->link); wl_list_insert(compositor->output_list.prev, &output->link); + output->enabled = true; wl_signal_emit(&compositor->output_created_signal, output); @@ -4540,8 +4542,6 @@ weston_output_enable_undo(struct weston_output *output) pixman_region32_fini(&output->region); pixman_region32_fini(&output->previous_damage); output->compositor->output_id_pool &= ~(1u << output->id); - - output->enabled = false; } /** Removes output from compositor's list of enabled outputs @@ -4577,6 +4577,7 @@ weston_compositor_remove_output(struct weston_output *output) struct weston_view *view; assert(output->destroying); + assert(output->enabled); wl_list_for_each(view, &compositor->view_list, link) { if (view->output_mask & (1u << output->id)) @@ -4589,6 +4590,7 @@ weston_compositor_remove_output(struct weston_output *output) wl_list_remove(&output->link); wl_list_insert(compositor->pending_output_list.prev, &output->link); + output->enabled = false; wl_signal_emit(&compositor->output_destroyed_signal, output); wl_signal_emit(&output->destroy_signal, output); @@ -4671,7 +4673,6 @@ weston_output_init(struct weston_output *output, assert(output->name); wl_list_init(&output->link); - output->enabled = false; /* Add some (in)sane defaults which can be used @@ -4791,8 +4792,6 @@ weston_output_enable(struct weston_output *output) wl_global_create(c->wl_display, &wl_output_interface, 3, output, bind_output); - output->enabled = true; - /* Enable the output (set up the crtc or create a * window representing the output, set up the * renderer, etc) diff --git a/libweston/compositor.h b/libweston/compositor.h index 4e01f056b..0be9157e0 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -233,7 +233,7 @@ struct weston_output { struct weston_timeline_object timeline; - bool enabled; + bool enabled; /**< is in the output_list, not pending list */ int scale; int (*enable)(struct weston_output *output); From cc201e47ba246dd8693577f36121b7a573657b85 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 30 Mar 2017 15:11:25 +0300 Subject: [PATCH 0114/1642] libweston: prevent double weston_output_enable() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enabling an already enabled output is an error, at least with the current implementation. However, disabling an output that has not been enabled is ok. Cope with the first and document the second. Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 6d3dc1cc0..6f8d93918 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4712,7 +4712,8 @@ weston_compositor_add_pending_output(struct weston_output *output, /** Constructs a weston_output object that can be used by the compositor. * - * \param output The weston_output object that needs to be enabled. + * \param output The weston_output object that needs to be enabled. Must not + * be enabled already. * * Output coordinates are calculated and each new output is by default * assigned to the right of previous one. @@ -4749,6 +4750,12 @@ weston_output_enable(struct weston_output *output) struct weston_output *iterator; int x = 0, y = 0; + if (output->enabled) { + weston_log("Error: attempt to enable an enabled output '%s'\n", + output->name); + return -1; + } + iterator = container_of(c->output_list.prev, struct weston_output, link); @@ -4829,6 +4836,10 @@ weston_output_enable(struct weston_output *output) * * See weston_output_init() for more information on the * state output is returned to. + * + * If the output has never been enabled yet, this function can still be + * called to ensure that the output is actually turned off rather than left + * in the state it was discovered in. */ WL_EXPORT void weston_output_disable(struct weston_output *output) From 3d2d49723bda7e3e088f4dba39d59e19fb0d820d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 30 Mar 2017 15:19:45 +0300 Subject: [PATCH 0115/1642] libweston: move output id into add/remove_output() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the output id management into weston_compositor_add_output() and weston_compositor_remove_output(). This is a more logical place, and works towards assimilating weston_output_enable_undo(). The output id is no longer available to the backend enable() vfuncs, but it was not used there to begin with. v2: moved assert earlier in weston_compositor_add_output() Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 6f8d93918..3c6d6db86 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4472,6 +4472,8 @@ weston_output_move(struct weston_output *output, int x, int y) * Removes the output from the pending list and adds it to the compositor's * list of enabled outputs. The output created signal is emitted. * + * The output gets an internal ID assigned. + * * \param compositor The compositor instance. * \param output The output to be added. * @@ -4484,6 +4486,17 @@ weston_compositor_add_output(struct weston_compositor *compositor, struct weston_view *view, *next; assert(!output->enabled); + + /* Verify we haven't reached the limit of 32 available output IDs */ + assert(ffs(~compositor->output_id_pool) > 0); + + /* Invert the output id pool and look for the lowest numbered + * switch (the least significant bit). Take that bit's position + * as our ID, and mark it used in the compositor's output_id_pool. + */ + output->id = ffs(~compositor->output_id_pool) - 1; + compositor->output_id_pool |= 1u << output->id; + wl_list_remove(&output->link); wl_list_insert(compositor->output_list.prev, &output->link); output->enabled = true; @@ -4532,7 +4545,6 @@ weston_output_transform_coordinate(struct weston_output *output, * Removes the repaint timer. * Destroys the Wayland global assigned to the output. * Destroys pixman regions allocated to the output. - * Deallocates output's ID and updates compositor's output_id_pool. */ static void weston_output_enable_undo(struct weston_output *output) @@ -4541,7 +4553,6 @@ weston_output_enable_undo(struct weston_output *output) pixman_region32_fini(&output->region); pixman_region32_fini(&output->previous_damage); - output->compositor->output_id_pool &= ~(1u << output->id); } /** Removes output from compositor's list of enabled outputs @@ -4566,6 +4577,8 @@ weston_output_enable_undo(struct weston_output *output) * - wl_output protocol objects referencing this weston_output * are made inert. * + * - The output's internal ID is released. + * * \memberof weston_output * \internal */ @@ -4598,6 +4611,9 @@ weston_compositor_remove_output(struct weston_output *output) wl_resource_for_each(resource, &output->resource_list) { wl_resource_set_destructor(resource, NULL); } + + compositor->output_id_pool &= ~(1u << output->id); + output->id = 0xffffffff; /* invalid */ } /** Sets the output scale for a given output. @@ -4768,9 +4784,6 @@ weston_output_enable(struct weston_output *output) /* Make sure we have a transform set */ assert(output->transform != UINT32_MAX); - /* Verify we haven't reached the limit of 32 available output IDs */ - assert(ffs(~c->output_id_pool) > 0); - output->x = x; output->y = y; output->dirty = 1; @@ -4788,13 +4801,6 @@ weston_output_enable(struct weston_output *output) wl_list_init(&output->resource_list); wl_list_init(&output->feedback_list); - /* Invert the output id pool and look for the lowest numbered - * switch (the least significant bit). Take that bit's position - * as our ID, and mark it used in the compositor's output_id_pool. - */ - output->id = ffs(~output->compositor->output_id_pool) - 1; - output->compositor->output_id_pool |= 1u << output->id; - output->global = wl_global_create(c->wl_display, &wl_output_interface, 3, output, bind_output); From 2210ad006cb2c4851ce768f99c8a4816214fea0c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 30 Mar 2017 15:48:06 +0300 Subject: [PATCH 0116/1642] libweston: move globals to weston_compositor_add_output() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the wl_output global management into weston_compositor_add_output() and weston_compositor_remove_output(). If weston_output_enable() fails, there is no need to clean up the global and the clients will not see a wl_output come and go. Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 3c6d6db86..eda043406 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4472,7 +4472,8 @@ weston_output_move(struct weston_output *output, int x, int y) * Removes the output from the pending list and adds it to the compositor's * list of enabled outputs. The output created signal is emitted. * - * The output gets an internal ID assigned. + * The output gets an internal ID assigned, and the wl_output global is + * created. * * \param compositor The compositor instance. * \param output The output to be added. @@ -4501,6 +4502,10 @@ weston_compositor_add_output(struct weston_compositor *compositor, wl_list_insert(compositor->output_list.prev, &output->link); output->enabled = true; + output->global = wl_global_create(compositor->wl_display, + &wl_output_interface, 3, + output, bind_output); + wl_signal_emit(&compositor->output_created_signal, output); wl_list_for_each_safe(view, next, &compositor->view_list, link) @@ -4543,14 +4548,11 @@ weston_output_transform_coordinate(struct weston_output *output, * \param output The weston_output object that needs the changes undone. * * Removes the repaint timer. - * Destroys the Wayland global assigned to the output. * Destroys pixman regions allocated to the output. */ static void weston_output_enable_undo(struct weston_output *output) { - wl_global_destroy(output->global); - pixman_region32_fini(&output->region); pixman_region32_fini(&output->previous_damage); } @@ -4575,7 +4577,7 @@ weston_output_enable_undo(struct weston_output *output) * object that the output is being destroyed. * * - wl_output protocol objects referencing this weston_output - * are made inert. + * are made inert, and the wl_output global is removed. * * - The output's internal ID is released. * @@ -4608,6 +4610,8 @@ weston_compositor_remove_output(struct weston_output *output) wl_signal_emit(&compositor->output_destroyed_signal, output); wl_signal_emit(&output->destroy_signal, output); + wl_global_destroy(output->global); + output->global = NULL; wl_resource_for_each(resource, &output->resource_list) { wl_resource_set_destructor(resource, NULL); } @@ -4801,10 +4805,6 @@ weston_output_enable(struct weston_output *output) wl_list_init(&output->resource_list); wl_list_init(&output->feedback_list); - output->global = - wl_global_create(c->wl_display, &wl_output_interface, 3, - output, bind_output); - /* Enable the output (set up the crtc or create a * window representing the output, set up the * renderer, etc) From 4b582c7cc088a1a363cde3f01ac2b268f9efb8d0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 30 Mar 2017 16:04:58 +0300 Subject: [PATCH 0117/1642] libweston: extend output->region lifetime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's a little awkward to try to keep the weston_output::region and weston_output::previous_damage allocate exactly only when the output is enabled. There was also a leak: weston_output_move() was calling weston_output_init_geometry() on an already allocated regions without fini in between. Fix both issues by allocating the regions in weston_output_init(), always fini/init'ing them in weston_output_init_geometry(), and fini'ing for good in weston_output_destroy(). This nicely gets rid of weston_output_enable_undo() so I do not need to try to figure out what to do with it later. Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor.c | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index eda043406..296b02eeb 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4426,7 +4426,10 @@ weston_output_init_geometry(struct weston_output *output, int x, int y) output->x = x; output->y = y; + pixman_region32_fini(&output->previous_damage); pixman_region32_init(&output->previous_damage); + + pixman_region32_fini(&output->region); pixman_region32_init_rect(&output->region, x, y, output->width, output->height); @@ -4543,20 +4546,6 @@ weston_output_transform_coordinate(struct weston_output *output, *y = p.f[1] / p.f[3]; } -/** Undoes changes to an output done by weston_output_enable() - * - * \param output The weston_output object that needs the changes undone. - * - * Removes the repaint timer. - * Destroys pixman regions allocated to the output. - */ -static void -weston_output_enable_undo(struct weston_output *output) -{ - pixman_region32_fini(&output->region); - pixman_region32_fini(&output->previous_damage); -} - /** Removes output from compositor's list of enabled outputs * * \param output The weston_output object that is being removed. @@ -4703,6 +4692,9 @@ weston_output_init(struct weston_output *output, output->scale = 0; /* Can't use -1 on uint32_t and 0 is valid enum value */ output->transform = UINT32_MAX; + + pixman_region32_init(&output->previous_damage); + pixman_region32_init(&output->region); } /** Adds weston_output object to pending output list. @@ -4811,8 +4803,6 @@ weston_output_enable(struct weston_output *output) */ if (output->enable(output) < 0) { weston_log("Enabling output \"%s\" failed.\n", output->name); - - weston_output_enable_undo(output); return -1; } @@ -4864,10 +4854,8 @@ weston_output_disable(struct weston_output *output) if (output->disable(output) < 0) return; - if (output->enabled) { + if (output->enabled) weston_compositor_remove_output(output); - weston_output_enable_undo(output); - } output->destroying = 0; } @@ -4902,11 +4890,11 @@ weston_output_destroy(struct weston_output *output) { output->destroying = 1; - if (output->enabled) { + if (output->enabled) weston_compositor_remove_output(output); - weston_output_enable_undo(output); - } + pixman_region32_fini(&output->region); + pixman_region32_fini(&output->previous_damage); wl_list_remove(&output->link); free(output->name); } From 0079a949e09816a7ae1ea0de69196c2a0e0875e7 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 24 Mar 2017 15:46:23 +0200 Subject: [PATCH 0118/1642] libweston: make weston_output::connection_internal a bool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It really is a boolean. Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- libweston/compositor-drm.c | 2 +- libweston/compositor.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 10adb463f..c51d24b6a 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2653,7 +2653,7 @@ drm_output_enable(struct weston_output *base) find_and_parse_output_edid(b, output, output->connector); if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) - output->base.connection_internal = 1; + output->base.connection_internal = true; weston_plane_init(&output->cursor_plane, b->compositor, INT32_MIN, INT32_MIN); diff --git a/libweston/compositor.h b/libweston/compositor.h index 0be9157e0..442d046ea 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -223,7 +223,7 @@ struct weston_output { void (*set_backlight)(struct weston_output *output, uint32_t value); void (*set_dpms)(struct weston_output *output, enum dpms_enum level); - int connection_internal; + bool connection_internal; uint16_t gamma_size; void (*set_gamma)(struct weston_output *output, uint16_t size, From 9ffb25009cbf2996f53b4ceb39cb9a7af1508ebf Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 27 Mar 2017 15:14:32 +0300 Subject: [PATCH 0119/1642] libweston: introduce weston_output_from_resource() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a simple wrapper for casting the user data of a wl_resource into a struct weston_output pointer. Using the wrapper clearly marks all the places where a wl_output protocol object is used. Replace ALL wl_output related calls to wl_resource_get_user_data() with a call to weston_output_from_resource(). v2: add type assert in weston_output_from_resource(). Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović --- compositor/weston-screenshooter.c | 2 +- desktop-shell/input-panel.c | 2 +- desktop-shell/shell.c | 4 ++-- fullscreen-shell/fullscreen-shell.c | 4 ++-- ivi-shell/input-panel-ivi.c | 2 +- libweston-desktop/wl-shell.c | 2 +- libweston-desktop/xdg-shell-v5.c | 2 +- libweston-desktop/xdg-shell-v6.c | 2 +- libweston/compositor.c | 16 ++++++++++++++++ libweston/compositor.h | 3 +++ tests/weston-test.c | 2 +- 11 files changed, 30 insertions(+), 11 deletions(-) diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c index 9999909e1..f874c3eb2 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -66,7 +66,7 @@ screenshooter_shoot(struct wl_client *client, struct wl_resource *buffer_resource) { struct weston_output *output = - wl_resource_get_user_data(output_resource); + weston_output_from_resource(output_resource); struct weston_buffer *buffer = weston_buffer_from_resource(buffer_resource); diff --git a/desktop-shell/input-panel.c b/desktop-shell/input-panel.c index 40a4092e4..e6b1541aa 100644 --- a/desktop-shell/input-panel.c +++ b/desktop-shell/input-panel.c @@ -274,7 +274,7 @@ input_panel_surface_set_toplevel(struct wl_client *client, wl_list_insert(&shell->input_panel.surfaces, &input_panel_surface->link); - input_panel_surface->output = wl_resource_get_user_data(output_resource); + input_panel_surface->output = weston_output_from_resource(output_resource); input_panel_surface->panel = 0; } diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index f1577c12d..832a7b748 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2941,7 +2941,7 @@ desktop_shell_set_background(struct wl_client *client, surface->committed = background_committed; surface->committed_private = shell; weston_surface_set_label_func(surface, background_get_label); - surface->output = wl_resource_get_user_data(output_resource); + surface->output = weston_output_from_resource(output_resource); view->output = surface->output; weston_desktop_shell_send_configure(resource, 0, surface_resource, @@ -3026,7 +3026,7 @@ desktop_shell_set_panel(struct wl_client *client, surface->committed = panel_committed; surface->committed_private = shell; weston_surface_set_label_func(surface, panel_get_label); - surface->output = wl_resource_get_user_data(output_resource); + surface->output = weston_output_from_resource(output_resource); view->output = surface->output; weston_desktop_shell_send_configure(resource, 0, surface_resource, diff --git a/fullscreen-shell/fullscreen-shell.c b/fullscreen-shell/fullscreen-shell.c index 7368cb425..6f4565a75 100644 --- a/fullscreen-shell/fullscreen-shell.c +++ b/fullscreen-shell/fullscreen-shell.c @@ -769,7 +769,7 @@ fullscreen_shell_present_surface(struct wl_client *client, } if (output_res) { - output = wl_resource_get_user_data(output_res); + output = weston_output_from_resource(output_res); fsout = fs_output_for_output(output); fs_output_set_surface(fsout, surface, method, 0, 0); } else { @@ -813,7 +813,7 @@ fullscreen_shell_present_surface_for_mode(struct wl_client *client, struct weston_seat *seat; struct fs_output *fsout; - output = wl_resource_get_user_data(output_res); + output = weston_output_from_resource(output_res); fsout = fs_output_for_output(output); if (surface_res == NULL) { diff --git a/ivi-shell/input-panel-ivi.c b/ivi-shell/input-panel-ivi.c index 57d1cb291..0008a52d3 100644 --- a/ivi-shell/input-panel-ivi.c +++ b/ivi-shell/input-panel-ivi.c @@ -275,7 +275,7 @@ input_panel_surface_set_toplevel(struct wl_client *client, wl_list_insert(&shell->input_panel.surfaces, &input_panel_surface->link); - input_panel_surface->output = wl_resource_get_user_data(output_resource); + input_panel_surface->output = weston_output_from_resource(output_resource); input_panel_surface->panel = 0; } diff --git a/libweston-desktop/wl-shell.c b/libweston-desktop/wl-shell.c index 399139cfa..66553f456 100644 --- a/libweston-desktop/wl-shell.c +++ b/libweston-desktop/wl-shell.c @@ -302,7 +302,7 @@ weston_desktop_wl_shell_surface_protocol_set_fullscreen(struct wl_client *wl_cli struct weston_output *output = NULL; if (output_resource != NULL) - output = wl_resource_get_user_data(output_resource); + output = weston_output_from_resource(output_resource); weston_desktop_wl_shell_change_state(surface, FULLSCREEN, NULL, 0, 0); weston_desktop_api_fullscreen_requested(surface->desktop, dsurface, diff --git a/libweston-desktop/xdg-shell-v5.c b/libweston-desktop/xdg-shell-v5.c index c91c25905..d7c49b150 100644 --- a/libweston-desktop/xdg-shell-v5.c +++ b/libweston-desktop/xdg-shell-v5.c @@ -553,7 +553,7 @@ weston_desktop_xdg_surface_protocol_set_fullscreen(struct wl_client *wl_client, struct weston_output *output = NULL; if (output_resource != NULL) - output = wl_resource_get_user_data(output_resource); + output = weston_output_from_resource(output_resource); weston_desktop_xdg_surface_ensure_added(surface); weston_desktop_api_fullscreen_requested(surface->desktop, dsurface, diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index de5d3e058..dda0bf921 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -507,7 +507,7 @@ weston_desktop_xdg_toplevel_protocol_set_fullscreen(struct wl_client *wl_client, struct weston_output *output = NULL; if (output_resource != NULL) - output = wl_resource_get_user_data(output_resource); + output = weston_output_from_resource(output_resource); weston_desktop_xdg_toplevel_ensure_added(toplevel); weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, diff --git a/libweston/compositor.c b/libweston/compositor.c index 296b02eeb..813b66340 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4323,6 +4323,22 @@ bind_output(struct wl_client *client, wl_output_send_done(resource); } +/** Get the backing object of wl_output + * + * \param resource A wl_output protocol object. + * \return The backing object (user data) of a wl_resource representing a + * wl_output protocol object. + */ +WL_EXPORT struct weston_output * +weston_output_from_resource(struct wl_resource *resource) +{ + assert(wl_resource_instance_of(resource, &wl_output_interface, + &output_interface)); + + return wl_resource_get_user_data(resource); +} + + /* Move other outputs when one is resized so the space remains contiguous. */ static void weston_compositor_reflow_outputs(struct weston_compositor *compositor, diff --git a/libweston/compositor.h b/libweston/compositor.h index 442d046ea..769203a1d 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1941,6 +1941,9 @@ weston_output_disable(struct weston_output *output); void weston_pending_output_coldplug(struct weston_compositor *compositor); +struct weston_output * +weston_output_from_resource(struct wl_resource *resource); + #ifdef __cplusplus } #endif diff --git a/tests/weston-test.c b/tests/weston-test.c index 0123e9946..189fcc1be 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -473,7 +473,7 @@ capture_screenshot(struct wl_client *client, struct wl_resource *buffer_resource) { struct weston_output *output = - wl_resource_get_user_data(output_resource); + weston_output_from_resource(output_resource); struct weston_buffer *buffer = weston_buffer_from_resource(buffer_resource); From 4c4b9cfb1a55ee864beaf25286bfe4364036f0df Mon Sep 17 00:00:00 2001 From: Bryce Harrington Date: Tue, 25 Jul 2017 16:32:20 -0700 Subject: [PATCH 0120/1642] configure.ac: bump to version 2.99.92 for the beta release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 67fd9c9a8..bb8ae1bda 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [2]) m4_define([weston_minor_version], [99]) -m4_define([weston_micro_version], [91]) +m4_define([weston_micro_version], [92]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [3]) From ba8a0d041ed83ebe18b919d8b2c8a02849ca1c7e Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Wed, 26 Jul 2017 12:02:15 +0200 Subject: [PATCH 0121/1642] desktop-shell: Track focused shell surface by main surface The focused surface is used for determining whether shell surfaces are activated. They should also be considered activated when a subsurface has focus. Inserting a call to weston_surface_get_main_surface fixes this. seat->focused_surface is only used for shell_surface keyboard focus tracking. Signed-off-by: Philipp Kerling Reviewed-by: Quentin Glidic --- desktop-shell/shell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 832a7b748..4608cf2fa 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -1852,7 +1852,7 @@ handle_keyboard_focus(struct wl_listener *listener, void *data) shell_surface_lose_keyboard_focus(shsurf); } - seat->focused_surface = keyboard->focus; + seat->focused_surface = weston_surface_get_main_surface(keyboard->focus); if (seat->focused_surface) { struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); From c623902ecaa191c7eccfcb7c212313407da82c56 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Wed, 26 Jul 2017 14:02:21 +0200 Subject: [PATCH 0122/1642] libweston-desktop/xdg-shell: Check window geometry instead of surface size against configured size Shell surfaces may have a geometry that is different to the size of their main surface, e.g. due to subcompositing. In states where size is strictly enforced (fullscreen and maximized), the size that the compositor wants must be checked against the window geometry and not just the main surface size. Fix by calling weston_desktop_surface_get_geometry and using that size instead of main surface size. Signed-off-by: Philipp Kerling Reviewed-by: Quentin Glidic --- libweston-desktop/xdg-shell-v5.c | 4 ++++ libweston-desktop/xdg-shell-v6.c | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libweston-desktop/xdg-shell-v5.c b/libweston-desktop/xdg-shell-v5.c index d7c49b150..77d004e1a 100644 --- a/libweston-desktop/xdg-shell-v5.c +++ b/libweston-desktop/xdg-shell-v5.c @@ -264,6 +264,10 @@ weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, weston_desktop_surface_get_surface(surface->surface); bool reconfigure = false; + /* TODO: use the window geometry and not the surface size here + * We need to check the next geometry if there is one, but not accept it + * until we checked it, maybe. + */ if (surface->next.state.maximized || surface->next.state.fullscreen) reconfigure = surface->next.size.width != wsurface->width || surface->next.size.height != wsurface->height; diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index dda0bf921..1344dda02 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -644,9 +644,12 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev if (!wsurface->buffer_ref.buffer) return; + struct weston_geometry geometry = + weston_desktop_surface_get_geometry(toplevel->base.desktop_surface); + if ((toplevel->next.state.maximized || toplevel->next.state.fullscreen) && - (toplevel->next.size.width != wsurface->width || - toplevel->next.size.height != wsurface->height)) { + (toplevel->next.size.width != geometry.width || + toplevel->next.size.height != geometry.height)) { struct weston_desktop_client *client = weston_desktop_surface_get_client(toplevel->base.desktop_surface); struct wl_resource *client_resource = From f981d69553f52ca50aaf864bf821bb022ab7da82 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 25 Jul 2017 16:17:36 -0500 Subject: [PATCH 0123/1642] logind: actually close fd in launcher_logind_close We still need to close fds passed to us - or we leak quite a few fds on VC switch. Regression, originally fixed in 8f5acc2f3a29c3831af4ddd6bed57f703c98dc77 and re-broken in commit 72dea06d7952e3ce8dd8057f7106186da4fa2678 but only for the logind launcher. Signed-off-by: Derek Foreman Reviewed-by: Pekka Paalanen --- libweston/launcher-logind.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c index f10a28317..a069bd4f2 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -216,6 +216,7 @@ launcher_logind_close(struct weston_launcher *launcher, int fd) int r; r = fstat(fd, &st); + close(fd); if (r < 0) { weston_log("logind: cannot fstat fd: %m\n"); return; From 6b65d8f12021d8fff0db37fd10b9a469769178b2 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 27 Jul 2017 13:44:32 +0300 Subject: [PATCH 0124/1642] compositor-drm: reset KMS state on VT-switch in Fix a regression with VT-switching away from Weston and then back causing drmModePageFlip() to fail with ENOSPC or EINVAL, leaving one or more outputs not updated. The regression appeared in 47224cc9312fef05c1a523ea0da0a1aae66f100d: compositor-drm: Delete drm_backend_set_modes Fix it by forcing a drmModeSetCrtc() on all outputs both initially created and after VT-switch in. Cc: Daniel Stone Signed-off-by: Pekka Paalanen v2: moved state_invalid=true from create_output_for_connector() to drm_output_enable() Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c51d24b6a..8e1e788fb 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -220,6 +220,8 @@ struct drm_output { enum dpms_enum dpms; struct backlight *backlight; + bool state_invalid; + int vblank_pending; int page_flip_pending; int destroy_pending; @@ -880,7 +882,7 @@ drm_output_repaint(struct weston_output *output_base, return -1; mode = container_of(output->base.current_mode, struct drm_mode, base); - if (!output->fb_current || + if (output->state_invalid || !output->fb_current || output->fb_current->stride != output->fb_pending->stride) { ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, output->fb_pending->fb_id, 0, 0, @@ -891,6 +893,8 @@ drm_output_repaint(struct weston_output *output_base, goto err_pageflip; } output_base->set_dpms(output_base, WESTON_DPMS_ON); + + output->state_invalid = false; } if (drmModePageFlip(backend->drm.fd, output->crtc_id, @@ -999,6 +1003,12 @@ drm_output_start_repaint_loop(struct weston_output *output_base) goto finish_frame; } + /* Need to smash all state in from scratch; current timings might not + * be what we want, page flip might not work, etc. + */ + if (output->state_invalid) + goto finish_frame; + /* Try to get current msc and timestamp via instant query */ vbl.request.type |= drm_waitvblank_pipe(output); ret = drmWaitVBlank(backend->drm.fd, &vbl); @@ -2675,6 +2685,8 @@ drm_output_enable(struct weston_output *base) output->connector->count_modes == 0 ? ", built-in" : ""); + output->state_invalid = true; + return 0; err_free: @@ -3130,6 +3142,10 @@ session_notify(struct wl_listener *listener, void *data) weston_log("activating session\n"); weston_compositor_wake(compositor); weston_compositor_damage_all(compositor); + + wl_list_for_each(output, &compositor->output_list, base.link) + output->state_invalid = true; + udev_input_enable(&b->input); } else { weston_log("deactivating session\n"); From 0e4e570caee9f857d13e35a9f4c78d52343872d7 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 25 Jul 2017 16:39:20 -0500 Subject: [PATCH 0125/1642] input: Stop leaking libinput event source on session deactivation This is easily noticed as a leaked fd on every VC switch. Signed-off-by: Derek Foreman Reviewed-by: Peter Hutterer Tested-by: Pekka Paalanen --- libweston/libinput-seat.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libweston/libinput-seat.c b/libweston/libinput-seat.c index 8cf5666b4..953f62059 100644 --- a/libweston/libinput-seat.c +++ b/libweston/libinput-seat.c @@ -134,6 +134,8 @@ udev_input_disable(struct udev_input *input) if (input->suspended) return; + wl_event_source_remove(input->libinput_source); + input->libinput_source = NULL; libinput_suspend(input->libinput); process_events(input); input->suspended = 1; @@ -337,7 +339,8 @@ udev_input_destroy(struct udev_input *input) { struct udev_seat *seat, *next; - wl_event_source_remove(input->libinput_source); + if (input->libinput_source) + wl_event_source_remove(input->libinput_source); wl_list_for_each_safe(seat, next, &input->compositor->seat_list, base.link) udev_seat_destroy(seat); libinput_unref(input->libinput); From e3715527b99dd7ccf30d06002555b8102a02d7d1 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 26 Jul 2017 14:35:58 -0500 Subject: [PATCH 0126/1642] libweston-desktop/xdg-shell: Properly properly handle ack_configure commit 749637a8a306588964885fe6b25fda6087a84ccd introduced this feature, but the break is outside of any conditional so only the first item in the list is ever tested. If a client skips a few configures and then acks the most recent it's still operating within spec, so the break should only occur when a match is found. This version also adds a break after we miss the target, as a tiny optimization (the list will be cleaned up on disconnect anyway), as it makes the code no more difficult to read or maintain. Signed-off-by: Derek Foreman Reviewed-by: Quentin Glidic --- libweston-desktop/xdg-shell-v5.c | 4 +++- libweston-desktop/xdg-shell-v6.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/libweston-desktop/xdg-shell-v5.c b/libweston-desktop/xdg-shell-v5.c index 77d004e1a..dd3608629 100644 --- a/libweston-desktop/xdg-shell-v5.c +++ b/libweston-desktop/xdg-shell-v5.c @@ -481,8 +481,10 @@ weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, } else if (configure->serial == serial) { wl_list_remove(&configure->link); found = true; + break; + } else { + break; } - break; } if (!found) { struct weston_desktop_client *client = diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index 1344dda02..10274e07d 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -1106,8 +1106,10 @@ weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, } else if (configure->serial == serial) { wl_list_remove(&configure->link); found = true; + break; + } else { + break; } - break; } if (!found) { struct weston_desktop_client *client = From c5f124169514e709699aedc97108837424af5682 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Fri, 28 Jul 2017 14:11:58 +0200 Subject: [PATCH 0127/1642] desktop-shell: Set surface resizing state during interactive resize xdg_shell requires this information to be shared with the client in order to conform with the specification. The code to forward this to the client by way of a configure() event is already in place and works fine, it was just never being used until now. Signed-off-by: Philipp Kerling Reviewed-by: Quentin Glidic --- desktop-shell/shell.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 4608cf2fa..415da1928 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -1635,9 +1635,12 @@ resize_grab_button(struct weston_pointer_grab *grab, struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; struct weston_pointer *pointer = grab->pointer; enum wl_pointer_button_state state = state_w; + struct weston_desktop_surface *desktop_surface = + resize->base.shsurf->desktop_surface; if (pointer->button_count == 0 && state == WL_POINTER_BUTTON_STATE_RELEASED) { + weston_desktop_surface_set_resizing(desktop_surface, false); shell_grab_end(&resize->base); free(grab); } @@ -1647,7 +1650,10 @@ static void resize_grab_cancel(struct weston_pointer_grab *grab) { struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; + struct weston_desktop_surface *desktop_surface = + resize->base.shsurf->desktop_surface; + weston_desktop_surface_set_resizing(desktop_surface, false); shell_grab_end(&resize->base); free(grab); } @@ -1731,6 +1737,7 @@ surface_resize(struct shell_surface *shsurf, resize->height = geometry.height; shsurf->resize_edges = edges; + weston_desktop_surface_set_resizing(shsurf->desktop_surface, true); shell_grab_start(&resize->base, &resize_grab_interface, shsurf, pointer, edges); From 18e77af67c0e5de775c6ecd479514d0419ddd8a1 Mon Sep 17 00:00:00 2001 From: Bryce Harrington Date: Tue, 1 Aug 2017 11:19:30 -0700 Subject: [PATCH 0128/1642] configure.ac: bump to version 2.99.93 for the RC1 release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index bb8ae1bda..61d7f37b6 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [2]) m4_define([weston_minor_version], [99]) -m4_define([weston_micro_version], [92]) +m4_define([weston_micro_version], [93]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [3]) From 3000a1c7c40bb4410146b6c68217e7028a1c5c97 Mon Sep 17 00:00:00 2001 From: Arnaud Vrac Date: Sat, 5 Aug 2017 13:59:01 +0200 Subject: [PATCH 0129/1642] libweston-desktop/xdg-shell-v5: initialize configure list Without this weston crashes when a client using xdg-shell-v5 is run. Signed-off-by: Arnaud Vrac Reviewed-by: Quentin Glidic --- libweston-desktop/xdg-shell-v5.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libweston-desktop/xdg-shell-v5.c b/libweston-desktop/xdg-shell-v5.c index dd3608629..ebe7940ed 100644 --- a/libweston-desktop/xdg-shell-v5.c +++ b/libweston-desktop/xdg-shell-v5.c @@ -745,6 +745,8 @@ weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, wl_event_loop_add_idle(loop, weston_desktop_xdg_surface_add_idle_callback, surface); + + wl_list_init(&surface->configure_list); } static void From 2a528187b2cc33414611a28d9baf1125f7cd4607 Mon Sep 17 00:00:00 2001 From: Bryce Harrington Date: Tue, 8 Aug 2017 11:46:14 -0700 Subject: [PATCH 0130/1642] configure.ac: bump to version 3.0.0 for the official release --- configure.ac | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 61d7f37b6..0961ca82a 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ -m4_define([weston_major_version], [2]) -m4_define([weston_minor_version], [99]) -m4_define([weston_micro_version], [93]) +m4_define([weston_major_version], [3]) +m4_define([weston_minor_version], [0]) +m4_define([weston_micro_version], [0]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [3]) From 1b3c05f558cb6bc78eb52d3437e1377bef57c5d0 Mon Sep 17 00:00:00 2001 From: Bryce Harrington Date: Tue, 8 Aug 2017 12:01:55 -0700 Subject: [PATCH 0131/1642] Reopen master for regular development A 3.0 branch has been established for stable release work. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 0961ca82a..8e8d747bb 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [3]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [0]) +m4_define([weston_micro_version], [90]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [3]) From 94e664d92be347c71b8fa2478100567d91fffa18 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 11 Aug 2017 10:44:57 +0300 Subject: [PATCH 0132/1642] configure: fix version number Fixes "configure: error: Weston version is greater than libweston." Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=102143 Signed-off-by: Pekka Paalanen --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8e8d747bb..f7ec2c941 100644 --- a/configure.ac +++ b/configure.ac @@ -5,7 +5,7 @@ m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [3]) m4_define([libweston_minor_version], [0]) -m4_define([libweston_patch_version], [0]) +m4_define([libweston_patch_version], [90]) AC_PREREQ([2.64]) AC_INIT([weston], From 7b5fe9b0762a64136a540d8c342c1b99f23508f2 Mon Sep 17 00:00:00 2001 From: Arnaud Vrac Date: Sat, 5 Aug 2017 13:58:58 +0200 Subject: [PATCH 0133/1642] gl-renderer: fix leak on dmabuf image destroy Signed-off-by: Arnaud Vrac Reviewed-by: Quentin Glidic --- libweston/gl-renderer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index da29b0725..5768f05a1 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -337,6 +337,7 @@ dmabuf_image_destroy(struct dmabuf_image *image) linux_dmabuf_buffer_set_user_data(image->dmabuf, NULL, NULL); wl_list_remove(&image->link); + free(image); } static const char * From d6d97dad03292c7b4f3d977189ef57188b51bdee Mon Sep 17 00:00:00 2001 From: Arnaud Vrac Date: Sat, 5 Aug 2017 13:58:59 +0200 Subject: [PATCH 0134/1642] configure.ac: fix linking when using compiler sanitizers The GCC address sanitizer overrides dlopen and dlclose, so the configure test does not detect libdl as a needed dependency for linking. It is still needed though, as dlsym is not exported by the sanitizer. The result is that linking fails in the end. Fix this by checking for dlsym instead of dlopen. This can be reproduced by configuring the build with: CFLAGS="-fsanitize=address -fsanitize=undefined" LDFLAGS="-fsanitize=address -fsanitize=undefined" Signed-off-by: Arnaud Vrac Reviewed-by: Quentin Glidic --- Makefile.am | 4 ++-- configure.ac | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile.am b/Makefile.am index e9679e685..58a5c5960 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,7 +74,7 @@ libweston_@LIBWESTON_MAJOR@_la_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON libweston_@LIBWESTON_MAJOR@_la_CFLAGS = $(AM_CFLAGS) \ $(COMPOSITOR_CFLAGS) $(EGL_CFLAGS) $(LIBUNWIND_CFLAGS) $(LIBDRM_CFLAGS) libweston_@LIBWESTON_MAJOR@_la_LIBADD = $(COMPOSITOR_LIBS) $(LIBUNWIND_LIBS) \ - $(DLOPEN_LIBS) -lm $(CLOCK_GETTIME_LIBS) \ + $(DL_LIBS) -lm $(CLOCK_GETTIME_LIBS) \ $(LIBINPUT_BACKEND_LIBS) libshared.la libweston_@LIBWESTON_MAJOR@_la_LDFLAGS = -version-info $(LT_VERSION_INFO) @@ -194,7 +194,7 @@ weston_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON \ weston_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBUNWIND_CFLAGS) weston_LDADD = libshared.la libweston-@LIBWESTON_MAJOR@.la \ $(COMPOSITOR_LIBS) $(LIBUNWIND_LIBS) \ - $(DLOPEN_LIBS) $(LIBINPUT_BACKEND_LIBS) \ + $(DL_LIBS) $(LIBINPUT_BACKEND_LIBS) \ $(CLOCK_GETRES_LIBS) \ -lm diff --git a/configure.ac b/configure.ac index f7ec2c941..1aca3d42c 100644 --- a/configure.ac +++ b/configure.ac @@ -86,7 +86,8 @@ AC_ARG_VAR([WESTON_SHELL_CLIENT], PKG_PROG_PKG_CONFIG() -WESTON_SEARCH_LIBS([DLOPEN], [dl], [dlopen]) +# Check for dlsym instead of dlopen because ASAN hijacks the latter +WESTON_SEARCH_LIBS([DL], [dl], [dlsym]) # In old glibc versions (< 2.17) clock_gettime() and clock_getres() are in librt WESTON_SEARCH_LIBS([CLOCK_GETTIME], [rt], [clock_gettime]) From 167bbb6d109c7c93be3b58232f8c5b8795df44f2 Mon Sep 17 00:00:00 2001 From: Arnaud Vrac Date: Sat, 5 Aug 2017 13:59:00 +0200 Subject: [PATCH 0135/1642] configure.ac: remove dependency on mtdev It's been unused since the legacy (non-libinput) input backends have been removed. Signed-off-by: Arnaud Vrac Reviewed-by: Quentin Glidic --- configure.ac | 4 ++-- libweston/libinput-device.c | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 1aca3d42c..0ea7b703b 100644 --- a/configure.ac +++ b/configure.ac @@ -199,7 +199,7 @@ AC_ARG_ENABLE(drm-compositor, [ --enable-drm-compositor],, AM_CONDITIONAL(ENABLE_DRM_COMPOSITOR, test x$enable_drm_compositor = xyes) if test x$enable_drm_compositor = xyes; then AC_DEFINE([BUILD_DRM_COMPOSITOR], [1], [Build the DRM compositor]) - PKG_CHECK_MODULES(DRM_COMPOSITOR, [libudev >= 136 libdrm >= 2.4.30 gbm mtdev >= 1.1.0]) + PKG_CHECK_MODULES(DRM_COMPOSITOR, [libudev >= 136 libdrm >= 2.4.30 gbm]) PKG_CHECK_MODULES(DRM_COMPOSITOR_GBM, [gbm >= 10.2], [AC_DEFINE([HAVE_GBM_FD_IMPORT], 1, [gbm supports dmabuf import])], [AC_MSG_WARN([gbm does not support dmabuf import, will omit that capability])]) @@ -242,7 +242,7 @@ AM_CONDITIONAL([ENABLE_FBDEV_COMPOSITOR], [test x$enable_fbdev_compositor = xyes]) AS_IF([test x$enable_fbdev_compositor = xyes], [ AC_DEFINE([BUILD_FBDEV_COMPOSITOR], [1], [Build the fbdev compositor]) - PKG_CHECK_MODULES([FBDEV_COMPOSITOR], [libudev >= 136 mtdev >= 1.1.0]) + PKG_CHECK_MODULES([FBDEV_COMPOSITOR], [libudev >= 136]) ]) AC_ARG_ENABLE([rdp-compositor], [ --enable-rdp-compositor],, diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index 5e7182da1..b1d269dbb 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -33,7 +33,6 @@ #include #include #include -#include #include #include From acd71fb0af82fb6065dd239865bd18bbbf1ceab4 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 15 Aug 2017 10:35:09 +0300 Subject: [PATCH 0136/1642] compositor-fbdev: fix start-up assertion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the failure to start with fbdev-backend: weston: /home/pq/git/weston/libweston/compositor.c:4733: weston_compositor_add_pending_output: Assertion `output->disable' failed. The disable hook was completely unimplemented, and the regression was caused by e952a01c3b42c7c870091e71488e9469bd897153 "libweston: move asserts to add_pending_output()". It used to work because Weston never tried to explicitly disable the fbdev output, but now it is hitting the assert. Fix it by tentatively implementing a disable hook. It has not been tested to work for explicit disabling, but it does solve the regression. Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=102208 Cc: bluescreen_avenger@verizon.net Signed-off-by: Pekka Paalanen Reviewed-by: Armin Krezović Tested-by: n3rdopolis --- libweston/compositor-fbdev.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index e80a5040d..6a3053856 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -472,6 +472,21 @@ fbdev_output_enable(struct weston_output *base) return -1; } +static int +fbdev_output_disable_handler(struct weston_output *base) +{ + if (!base->enabled) + return 0; + + /* Close the frame buffer. */ + fbdev_output_disable(base); + + if (base->renderer_state != NULL) + pixman_renderer_output_destroy(base); + + return 0; +} + static int fbdev_output_create(struct fbdev_backend *backend, const char *device) @@ -497,7 +512,7 @@ fbdev_output_create(struct fbdev_backend *backend, output->base.name = strdup("fbdev"); output->base.destroy = fbdev_output_destroy; - output->base.disable = NULL; + output->base.disable = fbdev_output_disable_handler; output->base.enable = fbdev_output_enable; weston_output_init(&output->base, backend->compositor); @@ -539,11 +554,7 @@ fbdev_output_destroy(struct weston_output *base) weston_log("Destroying fbdev output.\n"); - /* Close the frame buffer. */ - fbdev_output_disable(base); - - if (base->renderer_state != NULL) - pixman_renderer_output_destroy(base); + fbdev_output_disable_handler(base); /* Remove the output. */ weston_output_destroy(&output->base); From ec27271492cc3ad86db31436f5aa38032f99a16f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 5 Jun 2014 11:22:25 +0300 Subject: [PATCH 0137/1642] compositor-drm: Refactor sprite create/destroy into helpers This moves the single sprite creation code from create_sprites() into a new function. The readability clean-up is small, but my intention is to write an alternate version of create_sprites(), and sharing the single sprite creation code is useful. The removal code now actually removes the plane from the list. In doing this, the gymnastics required to exact the CRTC ID the plane was last on when making a disabling drmModeSetPlane call have been removed; specifying the CRTC is not necessary when disabling a plane. (The atomic API goes a step further, mandating it be zero.) [daniels: Genericised from drm_sprite to drm_plane, moving some of the logic back into create_sprites(), also symmetrical drm_plane_destroy.] Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Reviewed-by: Emre Ucan --- libweston/compositor-drm.c | 188 +++++++++++++++++++++++-------------- 1 file changed, 117 insertions(+), 71 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 8e1e788fb..decd586fc 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1826,6 +1826,123 @@ init_pixman(struct drm_backend *b) return pixman_renderer_init(b->compositor); } +/** + * Create a drm_plane for a hardware plane + * + * Creates one drm_plane structure for a hardware plane, and initialises its + * properties and formats. + * + * This function does not add the plane to the list of usable planes in Weston + * itself; the caller is responsible for this. + * + * Call drm_plane_destroy to clean up the plane. + * + * @param b DRM compositor backend + * @param kplane DRM plane to create + */ +static struct drm_plane * +drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) +{ + struct drm_plane *plane; + + plane = zalloc(sizeof(*plane) + ((sizeof(uint32_t)) * + kplane->count_formats)); + if (!plane) { + weston_log("%s: out of memory\n", __func__); + return NULL; + } + + plane->backend = b; + plane->possible_crtcs = kplane->possible_crtcs; + plane->plane_id = kplane->plane_id; + plane->count_formats = kplane->count_formats; + memcpy(plane->formats, kplane->formats, + kplane->count_formats * sizeof(kplane->formats[0])); + + weston_plane_init(&plane->base, b->compositor, 0, 0); + wl_list_insert(&b->sprite_list, &plane->link); + + return plane; +} + +/** + * Destroy one DRM plane + * + * Destroy a DRM plane, removing it from screen and releasing its retained + * buffers in the process. The counterpart to drm_plane_create. + * + * @param plane Plane to deallocate (will be freed) + */ +static void +drm_plane_destroy(struct drm_plane *plane) +{ + drmModeSetPlane(plane->backend->drm.fd, plane->plane_id, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0); + assert(!plane->fb_last); + assert(!plane->fb_pending); + drm_fb_unref(plane->fb_current); + weston_plane_release(&plane->base); + wl_list_remove(&plane->link); + free(plane); +} + +/** + * Initialise sprites (overlay planes) + * + * Walk the list of provided DRM planes, and add overlay planes. + * + * Call destroy_sprites to free these planes. + * + * @param b DRM compositor backend + */ +static void +create_sprites(struct drm_backend *b) +{ + drmModePlaneRes *kplane_res; + drmModePlane *kplane; + struct drm_plane *drm_plane; + uint32_t i; + + kplane_res = drmModeGetPlaneResources(b->drm.fd); + if (!kplane_res) { + weston_log("failed to get plane resources: %s\n", + strerror(errno)); + return; + } + + for (i = 0; i < kplane_res->count_planes; i++) { + kplane = drmModeGetPlane(b->drm.fd, kplane_res->planes[i]); + if (!kplane) + continue; + + drm_plane = drm_plane_create(b, kplane); + drmModeFreePlane(kplane); + if (!drm_plane) + continue; + + weston_compositor_stack_plane(b->compositor, &drm_plane->base, + &b->compositor->primary_plane); + } + + drmModeFreePlaneResources(kplane_res); +} + +/** + * Clean up sprites (overlay planes) + * + * The counterpart to create_sprites. + * + * @param b DRM compositor backend + */ +static void +destroy_sprites(struct drm_backend *b) +{ + struct drm_plane *plane, *next; + + wl_list_for_each_safe(plane, next, &b->sprite_list, link) + drm_plane_destroy(plane); +} + /** * Add a mode to output's mode list * @@ -2862,77 +2979,6 @@ create_output_for_connector(struct drm_backend *b, return -1; } -static void -create_sprites(struct drm_backend *b) -{ - struct drm_plane *plane; - drmModePlaneRes *kplane_res; - drmModePlane *kplane; - uint32_t i; - - kplane_res = drmModeGetPlaneResources(b->drm.fd); - if (!kplane_res) { - weston_log("failed to get plane resources: %s\n", - strerror(errno)); - return; - } - - for (i = 0; i < kplane_res->count_planes; i++) { - kplane = drmModeGetPlane(b->drm.fd, kplane_res->planes[i]); - if (!kplane) - continue; - - plane = zalloc(sizeof(*plane) + ((sizeof(uint32_t)) * - kplane->count_formats)); - if (!plane) { - weston_log("%s: out of memory\n", - __func__); - drmModeFreePlane(kplane); - continue; - } - - plane->possible_crtcs = kplane->possible_crtcs; - plane->plane_id = kplane->plane_id; - plane->fb_last = NULL; - plane->fb_current = NULL; - plane->fb_pending = NULL; - plane->backend = b; - plane->count_formats = kplane->count_formats; - memcpy(plane->formats, kplane->formats, - kplane->count_formats * sizeof(kplane->formats[0])); - drmModeFreePlane(kplane); - weston_plane_init(&plane->base, b->compositor, 0, 0); - weston_compositor_stack_plane(b->compositor, &plane->base, - &b->compositor->primary_plane); - - wl_list_insert(&b->sprite_list, &plane->link); - } - - drmModeFreePlaneResources(kplane_res); -} - -static void -destroy_sprites(struct drm_backend *backend) -{ - struct drm_plane *plane, *next; - struct drm_output *output; - - output = container_of(backend->compositor->output_list.next, - struct drm_output, base.link); - - wl_list_for_each_safe(plane, next, &backend->sprite_list, link) { - drmModeSetPlane(backend->drm.fd, - plane->plane_id, - output->crtc_id, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0); - assert(!plane->fb_last); - assert(!plane->fb_pending); - drm_fb_unref(plane->fb_current); - weston_plane_release(&plane->base); - free(plane); - } -} - static int create_outputs(struct drm_backend *b, struct udev_device *drm_device) { From 4202e50fc840101f606587e94182d84014b1fbc3 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 24 Oct 2016 13:26:42 +0100 Subject: [PATCH 0138/1642] compositor-drm: Rename fb_plane to scanout_plane All planes being displayed have a framebuffer. What makes 'fb_plane' special is that it's being displayed as the primary plane by KMS. Previous patchsets renamed this to 'primary_plane' to match the KMS terminology, namely the CRTC's base plane, which is controlled by drmModeSetCrtc in the legacy API, and identified by PLANE_TYPE == "Primary" in the universal-plane API. However, Weston uses 'primary_plane' internally to refer to the case where client content is _not_ directly displayed on a plane, but composited via the renderer, with the result of the compositing then shown. Rename to 'scanout_plane' as our least-ambiguous name, and document it a bit. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Reviewed-by: Emre Ucan --- libweston/compositor-drm.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index decd586fc..b707a01fc 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -235,7 +235,8 @@ struct drm_output { struct gbm_surface *gbm_surface; uint32_t gbm_format; - struct weston_plane fb_plane; + /* Plane for a fullscreen direct scanout view */ + struct weston_plane scanout_plane; /* The last framebuffer submitted to the kernel for this CRTC. */ struct drm_fb *fb_current; @@ -721,7 +722,7 @@ drm_output_prepare_scanout_view(struct drm_output *output, drm_fb_set_buffer(output->fb_pending, buffer); - return &output->fb_plane; + return &output->scanout_plane; } static struct drm_fb * @@ -2784,10 +2785,10 @@ drm_output_enable(struct weston_output *base) weston_plane_init(&output->cursor_plane, b->compositor, INT32_MIN, INT32_MIN); - weston_plane_init(&output->fb_plane, b->compositor, 0, 0); + weston_plane_init(&output->scanout_plane, b->compositor, 0, 0); weston_compositor_stack_plane(b->compositor, &output->cursor_plane, NULL); - weston_compositor_stack_plane(b->compositor, &output->fb_plane, + weston_compositor_stack_plane(b->compositor, &output->scanout_plane, &b->compositor->primary_plane); weston_log("Output %s, (connector %d, crtc %d)\n", @@ -2830,7 +2831,7 @@ drm_output_deinit(struct weston_output *base) else drm_output_fini_egl(output); - weston_plane_release(&output->fb_plane); + weston_plane_release(&output->scanout_plane); weston_plane_release(&output->cursor_plane); drmModeFreeProperty(output->dpms_prop); From 02cf4662312662056a849baa5844e9a0368f35a6 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 3 Mar 2017 16:19:39 +0000 Subject: [PATCH 0139/1642] compositor-drm: Add DRM property cache Add a cache for DRM property IDs and values, and use it for the two connector properties we currently update: DPMS and EDID. As DRM property ID values are not stable, we need to do a name -> ID lookup each run in order to discover the property IDs and enum values to use for those properties. Rather than open-coding this, add a property cache which we can use across multiple different object types. This patch takes substantial work from the universal planes support originally authored by Pekka Paalanen, though it has been heavily reworked. Signed-off-by: Daniel Stone Co-authored-by: Pekka Paalanen Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 337 +++++++++++++++++++++++++++++++------ 1 file changed, 285 insertions(+), 52 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index b707a01fc..83518a9fc 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -79,6 +79,44 @@ #define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64 #endif +/** + * List of properties attached to a DRM connector + */ +enum wdrm_connector_property { + WDRM_CONNECTOR_EDID = 0, + WDRM_CONNECTOR_DPMS, + WDRM_CONNECTOR__COUNT +}; + +/** + * Represents the values of an enum-type KMS property + */ +struct drm_property_enum_info { + const char *name; /**< name as string (static, not freed) */ + bool valid; /**< true if value is supported; ignore if false */ + uint64_t value; /**< raw value */ +}; + +/** + * Holds information on a DRM property, including its ID and the enum + * values it holds. + * + * DRM properties are allocated dynamically, and maintained as DRM objects + * within the normal object ID space; they thus do not have a stable ID + * to refer to. This includes enum values, which must be referred to by + * integer values, but these are not stable. + * + * drm_property_info allows a cache to be maintained where Weston can use + * enum values internally to refer to properties, with the mapping to DRM + * ID values being maintained internally. + */ +struct drm_property_info { + const char *name; /**< name as string (static, not freed) */ + uint32_t prop_id; /**< KMS property object ID */ + unsigned int num_enum_values; /**< number of enum values */ + struct drm_property_enum_info *enum_values; /**< array of enum values */ +}; + struct drm_backend { struct weston_backend base; struct weston_compositor *compositor; @@ -215,7 +253,9 @@ struct drm_output { uint32_t connector_id; drmModeCrtcPtr original_crtc; struct drm_edid edid; - drmModePropertyPtr dpms_prop; + + /* Holds the properties for the connector */ + struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT]; enum dpms_enum dpms; struct backlight *backlight; @@ -313,6 +353,208 @@ drm_output_pageflip_timer_create(struct drm_output *output) return 0; } +/** + * Get the current value of a KMS property + * + * Given a drmModeObjectGetProperties return, as well as the drm_property_info + * for the target property, return the current value of that property, + * with an optional default. If the property is a KMS enum type, the return + * value will be translated into the appropriate internal enum. + * + * If the property is not present, the default value will be returned. + * + * @param info Internal structure for property to look up + * @param props Raw KMS properties for the target object + * @param def Value to return if property is not found + */ +static uint64_t +drm_property_get_value(struct drm_property_info *info, + drmModeObjectPropertiesPtr props, + uint64_t def) +{ + unsigned int i; + + if (info->prop_id == 0) + return def; + + for (i = 0; i < props->count_props; i++) { + unsigned int j; + + if (props->props[i] != info->prop_id) + continue; + + /* Simple (non-enum) types can return the value directly */ + if (info->num_enum_values == 0) + return props->prop_values[i]; + + /* Map from raw value to enum value */ + for (j = 0; j < info->num_enum_values; j++) { + if (!info->enum_values[j].valid) + continue; + if (info->enum_values[j].value != props->prop_values[i]) + continue; + + return j; + } + + /* We don't have a mapping for this enum; return default. */ + break; + } + + return def; +} + +/** + * Cache DRM property values + * + * Update a per-object array of drm_property_info structures, given the + * DRM properties of the object. + * + * Call this every time an object newly appears (note that only connectors + * can be hotplugged), the first time it is seen, or when its status changes + * in a way which invalidates the potential property values (currently, the + * only case for this is connector hotplug). + * + * This updates the property IDs and enum values within the drm_property_info + * array. + * + * DRM property enum values are dynamic at runtime; the user must query the + * property to find out the desired runtime value for a requested string + * name. Using the 'type' field on planes as an example, there is no single + * hardcoded constant for primary plane types; instead, the property must be + * queried at runtime to find the value associated with the string "Primary". + * + * This helper queries and caches the enum values, to allow us to use a set + * of compile-time-constant enums portably across various implementations. + * The values given in enum_names are searched for, and stored in the + * same-indexed field of the map array. + * + * @param b DRM backend object + * @param src DRM property info array to source from + * @param info DRM property info array to copy into + * @param num_infos Number of entries in the source array + * @param props DRM object properties for the object + */ +static void +drm_property_info_populate(struct drm_backend *b, + const struct drm_property_info *src, + struct drm_property_info *info, + unsigned int num_infos, + drmModeObjectProperties *props) +{ + drmModePropertyRes *prop; + unsigned i, j; + + for (i = 0; i < num_infos; i++) { + unsigned int j; + + info[i].name = src[i].name; + info[i].prop_id = 0; + info[i].num_enum_values = src[i].num_enum_values; + + if (src[i].num_enum_values == 0) + continue; + + info[i].enum_values = + malloc(src[i].num_enum_values * + sizeof(*info[i].enum_values)); + assert(info[i].enum_values); + for (j = 0; j < info[i].num_enum_values; j++) { + info[i].enum_values[j].name = src[i].enum_values[j].name; + info[i].enum_values[j].valid = false; + } + } + + for (i = 0; i < props->count_props; i++) { + unsigned int k; + + prop = drmModeGetProperty(b->drm.fd, props->props[i]); + if (!prop) + continue; + + for (j = 0; j < num_infos; j++) { + if (!strcmp(prop->name, info[j].name)) + break; + } + + /* We don't know/care about this property. */ + if (j == num_infos) { +#ifdef DEBUG + weston_log("DRM debug: unrecognized property %u '%s'\n", + prop->prop_id, prop->name); +#endif + drmModeFreeProperty(prop); + continue; + } + + if (info[j].num_enum_values == 0 && + (prop->flags & DRM_MODE_PROP_ENUM)) { + weston_log("DRM: expected property %s to not be an" + " enum, but it is; ignoring\n", prop->name); + drmModeFreeProperty(prop); + continue; + } + + info[j].prop_id = props->props[i]; + + if (info[j].num_enum_values == 0) { + drmModeFreeProperty(prop); + continue; + } + + if (!(prop->flags & DRM_MODE_PROP_ENUM)) { + weston_log("DRM: expected property %s to be an enum," + " but it is not; ignoring\n", prop->name); + drmModeFreeProperty(prop); + info[j].prop_id = 0; + continue; + } + + for (k = 0; k < info[j].num_enum_values; k++) { + int l; + + for (l = 0; l < prop->count_enums; l++) { + if (!strcmp(prop->enums[l].name, + info[j].enum_values[k].name)) + break; + } + + if (l == prop->count_enums) + continue; + + info[j].enum_values[k].valid = true; + info[j].enum_values[k].value = prop->enums[l].value; + } + + drmModeFreeProperty(prop); + } + +#ifdef DEBUG + for (i = 0; i < num_infos; i++) { + if (info[i].prop_id == 0) + weston_log("DRM warning: property '%s' missing\n", + info[i].name); + } +#endif +} + +/** + * Free DRM property information + * + * Frees all memory associated with a DRM property info array. + * + * @param info DRM property info array + * @param num_props Number of entries in array to free + */ +static void +drm_property_info_free(struct drm_property_info *info, int num_props) +{ + int i; + + for (i = 0; i < num_props; i++) + free(info[i].enum_values); +} + static void drm_output_set_cursor(struct drm_output *output); @@ -2046,39 +2288,21 @@ drm_set_backlight(struct weston_output *output_base, uint32_t value) backlight_set_brightness(output->backlight, new_brightness); } -static drmModePropertyPtr -drm_get_prop(int fd, drmModeConnectorPtr connector, const char *name) -{ - drmModePropertyPtr props; - int i; - - for (i = 0; i < connector->count_props; i++) { - props = drmModeGetProperty(fd, connector->props[i]); - if (!props) - continue; - - if (!strcmp(props->name, name)) - return props; - - drmModeFreeProperty(props); - } - - return NULL; -} - static void drm_set_dpms(struct weston_output *output_base, enum dpms_enum level) { struct drm_output *output = to_drm_output(output_base); struct weston_compositor *ec = output_base->compositor; struct drm_backend *b = to_drm_backend(ec); + struct drm_property_info *prop = + &output->props_conn[WDRM_CONNECTOR_DPMS]; int ret; - if (!output->dpms_prop) + if (!prop->prop_id) return; ret = drmModeConnectorSetProperty(b->drm.fd, output->connector_id, - output->dpms_prop->prop_id, level); + prop->prop_id, level); if (ret) { weston_log("DRM: DPMS: failed property set for %s\n", output->base.name); @@ -2433,26 +2657,20 @@ edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length) } static void -find_and_parse_output_edid(struct drm_backend *b, - struct drm_output *output, - drmModeConnector *connector) +find_and_parse_output_edid(struct drm_backend *b, struct drm_output *output, + drmModeObjectPropertiesPtr props) { drmModePropertyBlobPtr edid_blob = NULL; - drmModePropertyPtr property; - int i; + uint32_t blob_id; int rc; - for (i = 0; i < connector->count_props && !edid_blob; i++) { - property = drmModeGetProperty(b->drm.fd, connector->props[i]); - if (!property) - continue; - if ((property->flags & DRM_MODE_PROP_BLOB) && - !strcmp(property->name, "EDID")) { - edid_blob = drmModeGetPropertyBlob(b->drm.fd, - connector->prop_values[i]); - } - drmModeFreeProperty(property); - } + blob_id = + drm_property_get_value(&output->props_conn[WDRM_CONNECTOR_EDID], + props, 0); + if (!blob_id) + return; + + edid_blob = drmModeGetPropertyBlob(b->drm.fd, blob_id); if (!edid_blob) return; @@ -2743,19 +2961,17 @@ drm_output_enable(struct weston_output *base) struct drm_backend *b = to_drm_backend(base->compositor); struct weston_mode *m; - output->dpms_prop = drm_get_prop(b->drm.fd, output->connector, "DPMS"); - if (b->pageflip_timeout) drm_output_pageflip_timer_create(output); if (b->use_pixman) { if (drm_output_init_pixman(output, b) < 0) { weston_log("Failed to init output pixman state\n"); - goto err_free; + goto err; } } else if (drm_output_init_egl(output, b) < 0) { weston_log("Failed to init output gl state\n"); - goto err_free; + goto err; } if (output->backlight) { @@ -2778,7 +2994,6 @@ drm_output_enable(struct weston_output *base) output->base.subpixel = drm_subpixel_to_wayland(output->connector->subpixel); - find_and_parse_output_edid(b, output, output->connector); if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) output->base.connection_internal = true; @@ -2807,9 +3022,7 @@ drm_output_enable(struct weston_output *base) return 0; -err_free: - drmModeFreeProperty(output->dpms_prop); - +err: return -1; } @@ -2834,8 +3047,6 @@ drm_output_deinit(struct weston_output *base) weston_plane_release(&output->scanout_plane); weston_plane_release(&output->cursor_plane); - drmModeFreeProperty(output->dpms_prop); - /* Turn off hardware cursor */ drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); } @@ -2876,6 +3087,8 @@ drm_output_destroy(struct weston_output *base) weston_output_destroy(&output->base); + drm_property_info_free(output->props_conn, WDRM_CONNECTOR__COUNT); + drmModeFreeConnector(output->connector); if (output->backlight) @@ -2927,9 +3140,15 @@ create_output_for_connector(struct drm_backend *b, struct udev_device *drm_device) { struct drm_output *output; + drmModeObjectPropertiesPtr props; struct drm_mode *drm_mode; int i; + static const struct drm_property_info connector_props[] = { + [WDRM_CONNECTOR_EDID] = { .name = "EDID" }, + [WDRM_CONNECTOR_DPMS] = { .name = "DPMS" }, + }; + i = find_crtc_for_connector(b, resources, connector); if (i < 0) { weston_log("No usable crtc/encoder pair for connector.\n"); @@ -2958,6 +3177,17 @@ create_output_for_connector(struct drm_backend *b, output->destroy_pending = 0; output->disable_pending = 0; + props = drmModeObjectGetProperties(b->drm.fd, connector->connector_id, + DRM_MODE_OBJECT_CONNECTOR); + if (!props) { + weston_log("failed to get connector properties\n"); + goto err; + } + drm_property_info_populate(b, connector_props, output->props_conn, + WDRM_CONNECTOR__COUNT, props); + find_and_parse_output_edid(b, output, props); + drmModeFreeObjectProperties(props); + weston_output_init(&output->base, b->compositor); wl_list_init(&output->base.mode_list); @@ -2999,6 +3229,8 @@ create_outputs(struct drm_backend *b, struct udev_device *drm_device) b->max_height = resources->max_height; for (i = 0; i < resources->count_connectors; i++) { + int ret; + connector = drmModeGetConnector(b->drm.fd, resources->connectors[i]); if (connector == NULL) @@ -3007,9 +3239,10 @@ create_outputs(struct drm_backend *b, struct udev_device *drm_device) if (connector->connection == DRM_MODE_CONNECTED && (b->connector == 0 || connector->connector_id == b->connector)) { - if (create_output_for_connector(b, resources, - connector, drm_device) < 0) - continue; + ret = create_output_for_connector(b, resources, + connector, drm_device); + if (ret < 0) + weston_log("failed to create new connector\n"); } else { drmModeFreeConnector(connector); } From c5de57f74280a24fb7cfd72d59864799cd69de38 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 20 May 2015 23:01:44 +0100 Subject: [PATCH 0140/1642] compositor-drm: Add universal plane awareness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add awareness of, rather than support for, universal planes. Activate the client cap when we start if possible, and if this is activated, studiously ignore non-overlay planes. For now. Signed-off-by: Daniel Stone Co-authored-by: Pekka Paalanen Co-authored-by: Louis-Francis Ratté-Boulianne Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 83518a9fc..11d818a80 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -67,6 +67,10 @@ #define DRM_CAP_TIMESTAMP_MONOTONIC 0x6 #endif +#ifndef DRM_CLIENT_CAP_UNIVERSAL_PLANES +#define DRM_CLIENT_CAP_UNIVERSAL_PLANES 2 +#endif + #ifndef DRM_CAP_CURSOR_WIDTH #define DRM_CAP_CURSOR_WIDTH 0x8 #endif @@ -79,6 +83,24 @@ #define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64 #endif +/** + * List of properties attached to DRM planes + */ +enum wdrm_plane_property { + WDRM_PLANE_TYPE = 0, + WDRM_PLANE__COUNT +}; + +/** + * Possible values for the WDRM_PLANE_TYPE property. + */ +enum wdrm_plane_type { + WDRM_PLANE_TYPE_PRIMARY = 0, + WDRM_PLANE_TYPE_CURSOR, + WDRM_PLANE_TYPE_OVERLAY, + WDRM_PLANE_TYPE__COUNT +}; + /** * List of properties attached to a DRM connector */ @@ -150,6 +172,8 @@ struct drm_backend { int cursors_are_broken; + bool universal_planes; + int use_pixman; struct udev_input input; @@ -223,10 +247,14 @@ struct drm_plane { struct drm_output *output; struct drm_backend *backend; + enum wdrm_plane_type type; + uint32_t possible_crtcs; uint32_t plane_id; uint32_t count_formats; + struct drm_property_info props[WDRM_PLANE__COUNT]; + /* The last framebuffer submitted to the kernel for this plane. */ struct drm_fb *fb_current; /* The previously-submitted framebuffer, where the hardware has not @@ -1974,6 +2002,11 @@ init_kms_caps(struct drm_backend *b) else b->cursor_height = 64; + ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + b->universal_planes = (ret == 0); + weston_log("DRM: %s universal planes\n", + b->universal_planes ? "supports" : "does not support"); + return 0; } @@ -2087,6 +2120,26 @@ static struct drm_plane * drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) { struct drm_plane *plane; + drmModeObjectProperties *props; + + static struct drm_property_enum_info plane_type_enums[] = { + [WDRM_PLANE_TYPE_PRIMARY] = { + .name = "Primary", + }, + [WDRM_PLANE_TYPE_OVERLAY] = { + .name = "Overlay", + }, + [WDRM_PLANE_TYPE_CURSOR] = { + .name = "Cursor", + }, + }; + static const struct drm_property_info plane_props[] = { + [WDRM_PLANE_TYPE] = { + .name = "type", + .enum_values = plane_type_enums, + .num_enum_values = WDRM_PLANE_TYPE__COUNT, + }, + }; plane = zalloc(sizeof(*plane) + ((sizeof(uint32_t)) * kplane->count_formats)); @@ -2102,6 +2155,21 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) memcpy(plane->formats, kplane->formats, kplane->count_formats * sizeof(kplane->formats[0])); + props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id, + DRM_MODE_OBJECT_PLANE); + if (!props) { + weston_log("couldn't get plane properties\n"); + free(plane); + return NULL; + } + drm_property_info_populate(b, plane_props, plane->props, + WDRM_PLANE__COUNT, props); + plane->type = + drm_property_get_value(&plane->props[WDRM_PLANE_TYPE], + props, + WDRM_PLANE_TYPE_OVERLAY); + drmModeFreeObjectProperties(props); + weston_plane_init(&plane->base, b->compositor, 0, 0); wl_list_insert(&b->sprite_list, &plane->link); @@ -2123,6 +2191,7 @@ drm_plane_destroy(struct drm_plane *plane) 0, 0, 0, 0, 0, 0, 0, 0); assert(!plane->fb_last); assert(!plane->fb_pending); + drm_property_info_free(plane->props, WDRM_PLANE__COUNT); drm_fb_unref(plane->fb_current); weston_plane_release(&plane->base); wl_list_remove(&plane->link); @@ -2163,6 +2232,12 @@ create_sprites(struct drm_backend *b) if (!drm_plane) continue; + /* Ignore non-overlay planes for now. */ + if (drm_plane->type != WDRM_PLANE_TYPE_OVERLAY) { + drm_plane_destroy(drm_plane); + continue; + } + weston_compositor_stack_plane(b->compositor, &drm_plane->base, &b->compositor->primary_plane); } From 085d2b9a01e1cbc384c81082568ddf21f3c9093c Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 21 May 2015 00:00:57 +0100 Subject: [PATCH 0141/1642] compositor-drm: Track all plane types Retain drm_plane tracking objects for all actual DRM planes when using universal planes, not just overlay planes. Rename uses of 'sprite' to 'plane' to make it clear that it can now be any kind of plane, not just an overlay/sprite. These are currently unused. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 74 ++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 11d818a80..133ef58cd 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -166,7 +166,7 @@ struct drm_backend { int min_height, max_height; int no_addfb2; - struct wl_list sprite_list; + struct wl_list plane_list; int sprites_are_broken; int sprites_hidden; @@ -1129,7 +1129,7 @@ drm_output_repaint(struct weston_output *output_base, struct drm_output *output = to_drm_output(output_base); struct drm_backend *backend = to_drm_backend(output->base.compositor); - struct drm_plane *s; + struct drm_plane *p; struct drm_mode *mode; int ret = 0; @@ -1191,28 +1191,29 @@ drm_output_repaint(struct weston_output *output_base, /* * Now, update all the sprite surfaces */ - wl_list_for_each(s, &backend->sprite_list, link) { + wl_list_for_each(p, &backend->plane_list, link) { uint32_t flags = 0, fb_id = 0; drmVBlank vbl = { .request.type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT, .request.sequence = 1, }; - /* XXX: Set output much earlier, so we don't attempt to place - * planes on entirely the wrong output. */ - if ((!s->fb_current && !s->fb_pending) || - !drm_plane_crtc_supported(output, s)) + if (p->type != WDRM_PLANE_TYPE_OVERLAY) + continue; + + if ((!p->fb_current && !p->fb_pending) || + !drm_plane_crtc_supported(output, p)) continue; - if (s->fb_pending && !backend->sprites_hidden) - fb_id = s->fb_pending->fb_id; + if (p->fb_pending && !backend->sprites_hidden) + fb_id = p->fb_pending->fb_id; - ret = drmModeSetPlane(backend->drm.fd, s->plane_id, + ret = drmModeSetPlane(backend->drm.fd, p->plane_id, output->crtc_id, fb_id, flags, - s->dest_x, s->dest_y, - s->dest_w, s->dest_h, - s->src_x, s->src_y, - s->src_w, s->src_h); + p->dest_x, p->dest_y, + p->dest_w, p->dest_h, + p->src_x, p->src_y, + p->src_w, p->src_h); if (ret) weston_log("setplane failed: %d: %s\n", ret, strerror(errno)); @@ -1223,17 +1224,17 @@ drm_output_repaint(struct weston_output *output_base, * Queue a vblank signal so we know when the surface * becomes active on the display or has been replaced. */ - vbl.request.signal = (unsigned long)s; + vbl.request.signal = (unsigned long) p; ret = drmWaitVBlank(backend->drm.fd, &vbl); if (ret) { weston_log("vblank event request failed: %d: %s\n", ret, strerror(errno)); } - s->output = output; - s->fb_last = s->fb_current; - s->fb_current = s->fb_pending; - s->fb_pending = NULL; + p->output = output; + p->fb_last = p->fb_current; + p->fb_current = p->fb_pending; + p->fb_pending = NULL; output->vblank_pending++; } @@ -1489,7 +1490,10 @@ drm_output_prepare_overlay_view(struct drm_output *output, if (ev->alpha != 1.0f) return NULL; - wl_list_for_each(p, &b->sprite_list, link) { + wl_list_for_each(p, &b->plane_list, link) { + if (p->type != WDRM_PLANE_TYPE_OVERLAY) + continue; + if (!drm_plane_crtc_supported(output, p)) continue; @@ -2171,7 +2175,7 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) drmModeFreeObjectProperties(props); weston_plane_init(&plane->base, b->compositor, 0, 0); - wl_list_insert(&b->sprite_list, &plane->link); + wl_list_insert(&b->plane_list, &plane->link); return plane; } @@ -2232,14 +2236,10 @@ create_sprites(struct drm_backend *b) if (!drm_plane) continue; - /* Ignore non-overlay planes for now. */ - if (drm_plane->type != WDRM_PLANE_TYPE_OVERLAY) { - drm_plane_destroy(drm_plane); - continue; - } - - weston_compositor_stack_plane(b->compositor, &drm_plane->base, - &b->compositor->primary_plane); + if (drm_plane->type == WDRM_PLANE_TYPE_OVERLAY) + weston_compositor_stack_plane(b->compositor, + &drm_plane->base, + &b->compositor->primary_plane); } drmModeFreePlaneResources(kplane_res); @@ -2257,7 +2257,7 @@ destroy_sprites(struct drm_backend *b) { struct drm_plane *plane, *next; - wl_list_for_each_safe(plane, next, &b->sprite_list, link) + wl_list_for_each_safe(plane, next, &b->plane_list, link) drm_plane_destroy(plane); } @@ -3490,7 +3490,7 @@ session_notify(struct wl_listener *listener, void *data) { struct weston_compositor *compositor = data; struct drm_backend *b = to_drm_backend(compositor); - struct drm_plane *sprite; + struct drm_plane *plane; struct drm_output *output; if (compositor->session_active) { @@ -3524,12 +3524,16 @@ session_notify(struct wl_listener *listener, void *data) output = container_of(compositor->output_list.next, struct drm_output, base.link); - wl_list_for_each(sprite, &b->sprite_list, link) + wl_list_for_each(plane, &b->plane_list, link) { + if (plane->type != WDRM_PLANE_TYPE_OVERLAY) + continue; + drmModeSetPlane(b->drm.fd, - sprite->plane_id, + plane->plane_id, output->crtc_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - }; + } + } } /** @@ -3950,7 +3954,7 @@ drm_backend_create(struct weston_compositor *compositor, weston_setup_vt_switch_bindings(compositor); - wl_list_init(&b->sprite_list); + wl_list_init(&b->plane_list); create_sprites(b); if (udev_input_init(&b->input, From eedf84c68f57c5d9d171520d8984e5ef293035f7 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 10 Feb 2017 18:06:04 +0000 Subject: [PATCH 0142/1642] compositor-drm: Introduce drm_pending_state structure drm_pending_state is currently skeletal, but will be used to retain data through begin_repaint -> assign_planes -> repaint -> repaint_flush. The flush and cancel functions are currently identical, only freeing the state, but they will be used for different purposes in later patches. Specifically, the intent is to apply any pending output changes (through PageFlip/SetCrtc, or the atomic ioctls) in flush, and only free the state in cancel. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 103 +++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 133ef58cd..0259c630a 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -170,6 +170,8 @@ struct drm_backend { int sprites_are_broken; int sprites_hidden; + void *repaint_data; + int cursors_are_broken; bool universal_planes; @@ -224,6 +226,16 @@ struct drm_edid { char serial_number[13]; }; +/** + * Pending state holds one or more drm_output_state structures, collected from + * performing repaint. This pending state is transient, and only lives between + * beginning a repaint group and flushing the results: after flush, each + * output state will complete and be retired separately. + */ +struct drm_pending_state { + struct drm_backend *backend; +}; + /** * A plane represents one buffer, positioned within a CRTC, and stacked * relative to other planes on the same CRTC. @@ -896,6 +908,45 @@ drm_view_transform_supported(struct weston_view *ev) (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); } +/** + * Allocate a new drm_pending_state + * + * Allocate a new, empty, 'pending state' structure to be used across a + * repaint cycle or similar. + * + * @param backend DRM backend + * @returns Newly-allocated pending state structure + */ +static struct drm_pending_state * +drm_pending_state_alloc(struct drm_backend *backend) +{ + struct drm_pending_state *ret; + + ret = calloc(1, sizeof(*ret)); + if (!ret) + return NULL; + + ret->backend = backend; + + return ret; +} + +/** + * Free a drm_pending_state structure + * + * Frees a pending_state structure. + * + * @param pending_state Pending state structure to free + */ +static void +drm_pending_state_free(struct drm_pending_state *pending_state) +{ + if (!pending_state) + return; + + free(pending_state); +} + static uint32_t drm_output_check_scanout_format(struct drm_output *output, struct weston_surface *es, struct gbm_bo *bo) @@ -1417,6 +1468,55 @@ page_flip_handler(int fd, unsigned int frame, } } +/** + * Begin a new repaint cycle + * + * Called by the core compositor at the beginning of a repaint cycle. + */ +static void * +drm_repaint_begin(struct weston_compositor *compositor) +{ + struct drm_backend *b = to_drm_backend(compositor); + struct drm_pending_state *ret; + + ret = drm_pending_state_alloc(b); + b->repaint_data = ret; + + return ret; +} + +/** + * Flush a repaint set + * + * Called by the core compositor when a repaint cycle has been completed + * and should be flushed. + */ +static void +drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) +{ + struct drm_backend *b = to_drm_backend(compositor); + struct drm_pending_state *pending_state = repaint_data; + + drm_pending_state_free(pending_state); + b->repaint_data = NULL; +} + +/** + * Cancel a repaint set + * + * Called by the core compositor when a repaint has finished, so the data + * held across the repaint cycle should be discarded. + */ +static void +drm_repaint_cancel(struct weston_compositor *compositor, void *repaint_data) +{ + struct drm_backend *b = to_drm_backend(compositor); + struct drm_pending_state *pending_state = repaint_data; + + drm_pending_state_free(pending_state); + b->repaint_data = NULL; +} + static uint32_t drm_output_check_plane_format(struct drm_plane *p, struct weston_view *ev, struct gbm_bo *bo) @@ -3951,6 +4051,9 @@ drm_backend_create(struct weston_compositor *compositor, b->base.destroy = drm_destroy; b->base.restore = drm_restore; + b->base.repaint_begin = drm_repaint_begin; + b->base.repaint_flush = drm_repaint_flush; + b->base.repaint_cancel = drm_repaint_cancel; weston_setup_vt_switch_bindings(compositor); From 6914c80210df7edde257a3cda96d006bcd363579 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Mon, 28 Aug 2017 20:12:01 +0200 Subject: [PATCH 0143/1642] libweston-desktop/xdg-shell-v6: Ensure first configure is sent The old code for scheduling configure events on idle looked like: if (configure_scheduled) { if (this_event_is_the_same) { remove_timer(); return; } } If we queued one new event (either changed, or the client had never received any configure event), followed immediately by one event which was the same as the first, we would delete the scheduled send of the first event. Fix this by treating unconfigured surface as never the same. Signed-off-by: Quentin Glidic Reviewed-by: Daniel Stone --- libweston-desktop/xdg-shell-v6.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index 10274e07d..d82a507fa 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -303,8 +303,7 @@ static const struct zxdg_positioner_v6_interface weston_desktop_xdg_positioner_i }; static void -weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface, - bool force); +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface); static void weston_desktop_xdg_toplevel_ensure_added(struct weston_desktop_xdg_toplevel *toplevel) @@ -314,7 +313,7 @@ weston_desktop_xdg_toplevel_ensure_added(struct weston_desktop_xdg_toplevel *top weston_desktop_api_surface_added(toplevel->base.desktop, toplevel->base.desktop_surface); - weston_desktop_xdg_surface_schedule_configure(&toplevel->base, true); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); toplevel->added = true; } @@ -584,7 +583,7 @@ weston_desktop_xdg_toplevel_set_maximized(struct weston_desktop_surface *dsurfac struct weston_desktop_xdg_toplevel *toplevel = user_data; toplevel->pending.state.maximized = maximized; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); } static void @@ -594,7 +593,7 @@ weston_desktop_xdg_toplevel_set_fullscreen(struct weston_desktop_surface *dsurfa struct weston_desktop_xdg_toplevel *toplevel = user_data; toplevel->pending.state.fullscreen = fullscreen; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); } static void @@ -604,7 +603,7 @@ weston_desktop_xdg_toplevel_set_resizing(struct weston_desktop_surface *dsurface struct weston_desktop_xdg_toplevel *toplevel = user_data; toplevel->pending.state.resizing = resizing; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); } static void @@ -614,7 +613,7 @@ weston_desktop_xdg_toplevel_set_activated(struct weston_desktop_surface *dsurfac struct weston_desktop_xdg_toplevel *toplevel = user_data; toplevel->pending.state.activated = activated; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); } static void @@ -627,7 +626,7 @@ weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, toplevel->pending.size.width = width; toplevel->pending.size.height = height; - weston_desktop_xdg_surface_schedule_configure(&toplevel->base, false); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); } static void @@ -807,7 +806,7 @@ static void weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) { if (!popup->committed) - weston_desktop_xdg_surface_schedule_configure(&popup->base, true); + weston_desktop_xdg_surface_schedule_configure(&popup->base); popup->committed = true; weston_desktop_xdg_popup_update_position(popup->base.desktop_surface, popup); @@ -904,6 +903,9 @@ weston_desktop_xdg_surface_send_configure(void *user_data) static bool weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) { + if (!toplevel->base.configured) + return false; + if (toplevel->pending.state.activated != toplevel->current.state.activated) return false; if (toplevel->pending.state.fullscreen != toplevel->current.state.fullscreen) @@ -925,20 +927,18 @@ weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *to } static void -weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface, - bool force) +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface) { struct wl_display *display = weston_desktop_get_display(surface->desktop); struct wl_event_loop *loop = wl_display_get_event_loop(display); - bool pending_same = !force; + bool pending_same = false; switch (surface->role) { case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: assert(0 && "not reached"); break; case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: - pending_same = pending_same && - weston_desktop_xdg_toplevel_state_compare((struct weston_desktop_xdg_toplevel *) surface); + pending_same = weston_desktop_xdg_toplevel_state_compare((struct weston_desktop_xdg_toplevel *) surface); break; case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: break; From 156bd065c9f5532bfd40d0391e6cb98e802c3e14 Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Wed, 26 Jul 2017 14:22:49 +0200 Subject: [PATCH 0144/1642] ivi-shell: Added tests for screen-remove-layer API Two cases are tested: success and fail case of the screen-remove-layer API. Signed-off-by: Michael Teyfel Reviewed-by: Pekka Paalanen --- tests/ivi_layout-internal-test.c | 69 ++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/ivi_layout-internal-test.c b/tests/ivi_layout-internal-test.c index e56eb1250..f7f7c8056 100644 --- a/tests/ivi_layout-internal-test.c +++ b/tests/ivi_layout-internal-test.c @@ -622,6 +622,73 @@ test_screen_add_layers(struct test_context *ctx) #undef LAYER_NUM } +static void +test_screen_remove_layer(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + struct weston_output *output; + struct ivi_layout_layer **array; + int32_t length = 0; + + if (wl_list_empty(&ctx->compositor->output_list)) + return; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + output = wl_container_of(ctx->compositor->output_list.next, output, link); + + iassert(lyt->screen_add_layer(output, ivilayer) == IVI_SUCCEEDED); + lyt->commit_changes(); + + iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + iassert(length == 1); + iassert(array[0] == ivilayer); + + iassert(lyt->screen_remove_layer(output, ivilayer) == IVI_SUCCEEDED); + lyt->commit_changes(); + + if (length > 0) + free(array); + + array = NULL; + + iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + iassert(length == 0); + iassert(array == NULL); + + lyt->layer_destroy(ivilayer); +} + +static void +test_screen_bad_remove_layer(struct test_context *ctx) +{ + const struct ivi_layout_interface *lyt = ctx->layout_interface; + struct ivi_layout_layer *ivilayer; + struct weston_output *output; + + if (wl_list_empty(&ctx->compositor->output_list)) + return; + + ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); + iassert(ivilayer != NULL); + + output = wl_container_of(ctx->compositor->output_list.next, output, link); + + iassert(lyt->screen_remove_layer(NULL, ivilayer) == IVI_FAILED); + lyt->commit_changes(); + + iassert(lyt->screen_remove_layer(output, NULL) == IVI_FAILED); + lyt->commit_changes(); + + iassert(lyt->screen_remove_layer(NULL, NULL) == IVI_FAILED); + lyt->commit_changes(); + + lyt->layer_destroy(ivilayer); +} + + static void test_commit_changes_after_render_order_set_layer_destroy( struct test_context *ctx) @@ -906,6 +973,8 @@ run_internal_tests(void *data) test_screen_render_order(ctx); test_screen_bad_render_order(ctx); test_screen_add_layers(ctx); + test_screen_remove_layer(ctx); + test_screen_bad_remove_layer(ctx); test_commit_changes_after_render_order_set_layer_destroy(ctx); test_layer_properties_changed_notification(ctx); From 38ea2d2254c606e69238b641b422816c79cb1f44 Mon Sep 17 00:00:00 2001 From: "Ucan, Emre (ADITG/SW1)" Date: Wed, 8 Mar 2017 15:43:18 +0000 Subject: [PATCH 0145/1642] compositor-drm: remove connector option Remove the option, because it is hard to use. Drm connector ids are hard to reach for users, and they can change when kernel or device tree is modified. Signed-off-by: Emre Ucan Reviewed-by: Daniel Stone [Pekka: bump WESTON_DRM_BACKEND_CONFIG_VERSION] Signed-off-by: Pekka Paalanen --- compositor/main.c | 2 -- libweston/compositor-drm.c | 12 +----------- libweston/compositor-drm.h | 8 +------- man/weston-drm.man | 5 ----- 4 files changed, 2 insertions(+), 25 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index f8a60e976..0615d87e5 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -562,7 +562,6 @@ usage(int error_code) #if defined(BUILD_DRM_COMPOSITOR) fprintf(stderr, "Options for drm-backend.so:\n\n" - " --connector=ID\tBring up only this connector\n" " --seat=SEAT\t\tThe seat that weston should run on\n" " --tty=TTY\t\tThe tty to use\n" " --use-pixman\t\tUse the pixman (CPU) renderer\n" @@ -1222,7 +1221,6 @@ load_drm_backend(struct weston_compositor *c, wet->drm_use_current_mode = false; const struct weston_option options[] = { - { WESTON_OPTION_INTEGER, "connector", 0, &config.connector }, { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, { WESTON_OPTION_BOOLEAN, "current-mode", 0, &wet->drm_use_current_mode }, diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 0259c630a..1a9613897 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -183,7 +183,6 @@ struct drm_backend { int32_t cursor_width; int32_t cursor_height; - uint32_t connector; uint32_t pageflip_timeout; }; @@ -3411,9 +3410,7 @@ create_outputs(struct drm_backend *b, struct udev_device *drm_device) if (connector == NULL) continue; - if (connector->connection == DRM_MODE_CONNECTED && - (b->connector == 0 || - connector->connector_id == b->connector)) { + if (connector->connection == DRM_MODE_CONNECTED) { ret = create_output_for_connector(b, resources, connector, drm_device); if (ret < 0) @@ -3466,11 +3463,6 @@ update_outputs(struct drm_backend *b, struct udev_device *drm_device) continue; } - if (b->connector && (b->connector != connector_id)) { - drmModeFreeConnector(connector); - continue; - } - connected[i] = connector_id; if (drm_output_find_by_connector(b, connector_id)) { @@ -4067,8 +4059,6 @@ drm_backend_create(struct weston_compositor *compositor, goto err_sprite; } - b->connector = config->connector; - if (create_outputs(b, drm_device) < 0) { weston_log("failed to create output for %s\n", b->drm.filename); goto err_udev_input; diff --git a/libweston/compositor-drm.h b/libweston/compositor-drm.h index 087071971..8181492fb 100644 --- a/libweston/compositor-drm.h +++ b/libweston/compositor-drm.h @@ -35,7 +35,7 @@ extern "C" { #endif -#define WESTON_DRM_BACKEND_CONFIG_VERSION 2 +#define WESTON_DRM_BACKEND_CONFIG_VERSION 3 struct libinput_device; @@ -98,12 +98,6 @@ weston_drm_output_get_api(struct weston_compositor *compositor) struct weston_drm_backend_config { struct weston_backend_config base; - /** The connector id of the output to be initialized. - * - * A value of 0 will enable all available outputs. - */ - int connector; - /** The tty to be used. Set to 0 to use the current tty. */ int tty; diff --git a/man/weston-drm.man b/man/weston-drm.man index 35d62ae69..d7fd56142 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -87,11 +87,6 @@ When the DRM backend is loaded, .B weston will understand the following additional command line options. .TP -\fB\-\-connector\fR=\fIconnectorid\fR -Use the connector with id number -.I connectorid -as the only initial output. -.TP .B \-\-current\-mode By default, use the current video mode of all outputs, instead of switching to the monitor preferred mode. From c05ee89ab0c08eab4c3a839be6181513cc4852a4 Mon Sep 17 00:00:00 2001 From: Eric Engestrom Date: Mon, 11 Sep 2017 13:52:28 +0100 Subject: [PATCH 0146/1642] clients/nested: fix boolean test weston_check_egl_extension() returns a bool, not a pointer. Fixes: ce5b614c80b4dfe8e899 "clients/nested: use weston_check_egl_extension over strstr" Cc: Emil Velikov Signed-off-by: Eric Engestrom Reviewed-by: Emil Velikov --- clients/nested.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/nested.c b/clients/nested.c index 173076a68..e9070e9bb 100644 --- a/clients/nested.c +++ b/clients/nested.c @@ -748,7 +748,7 @@ nested_init_compositor(struct nested *nested) nested->egl_display = display_get_egl_display(nested->display); extensions = eglQueryString(nested->egl_display, EGL_EXTENSIONS); - if (weston_check_egl_extension(extensions, "EGL_WL_bind_wayland_display") == NULL) { + if (!weston_check_egl_extension(extensions, "EGL_WL_bind_wayland_display")) { fprintf(stderr, "no EGL_WL_bind_wayland_display extension\n"); return -1; } From e80bdcdad2082b6710f9a47c355f8ace20ed788b Mon Sep 17 00:00:00 2001 From: Fabien Lahoudere Date: Thu, 7 Sep 2017 15:11:58 +0200 Subject: [PATCH 0147/1642] calibrator: Make mouse button optional When calibrating touchscreen with weston-calibrator, you can use the mouse to click on the cross which is recorded as a touch event. This event is used to compute the final calibration of the touchscreen which results in invalid touchscreen calibration and broken touchscreen behaviour. In order to avoid to use the mouse in weston-calibrator, we disable mouse operation by default and add a parameter "--enable-mouse" to enable it. Signed-off-by: Fabien Lahoudere Reviewed-by: Pekka Paalanen --- clients/calibrator.c | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/clients/calibrator.c b/clients/calibrator.c index 04c1cfcbc..778c23cfa 100644 --- a/clients/calibrator.c +++ b/clients/calibrator.c @@ -24,12 +24,14 @@ #include "config.h" #include +#include #include #include #include #include #include #include +#include #include #include @@ -218,7 +220,7 @@ redraw_handler(struct widget *widget, void *data) } static struct calibrator * -calibrator_create(struct display *display) +calibrator_create(struct display *display, bool enable_button) { struct calibrator *calibrator; @@ -233,7 +235,8 @@ calibrator_create(struct display *display) calibrator->current_test = ARRAY_LENGTH(test_ratios) - 1; - widget_set_button_handler(calibrator->widget, button_handler); + if (enable_button) + widget_set_button_handler(calibrator->widget, button_handler); widget_set_touch_down_handler(calibrator->widget, touch_handler); widget_set_redraw_handler(calibrator->widget, redraw_handler); @@ -250,13 +253,40 @@ calibrator_destroy(struct calibrator *calibrator) free(calibrator); } +static void +help(const char *name) +{ + fprintf(stderr, "Usage: %s [args...]\n", name); + fprintf(stderr, " -m, --enable-mouse Enable mouse for testing the touchscreen\n"); + fprintf(stderr, " -h, --help Display this help message\n"); +} int main(int argc, char *argv[]) { struct display *display; struct calibrator *calibrator; - + int c; + bool enable_mouse = 0; + struct option opts[] = { + { "enable-mouse", no_argument, NULL, 'm' }, + { "help", no_argument, NULL, 'h' }, + { 0, 0, NULL, 0 } + }; + + while ((c = getopt_long(argc, argv, "mh", opts, NULL)) != -1) { + switch (c) { + case 'm': + enable_mouse = 1; + break; + case 'h': + help(argv[0]); + exit(EXIT_FAILURE); + default: + break; + } + } + display = display_create(&argc, argv); if (display == NULL) { @@ -264,7 +294,7 @@ main(int argc, char *argv[]) return -1; } - calibrator = calibrator_create(display); + calibrator = calibrator_create(display, enable_mouse); if (!calibrator) return -1; From ab7c0b6afd76448d5d84e9f81830b3531d7cff4f Mon Sep 17 00:00:00 2001 From: Ian Ray Date: Mon, 18 Sep 2017 15:22:00 +0300 Subject: [PATCH 0148/1642] desktop-shell: use binding_modifier for zoom This patch changes the zoom binding to use the modifier configured in weston.ini instead of hardcoding MODIFIER_SUPER. Signed-off-by: Ian Ray Reviewed-by: Pekka Paalanen --- desktop-shell/shell.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 415da1928..3872686a5 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -4863,9 +4863,6 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, MODIFIER_SUPER | MODIFIER_ALT, surface_opacity_binding, NULL); - weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, - MODIFIER_SUPER, zoom_axis_binding, - NULL); weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSDOWN, 0, backlight_binding, ec); weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSUP, 0, @@ -4880,6 +4877,10 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) if (!mod) return; + weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, + mod, zoom_axis_binding, + NULL); + weston_compositor_add_key_binding(ec, KEY_PAGEUP, mod, zoom_key_binding, NULL); weston_compositor_add_key_binding(ec, KEY_PAGEDOWN, mod, From 13404c40161c6ca432c5fa24432c991bb7850a7a Mon Sep 17 00:00:00 2001 From: Ian Ray Date: Mon, 18 Sep 2017 15:22:01 +0300 Subject: [PATCH 0149/1642] desktop-shell: disable opacity binding when modifier is none This patch disables the opacity binding when the modifier is configured to `none' in weston.ini, and thus supports use cases where one does not want to have this binding. Signed-off-by: Ian Ray Reviewed-by: Pekka Paalanen --- desktop-shell/shell.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 3872686a5..1f99efe3f 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -4860,9 +4860,6 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) weston_compositor_add_touch_binding(ec, 0, touch_to_activate_binding, shell); - weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, - MODIFIER_SUPER | MODIFIER_ALT, - surface_opacity_binding, NULL); weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSDOWN, 0, backlight_binding, ec); weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSUP, 0, @@ -4877,6 +4874,12 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) if (!mod) return; + /* This binding is not configurable, but is only enabled if there is a + * valid binding modifier. */ + weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, + MODIFIER_SUPER | MODIFIER_ALT, + surface_opacity_binding, NULL); + weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, mod, zoom_axis_binding, NULL); From 1f21ef1df77fc105502cdad3b72f14bbf6ef4b9d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 3 Apr 2017 13:33:26 +0300 Subject: [PATCH 0150/1642] compositor-drm: use asprintf in make_connector_name() Gets rid of the constant size char array. While here, document the function. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray --- libweston/compositor-drm.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 1a9613897..578ff9d60 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2508,11 +2508,20 @@ static const char * const connector_type_names[] = { #endif }; +/** Create a name given a DRM connector + * + * \param con The DRM connector whose type and id form the name. + * \return A newly allocate string, or NULL on error. Must be free()'d + * after use. + * + * The name does not identify the DRM display device. + */ static char * make_connector_name(const drmModeConnector *con) { - char name[32]; + char *name; const char *type_name = NULL; + int ret; if (con->connector_type < ARRAY_LENGTH(connector_type_names)) type_name = connector_type_names[con->connector_type]; @@ -2520,9 +2529,11 @@ make_connector_name(const drmModeConnector *con) if (!type_name) type_name = "UNNAMED"; - snprintf(name, sizeof name, "%s-%d", type_name, con->connector_type_id); + ret = asprintf(&name, "%s-%d", type_name, con->connector_type_id); + if (ret < 0) + return NULL; - return strdup(name); + return name; } static int From ffa42ffdaa006f73758cf3e9c6f9c80aff2ecbff Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 11 Aug 2017 15:55:32 +0300 Subject: [PATCH 0151/1642] compositor-wayland: use asprintf for output title Simplifies the code, and makes moving weston_output_init() into wayland_output_create_common() a little easier. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray --- libweston/compositor-wayland.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 77a736896..b144d6e5d 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1245,7 +1245,7 @@ static struct wayland_output * wayland_output_create_common(const char *name) { struct wayland_output *output; - size_t len; + char *title; /* name can't be NULL. */ assert(name); @@ -1256,21 +1256,16 @@ wayland_output_create_common(const char *name) return NULL; } - output->base.destroy = wayland_output_destroy; - output->base.disable = wayland_output_disable; - output->base.enable = wayland_output_enable; - output->base.name = strdup(name); - - /* setup output name/title. */ - len = strlen(WINDOW_TITLE " - ") + strlen(name) + 1; - output->title = zalloc(len); - if (!output->title) { - free(output->base.name); + if (asprintf(&title, "%s - %s", WINDOW_TITLE, name) < 0) { free(output); return NULL; } + output->title = title; - snprintf(output->title, len, WINDOW_TITLE " - %s", name); + output->base.destroy = wayland_output_destroy; + output->base.disable = wayland_output_disable; + output->base.enable = wayland_output_enable; + output->base.name = strdup(name); return output; } From 82b8ddf9d9640b23237a1799b2912641082ad2d1 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Sep 2017 11:58:00 +0300 Subject: [PATCH 0152/1642] compositor-fbdev: MAP_FAILED is not NULL Fix the assumption that MAP_FAILED would be equal to NULL. It is not. Set 'fb' explicitly to NULL on mmap failure so that comparisons to NULL would produce the expected result. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- libweston/compositor-fbdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 6a3053856..b1cc5dcae 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -383,6 +383,7 @@ fbdev_frame_buffer_map(struct fbdev_output *output, int fd) if (output->fb == MAP_FAILED) { weston_log("Failed to mmap frame buffer: %s\n", strerror(errno)); + output->fb = NULL; goto out_close; } From 693872391fbed3c3ad15d5b8b57a3f3e4b51b1ca Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 14 Sep 2017 14:41:48 +0300 Subject: [PATCH 0153/1642] compositor-fbdev: remove unused field 'depth' Not referenced anywhere ever, has been there since the introduction of fbdev-backend. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray --- libweston/compositor-fbdev.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index b1cc5dcae..10229bebb 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -91,7 +91,6 @@ struct fbdev_output { /* pixman details. */ pixman_image_t *hw_surface; - uint8_t depth; }; static const char default_seat[] = "seat0"; From 513f9a441287dd111e1de7e75a2dfebd5f48c80a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Sep 2017 16:49:02 +0300 Subject: [PATCH 0154/1642] compositor-fbdev: unref udev on backend destruction Fixes a small memory leak, spotted with Valgrind. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray --- libweston/compositor-fbdev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 10229bebb..62ffa0ba5 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -665,6 +665,8 @@ fbdev_backend_destroy(struct weston_compositor *base) /* Chain up. */ weston_launcher_destroy(base->launcher); + udev_unref(backend->udev); + free(backend); } From 2a0c6c331e79b8f2926d4e3529421f8f5f592a0b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Sep 2017 16:48:01 +0300 Subject: [PATCH 0155/1642] compositor-drm: unref udev on backend destruction Fixes a small memory leak, spotted with Valgrind. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray --- libweston/compositor-drm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 578ff9d60..5e87cb416 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3582,6 +3582,8 @@ drm_destroy(struct weston_compositor *ec) if (b->gbm) gbm_device_destroy(b->gbm); + udev_unref(b->udev); + weston_launcher_destroy(ec->launcher); close(b->drm.fd); From 3052bc7e6d9e05449908a22105a7b3c5143838e7 Mon Sep 17 00:00:00 2001 From: Matt Hoosier Date: Tue, 26 Sep 2017 08:09:40 -0500 Subject: [PATCH 0156/1642] compositor: fix starvation of wl_buffer::release This change replaces a queued emission of buffer-release events (which is prone to starvation) with a regular event emission. This means that client programs no longer need to secretly install surface frame listeners just to guarantee that they get correctly notified of buffer lifecycle events. v2: More information about the historical reasons why this change hadn't happened yet, and the consensus to finally move ahead with it can be found at the discussion terminating in this message: https://lists.freedesktop.org/archives/wayland-devel/2017-September/035147.html Signed-off-by: Matt Hoosier Reviewed-by: Daniel Stone Reviewed-by: Pekka Paalanen Reviewed-by: Derek Foreman --- clients/nested.c | 3 +-- libweston/compositor.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/clients/nested.c b/clients/nested.c index e9070e9bb..e2bdf6849 100644 --- a/clients/nested.c +++ b/clients/nested.c @@ -228,8 +228,7 @@ nested_buffer_reference(struct nested_buffer_reference *ref, ref->buffer->busy_count--; if (ref->buffer->busy_count == 0) { assert(wl_resource_get_client(ref->buffer->resource)); - wl_resource_queue_event(ref->buffer->resource, - WL_BUFFER_RELEASE); + wl_buffer_send_release(ref->buffer->resource); } wl_list_remove(&ref->destroy_listener.link); } diff --git a/libweston/compositor.c b/libweston/compositor.c index 813b66340..878cd5351 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -1954,8 +1954,7 @@ weston_buffer_reference(struct weston_buffer_reference *ref, ref->buffer->busy_count--; if (ref->buffer->busy_count == 0) { assert(wl_resource_get_client(ref->buffer->resource)); - wl_resource_queue_event(ref->buffer->resource, - WL_BUFFER_RELEASE); + wl_buffer_send_release(ref->buffer->resource); } wl_list_remove(&ref->destroy_listener.link); } From 75d38ef1ed100bbb2883e242f4c87fbab3dd0617 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 27 Sep 2017 15:09:13 +0300 Subject: [PATCH 0157/1642] timeline: Add GPU timestamp timepoint argument The purpose of this argument is to hold timestamp information about events that occurred on the GPU. This argument allows us to include GPU timestamps in timepoints such as the beginning and end of frame rendering. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- libweston/timeline.c | 12 ++++++++++++ libweston/timeline.h | 2 ++ 2 files changed, 14 insertions(+) diff --git a/libweston/timeline.c b/libweston/timeline.c index cf82428e8..8234c27cd 100644 --- a/libweston/timeline.c +++ b/libweston/timeline.c @@ -232,12 +232,24 @@ emit_vblank_timestamp(struct timeline_emit_context *ctx, void *obj) return 1; } +static int +emit_gpu_timestamp(struct timeline_emit_context *ctx, void *obj) +{ + struct timespec *ts = obj; + + fprintf(ctx->cur, "\"gpu\":[%" PRId64 ", %ld]", + (int64_t)ts->tv_sec, ts->tv_nsec); + + return 1; +} + typedef int (*type_func)(struct timeline_emit_context *ctx, void *obj); static const type_func type_dispatch[] = { [TLT_OUTPUT] = emit_weston_output, [TLT_SURFACE] = emit_weston_surface, [TLT_VBLANK] = emit_vblank_timestamp, + [TLT_GPU] = emit_gpu_timestamp, }; WL_EXPORT void diff --git a/libweston/timeline.h b/libweston/timeline.h index b10a81577..9599d8138 100644 --- a/libweston/timeline.h +++ b/libweston/timeline.h @@ -42,6 +42,7 @@ enum timeline_type { TLT_OUTPUT, TLT_SURFACE, TLT_VBLANK, + TLT_GPU, }; #define TYPEVERIFY(type, arg) ({ \ @@ -53,6 +54,7 @@ enum timeline_type { #define TLP_OUTPUT(o) TLT_OUTPUT, TYPEVERIFY(struct weston_output *, (o)) #define TLP_SURFACE(s) TLT_SURFACE, TYPEVERIFY(struct weston_surface *, (s)) #define TLP_VBLANK(t) TLT_VBLANK, TYPEVERIFY(const struct timespec *, (t)) +#define TLP_GPU(t) TLT_GPU, TYPEVERIFY(const struct timespec *, (t)) #define TL_POINT(...) do { \ if (weston_timeline_enabled_) \ From 7192b17f3e266bfab501e7c3987aaa71815c6ecf Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 27 Sep 2017 15:09:14 +0300 Subject: [PATCH 0158/1642] gl-renderer: Add support for fence sync extensions Check for the EGL_KHR_fence_sync and EGL_ANDROID_native_fence_sync extensions and get pointers to required extension functions. These extensions allow us to acquire GPU timestamp information asynchronously, and are required by the upcoming work to add rendering begin/end timepoints to the weston timeline. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- libweston/gl-renderer.c | 16 ++++++++++++++++ shared/weston-egl-ext.h | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 5768f05a1..c2e88a65b 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -231,6 +231,11 @@ struct gl_renderer { int has_dmabuf_import_modifiers; PFNEGLQUERYDMABUFFORMATSEXTPROC query_dmabuf_formats; PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dmabuf_modifiers; + + int has_native_fence_sync; + PFNEGLCREATESYNCKHRPROC create_sync; + PFNEGLDESTROYSYNCKHRPROC destroy_sync; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd; }; static PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = NULL; @@ -3028,6 +3033,17 @@ gl_renderer_setup_egl_extensions(struct weston_compositor *ec) if (weston_check_egl_extension(extensions, "GL_EXT_texture_rg")) gr->has_gl_texture_rg = 1; + if (weston_check_egl_extension(extensions, "EGL_KHR_fence_sync") && + weston_check_egl_extension(extensions, "EGL_ANDROID_native_fence_sync")) { + gr->create_sync = + (void *) eglGetProcAddress("eglCreateSyncKHR"); + gr->destroy_sync = + (void *) eglGetProcAddress("eglDestroySyncKHR"); + gr->dup_native_fence_fd = + (void *) eglGetProcAddress("eglDupNativeFenceFDANDROID"); + gr->has_native_fence_sync = 1; + } + renderer_setup_egl_client_extensions(gr); return 0; diff --git a/shared/weston-egl-ext.h b/shared/weston-egl-ext.h index ffea438b2..8aacbd01d 100644 --- a/shared/weston-egl-ext.h +++ b/shared/weston-egl-ext.h @@ -176,6 +176,22 @@ typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC) (EGLD #define EGL_PLATFORM_X11_KHR 0x31D5 #endif +#ifndef EGL_KHR_cl_event2 +#define EGL_KHR_cl_event2 1 +typedef void *EGLSyncKHR; +#endif /* EGL_KHR_cl_event2 */ + +#ifndef EGL_KHR_fence_sync +#define EGL_KHR_fence_sync 1 +typedef EGLSyncKHR (EGLAPIENTRYP PFNEGLCREATESYNCKHRPROC) (EGLDisplay dpy, EGLenum type, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync); +#endif /* EGL_KHR_fence_sync */ + +#ifndef EGL_ANDROID_native_fence_sync +#define EGL_ANDROID_native_fence_sync 1 +typedef EGLint (EGLAPIENTRYP PFNEGLDUPNATIVEFENCEFDANDROIDPROC) (EGLDisplay dpy, EGLSyncKHR sync); +#endif /* EGL_ANDROID_native_fence_sync */ + #else /* ENABLE_EGL */ /* EGL platform definition are keept to allow compositor-xx.c to build */ From e39eb8f896f9640cf731715ccba64bad26556f4d Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 27 Sep 2017 15:09:15 +0300 Subject: [PATCH 0159/1642] libweston: Add check and fallback for linux/sync_file.h The sync file functionality is required by the upcoming GPU render timeline work, but it's only available in relatively new linux kernel versions (4.7 and above). This commit provides a "sanitized" version of the required sync file definitions. On systems that don't have the sync file header (due to having an older kernel), we will be able to fall back to our own definitions when building. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- Makefile.am | 1 + configure.ac | 1 + libweston/weston-sync-file.h | 30 ++++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 libweston/weston-sync-file.h diff --git a/Makefile.am b/Makefile.am index 58a5c5960..9d99c6949 100644 --- a/Makefile.am +++ b/Makefile.am @@ -334,6 +334,7 @@ gl_renderer_la_SOURCES = \ libweston/gl-renderer.c \ libweston/vertex-clipping.c \ libweston/vertex-clipping.h \ + libweston/weston-sync-file.h \ shared/helpers.h endif diff --git a/configure.ac b/configure.ac index 0ea7b703b..c287fac64 100644 --- a/configure.ac +++ b/configure.ac @@ -142,6 +142,7 @@ if test x$enable_egl = xyes; then AC_DEFINE([ENABLE_EGL], [1], [Build Weston with EGL support]) PKG_CHECK_MODULES(EGL, [egl glesv2]) PKG_CHECK_MODULES([EGL_TESTS], [egl glesv2 wayland-client wayland-egl]) + AC_CHECK_HEADERS([linux/sync_file.h]) fi COMPOSITOR_MODULES="$COMPOSITOR_MODULES xkbcommon >= 0.3.0" diff --git a/libweston/weston-sync-file.h b/libweston/weston-sync-file.h new file mode 100644 index 000000000..114e0b6eb --- /dev/null +++ b/libweston/weston-sync-file.h @@ -0,0 +1,30 @@ +/* Sync file Linux kernel UAPI */ + +#ifndef WESTON_SYNC_FILE_H +#define WESTON_SYNC_FILE_H + +#include +#include + +struct sync_fence_info { + char obj_name[32]; + char driver_name[32]; + __s32 status; + __u32 flags; + __u64 timestamp_ns; +}; + +struct sync_file_info { + char name[32]; + __s32 status; + __u32 flags; + __u32 num_fences; + __u32 pad; + + __u64 sync_fence_info; +}; + +#define SYNC_IOC_MAGIC '>' +#define SYNC_IOC_FILE_INFO _IOWR(SYNC_IOC_MAGIC, 4, struct sync_file_info) + +#endif From df0e4b965fc9d0e765416fda28cac3af137bd410 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 27 Sep 2017 15:09:16 +0300 Subject: [PATCH 0160/1642] gl-renderer: Emit GPU rendering begin and end timeline timepoints Use EGL fence sync objects to emit timepoints for the beginning and the end of rendering on the GPU. The timepoints are emitted asynchronously using the sync file fds associated with the fence sync objects. The sync file fds are acquired using the facilities provided by the EGL_ANDROID_native_fence_sync extension. The asynchronous timepoint submissions are stored in a list in gl_output_state until they are executed, and any pending submissions that remain at output destruction time are cleaned up. If timelining is inactive or the required EGL extensions are not present, then GPU timepoint processing and emission are skipped. Note that the GPU timestamps returned by sync files are in the CLOCK_MONOTONIC clock domain, and are thus compatible with the timeline timestamps (which also are in the CLOCK_MONOTONIC domain). Signed-off-by: Alexandros Frantzis Reviewed-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/gl-renderer.c | 163 ++++++++++++++++++++++++++++++++++++++++ shared/weston-egl-ext.h | 12 +++ 2 files changed, 175 insertions(+) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index c2e88a65b..5749aa716 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -39,6 +39,16 @@ #include #include #include +#include +#include + +#ifdef HAVE_LINUX_SYNC_FILE_H +#include +#else +#include "weston-sync-file.h" +#endif + +#include "timeline.h" #include "gl-renderer.h" #include "vertex-clipping.h" @@ -47,6 +57,7 @@ #include "shared/helpers.h" #include "shared/platform.h" +#include "shared/timespec-util.h" #include "weston-egl-ext.h" struct gl_shader { @@ -87,6 +98,9 @@ struct gl_output_state { enum gl_border_status border_status; struct weston_matrix output_matrix; + + /* struct timeline_render_point::link */ + struct wl_list timeline_render_point_list; }; enum buffer_type { @@ -238,6 +252,20 @@ struct gl_renderer { PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd; }; +enum timeline_render_point_type { + TIMELINE_RENDER_POINT_TYPE_BEGIN, + TIMELINE_RENDER_POINT_TYPE_END +}; + +struct timeline_render_point { + struct wl_list link; /* gl_output_state::timeline_render_point_list */ + + enum timeline_render_point_type type; + int fd; + struct weston_output *output; + struct wl_event_source *event_source; +}; + static PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = NULL; static inline const char * @@ -274,6 +302,115 @@ get_renderer(struct weston_compositor *ec) return (struct gl_renderer *)ec->renderer; } +static int +linux_sync_file_read_timestamp(int fd, uint64_t *ts) +{ + struct sync_file_info file_info = { { 0 } }; + struct sync_fence_info fence_info = { { 0 } }; + + assert(ts != NULL); + + file_info.sync_fence_info = (uint64_t)(uintptr_t)&fence_info; + file_info.num_fences = 1; + + if (ioctl(fd, SYNC_IOC_FILE_INFO, &file_info) < 0) + return -1; + + *ts = fence_info.timestamp_ns; + + return 0; +} + +static void +timeline_render_point_destroy(struct timeline_render_point *trp) +{ + wl_list_remove(&trp->link); + wl_event_source_remove(trp->event_source); + close(trp->fd); + free(trp); +} + +static int +timeline_render_point_handler(int fd, uint32_t mask, void *data) +{ + struct timeline_render_point *trp = data; + const char *tp_name = trp->type == TIMELINE_RENDER_POINT_TYPE_BEGIN ? + "renderer_gpu_begin" : "renderer_gpu_end"; + + if (mask & WL_EVENT_READABLE) { + uint64_t ts; + + if (linux_sync_file_read_timestamp(trp->fd, &ts) == 0) { + struct timespec tspec = { 0 }; + + timespec_add_nsec(&tspec, &tspec, ts); + + TL_POINT(tp_name, TLP_GPU(&tspec), + TLP_OUTPUT(trp->output), TLP_END); + } + } + + timeline_render_point_destroy(trp); + + return 0; +} + +static EGLSyncKHR +timeline_create_render_sync(struct gl_renderer *gr) +{ + static const EGLint attribs[] = { EGL_NONE }; + + if (!weston_timeline_enabled_ || !gr->has_native_fence_sync) + return EGL_NO_SYNC_KHR; + + return gr->create_sync(gr->egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, + attribs); +} + +static void +timeline_submit_render_sync(struct gl_renderer *gr, + struct weston_compositor *ec, + struct weston_output *output, + EGLSyncKHR sync, + enum timeline_render_point_type type) +{ + struct gl_output_state *go; + struct wl_event_loop *loop; + int fd; + struct timeline_render_point *trp; + + if (!weston_timeline_enabled_ || + !gr->has_native_fence_sync || + sync == EGL_NO_SYNC_KHR) + return; + + go = get_output_state(output); + loop = wl_display_get_event_loop(ec->wl_display); + + fd = gr->dup_native_fence_fd(gr->egl_display, sync); + if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) + goto out; + + trp = zalloc(sizeof *trp); + if (trp == NULL) { + close(fd); + goto out; + } + + trp->type = type; + trp->fd = fd; + trp->output = output; + trp->event_source = wl_event_loop_add_fd(loop, fd, + WL_EVENT_READABLE, + timeline_render_point_handler, + trp); + + wl_list_insert(&go->timeline_render_point_list, &trp->link); + +out: + gr->destroy_sync(gr->egl_display, sync); +} + static struct egl_image* egl_image_create(struct gl_renderer *gr, EGLenum target, EGLClientBuffer buffer, const EGLint *attribs) @@ -1106,10 +1243,13 @@ gl_renderer_repaint_output(struct weston_output *output, pixman_box32_t *rects; pixman_region32_t buffer_damage, total_damage; enum gl_border_status border_damage = BORDER_STATUS_CLEAN; + EGLSyncKHR begin_render_sync, end_render_sync; if (use_output(output) < 0) return; + begin_render_sync = timeline_create_render_sync(gr); + /* Calculate the viewport */ glViewport(go->borders[GL_RENDERER_BORDER_LEFT].width, go->borders[GL_RENDERER_BORDER_BOTTOM].height, @@ -1158,6 +1298,8 @@ gl_renderer_repaint_output(struct weston_output *output, pixman_region32_copy(&output->previous_damage, output_damage); wl_signal_emit(&output->frame_signal, output); + end_render_sync = timeline_create_render_sync(gr); + if (gr->swap_buffers_with_damage) { pixman_region32_init(&buffer_damage); weston_transformed_region(output->width, output->height, @@ -1203,6 +1345,14 @@ gl_renderer_repaint_output(struct weston_output *output, } go->border_status = BORDER_STATUS_CLEAN; + + /* We have to submit the render sync objects after swap buffers, since + * the objects get assigned a valid sync file fd only after a gl flush. + */ + timeline_submit_render_sync(gr, compositor, output, begin_render_sync, + TIMELINE_RENDER_POINT_TYPE_BEGIN); + timeline_submit_render_sync(gr, compositor, output, end_render_sync, + TIMELINE_RENDER_POINT_TYPE_END); } static int @@ -2827,6 +2977,8 @@ gl_renderer_output_create(struct weston_output *output, for (i = 0; i < BUFFER_DAMAGE_COUNT; i++) pixman_region32_init(&go->buffer_damage[i]); + wl_list_init(&go->timeline_render_point_list); + output->renderer_state = go; return 0; @@ -2867,6 +3019,7 @@ gl_renderer_output_destroy(struct weston_output *output) { struct gl_renderer *gr = get_renderer(output->compositor); struct gl_output_state *go = get_output_state(output); + struct timeline_render_point *trp, *tmp; int i; for (i = 0; i < 2; i++) @@ -2878,6 +3031,13 @@ gl_renderer_output_destroy(struct weston_output *output) weston_platform_destroy_egl_surface(gr->egl_display, go->egl_surface); + if (!wl_list_empty(&go->timeline_render_point_list)) + weston_log("warning: discarding pending timeline render" + "objects at output destruction"); + + wl_list_for_each_safe(trp, tmp, &go->timeline_render_point_list, link) + timeline_render_point_destroy(trp); + free(go); } @@ -3042,6 +3202,9 @@ gl_renderer_setup_egl_extensions(struct weston_compositor *ec) gr->dup_native_fence_fd = (void *) eglGetProcAddress("eglDupNativeFenceFDANDROID"); gr->has_native_fence_sync = 1; + } else { + weston_log("warning: Disabling render GPU timeline due to " + "missing EGL_ANDROID_native_fence_sync extension\n"); } renderer_setup_egl_client_extensions(gr); diff --git a/shared/weston-egl-ext.h b/shared/weston-egl-ext.h index 8aacbd01d..0784ea2dd 100644 --- a/shared/weston-egl-ext.h +++ b/shared/weston-egl-ext.h @@ -181,6 +181,10 @@ typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC) (EGLD typedef void *EGLSyncKHR; #endif /* EGL_KHR_cl_event2 */ +#ifndef EGL_NO_SYNC_KHR +#define EGL_NO_SYNC_KHR ((EGLSyncKHR)0) +#endif + #ifndef EGL_KHR_fence_sync #define EGL_KHR_fence_sync 1 typedef EGLSyncKHR (EGLAPIENTRYP PFNEGLCREATESYNCKHRPROC) (EGLDisplay dpy, EGLenum type, const EGLint *attrib_list); @@ -192,6 +196,14 @@ typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCKHRPROC) (EGLDisplay dpy, EGLS typedef EGLint (EGLAPIENTRYP PFNEGLDUPNATIVEFENCEFDANDROIDPROC) (EGLDisplay dpy, EGLSyncKHR sync); #endif /* EGL_ANDROID_native_fence_sync */ +#ifndef EGL_SYNC_NATIVE_FENCE_ANDROID +#define EGL_SYNC_NATIVE_FENCE_ANDROID 0x3144 +#endif + +#ifndef EGL_NO_NATIVE_FENCE_FD_ANDROID +#define EGL_NO_NATIVE_FENCE_FD_ANDROID -1 +#endif + #else /* ENABLE_EGL */ /* EGL platform definition are keept to allow compositor-xx.c to build */ From fa41bdfbc0b962fd73b89f01aab1a5370c9c28eb Mon Sep 17 00:00:00 2001 From: "Yann E. MORIN" Date: Sun, 1 Oct 2017 14:31:10 +0200 Subject: [PATCH 0161/1642] shared: struct timespec is in time.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the musl C library, tests/timespec-text.c does not build, with the following error: In file included from tests/timespec-test.c:36:0: ./shared/timespec-util.h:41:21: warning: ‘struct timespec’ declared inside parameter list will not be visible outside of this definition or declaration timespec_sub(struct timespec *r, ^~~~~~~~ [...] Indeed, struct timespec is defined in time.h, so we must include it. Signed-off-by: "Yann E. MORIN" Reviewed-by: Pekka Paalanen --- shared/timespec-util.h | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/timespec-util.h b/shared/timespec-util.h index 576b3e8f0..34a120ae6 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -28,6 +28,7 @@ #include #include +#include #define NSEC_PER_SEC 1000000000 From be2f6b0f75a6fb6ac1278d2cd935a36419a09d0f Mon Sep 17 00:00:00 2001 From: David Fort Date: Wed, 27 Sep 2017 12:01:10 +0200 Subject: [PATCH 0162/1642] Fix API troubles with FreeRDP 2.0 v2 With FreeRDP 2.0 the crypto needs to be initialized or we fail as soon as we try to compute a md5. The API also changed for the suppress output callback. Reviewed-by: Pekka Paalanen --- libweston/compositor-rdp.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 091472b08..ddc49ab68 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -76,6 +76,10 @@ #include #include +#if FREERDP_VERSION_MAJOR >= 2 +#include +#endif + #include "shared/helpers.h" #include "compositor.h" #include "compositor-rdp.h" @@ -1013,7 +1017,8 @@ xf_peer_activate(freerdp_peer* client) return TRUE; } -static BOOL xf_peer_post_connect(freerdp_peer *client) +static BOOL +xf_peer_post_connect(freerdp_peer *client) { return TRUE; } @@ -1166,7 +1171,7 @@ xf_input_unicode_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) static FREERDP_CB_RET_TYPE -xf_suppress_output(rdpContext *context, BYTE allow, RECTANGLE_16 *area) +xf_suppress_output(rdpContext *context, BYTE allow, const RECTANGLE_16 *area) { RdpPeerContext *peerContext = (RdpPeerContext *)context; @@ -1227,7 +1232,7 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) client->PostConnect = xf_peer_post_connect; client->Activate = xf_peer_activate; - client->update->SuppressOutput = xf_suppress_output; + client->update->SuppressOutput = (pSuppressOutput)xf_suppress_output; input = client->input; input->SynchronizeEvent = xf_input_synchronize_event; @@ -1387,6 +1392,9 @@ weston_backend_init(struct weston_compositor *compositor, struct weston_rdp_backend_config config = {{ 0, }}; int major, minor, revision; +#if FREERDP_VERSION_MAJOR >= 2 + winpr_InitializeSSL(0); +#endif freerdp_get_version(&major, &minor, &revision); weston_log("using FreeRDP version %d.%d.%d\n", major, minor, revision); From 427041454952502e7e085ed891a7eed4151b26ab Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 6 Sep 2017 16:47:52 +0300 Subject: [PATCH 0163/1642] libweston: move weston_output::mode_list init to core Initialize the list in weston_output_init() instead of doing it separately in each backend. One would expect weston_output_init() to initialize all weston_output members, at least those that are not NULL. We rely on the set_size() functions to be called only once, as is assert()'d. If set_size() becomes callable multiple times, this patch will force them to be fixed to properly manage the mode list instead of losing all members. compositor-wayland.c is strange in wayland_output_create_for_parent_output(): it first called wayland_output_set_size() that initialized the mode list with a single mode manufactured from width and height and set that mode as current. Then it continued to reset the mode list and adding the list of modes from the parent output, leaving the current mode left to point to a mode struct that is no longer in the mode list and with a broken 'link' element. This patch changes things such that the manufactured mode is left in the list, and the parent mode list is added. This is probably not quite right either. Signed-off-by: Pekka Paalanen Reviewed-by: Emre Ucan Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor-drm.c | 2 -- libweston/compositor-fbdev.c | 1 - libweston/compositor-headless.c | 1 - libweston/compositor-rdp.c | 1 - libweston/compositor-wayland.c | 2 -- libweston/compositor-x11.c | 1 - libweston/compositor.c | 1 + 7 files changed, 1 insertion(+), 8 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 5e87cb416..e3fe7d595 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3375,8 +3375,6 @@ create_output_for_connector(struct drm_backend *b, weston_output_init(&output->base, b->compositor); - wl_list_init(&output->base.mode_list); - for (i = 0; i < output->connector->count_modes; i++) { drm_mode = drm_output_add_mode(output, &output->connector->modes[i]); if (!drm_mode) { diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 62ffa0ba5..9d49f4b95 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -523,7 +523,6 @@ fbdev_output_create(struct fbdev_backend *backend, output->mode.width = output->fb_info.x_resolution; output->mode.height = output->fb_info.y_resolution; output->mode.refresh = output->fb_info.refresh_rate; - wl_list_init(&output->base.mode_list); wl_list_insert(&output->base.mode_list, &output->mode.link); output->base.current_mode = &output->mode; diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index 9e42e7f62..5425ad5bc 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -201,7 +201,6 @@ headless_output_set_size(struct weston_output *base, output->mode.width = output_width; output->mode.height = output_height; output->mode.refresh = 60000; - wl_list_init(&output->base.mode_list); wl_list_insert(&output->base.mode_list, &output->mode.link); output->base.current_mode = &output->mode; diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index ddc49ab68..2f9c9783d 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -488,7 +488,6 @@ rdp_output_set_size(struct weston_output *base, assert(!output->base.current_mode); wl_list_init(&output->peers); - wl_list_init(&output->base.mode_list); initMode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; initMode.width = width; diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index b144d6e5d..1fb1be6c6 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1318,7 +1318,6 @@ wayland_output_set_size(struct weston_output *base, int width, int height) output->mode.height = output_height; output->mode.refresh = 60000; output->scale = output->base.scale; - wl_list_init(&output->base.mode_list); wl_list_insert(&output->base.mode_list, &output->mode.link); output->base.current_mode = &output->mode; @@ -1369,7 +1368,6 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, output->base.make = poutput->physical.make; output->base.model = poutput->physical.model; - wl_list_init(&output->base.mode_list); wl_list_insert_list(&output->base.mode_list, &poutput->mode_list); wl_list_init(&poutput->mode_list); diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 02cdf3eaf..8cf512feb 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -999,7 +999,6 @@ x11_output_set_size(struct weston_output *base, int width, int height) output->mode.height = output_height; output->mode.refresh = 60000; output->scale = output->base.scale; - wl_list_init(&output->base.mode_list); wl_list_insert(&output->base.mode_list, &output->mode.link); output->base.current_mode = &output->mode; diff --git a/libweston/compositor.c b/libweston/compositor.c index 878cd5351..bed3cd8f5 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4710,6 +4710,7 @@ weston_output_init(struct weston_output *output, pixman_region32_init(&output->previous_damage); pixman_region32_init(&output->region); + wl_list_init(&output->mode_list); } /** Adds weston_output object to pending output list. From d05a8192527cc85c2ae86f823581872722386214 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 17 Aug 2017 13:30:11 +0300 Subject: [PATCH 0164/1642] tests: ensure output dependent IVI tests run There are IVI tests that require an output. Previously these tests would silently skip if no outputs were present. However, a test setup should always have outputs with these tests. Skipping could easily leave the tests dead without notice. Make these tests fail instead of skip if there are no outputs. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Emre Ucan Acked-by Daniel Stone --- tests/ivi_layout-internal-test.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ivi_layout-internal-test.c b/tests/ivi_layout-internal-test.c index f7f7c8056..4d73eff1a 100644 --- a/tests/ivi_layout-internal-test.c +++ b/tests/ivi_layout-internal-test.c @@ -501,7 +501,7 @@ test_screen_render_order(struct test_context *ctx) int32_t length = 0; uint32_t i; - if (wl_list_empty(&ctx->compositor->output_list)) + if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) return; output = wl_container_of(ctx->compositor->output_list.next, output, link); @@ -547,7 +547,7 @@ test_screen_bad_render_order(struct test_context *ctx) int32_t length = 0; uint32_t i; - if (wl_list_empty(&ctx->compositor->output_list)) + if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) return; output = wl_container_of(ctx->compositor->output_list.next, output, link); @@ -580,7 +580,7 @@ test_screen_add_layers(struct test_context *ctx) int32_t length = 0; uint32_t i; - if (wl_list_empty(&ctx->compositor->output_list)) + if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) return; output = wl_container_of(ctx->compositor->output_list.next, output, link); @@ -699,7 +699,7 @@ test_commit_changes_after_render_order_set_layer_destroy( struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; uint32_t i; - if (wl_list_empty(&ctx->compositor->output_list)) + if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) return; output = wl_container_of(ctx->compositor->output_list.next, output, link); From 26ac2e12189c93d7e01aace7c4a702b77e519a90 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 3 Apr 2017 13:18:13 +0300 Subject: [PATCH 0165/1642] libweston: weston_output_init(..., +name) Add 'name' argument to weston_output_init(). This is much more obvious than the assert inside weston_output_init() to ensure the caller has set a field in weston_output first. Now weston_output_init() will strdup() the name itself, which means we can drop a whole bunch of strdup()s in the backends. This matches weston_output_destroy() which was already calling free() on the name. All backends are slightly reordered to call weston_output_init() before accessing any fields of weston_output, except the Wayland backend which would make it a little awkward to do it in this patch. Mind, that weston_output_init() still does not reset the struct to zero - it is presumed the caller has done it, since weston_output is embedded in the backend output structs. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: David Fort [Daniel: document name copying] Acked-by Daniel Stone --- libweston/compositor-drm.c | 8 +++++--- libweston/compositor-fbdev.c | 5 ++--- libweston/compositor-headless.c | 4 ++-- libweston/compositor-rdp.c | 4 ++-- libweston/compositor-wayland.c | 7 +++---- libweston/compositor-x11.c | 4 ++-- libweston/compositor.c | 12 +++++++----- libweston/compositor.h | 3 ++- 8 files changed, 25 insertions(+), 22 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index e3fe7d595..b9014c8ac 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3327,6 +3327,7 @@ create_output_for_connector(struct drm_backend *b, struct drm_output *output; drmModeObjectPropertiesPtr props; struct drm_mode *drm_mode; + char *name; int i; static const struct drm_property_info connector_props[] = { @@ -3354,10 +3355,13 @@ create_output_for_connector(struct drm_backend *b, output->original_crtc = drmModeGetCrtc(b->drm.fd, output->crtc_id); + name = make_connector_name(connector); + weston_output_init(&output->base, b->compositor, name); + free(name); + output->base.enable = drm_output_enable; output->base.destroy = drm_output_destroy; output->base.disable = drm_output_disable; - output->base.name = make_connector_name(connector); output->destroy_pending = 0; output->disable_pending = 0; @@ -3373,8 +3377,6 @@ create_output_for_connector(struct drm_backend *b, find_and_parse_output_edid(b, output, props); drmModeFreeObjectProperties(props); - weston_output_init(&output->base, b->compositor); - for (i = 0; i < output->connector->count_modes; i++) { drm_mode = drm_output_add_mode(output, &output->connector->modes[i]); if (!drm_mode) { diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 9d49f4b95..fe169cadc 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -510,13 +510,12 @@ fbdev_output_create(struct fbdev_backend *backend, goto out_free; } - output->base.name = strdup("fbdev"); + weston_output_init(&output->base, backend->compositor, "fbdev"); + output->base.destroy = fbdev_output_destroy; output->base.disable = fbdev_output_disable_handler; output->base.enable = fbdev_output_enable; - weston_output_init(&output->base, backend->compositor); - /* only one static mode in list */ output->mode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index 5425ad5bc..ac762c18e 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -234,12 +234,12 @@ headless_output_create(struct weston_compositor *compositor, if (output == NULL) return -1; - output->base.name = strdup(name); + weston_output_init(&output->base, compositor, name); + output->base.destroy = headless_output_destroy; output->base.disable = headless_output_disable; output->base.enable = headless_output_enable; - weston_output_init(&output->base, compositor); weston_compositor_add_pending_output(&output->base, compositor); return 0; diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 2f9c9783d..f2fa0f4da 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -584,12 +584,12 @@ rdp_backend_create_output(struct weston_compositor *compositor) if (output == NULL) return -1; - output->base.name = strdup("rdp"); + weston_output_init(&output->base, compositor, "rdp"); + output->base.destroy = rdp_output_destroy; output->base.disable = rdp_output_disable; output->base.enable = rdp_output_enable; - weston_output_init(&output->base, compositor); weston_compositor_add_pending_output(&output->base, compositor); return 0; diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 1fb1be6c6..dee972c82 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1265,7 +1265,6 @@ wayland_output_create_common(const char *name) output->base.destroy = wayland_output_destroy; output->base.disable = wayland_output_disable; output->base.enable = wayland_output_enable; - output->base.name = strdup(name); return output; } @@ -1278,7 +1277,7 @@ wayland_output_create(struct weston_compositor *compositor, const char *name) if (!output) return -1; - weston_output_init(&output->base, compositor); + weston_output_init(&output->base, compositor, name); weston_compositor_add_pending_output(&output->base, compositor); return 0; @@ -1354,7 +1353,7 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, goto out; } - weston_output_init(&output->base, b->compositor); + weston_output_init(&output->base, b->compositor, "wlparent"); output->base.scale = 1; output->base.transform = WL_OUTPUT_TRANSFORM_NORMAL; @@ -1393,7 +1392,7 @@ wayland_output_create_fullscreen(struct wayland_backend *b) if (!output) return -1; - weston_output_init(&output->base, b->compositor); + weston_output_init(&output->base, b->compositor, "wayland-fullscreen"); output->base.scale = 1; output->base.transform = WL_OUTPUT_TRANSFORM_NORMAL; diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 8cf512feb..9d3714284 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -1028,12 +1028,12 @@ x11_output_create(struct weston_compositor *compositor, return -1; } - output->base.name = strdup(name); + weston_output_init(&output->base, compositor, name); + output->base.destroy = x11_output_destroy; output->base.disable = x11_output_disable; output->base.enable = x11_output_enable; - weston_output_init(&output->base, compositor); weston_compositor_add_pending_output(&output->base, compositor); return 0; diff --git a/libweston/compositor.c b/libweston/compositor.c index bed3cd8f5..165ff0132 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4679,23 +4679,25 @@ weston_output_set_transform(struct weston_output *output, * * \param output The weston_output object to initialize * \param compositor The compositor instance. + * \param name Name for the output (the string is copied). * * Sets initial values for fields that are expected to be * configured either by compositors or backends. * + * The name is used in logs, and can be used by compositors as a configuration + * identifier. + * * \memberof weston_output * \internal */ WL_EXPORT void weston_output_init(struct weston_output *output, - struct weston_compositor *compositor) + struct weston_compositor *compositor, + const char *name) { output->compositor = compositor; output->destroying = 0; - - /* Backends must set output->name */ - assert(output->name); - + output->name = strdup(name); wl_list_init(&output->link); output->enabled = false; diff --git a/libweston/compositor.h b/libweston/compositor.h index 769203a1d..804d0912f 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1926,7 +1926,8 @@ weston_output_set_transform(struct weston_output *output, void weston_output_init(struct weston_output *output, - struct weston_compositor *compositor); + struct weston_compositor *compositor, + const char *name); void weston_compositor_add_pending_output(struct weston_output *output, From 1580be68fda378e640aa92f5072d6b4366c03454 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 11 Aug 2017 16:05:41 +0300 Subject: [PATCH 0166/1642] compositor-wayland: move output init into common, fix error path Move the weston_output_init() call into wayland_output_create_common(). This avoids passing the name twice to different functions, and follows the precedent set in "libweston: weston_output_init(..., +name)" for calling init before accessing fields. Since the error paths in wayland_output_create_for_parent_output() and wayland_output_create_fullscreen() are now guaranteed to have weston_output init'd, call weston_output_destroy() appropriately. There might be more to free than just the name. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor-wayland.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index dee972c82..e8639a0e0 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1242,7 +1242,8 @@ wayland_output_enable(struct weston_output *base) } static struct wayland_output * -wayland_output_create_common(const char *name) +wayland_output_create_common(struct weston_compositor *compositor, + const char *name) { struct wayland_output *output; char *title; @@ -1262,6 +1263,8 @@ wayland_output_create_common(const char *name) } output->title = title; + weston_output_init(&output->base, compositor, name); + output->base.destroy = wayland_output_destroy; output->base.disable = wayland_output_disable; output->base.enable = wayland_output_enable; @@ -1272,12 +1275,12 @@ wayland_output_create_common(const char *name) static int wayland_output_create(struct weston_compositor *compositor, const char *name) { - struct wayland_output *output = wayland_output_create_common(name); + struct wayland_output *output; + output = wayland_output_create_common(compositor, name); if (!output) return -1; - weston_output_init(&output->base, compositor, name); weston_compositor_add_pending_output(&output->base, compositor); return 0; @@ -1337,7 +1340,7 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, struct wayland_output *output; struct weston_mode *mode; - output = wayland_output_create_common("wlparent"); + output = wayland_output_create_common(b->compositor, "wlparent"); if (!output) return -1; @@ -1353,8 +1356,6 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, goto out; } - weston_output_init(&output->base, b->compositor, "wlparent"); - output->base.scale = 1; output->base.transform = WL_OUTPUT_TRANSFORM_NORMAL; @@ -1375,7 +1376,7 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, return 0; out: - free(output->base.name); + weston_output_destroy(&output->base); free(output->title); free(output); @@ -1388,12 +1389,10 @@ wayland_output_create_fullscreen(struct wayland_backend *b) struct wayland_output *output; int width = 0, height = 0; - output = wayland_output_create_common("wayland-fullscreen"); + output = wayland_output_create_common(b->compositor, "wayland-fullscreen"); if (!output) return -1; - weston_output_init(&output->base, b->compositor, "wayland-fullscreen"); - output->base.scale = 1; output->base.transform = WL_OUTPUT_TRANSFORM_NORMAL; @@ -1425,7 +1424,7 @@ wayland_output_create_fullscreen(struct wayland_backend *b) err_set_size: wayland_backend_destroy_output_surface(output); err_surface: - free(output->base.name); + weston_output_destroy(&output->base); free(output->title); free(output); From 6f1866b3fa40e2bce00881e288c884352244d07d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 3 Apr 2017 14:22:51 +0300 Subject: [PATCH 0167/1642] compositor-drm: set output make/model/serial once This fixes a regression where monitor make and model would always be advertised as "unknown" to Wayland clients. The EDID strings were parsed at create_output_for_connector() time, but the fallback "unknown" values were set in weston_drm_output_api::set_mode vfunc later. This made the correct monitor info be shown in the log, but not sent to clients. The purpose of the "unknown" assignments is to give fallback values in case EDID is not providing them. Fix all that by moving all setting of the make, model and serial into create_output_for_connector(). These values cannot change afterwards anyway. While at it, document find_and_parse_output_edid(). The ugly casts in create_output_for_connector() are required to silence compositor warnings from ignoring const attribute. This is temporary, and a future refactoring will get rid of the casts. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor-drm.c | 40 +++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index b9014c8ac..f8525532c 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2841,9 +2841,26 @@ edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length) return 0; } +/** Parse monitor make, model and serial from EDID + * + * \param b The backend instance. + * \param output The output whose \c drm_edid to fill in. + * \param props The DRM connector properties to get the EDID from. + * \param make[out] The monitor make (PNP ID). + * \param model[out] The monitor model (name). + * \param serial_number[out] The monitor serial number. + * + * Each of \c *make, \c *model and \c *serial_number are set only if the + * information is found in the EDID. The pointers they are set to must not + * be free()'d explicitly, instead they get implicitly freed when the + * \c drm_output is destroyed. + */ static void find_and_parse_output_edid(struct drm_backend *b, struct drm_output *output, - drmModeObjectPropertiesPtr props) + drmModeObjectPropertiesPtr props, + const char **make, + const char **model, + const char **serial_number) { drmModePropertyBlobPtr edid_blob = NULL; uint32_t blob_id; @@ -2868,17 +2885,15 @@ find_and_parse_output_edid(struct drm_backend *b, struct drm_output *output, output->edid.monitor_name, output->edid.serial_number); if (output->edid.pnp_id[0] != '\0') - output->base.make = output->edid.pnp_id; + *make = output->edid.pnp_id; if (output->edid.monitor_name[0] != '\0') - output->base.model = output->edid.monitor_name; + *model = output->edid.monitor_name; if (output->edid.serial_number[0] != '\0') - output->base.serial_number = output->edid.serial_number; + *serial_number = output->edid.serial_number; } drmModeFreePropertyBlob(edid_blob); } - - static int parse_modeline(const char *s, drmModeModeInfo *mode) { @@ -3093,10 +3108,6 @@ drm_output_set_mode(struct weston_output *base, struct drm_mode *current; drmModeModeInfo crtc_mode; - output->base.make = "unknown"; - output->base.model = "unknown"; - output->base.serial_number = "unknown"; - if (connector_get_current_mode(output->connector, b->drm.fd, &crtc_mode) < 0) return -1; @@ -3328,6 +3339,9 @@ create_output_for_connector(struct drm_backend *b, drmModeObjectPropertiesPtr props; struct drm_mode *drm_mode; char *name; + const char *make = "unknown"; + const char *model = "unknown"; + const char *serial_number = "unknown"; int i; static const struct drm_property_info connector_props[] = { @@ -3374,7 +3388,11 @@ create_output_for_connector(struct drm_backend *b, } drm_property_info_populate(b, connector_props, output->props_conn, WDRM_CONNECTOR__COUNT, props); - find_and_parse_output_edid(b, output, props); + find_and_parse_output_edid(b, output, props, + &make, &model, &serial_number); + output->base.make = (char *)make; + output->base.model = (char *)model; + output->base.serial_number = (char *)serial_number; drmModeFreeObjectProperties(props); for (i = 0; i < output->connector->count_modes; i++) { From a0bfedc1cf01e6ed60e303e33efe977f9a70b4d8 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 3 Apr 2017 14:42:51 +0300 Subject: [PATCH 0168/1642] compositor-drm: set all properties in create_output_for_connector Move the remaining scattered setup of the fixed properties into create_output_for_connector(). All these are already known and they cannot change. This helps future refactoring. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor-drm.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index f8525532c..a2e6db492 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3122,9 +3122,6 @@ drm_output_set_mode(struct weston_output *base, output->base.native_mode = output->base.current_mode; output->base.native_scale = output->base.current_scale; - output->base.mm_width = output->connector->mmWidth; - output->base.mm_height = output->connector->mmHeight; - return 0; } @@ -3188,12 +3185,6 @@ drm_output_enable(struct weston_output *base) output->base.gamma_size = output->original_crtc->gamma_size; output->base.set_gamma = drm_output_set_gamma; - output->base.subpixel = drm_subpixel_to_wayland(output->connector->subpixel); - - if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || - output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) - output->base.connection_internal = true; - weston_plane_init(&output->cursor_plane, b->compositor, INT32_MIN, INT32_MIN); weston_plane_init(&output->scanout_plane, b->compositor, 0, 0); @@ -3393,6 +3384,15 @@ create_output_for_connector(struct drm_backend *b, output->base.make = (char *)make; output->base.model = (char *)model; output->base.serial_number = (char *)serial_number; + output->base.subpixel = drm_subpixel_to_wayland(output->connector->subpixel); + + if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || + output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) + output->base.connection_internal = true; + + output->base.mm_width = output->connector->mmWidth; + output->base.mm_height = output->connector->mmHeight; + drmModeFreeObjectProperties(props); for (i = 0; i < output->connector->count_modes; i++) { From 01e0068868a2424aa53c5df9849f0aaa43b5bf37 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 24 Mar 2017 16:21:06 +0200 Subject: [PATCH 0169/1642] libweston: send more wl_surface.enter/leave events A client may have bound the same wl_output multiple times, for who knows what reason. As the server cannot know which wl_output resource to use for which wl_surface, send enter/leave events for all of them. This is a protocol correctness fix. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor.c | 53 +++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 165ff0132..e647d30fa 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -918,6 +918,39 @@ weston_view_damage_below(struct weston_view *view) weston_view_schedule_repaint(view); } +/** Send wl_surface.enter/leave events + * + * \param surface The surface. + * \param output The entered/left output. + * \param enter True if entered. + * \param left True if left. + * + * Send the enter/leave events for all protocol objects bound to the given + * output by the client owning the surface. + */ +static void +weston_surface_send_enter_leave(struct weston_surface *surface, + struct weston_output *output, + bool enter, + bool leave) +{ + struct wl_resource *wloutput; + struct wl_client *client; + + assert(enter != leave); + + client = wl_resource_get_client(surface->resource); + wl_resource_for_each(wloutput, &output->resource_list) { + if (wl_resource_get_client(wloutput) != client) + continue; + + if (enter) + wl_surface_send_enter(surface->resource, wloutput); + if (leave) + wl_surface_send_leave(surface->resource, wloutput); + } +} + /** * \param es The surface * \param mask The new set of outputs for the surface @@ -933,9 +966,8 @@ weston_surface_update_output_mask(struct weston_surface *es, uint32_t mask) uint32_t different = es->output_mask ^ mask; uint32_t entered = mask & different; uint32_t left = es->output_mask & different; + uint32_t output_bit; struct weston_output *output; - struct wl_resource *resource = NULL; - struct wl_client *client; es->output_mask = mask; if (es->resource == NULL) @@ -943,19 +975,14 @@ weston_surface_update_output_mask(struct weston_surface *es, uint32_t mask) if (different == 0) return; - client = wl_resource_get_client(es->resource); - wl_list_for_each(output, &es->compositor->output_list, link) { - if (1u << output->id & different) - resource = - wl_resource_find_for_client(&output->resource_list, - client); - if (resource == NULL) + output_bit = 1u << output->id; + if (!(output_bit & different)) continue; - if (1u << output->id & entered) - wl_surface_send_enter(es->resource, resource); - if (1u << output->id & left) - wl_surface_send_leave(es->resource, resource); + + weston_surface_send_enter_leave(es, output, + output_bit & entered, + output_bit & left); } } From ae6d35db140afc303753972447e0125465997fe7 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 16 Aug 2017 12:07:14 +0300 Subject: [PATCH 0170/1642] libweston: rename weston_output_destroy() to weston_output_release() 'release' is a more appropriate name because the function does not free the underlying memory. The main reason for this is that we need the name weston_output_destroy() for new API that actually will free also the underlying memory. Since the function is only used in backends and external backends are not a thing, this does not cause libweston major version bump, even though it does change the ABI. There is no way external users could have successfully used this function. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor-drm.c | 2 +- libweston/compositor-fbdev.c | 2 +- libweston/compositor-headless.c | 2 +- libweston/compositor-rdp.c | 4 ++-- libweston/compositor-wayland.c | 6 +++--- libweston/compositor-x11.c | 2 +- libweston/compositor.c | 4 ++-- libweston/compositor.h | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index a2e6db492..6aca10c3e 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3272,7 +3272,7 @@ drm_output_destroy(struct weston_output *base) if (output->pageflip_timer) wl_event_source_remove(output->pageflip_timer); - weston_output_destroy(&output->base); + weston_output_release(&output->base); drm_property_info_free(output->props_conn, WDRM_CONNECTOR__COUNT); diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index fe169cadc..1765fa8f2 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -555,7 +555,7 @@ fbdev_output_destroy(struct weston_output *base) fbdev_output_disable_handler(base); /* Remove the output. */ - weston_output_destroy(&output->base); + weston_output_release(&output->base); free(output->device); free(output); diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index ac762c18e..64fe6f3f4 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -134,7 +134,7 @@ headless_output_destroy(struct weston_output *base) struct headless_output *output = to_headless_output(base); headless_output_disable(&output->base); - weston_output_destroy(&output->base); + weston_output_release(&output->base); free(output); } diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index f2fa0f4da..393c11856 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -570,7 +570,7 @@ rdp_output_destroy(struct weston_output *base) struct rdp_output *output = to_rdp_output(base); rdp_output_disable(&output->base); - weston_output_destroy(&output->base); + weston_output_release(&output->base); free(output); } @@ -1360,7 +1360,7 @@ rdp_backend_create(struct weston_compositor *compositor, err_listener: freerdp_listener_free(b->listener); err_output: - weston_output_destroy(&b->output->base); + weston_output_release(&b->output->base); err_compositor: weston_compositor_shutdown(compositor); err_free_strings: diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index e8639a0e0..26cfdd596 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -703,7 +703,7 @@ wayland_output_destroy(struct weston_output *base) wayland_output_disable(&output->base); - weston_output_destroy(&output->base); + weston_output_release(&output->base); if (output->frame_cb) wl_callback_destroy(output->frame_cb); @@ -1376,7 +1376,7 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, return 0; out: - weston_output_destroy(&output->base); + weston_output_release(&output->base); free(output->title); free(output); @@ -1424,7 +1424,7 @@ wayland_output_create_fullscreen(struct wayland_backend *b) err_set_size: wayland_backend_destroy_output_surface(output); err_surface: - weston_output_destroy(&output->base); + weston_output_release(&output->base); free(output->title); free(output); diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 9d3714284..070f28757 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -801,7 +801,7 @@ x11_output_destroy(struct weston_output *base) struct x11_output *output = to_x11_output(base); x11_output_disable(&output->base); - weston_output_destroy(&output->base); + weston_output_release(&output->base); free(output); } diff --git a/libweston/compositor.c b/libweston/compositor.c index e647d30fa..53bbf55d9 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4750,7 +4750,7 @@ weston_output_init(struct weston_output *output, * Also notifies the compositor that an output is pending for * configuration. * - * The opposite of this operation is built into weston_output_destroy(). + * The opposite of this operation is built into weston_output_release(). * * \memberof weston_output * \internal @@ -4931,7 +4931,7 @@ weston_pending_output_coldplug(struct weston_compositor *compositor) * \internal */ WL_EXPORT void -weston_output_destroy(struct weston_output *output) +weston_output_release(struct weston_output *output) { output->destroying = 1; diff --git a/libweston/compositor.h b/libweston/compositor.h index 804d0912f..8b2d2b069 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1717,7 +1717,7 @@ void weston_output_move(struct weston_output *output, int x, int y); void -weston_output_destroy(struct weston_output *output); +weston_output_release(struct weston_output *output); void weston_output_transform_coordinate(struct weston_output *output, double device_x, double device_y, From d7e351189e160470fc51900eabcdec0943291da1 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 29 Aug 2017 17:04:12 +0300 Subject: [PATCH 0171/1642] libweston: ensure backend is not loaded twice Check and ensure that a compositor can only load one backend successfully. If a backend fails to load, it is theoretically possible to try another backend. Once loading succeeds, only destroying the compositor would allow "unloading" a backend. If backend init fail, ensure the backend pointer remains NULL to avoid calling into a half-loaded backend on e.g. compositor destruction. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 53bbf55d9..71a9b38c9 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5653,6 +5653,11 @@ weston_compositor_load_backend(struct weston_compositor *compositor, int (*backend_init)(struct weston_compositor *c, struct weston_backend_config *config_base); + if (compositor->backend) { + weston_log("Error: attempt to load a backend when one is already loaded\n"); + return -1; + } + if (backend >= ARRAY_LENGTH(backend_map)) return -1; @@ -5660,7 +5665,12 @@ weston_compositor_load_backend(struct weston_compositor *compositor, if (!backend_init) return -1; - return backend_init(compositor, config_base); + if (backend_init(compositor, config_base) < 0) { + compositor->backend = NULL; + return -1; + } + + return 0; } WL_EXPORT int From 7da9a3802fc969af67f5dd00382af47aa6f34be9 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 30 Aug 2017 11:29:49 +0300 Subject: [PATCH 0172/1642] libweston: set backend pointer earlier Change all backends to set the core backend pointer early. This is necessary for libweston core to be able to access the backend vfuncs before the backend init function returns. Particularly, weston_output_init() will be needing to inspect the backend vfuncs to see if the backend has been converted to a new API. Backends that create outputs as part of their init would fail without setting the pointer earlier. For consistency, all backends are modified instead of just those that could hit an issue. Libweston core will take care of resetting the backend pointer to NULL in case of error since "libweston: ensure backend is not loaded twice". Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor-drm.c | 4 ++-- libweston/compositor-fbdev.c | 2 +- libweston/compositor-headless.c | 4 ++-- libweston/compositor-rdp.c | 4 ++-- libweston/compositor-wayland.c | 3 ++- libweston/compositor-x11.c | 4 ++-- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 6aca10c3e..dc9078186 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4025,6 +4025,8 @@ drm_backend_create(struct weston_compositor *compositor, b->use_pixman = config->use_pixman; b->pageflip_timeout = config->pageflip_timeout; + compositor->backend = &b->base; + if (parse_gbm_format(config->gbm_format, GBM_FORMAT_XRGB8888, &b->gbm_format) < 0) goto err_compositor; @@ -4141,8 +4143,6 @@ drm_backend_create(struct weston_compositor *compositor, "support failed.\n"); } - compositor->backend = &b->base; - ret = weston_plugin_api_register(compositor, WESTON_DRM_OUTPUT_API_NAME, &api, sizeof(api)); diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 1765fa8f2..992eadb8a 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -731,6 +731,7 @@ fbdev_backend_create(struct weston_compositor *compositor, return NULL; backend->compositor = compositor; + compositor->backend = &backend->base; if (weston_compositor_set_presentation_clock_software( compositor) < 0) goto out_compositor; @@ -769,7 +770,6 @@ fbdev_backend_create(struct weston_compositor *compositor, udev_input_init(&backend->input, compositor, backend->udev, seat_id, param->configure_device); - compositor->backend = &backend->base; return backend; out_launcher: diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index 64fe6f3f4..2f01b64a4 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -277,6 +277,8 @@ headless_backend_create(struct weston_compositor *compositor, return NULL; b->compositor = compositor; + compositor->backend = &b->base; + if (weston_compositor_set_presentation_clock_software(compositor) < 0) goto err_free; @@ -291,8 +293,6 @@ headless_backend_create(struct weston_compositor *compositor, if (!b->use_pixman && noop_renderer_init(compositor) < 0) goto err_input; - compositor->backend = &b->base; - ret = weston_plugin_api_register(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, &api, sizeof(api)); diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 393c11856..990ddc66d 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -1299,6 +1299,8 @@ rdp_backend_create(struct weston_compositor *compositor, b->rdp_key = config->rdp_key ? strdup(config->rdp_key) : NULL; b->no_clients_resize = config->no_clients_resize; + compositor->backend = &b->base; + /* activate TLS only if certificate/key are available */ if (config->server_cert && config->server_key) { weston_log("TLS support activated\n"); @@ -1345,8 +1347,6 @@ rdp_backend_create(struct weston_compositor *compositor, goto err_output; } - compositor->backend = &b->base; - ret = weston_plugin_api_register(compositor, WESTON_RDP_OUTPUT_API_NAME, &api, sizeof(api)); diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 26cfdd596..fc929364c 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -2487,6 +2487,8 @@ wayland_backend_create(struct weston_compositor *compositor, return NULL; b->compositor = compositor; + compositor->backend = &b->base; + if (weston_compositor_set_presentation_clock_software(compositor) < 0) goto err_compositor; @@ -2559,7 +2561,6 @@ wayland_backend_create(struct weston_compositor *compositor, "support failed.\n"); } - compositor->backend = &b->base; return b; err_display: wl_display_disconnect(b->parent.wl_display); diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 070f28757..60843ac15 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -1669,6 +1669,8 @@ x11_backend_create(struct weston_compositor *compositor, b->fullscreen = config->fullscreen; b->no_input = config->no_input; + compositor->backend = &b->base; + if (weston_compositor_set_presentation_clock_software(compositor) < 0) goto err_free; @@ -1728,8 +1730,6 @@ x11_backend_create(struct weston_compositor *compositor, "support failed.\n"); } - compositor->backend = &b->base; - ret = weston_plugin_api_register(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, &api, sizeof(api)); From 82db6b79a3736dba4896b5bd16feec56d1ed6499 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 12 Sep 2017 17:15:42 +0300 Subject: [PATCH 0173/1642] compositor-fbdev: remove unused output arguments A few functions had argument 'output' which was not used at all. Remove such unused arguments. The coming migration to the head-based output API would have made it awkward to come up with the output argument for these, but luckily they are not actually needed. Signed-off-by: Pekka Paalanen Reviewed-by: Sergi Granell Acked-by Daniel Stone --- libweston/compositor-fbdev.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 992eadb8a..e1ad5b2f0 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -260,8 +260,7 @@ calculate_refresh_rate(struct fb_var_screeninfo *vinfo) } static int -fbdev_query_screen_info(struct fbdev_output *output, int fd, - struct fbdev_screeninfo *info) +fbdev_query_screen_info(int fd, struct fbdev_screeninfo *info) { struct fb_var_screeninfo varinfo; struct fb_fix_screeninfo fixinfo; @@ -296,8 +295,7 @@ fbdev_query_screen_info(struct fbdev_output *output, int fd, } static int -fbdev_set_screen_info(struct fbdev_output *output, int fd, - struct fbdev_screeninfo *info) +fbdev_set_screen_info(int fd, struct fbdev_screeninfo *info) { struct fb_var_screeninfo varinfo; @@ -340,8 +338,8 @@ static void fbdev_frame_buffer_destroy(struct fbdev_output *output); /* Returns an FD for the frame buffer device. */ static int -fbdev_frame_buffer_open(struct fbdev_output *output, const char *fb_dev, - struct fbdev_screeninfo *screen_info) +fbdev_frame_buffer_open(const char *fb_dev, + struct fbdev_screeninfo *screen_info) { int fd = -1; @@ -356,7 +354,7 @@ fbdev_frame_buffer_open(struct fbdev_output *output, const char *fb_dev, } /* Grab the screen info. */ - if (fbdev_query_screen_info(output, fd, screen_info) < 0) { + if (fbdev_query_screen_info(fd, screen_info) < 0) { weston_log("Failed to get frame buffer info: %s\n", strerror(errno)); @@ -436,7 +434,7 @@ fbdev_output_enable(struct weston_output *base) struct wl_event_loop *loop; /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(output, output->device, &output->fb_info); + fb_fd = fbdev_frame_buffer_open(output->device, &output->fb_info); if (fb_fd < 0) { weston_log("Creating frame buffer failed.\n"); return -1; @@ -504,7 +502,7 @@ fbdev_output_create(struct fbdev_backend *backend, output->device = strdup(device); /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(output, device, &output->fb_info); + fb_fd = fbdev_frame_buffer_open(device, &output->fb_info); if (fb_fd < 0) { weston_log("Creating frame buffer failed.\n"); goto out_free; @@ -590,8 +588,7 @@ fbdev_output_reenable(struct fbdev_backend *backend, weston_log("Re-enabling fbdev output.\n"); /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(output, output->device, - &new_screen_info); + fb_fd = fbdev_frame_buffer_open(output->device, &new_screen_info); if (fb_fd < 0) { weston_log("Creating frame buffer failed.\n"); goto err; @@ -601,8 +598,7 @@ fbdev_output_reenable(struct fbdev_backend *backend, * disabled. */ if (compare_screen_info (&output->fb_info, &new_screen_info) != 0) { /* Perform a mode-set to restore the old mode. */ - if (fbdev_set_screen_info(output, fb_fd, - &output->fb_info) < 0) { + if (fbdev_set_screen_info(fb_fd, &output->fb_info) < 0) { weston_log("Failed to restore mode settings. " "Attempting to re-open output anyway.\n"); } From a51e71fbf0b3b1faceca2fb1272728177c56951d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Sep 2017 17:14:19 +0300 Subject: [PATCH 0174/1642] compositor-fbdev: simplify FB destroy/unmap/disable Rename fbdev_frame_buffer_destroy() to fbdev_frame_buffer_unmap() because that is what it does. Adding the destruction of hw_surface in it makes it the perfect counterpart to fbdev_frame_buffer_map() which simplifies the code. fbdev_frame_buffer_map() can no longer call that, so just open-code the munmap() there. It is an error path, we don't really care about failures in an error path. The error path of fbdev_output_enable() is converted to call buffer_unmap() since that is exactly what it did. fbdev_output_disable() became redundant, being identical to fbdev_frame_buffer_unmap(). Invariant: output->hw_surface cannot be non-NULL without output->fb being non-NULL. hw_surface wraps the mmapped memory so cannot exist without the mmap. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor-fbdev.c | 52 +++++++++++++++--------------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index e1ad5b2f0..01ee18f0f 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -40,6 +40,7 @@ #include #include #include +#include #include @@ -334,8 +335,6 @@ fbdev_set_screen_info(int fd, struct fbdev_screeninfo *info) return 1; } -static void fbdev_frame_buffer_destroy(struct fbdev_output *output); - /* Returns an FD for the frame buffer device. */ static int fbdev_frame_buffer_open(const char *fb_dev, @@ -400,8 +399,10 @@ fbdev_frame_buffer_map(struct fbdev_output *output, int fd) retval = 0; out_unmap: - if (retval != 0 && output->fb != NULL) - fbdev_frame_buffer_destroy(output); + if (retval != 0 && output->fb != NULL) { + munmap(output->fb, output->fb_info.buffer_length); + output->fb = NULL; + } out_close: if (fd >= 0) @@ -411,9 +412,18 @@ fbdev_frame_buffer_map(struct fbdev_output *output, int fd) } static void -fbdev_frame_buffer_destroy(struct fbdev_output *output) +fbdev_frame_buffer_unmap(struct fbdev_output *output) { - weston_log("Destroying fbdev frame buffer.\n"); + if (!output->fb) { + assert(!output->hw_surface); + return; + } + + weston_log("Unmapping fbdev frame buffer.\n"); + + if (output->hw_surface) + pixman_image_unref(output->hw_surface); + output->hw_surface = NULL; if (munmap(output->fb, output->fb_info.buffer_length) < 0) weston_log("Failed to munmap frame buffer: %s\n", @@ -423,7 +433,6 @@ fbdev_frame_buffer_destroy(struct fbdev_output *output) } static void fbdev_output_destroy(struct weston_output *base); -static void fbdev_output_disable(struct weston_output *base); static int fbdev_output_enable(struct weston_output *base) @@ -463,9 +472,7 @@ fbdev_output_enable(struct weston_output *base) return 0; out_hw_surface: - pixman_image_unref(output->hw_surface); - output->hw_surface = NULL; - fbdev_frame_buffer_destroy(output); + fbdev_frame_buffer_unmap(output); return -1; } @@ -473,11 +480,12 @@ fbdev_output_enable(struct weston_output *base) static int fbdev_output_disable_handler(struct weston_output *base) { + struct fbdev_output *output = to_fbdev_output(base); + if (!base->enabled) return 0; - /* Close the frame buffer. */ - fbdev_output_disable(base); + fbdev_frame_buffer_unmap(output); if (base->renderer_state != NULL) pixman_renderer_output_destroy(base); @@ -628,24 +636,6 @@ fbdev_output_reenable(struct fbdev_backend *backend, return -1; } -/* NOTE: This leaves output->fb_info populated, caching data so that if - * fbdev_output_reenable() is called again, it can determine whether a mode-set - * is needed. */ -static void -fbdev_output_disable(struct weston_output *base) -{ - struct fbdev_output *output = to_fbdev_output(base); - - weston_log("Disabling fbdev output.\n"); - - if (output->hw_surface != NULL) { - pixman_image_unref(output->hw_surface); - output->hw_surface = NULL; - } - - fbdev_frame_buffer_destroy(output); -} - static void fbdev_backend_destroy(struct weston_compositor *base) { @@ -687,7 +677,7 @@ session_notify(struct wl_listener *listener, void *data) udev_input_disable(&backend->input); wl_list_for_each(output, &compositor->output_list, link) { - fbdev_output_disable(output); + fbdev_frame_buffer_unmap(to_fbdev_output(output)); } backend->prev_state = compositor->state; From 61e5a2727a0625a7f082ced749b6d41f88ea84e3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Sep 2017 17:22:38 +0300 Subject: [PATCH 0175/1642] compositor-fbdev: always destroy renderer-output on disable If we pass the base->enabled test, then the renderer output is guaranteed to be there, so we can just destroy it. Destroying it before unmap makes the sequence match better the enable path. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor-fbdev.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 01ee18f0f..8a3ce0de7 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -485,11 +485,9 @@ fbdev_output_disable_handler(struct weston_output *base) if (!base->enabled) return 0; + pixman_renderer_output_destroy(&output->base); fbdev_frame_buffer_unmap(output); - if (base->renderer_state != NULL) - pixman_renderer_output_destroy(base); - return 0; } From 82ffe79b18bd01e016559d024b16e79d4abd736c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Sep 2017 17:25:41 +0300 Subject: [PATCH 0176/1642] compositor-fbdev: rename fbdev_output_disable_handler() This is a more logical name for the function, matching the pattern used in other backends and the hook names. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor-fbdev.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 8a3ce0de7..f5aa44b70 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -478,7 +478,7 @@ fbdev_output_enable(struct weston_output *base) } static int -fbdev_output_disable_handler(struct weston_output *base) +fbdev_output_disable(struct weston_output *base) { struct fbdev_output *output = to_fbdev_output(base); @@ -517,7 +517,7 @@ fbdev_output_create(struct fbdev_backend *backend, weston_output_init(&output->base, backend->compositor, "fbdev"); output->base.destroy = fbdev_output_destroy; - output->base.disable = fbdev_output_disable_handler; + output->base.disable = fbdev_output_disable; output->base.enable = fbdev_output_enable; /* only one static mode in list */ @@ -556,7 +556,7 @@ fbdev_output_destroy(struct weston_output *base) weston_log("Destroying fbdev output.\n"); - fbdev_output_disable_handler(base); + fbdev_output_disable(base); /* Remove the output. */ weston_output_release(&output->base); From cbe7fb0bb5fbca0ffc37cfc7f1c78fc96fad7c1e Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 14 Sep 2017 11:43:29 +0300 Subject: [PATCH 0177/1642] compositor-fbdev: fix finish_frame_timer leak The timer was never removed anywhere. Remove it in disable() to match what happens in enable(). Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by Daniel Stone --- libweston/compositor-fbdev.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index f5aa44b70..4b3605cf7 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -485,6 +485,9 @@ fbdev_output_disable(struct weston_output *base) if (!base->enabled) return 0; + wl_event_source_remove(output->finish_frame_timer); + output->finish_frame_timer = NULL; + pixman_renderer_output_destroy(&output->base); fbdev_frame_buffer_unmap(output); From b138d7afb3a2a7d51dccb12f08d70c2d86766901 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 3 Oct 2017 12:58:53 +0100 Subject: [PATCH 0178/1642] gl-renderer: Ignore INVALID modifier If the user has passed an INVALID modifier, it's because there is no applicable modifier, and the buffer layout should be determined by a magic side-channel call (e.g. bo_get_tiling). If the modifier is INVALID, don't try to pass it through to EGL, but just drop it. On the other hand, if a modifier _is_ explicitly specified and we don't have the modifiers extension, then refuse to import the buffer. Signed-off-by: Daniel Stone --- libweston/gl-renderer.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 5749aa716..244ce3099 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -1735,6 +1735,7 @@ import_simple_dmabuf(struct gl_renderer *gr, struct egl_image *image; EGLint attribs[50]; int atti = 0; + bool has_modifier; /* This requires the Mesa commit in * Mesa 10.3 (08264e5dad4df448e7718e782ad9077902089a07) or @@ -1751,6 +1752,14 @@ import_simple_dmabuf(struct gl_renderer *gr, attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; attribs[atti++] = attributes->format; + if (attributes->modifier[0] != DRM_FORMAT_MOD_INVALID) { + if (!gr->has_dmabuf_import_modifiers) + return NULL; + has_modifier = true; + } else { + has_modifier = false; + } + if (attributes->n_planes > 0) { attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT; attribs[atti++] = attributes->fd[0]; @@ -1758,7 +1767,7 @@ import_simple_dmabuf(struct gl_renderer *gr, attribs[atti++] = attributes->offset[0]; attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; attribs[atti++] = attributes->stride[0]; - if (gr->has_dmabuf_import_modifiers) { + if (has_modifier) { attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; attribs[atti++] = attributes->modifier[0] & 0xFFFFFFFF; attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; @@ -1773,7 +1782,7 @@ import_simple_dmabuf(struct gl_renderer *gr, attribs[atti++] = attributes->offset[1]; attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT; attribs[atti++] = attributes->stride[1]; - if (gr->has_dmabuf_import_modifiers) { + if (has_modifier) { attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT; attribs[atti++] = attributes->modifier[1] & 0xFFFFFFFF; attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT; @@ -1788,7 +1797,7 @@ import_simple_dmabuf(struct gl_renderer *gr, attribs[atti++] = attributes->offset[2]; attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT; attribs[atti++] = attributes->stride[2]; - if (gr->has_dmabuf_import_modifiers) { + if (has_modifier) { attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT; attribs[atti++] = attributes->modifier[2] & 0xFFFFFFFF; attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT; From be1090b5234a583ce82edfc3f562066717c41d73 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 6 Sep 2017 17:29:57 +0100 Subject: [PATCH 0179/1642] compositor-drm: Allow disabling universal planes Add a test environment variable to allow disabling universal planes. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index dc9078186..b641d61e3 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2105,8 +2105,10 @@ init_kms_caps(struct drm_backend *b) else b->cursor_height = 64; - ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); - b->universal_planes = (ret == 0); + if (!getenv("WESTON_DISABLE_UNIVERSAL_PLANES")) { + ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + b->universal_planes = (ret == 0); + } weston_log("DRM: %s universal planes\n", b->universal_planes ? "supports" : "does not support"); From cafc652cf647adce97522478410f89e8c30e854b Mon Sep 17 00:00:00 2001 From: Sergi Granell Date: Mon, 25 Sep 2017 11:57:37 +0200 Subject: [PATCH 0180/1642] compositor-wayland: use input region instead of opaque region to zxdg_shell_v6::set_window_geometry The opaque region is a few pixels off due to the rounded corners of the frame decorations, and, therefore, the input region matches the window's geometry more closely. Signed-off-by: Sergi Granell Reviewed-by: Pekka Paalanen --- libweston/compositor-wayland.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index fc929364c..00cc4d47f 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -779,12 +779,6 @@ wayland_output_resize_surface(struct wayland_output *output) wl_surface_set_input_region(output->parent.surface, region); wl_region_destroy(region); - frame_opaque_rect(output->frame, &ix, &iy, &iwidth, &iheight); - region = wl_compositor_create_region(b->parent.compositor); - wl_region_add(region, ix, iy, iwidth, iheight); - wl_surface_set_opaque_region(output->parent.surface, region); - wl_region_destroy(region); - if (output->parent.xdg_surface) { zxdg_surface_v6_set_window_geometry(output->parent.xdg_surface, ix, @@ -793,6 +787,12 @@ wayland_output_resize_surface(struct wayland_output *output) iheight); } + frame_opaque_rect(output->frame, &ix, &iy, &iwidth, &iheight); + region = wl_compositor_create_region(b->parent.compositor); + wl_region_add(region, ix, iy, iwidth, iheight); + wl_surface_set_opaque_region(output->parent.surface, region); + wl_region_destroy(region); + width = frame_width(output->frame); height = frame_height(output->frame); } else { From b4e239f29fb17f298c78a7d99b47ef6919edd326 Mon Sep 17 00:00:00 2001 From: Sergi Granell Date: Wed, 27 Sep 2017 16:06:37 +0200 Subject: [PATCH 0181/1642] compositor-wayland: destroy the appropriate output instead of exiting when receiving an xdg_toplevel::close event v2: Fix use after free spotted by Daniel Stone Signed-off-by: Sergi Granell Reviewed-by: Pekka Paalanen --- libweston/compositor-wayland.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 00cc4d47f..e2f416a38 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1098,8 +1098,12 @@ static void handle_xdg_toplevel_close(void *data, struct zxdg_toplevel_v6 *xdg_toplevel) { struct wayland_output *output = data; + struct weston_compositor *compositor = output->base.compositor; - weston_compositor_exit(output->base.compositor); + wayland_output_destroy(&output->base); + + if (wl_list_empty(&compositor->output_list)) + weston_compositor_exit(compositor); } static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { From 71ebc052505732b26591b2a5bc5a8be9b7c3e09f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Krezovi=C4=87?= Date: Tue, 7 Feb 2017 21:03:40 +0100 Subject: [PATCH 0182/1642] compositor-wayland: Don't use two different presentation methods for fs shell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch fixes the wayland backend to not use two different presentation methods when running on fullscreen-shell. See also: https://patchwork.freedesktop.org/patch/114534/ v2: - Add missing wayland_output_resize_surface() call - Start repaint loop after initial frame has been drawn v3: - Redraw the initial frame if present for mode fails Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=93514 Signed-off-by: Armin Krezović Tested-by: nerdopolis Reviewed-by: Pekka Paalanen --- libweston/compositor-wayland.c | 90 +++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index e2f416a38..1f77385b5 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -971,6 +971,38 @@ struct zwp_fullscreen_shell_mode_feedback_v1_listener mode_feedback_listener = { mode_feedback_cancelled, }; +static enum mode_status +wayland_output_fullscreen_shell_mode_feedback(struct wayland_output *output, + struct weston_mode *mode) +{ + struct wayland_backend *b = to_wayland_backend(output->base.compositor); + struct zwp_fullscreen_shell_mode_feedback_v1 *mode_feedback; + enum mode_status mode_status; + int ret = 0; + + mode_feedback = + zwp_fullscreen_shell_v1_present_surface_for_mode(b->parent.fshell, + output->parent.surface, + output->parent.output, + mode->refresh); + + zwp_fullscreen_shell_mode_feedback_v1_add_listener(mode_feedback, + &mode_feedback_listener, + &mode_status); + + output->parent.draw_initial_frame = false; + draw_initial_frame(output); + wl_surface_commit(output->parent.surface); + + mode_status = MODE_STATUS_UNKNOWN; + while (mode_status == MODE_STATUS_UNKNOWN && ret >= 0) + ret = wl_display_dispatch(b->parent.wl_display); + + zwp_fullscreen_shell_mode_feedback_v1_destroy(mode_feedback); + + return mode_status; +} + static int wayland_output_switch_mode(struct weston_output *output_base, struct weston_mode *mode) @@ -979,9 +1011,7 @@ wayland_output_switch_mode(struct weston_output *output_base, struct wayland_backend *b; struct wl_surface *old_surface; struct weston_mode *old_mode; - struct zwp_fullscreen_shell_mode_feedback_v1 *mode_feedback; enum mode_status mode_status; - int ret = 0; if (output_base == NULL) { weston_log("output is NULL.\n"); @@ -1015,25 +1045,11 @@ wayland_output_switch_mode(struct weston_output *output_base, /* Blow the old buffers because we changed size/surfaces */ wayland_output_resize_surface(output); - mode_feedback = - zwp_fullscreen_shell_v1_present_surface_for_mode(b->parent.fshell, - output->parent.surface, - output->parent.output, - mode->refresh); - zwp_fullscreen_shell_mode_feedback_v1_add_listener(mode_feedback, - &mode_feedback_listener, - &mode_status); + mode_status = wayland_output_fullscreen_shell_mode_feedback(output, mode); /* This should kick-start things again */ - output->parent.draw_initial_frame = true; wayland_output_start_repaint_loop(&output->base); - mode_status = MODE_STATUS_UNKNOWN; - while (mode_status == MODE_STATUS_UNKNOWN && ret >= 0) - ret = wl_display_dispatch(b->parent.wl_display); - - zwp_fullscreen_shell_mode_feedback_v1_destroy(mode_feedback); - if (mode_status == MODE_STATUS_FAIL) { output->base.current_mode = old_mode; wl_surface_destroy(output->parent.surface); @@ -1171,6 +1187,7 @@ wayland_output_enable(struct weston_output *base) { struct wayland_output *output = to_wayland_output(base); struct wayland_backend *b = to_wayland_backend(base->compositor); + enum mode_status mode_status; int ret = 0; weston_log("Creating %dx%d wayland output at (%d, %d)\n", @@ -1208,28 +1225,23 @@ wayland_output_enable(struct weston_output *base) output->base.switch_mode = wayland_output_switch_mode; if (b->sprawl_across_outputs) { - wayland_output_set_fullscreen(output, - WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER, - output->mode.refresh, output->parent.output); - - if (output->parent.xdg_toplevel) { - zxdg_toplevel_v6_set_fullscreen(output->parent.xdg_toplevel, - output->parent.output); - } - else if (output->parent.shell_surface) { - wl_shell_surface_set_fullscreen(output->parent.shell_surface, - WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER, - output->mode.refresh, output->parent.output); - } else if (b->parent.fshell) { - zwp_fullscreen_shell_v1_present_surface(b->parent.fshell, - output->parent.surface, - ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_CENTER, - output->parent.output); - zwp_fullscreen_shell_mode_feedback_v1_destroy( - zwp_fullscreen_shell_v1_present_surface_for_mode(b->parent.fshell, - output->parent.surface, - output->parent.output, - output->mode.refresh)); + if (b->parent.fshell) { + wayland_output_resize_surface(output); + + mode_status = wayland_output_fullscreen_shell_mode_feedback(output, &output->mode); + + if (mode_status == MODE_STATUS_FAIL) { + zwp_fullscreen_shell_v1_present_surface(b->parent.fshell, + output->parent.surface, + ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_CENTER, + output->parent.output); + + output->parent.draw_initial_frame = true; + } + } else { + wayland_output_set_fullscreen(output, + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER, + output->mode.refresh, output->parent.output); } } else if (b->fullscreen) { wayland_output_set_fullscreen(output, 0, 0, NULL); From efade28db954ad02ac283a626238df04bae146da Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Tue, 17 Oct 2017 16:14:49 +0200 Subject: [PATCH 0183/1642] libweston-desktop/xdg-shell-v6: Actually send same-as-current configure if needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a surface is in state A, and we just sent a configure for state B, setting back state A would be ignored, because state B has not been committed yet. Now, we check against the latest configured state (which is current state if configure list is empty). Reported on wlroots https://github.com/swaywm/wlroots/pull/280 Signed-off-by: Quentin Glidic Reviewed-by: Jonas Ådahl --- libweston-desktop/xdg-shell-v6.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index d82a507fa..4db3748b7 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -903,20 +903,39 @@ weston_desktop_xdg_surface_send_configure(void *user_data) static bool weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) { + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } configured; + if (!toplevel->base.configured) return false; - if (toplevel->pending.state.activated != toplevel->current.state.activated) + if (wl_list_empty(&toplevel->base.configure_list)) { + /* Last configure is actually the current state, just use it */ + configured.state = toplevel->current.state; + configured.size.width = toplevel->base.surface->width; + configured.size.height = toplevel->base.surface->height; + } else { + struct weston_desktop_xdg_toplevel_configure *configure = + wl_container_of(toplevel->base.configure_list.prev, + configure, base.link); + + configured.state = configure->state; + configured.size = configure->size; + } + + if (toplevel->pending.state.activated != configured.state.activated) return false; - if (toplevel->pending.state.fullscreen != toplevel->current.state.fullscreen) + if (toplevel->pending.state.fullscreen != configured.state.fullscreen) return false; - if (toplevel->pending.state.maximized != toplevel->current.state.maximized) + if (toplevel->pending.state.maximized != configured.state.maximized) return false; - if (toplevel->pending.state.resizing != toplevel->current.state.resizing) + if (toplevel->pending.state.resizing != configured.state.resizing) return false; - if (toplevel->base.surface->width == toplevel->pending.size.width && - toplevel->base.surface->height == toplevel->pending.size.height) + if (toplevel->pending.size.width == configured.size.width && + toplevel->pending.size.height == configured.size.height) return true; if (toplevel->pending.size.width == 0 && From aedcd8ebb08de9ea9d4d6ebdb37b3b6f8368b83b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 20 Oct 2017 16:22:44 +0300 Subject: [PATCH 0184/1642] weston: fix boolean wayland backend options Surprisingly, WESTON_OPTION_BOOLEAN uses the type int32_t, not bool. Passing in a pointer bool does not end well. Fix this to pass in pointers as parse_options() expects. This fixes a bug where 'weston --use-pixman --sprawl' would work but 'weston --sprawl --use-pixman' would ignore the --sprawl option. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic Acked-by: Daniel Stone --- compositor/main.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 0615d87e5..9e4451e5f 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1612,6 +1612,9 @@ load_wayland_backend(struct weston_compositor *c, int count = 1; int ret = 0; int i; + int32_t use_pixman_ = 0; + int32_t sprawl_ = 0; + int32_t fullscreen_ = 0; struct wet_output_config *parsed_options = wet_init_parsed_options(c); if (!parsed_options) @@ -1620,22 +1623,22 @@ load_wayland_backend(struct weston_compositor *c, config.cursor_size = 32; config.cursor_theme = NULL; config.display_name = NULL; - config.fullscreen = false; - config.sprawl = false; - config.use_pixman = false; const struct weston_option wayland_options[] = { { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, { WESTON_OPTION_STRING, "display", 0, &config.display_name }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &use_pixman_ }, { WESTON_OPTION_INTEGER, "output-count", 0, &count }, - { WESTON_OPTION_BOOLEAN, "fullscreen", 0, &config.fullscreen }, - { WESTON_OPTION_BOOLEAN, "sprawl", 0, &config.sprawl }, + { WESTON_OPTION_BOOLEAN, "fullscreen", 0, &fullscreen_ }, + { WESTON_OPTION_BOOLEAN, "sprawl", 0, &sprawl_ }, }; parse_options(wayland_options, ARRAY_LENGTH(wayland_options), argc, argv); + config.sprawl = sprawl_; + config.use_pixman = use_pixman_; + config.fullscreen = fullscreen_; section = weston_config_get_section(wc, "shell", NULL, NULL); weston_config_section_get_string(section, "cursor-theme", From b07de934ccc8783a48b1055d0207dc06701eeb31 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 19 Oct 2017 12:03:06 +0300 Subject: [PATCH 0185/1642] compositor-wayland: avoid recursive dispatch with wl_outputs Calling wl_display_roundtrip() from within a Wayland event handler means we will re-enter event dispatch, that is, it will lead to recursive dispatch. Even if libwayland-client was safe, this would lead to unexpected code paths: the first event handler function has not returned when other event handler functions may get called. In this particular case it maybe didn't hurt, but it's still a fragile pattern to use. Replace the wl_display_roundtrip() with a manual sync callback to do the work. This does not break the wayland-backend initialization sequence, because sprawl_across_outputs was set only after the roundtrip to ensure wl_registry globals have been received so the code would not have been hit anyway, and weston_backend_init() also has a second roundtrip that ensures the per wl_output events have been received before continuing. For wayland-backend output hotplug the change is insignificant because it will only delay the output creation a bit, and the parent outputs are not processed anywhere in between. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic Acked-by: Daniel Stone --- libweston/compositor-wayland.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 1f77385b5..33910316c 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -136,6 +136,7 @@ struct wayland_output { }; struct wayland_parent_output { + struct wayland_backend *backend; /**< convenience */ struct wayland_output *output; struct wl_list link; @@ -153,6 +154,8 @@ struct wayland_parent_output { uint32_t transform; uint32_t scale; + struct wl_callback *sync_cb; /**< wl_output < 2 done replacement */ + struct wl_list mode_list; struct weston_mode *preferred_mode; struct weston_mode *current_mode; @@ -2258,6 +2261,24 @@ static const struct wl_output_listener output_listener = { wayland_parent_output_mode }; +static void +output_sync_callback(void *data, struct wl_callback *callback, uint32_t unused) +{ + struct wayland_parent_output *output = data; + + assert(output->sync_cb == callback); + wl_callback_destroy(callback); + output->sync_cb = NULL; + + assert(output->backend->sprawl_across_outputs); + + wayland_output_create_for_parent_output(output->backend, output); +} + +static const struct wl_callback_listener output_sync_listener = { + output_sync_callback +}; + static void wayland_backend_register_output(struct wayland_backend *b, uint32_t id) { @@ -2267,6 +2288,7 @@ wayland_backend_register_output(struct wayland_backend *b, uint32_t id) if (!output) return; + output->backend = b; output->id = id; output->global = wl_registry_bind(b->parent.registry, id, &wl_output_interface, 1); @@ -2284,8 +2306,9 @@ wayland_backend_register_output(struct wayland_backend *b, uint32_t id) wl_list_insert(&b->parent.output_list, &output->link); if (b->sprawl_across_outputs) { - wl_display_roundtrip(b->parent.wl_display); - wayland_output_create_for_parent_output(b, output); + output->sync_cb = wl_display_sync(b->parent.wl_display); + wl_callback_add_listener(output->sync_cb, + &output_sync_listener, output); } } @@ -2294,6 +2317,9 @@ wayland_parent_output_destroy(struct wayland_parent_output *output) { struct weston_mode *mode, *next; + if (output->sync_cb) + wl_callback_destroy(output->sync_cb); + if (output->output) wayland_output_destroy(&output->output->base); From afee695adc4879d88a7d7d25d6a5e87de3986d10 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 19 Oct 2017 13:18:14 +0300 Subject: [PATCH 0186/1642] compositor-wayland: remove unused 'scale' This member is only ever set and never read, therefore it is dead. Delete dead code. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic Acked-by: Daniel Stone --- libweston/compositor-wayland.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 33910316c..d61f08c1e 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -130,7 +130,6 @@ struct wayland_output { } shm; struct weston_mode mode; - uint32_t scale; struct wl_callback *frame_cb; }; @@ -1338,7 +1337,6 @@ wayland_output_set_size(struct weston_output *base, int width, int height) output->mode.width = output_width; output->mode.height = output_height; output->mode.refresh = 60000; - output->scale = output->base.scale; wl_list_insert(&output->base.mode_list, &output->mode.link); output->base.current_mode = &output->mode; From 4dab58343bd6eebda5da77b165be93926d147b67 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 19 Oct 2017 13:19:50 +0300 Subject: [PATCH 0187/1642] compositor-wayland: fix mode_list corruption on --sprawl The wayland-backend with --sprawl is one way to trigger wayland_output_create_for_parent_output(), which intends to find a mode from the parent mode list and use it. Calling wayland_output_set_size() initialized an embedded struct weston_mode and inserts that into the mode list. Then the assignment output->mode = *mode; corrupts the mode_list by overwriting the link entry. This leads to an endless loop in bind_output() in compositor.c. Fix this by manually doing the setup that wayland_output_set_size() did and do not call it. As a side effect, it now relays the parent compositor's physical output size to our own clients. It no longer smashes the refresh rate either. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic Acked-by: Daniel Stone --- libweston/compositor-wayland.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index d61f08c1e..c0f67e9a1 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1376,18 +1376,22 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, output->base.scale = 1; output->base.transform = WL_OUTPUT_TRANSFORM_NORMAL; - if (wayland_output_set_size(&output->base, mode->width, mode->height) < 0) - goto out; - - output->mode = *mode; output->parent.output = poutput->global; output->base.make = poutput->physical.make; output->base.model = poutput->physical.model; + output->base.mm_width = poutput->physical.width; + output->base.mm_height = poutput->physical.height; wl_list_insert_list(&output->base.mode_list, &poutput->mode_list); wl_list_init(&poutput->mode_list); + /* No other mode should have CURRENT already. */ + mode->flags |= WL_OUTPUT_MODE_CURRENT; + output->base.current_mode = mode; + + /* output->mode is unused in this path. */ + weston_compositor_add_pending_output(&output->base, b->compositor); return 0; From f13227945515f3d2f654573bda861505e86e6ffa Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 19 Oct 2017 10:26:27 +0300 Subject: [PATCH 0188/1642] compositor-wayland: actually free parent_output I could not find anywhere where struct parent_output was freed, so apparently we were leaking it. Check against wayland_backend_register_output() and add the missing clean-up: removal from the parent output list, and free(). registry_handle_global_remove() also needs fixing to use a safer loop, because now we are actually removing the list item. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic Acked-by: Daniel Stone --- libweston/compositor-wayland.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index c0f67e9a1..9486deaeb 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -2333,6 +2333,9 @@ wayland_parent_output_destroy(struct wayland_parent_output *output) wl_list_remove(&mode->link); free(mode); } + + wl_list_remove(&output->link); + free(output); } static void @@ -2385,9 +2388,9 @@ registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct wayland_backend *b = data; - struct wayland_parent_output *output; + struct wayland_parent_output *output, *next; - wl_list_for_each(output, &b->parent.output_list, link) + wl_list_for_each_safe(output, next, &b->parent.output_list, link) if (output->id == name) wayland_parent_output_destroy(output); } From 7d2fc9291b505cf0df2618686bb885a3219f1435 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 18 Oct 2017 12:18:39 +0300 Subject: [PATCH 0189/1642] compositor-wayland: allow to unset fullscreen To be more symmetric with wayland_output_set_fullscreen(), implement the xdg-shell path in wayland_output_set_windowed(). This should make it possible to use the fullscreen key binding to toggle between a floating window and fullscreen also under xdg-shell. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic Acked-by: Daniel Stone --- libweston/compositor-wayland.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 9486deaeb..4266bb64f 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -873,8 +873,11 @@ wayland_output_set_windowed(struct wayland_output *output) wayland_output_resize_surface(output); - if (output->parent.shell_surface) + if (output->parent.xdg_toplevel) { + zxdg_toplevel_v6_unset_fullscreen(output->parent.xdg_toplevel); + } else if (output->parent.shell_surface) { wl_shell_surface_set_toplevel(output->parent.shell_surface); + } return 0; } From 441954325e90fd345b68b4b9aee1e5021f50717e Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 18 Oct 2017 12:31:40 +0300 Subject: [PATCH 0190/1642] compositor-wayland: windowed/fullscreen not on fullscreen-shell The set_windowed and set_fullscreen functions are only useful on a desktop shell, and never called on fullscreen-shell. Remove the confusing dead code, and ensure we notice if these functions get called in the wrong environment. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic Acked-by: Daniel Stone --- libweston/compositor-wayland.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 4266bb64f..d27b863fc 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -877,6 +877,8 @@ wayland_output_set_windowed(struct wayland_output *output) zxdg_toplevel_v6_unset_fullscreen(output->parent.xdg_toplevel); } else if (output->parent.shell_surface) { wl_shell_surface_set_toplevel(output->parent.shell_surface); + } else { + abort(); } return 0; @@ -887,9 +889,6 @@ wayland_output_set_fullscreen(struct wayland_output *output, enum wl_shell_surface_fullscreen_method method, uint32_t framerate, struct wl_output *target) { - struct wayland_backend *b = - to_wayland_backend(output->base.compositor); - if (output->frame) { frame_destroy(output->frame); output->frame = NULL; @@ -902,10 +901,8 @@ wayland_output_set_fullscreen(struct wayland_output *output, } else if (output->parent.shell_surface) { wl_shell_surface_set_fullscreen(output->parent.shell_surface, method, framerate, target); - } else if (b->parent.fshell) { - zwp_fullscreen_shell_v1_present_surface(b->parent.fshell, - output->parent.surface, - method, target); + } else { + abort(); } } From 99c92e7e52f22b030aa804af4e0b1acb5dc09620 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 23 Oct 2017 12:03:38 +0300 Subject: [PATCH 0191/1642] compositor-wayland: clarify wl_display_connect() error Clarify the error message to explicitly say one was trying to connect to a parent Wayland compositor. This hopefully is a good enough hint on what using the wayland-backend is trying to do. Add the command line display option value and WAYLAND_DISPLAY values for good measure. WAYLAND_SOCKET is not shown as libwayland-client removes it. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic Acked-by: Daniel Stone --- libweston/compositor-wayland.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index d27b863fc..bc78cbdb5 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -2541,7 +2541,10 @@ wayland_backend_create(struct weston_compositor *compositor, b->parent.wl_display = wl_display_connect(new_config->display_name); if (b->parent.wl_display == NULL) { - weston_log("failed to create display: %m\n"); + weston_log("Error: Failed to connect to parent Wayland compositor: %m\n"); + weston_log_continue(STAMP_SPACE "display option: %s, WAYLAND_DISPLAY=%s\n", + new_config->display_name ?: "(none)", + getenv("WAYLAND_DISPLAY") ?: "(not set)"); goto err_compositor; } From 2b3c97d560a8a60d6029be9795e81712e0868307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Krezovi=C4=87?= Date: Mon, 23 Oct 2017 16:10:01 +0200 Subject: [PATCH 0192/1642] compositor-wayland: fix damage coordinates with pixman renderer Damage coordinates are in global coordinate space, and they need to be translated to local coordinate space so multiple outputs can work. This path now matches the similar path in the X11 backend. This patch fixes the appearance of multiple windows in the parent compositor. Previously, all windows except the one with nested output position 0,0 would have their damage for the parent wl_surface always fall outside of the wl_surface, save the decorations which were handled separately. If the parent compositor was Weston/GL, this would lead to the output area remaining black as partial GL texture uploads would practically never update the texture. If the parent compositor was Weston/pixman, the parent windows would not update on screen unless something else caused the area to be repainted. [Pekka: adjusted commit message] Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic Acked-by: Daniel Stone --- libweston/compositor-wayland.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index bc78cbdb5..eacf385d3 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -558,11 +558,15 @@ wayland_shm_buffer_attach(struct wayland_shm_buffer *sb) int i, n; pixman_region32_init(&damage); + pixman_region32_copy(&damage, &sb->damage); + pixman_region32_translate(&damage, -sb->output->base.x, + -sb->output->base.y); + weston_transformed_region(sb->output->base.width, sb->output->base.height, sb->output->base.transform, sb->output->base.current_scale, - &sb->damage, &damage); + &damage, &damage); if (sb->output->frame) { frame_interior(sb->output->frame, &ix, &iy, &iwidth, &iheight); From 323d4b37a09ae5c8800b390ab88a3b714aa21655 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 24 Oct 2017 10:28:43 +0300 Subject: [PATCH 0193/1642] compositor-wayland: fix shm_buffer damage init It appears that wayland_shm_buffer::damage is in the global coordinate space. Therefore initializing it to width x height at 0,0 is not correct for any output not positioned at 0,0. That is, all outputs after the first one get it wrong. Initialize it from the output region, which is in the global coordinate space. While at it, add a comment to note that damage is in global coordinate space. As I can see, this was the last confusion about it. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic Acked-by: Daniel Stone --- libweston/compositor-wayland.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index eacf385d3..a0d0e62a0 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -168,7 +168,7 @@ struct wayland_shm_buffer { struct wl_buffer *buffer; void *data; size_t size; - pixman_region32_t damage; + pixman_region32_t damage; /**< in global coords */ int frame_damaged; pixman_image_t *pm_image; @@ -311,8 +311,8 @@ wayland_output_get_shm_buffer(struct wayland_output *output) wl_list_init(&sb->free_link); wl_list_insert(&output->shm.buffers, &sb->link); - pixman_region32_init_rect(&sb->damage, 0, 0, - output->base.width, output->base.height); + pixman_region32_init(&sb->damage); + pixman_region32_copy(&sb->damage, &output->base.region); sb->frame_damaged = 1; sb->data = data; From 71c4f70e08faad6002ec8fe8cd1c7930bee8373b Mon Sep 17 00:00:00 2001 From: "Reynaldo H. Verdejo Pinochet" Date: Fri, 3 Nov 2017 12:48:31 -0700 Subject: [PATCH 0194/1642] buildsystem: add missing pkg-config stub for libweston uninstalled -uninstalled.pc files are a pkg-config facility for working with uninstalled libraries. With pkg-config, foo-uninstalled.pc overrides foo.pc. foo-uninstalled.pc should never be installed, and will be generated with references to the build directory. If you set up your environment so pkg-config looks for .pc files in your build directories, you can use this to build and link against libraries you haven't installed with "make install". This can save time and space over installing with a prefix. Signed-off-by: Reynaldo H. Verdejo Pinochet Acked-by: Bryce Harrington Reviewed-by: Derek Foreman --- configure.ac | 2 ++ libweston-desktop/libweston-desktop-uninstalled.pc.in | 9 +++++++++ libweston/libweston-uninstalled.pc.in | 9 +++++++++ 3 files changed, 20 insertions(+) create mode 100644 libweston-desktop/libweston-desktop-uninstalled.pc.in create mode 100644 libweston/libweston-uninstalled.pc.in diff --git a/configure.ac b/configure.ac index c287fac64..5f31bbcb0 100644 --- a/configure.ac +++ b/configure.ac @@ -679,7 +679,9 @@ AC_CONFIG_FILES([Makefile libweston/version.h compositor/weston.pc]) # AC_CONFIG_FILES needs the full name when running autoconf, so we need to use # libweston_abi_version here, and outside [] because of m4 quoting rules AC_CONFIG_FILES([libweston/libweston-]libweston_major_version[.pc:libweston/libweston.pc.in]) +AC_CONFIG_FILES([libweston/libweston-]libweston_major_version[-uninstalled.pc:libweston/libweston-uninstalled.pc.in]) AC_CONFIG_FILES([libweston-desktop/libweston-desktop-]libweston_major_version[.pc:libweston-desktop/libweston-desktop.pc.in]) +AC_CONFIG_FILES([libweston-desktop/libweston-desktop-]libweston_major_version[-uninstalled.pc:libweston-desktop/libweston-desktop-uninstalled.pc.in]) AM_CONDITIONAL([HAVE_GIT_REPO], [test -f $srcdir/.git/logs/HEAD]) diff --git a/libweston-desktop/libweston-desktop-uninstalled.pc.in b/libweston-desktop/libweston-desktop-uninstalled.pc.in new file mode 100644 index 000000000..4fb798d58 --- /dev/null +++ b/libweston-desktop/libweston-desktop-uninstalled.pc.in @@ -0,0 +1,9 @@ +libdir=@abs_top_builddir@/.libs +includedir=@abs_top_srcdir@ + +Name: libweston-desktop, uninstalled +Description: Desktop shells abstraction library for libweston compositors (not installed) +Version: @WESTON_VERSION@ +Requires.private: libweston-@LIBWESTON_MAJOR@-uninstalled wayland-server +Cflags: -I${includedir}/libweston-desktop -I${includedir}/shared +Libs: -L${libdir} -lweston-desktop-@LIBWESTON_MAJOR@ diff --git a/libweston/libweston-uninstalled.pc.in b/libweston/libweston-uninstalled.pc.in new file mode 100644 index 000000000..b6efd8a4d --- /dev/null +++ b/libweston/libweston-uninstalled.pc.in @@ -0,0 +1,9 @@ +libdir=@abs_top_builddir@/.libs +includedir=@abs_top_srcdir@ + +Name: libweston API, uninstalled +Description: Header files for libweston compositors development (not installed) +Version: @WESTON_VERSION@ +Requires.private: wayland-server pixman-1 xkbcommon +Cflags: -I${includedir}/libweston -I${includedir}/shared +Libs: -L${libdir} -lweston-@LIBWESTON_MAJOR@ From 0343c6ac69ac8bd88a625e2429e87a8db86a04dc Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 17 Nov 2017 13:39:06 +0200 Subject: [PATCH 0195/1642] ivi-shell: Fix incorrect use of logical instead of bitwise operator Fix the code to use the correct bitwise AND operator '&', instead of the currently used logical AND operator '&&', to check the value of a bit flag in a bit mask. This problem was reported as a warning when building with clang. Reviewed-by: Emre Ucan --- ivi-shell/ivi-layout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 87adde326..394179b8c 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -617,7 +617,7 @@ commit_changes(struct ivi_layout *layout) * the weston_view below this ivi_view. Otherwise content * of this ivi_view will stay visible. */ - if ((ivilayer->prop.event_mask | ivisurf->prop.event_mask) && + if ((ivilayer->prop.event_mask | ivisurf->prop.event_mask) & IVI_NOTIFICATION_VISIBILITY) weston_view_damage_below(ivi_view->view); From e2a5f9e02d22f2271f6f0d6be10ba3d1b320dca2 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Mon, 27 Nov 2017 10:54:49 +0200 Subject: [PATCH 0196/1642] shared: Add timespec_is_zero helper Add a helper function to check if a struct timespec is zero. This helper will be used in the upcoming commits to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- shared/timespec-util.h | 12 ++++++++++++ tests/timespec-test.c | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/shared/timespec-util.h b/shared/timespec-util.h index 34a120ae6..7260dc8b0 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -29,6 +29,7 @@ #include #include #include +#include #define NSEC_PER_SEC 1000000000 @@ -133,6 +134,17 @@ timespec_sub_to_msec(const struct timespec *a, const struct timespec *b) return timespec_sub_to_nsec(a, b) / 1000000; } +/* Check if a timespec is zero + * + * \param a timespec + * \return whether the timespec is zero + */ +static inline bool +timespec_is_zero(const struct timespec *a) +{ + return a->tv_sec == 0 && a->tv_nsec == 0; +} + /* Convert milli-Hertz to nanoseconds * * \param mhz frequency in mHz, not zero diff --git a/tests/timespec-test.c b/tests/timespec-test.c index a50391108..4e83605d3 100644 --- a/tests/timespec-test.c +++ b/tests/timespec-test.c @@ -164,3 +164,14 @@ ZUC_TEST(timespec_test, timespec_sub_to_msec) b.tv_nsec = 1000000L; ZUC_ASSERT_EQ((998 * 1000) + 1, timespec_sub_to_msec(&a, &b)); } + +ZUC_TEST(timespec_test, timespec_is_zero) +{ + struct timespec zero = { 0 }; + struct timespec non_zero_sec = { .tv_sec = 1, .tv_nsec = 0 }; + struct timespec non_zero_nsec = { .tv_sec = 0, .tv_nsec = 1 }; + + ZUC_ASSERT_TRUE(timespec_is_zero(&zero)); + ZUC_ASSERT_FALSE(timespec_is_zero(&non_zero_nsec)); + ZUC_ASSERT_FALSE(timespec_is_zero(&non_zero_sec)); +} From 6c2752a863becf41a43c1b0af99e1b713698e0b1 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 16 Nov 2017 18:20:51 +0200 Subject: [PATCH 0197/1642] shared: Add helpers to convert between various time units and timespec Add helper functions to make it easy and less error-prone to convert between values in various time units (nsec, usec, msec) and struct timespec. These helpers are going to be used in the upcoming commits to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- shared/timespec-util.h | 47 +++++++++++++++++++++++++++ tests/timespec-test.c | 72 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/shared/timespec-util.h b/shared/timespec-util.h index 7260dc8b0..f9736c270 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -134,6 +134,53 @@ timespec_sub_to_msec(const struct timespec *a, const struct timespec *b) return timespec_sub_to_nsec(a, b) / 1000000; } +/* Convert timespec to microseconds + * + * \param a timespec + * \return microseconds + * + * Rounding to integer microseconds happens always down (floor()). + */ +static inline int64_t +timespec_to_usec(const struct timespec *a) +{ + return (int64_t)a->tv_sec * 1000000 + a->tv_nsec / 1000; +} + +/* Convert nanoseconds to timespec + * + * \param a timespec + * \param b nanoseconds + */ +static inline void +timespec_from_nsec(struct timespec *a, int64_t b) +{ + a->tv_sec = b / NSEC_PER_SEC; + a->tv_nsec = b % NSEC_PER_SEC; +} + +/* Convert microseconds to timespec + * + * \param a timespec + * \param b microseconds + */ +static inline void +timespec_from_usec(struct timespec *a, int64_t b) +{ + timespec_from_nsec(a, b * 1000); +} + +/* Convert milliseconds to timespec + * + * \param a timespec + * \param b milliseconds + */ +static inline void +timespec_from_msec(struct timespec *a, int64_t b) +{ + timespec_from_nsec(a, b * 1000000); +} + /* Check if a timespec is zero * * \param a timespec diff --git a/tests/timespec-test.c b/tests/timespec-test.c index 4e83605d3..f127bceeb 100644 --- a/tests/timespec-test.c +++ b/tests/timespec-test.c @@ -60,6 +60,15 @@ ZUC_TEST(timespec_test, timespec_to_nsec) ZUC_ASSERT_EQ(timespec_to_nsec(&a), (NSEC_PER_SEC * 4ULL) + 4); } +ZUC_TEST(timespec_test, timespec_to_usec) +{ + struct timespec a; + + a.tv_sec = 4; + a.tv_nsec = 4000; + ZUC_ASSERT_EQ(timespec_to_usec(&a), (4000000ULL) + 4); +} + ZUC_TEST(timespec_test, timespec_to_msec) { struct timespec a; @@ -165,6 +174,69 @@ ZUC_TEST(timespec_test, timespec_sub_to_msec) ZUC_ASSERT_EQ((998 * 1000) + 1, timespec_sub_to_msec(&a, &b)); } +ZUC_TEST(timespec_test, timespec_from_nsec) +{ + struct timespec a; + + timespec_from_nsec(&a, 0); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_nsec(&a, NSEC_PER_SEC - 1); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(NSEC_PER_SEC - 1, a.tv_nsec); + + timespec_from_nsec(&a, NSEC_PER_SEC); + ZUC_ASSERT_EQ(1, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_nsec(&a, (5L * NSEC_PER_SEC) + 1); + ZUC_ASSERT_EQ(5, a.tv_sec); + ZUC_ASSERT_EQ(1, a.tv_nsec); +} + +ZUC_TEST(timespec_test, timespec_from_usec) +{ + struct timespec a; + + timespec_from_usec(&a, 0); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_usec(&a, 999999); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(999999 * 1000, a.tv_nsec); + + timespec_from_usec(&a, 1000000); + ZUC_ASSERT_EQ(1, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_usec(&a, 5000001); + ZUC_ASSERT_EQ(5, a.tv_sec); + ZUC_ASSERT_EQ(1000, a.tv_nsec); +} + +ZUC_TEST(timespec_test, timespec_from_msec) +{ + struct timespec a; + + timespec_from_msec(&a, 0); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_msec(&a, 999); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(999 * 1000000, a.tv_nsec); + + timespec_from_msec(&a, 1000); + ZUC_ASSERT_EQ(1, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_msec(&a, 5001); + ZUC_ASSERT_EQ(5, a.tv_sec); + ZUC_ASSERT_EQ(1000000, a.tv_nsec); +} + ZUC_TEST(timespec_test, timespec_is_zero) { struct timespec zero = { 0 }; From 8250a61de139a1e3da41bf977ce6e1a657aa69c8 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 16 Nov 2017 18:20:52 +0200 Subject: [PATCH 0198/1642] build,libweston: Use struct timespec for animations Change code related to animations to use struct timespec to represent time. This commit is part of a larger effort to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen This bumps the libweston major version due to breakage in the animation ABI. The commits following this one break more ABI in other parts. Signed-off-by: Pekka Paalanen --- configure.ac | 4 ++-- desktop-shell/shell.c | 26 ++++++++++++++------------ desktop-shell/shell.h | 2 +- libweston/animation.c | 29 ++++++++++++++++++----------- libweston/compositor.c | 5 ++++- libweston/compositor.h | 7 ++++--- libweston/spring-tool.c | 13 ++++++++----- libweston/zoom.c | 7 ++++--- 8 files changed, 55 insertions(+), 38 deletions(-) diff --git a/configure.ac b/configure.ac index 5f31bbcb0..d6a9cbd2a 100644 --- a/configure.ac +++ b/configure.ac @@ -3,9 +3,9 @@ m4_define([weston_minor_version], [0]) m4_define([weston_micro_version], [90]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) -m4_define([libweston_major_version], [3]) +m4_define([libweston_major_version], [4]) m4_define([libweston_minor_version], [0]) -m4_define([libweston_patch_version], [90]) +m4_define([libweston_patch_version], [0]) AC_PREREQ([2.64]) AC_INIT([weston], diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 1f99efe3f..553804177 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -41,6 +41,7 @@ #include "weston-desktop-shell-server-protocol.h" #include "shared/config-parser.h" #include "shared/helpers.h" +#include "shared/timespec-util.h" #include "libweston-desktop/libweston-desktop.h" #define DEFAULT_NUM_WORKSPACES 1 @@ -1027,7 +1028,7 @@ reverse_workspace_change_animation(struct desktop_shell *shell, shell->workspaces.anim_to = to; shell->workspaces.anim_from = from; shell->workspaces.anim_dir = -1 * shell->workspaces.anim_dir; - shell->workspaces.anim_timestamp = 0; + shell->workspaces.anim_timestamp = (struct timespec) { 0 }; weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); weston_layer_set_position(&from->layer, WESTON_LAYER_POSITION_NORMAL - 1); @@ -1084,14 +1085,15 @@ finish_workspace_change_animation(struct desktop_shell *shell, static void animate_workspace_change_frame(struct weston_animation *animation, - struct weston_output *output, uint32_t msecs) + struct weston_output *output, + const struct timespec *time) { struct desktop_shell *shell = container_of(animation, struct desktop_shell, workspaces.animation); struct workspace *from = shell->workspaces.anim_from; struct workspace *to = shell->workspaces.anim_to; - uint32_t t; + int64_t t; double x, y; if (workspace_is_empty(from) && workspace_is_empty(to)) { @@ -1099,19 +1101,19 @@ animate_workspace_change_frame(struct weston_animation *animation, return; } - if (shell->workspaces.anim_timestamp == 0) { + if (timespec_is_zero(&shell->workspaces.anim_timestamp)) { if (shell->workspaces.anim_current == 0.0) - shell->workspaces.anim_timestamp = msecs; + shell->workspaces.anim_timestamp = *time; else - shell->workspaces.anim_timestamp = - msecs - + timespec_add_msec(&shell->workspaces.anim_timestamp, + time, /* Invers of movement function 'y' below. */ - (asin(1.0 - shell->workspaces.anim_current) * - DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH * - M_2_PI); + -(asin(1.0 - shell->workspaces.anim_current) * + DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH * + M_2_PI)); } - t = msecs - shell->workspaces.anim_timestamp; + t = timespec_sub_to_msec(time, &shell->workspaces.anim_timestamp); /* * x = [0, π/2] @@ -1154,7 +1156,7 @@ animate_workspace_change(struct desktop_shell *shell, shell->workspaces.anim_from = from; shell->workspaces.anim_to = to; shell->workspaces.anim_current = 0.0; - shell->workspaces.anim_timestamp = 0; + shell->workspaces.anim_timestamp = (struct timespec) { 0 }; output = container_of(shell->compositor->output_list.next, struct weston_output, link); diff --git a/desktop-shell/shell.h b/desktop-shell/shell.h index 063641d21..0ff737bbc 100644 --- a/desktop-shell/shell.h +++ b/desktop-shell/shell.h @@ -188,7 +188,7 @@ struct desktop_shell { struct weston_animation animation; struct wl_list anim_sticky_list; int anim_dir; - uint32_t anim_timestamp; + struct timespec anim_timestamp; double anim_current; struct workspace *anim_from; struct workspace *anim_to; diff --git a/libweston/animation.c b/libweston/animation.c index 914135a23..c2f8b9bad 100644 --- a/libweston/animation.c +++ b/libweston/animation.c @@ -30,12 +30,14 @@ #include #include #include +#include #include #include #include "compositor.h" #include "shared/helpers.h" +#include "shared/timespec-util.h" WL_EXPORT void weston_spring_init(struct weston_spring *spring, @@ -52,7 +54,7 @@ weston_spring_init(struct weston_spring *spring, } WL_EXPORT void -weston_spring_update(struct weston_spring *spring, uint32_t msec) +weston_spring_update(struct weston_spring *spring, const struct timespec *time) { double force, v, current, step; @@ -61,14 +63,16 @@ weston_spring_update(struct weston_spring *spring, uint32_t msec) * This handles the case where time moves backwards or forwards in * large jumps. */ - if (msec - spring->timestamp > 1000) { - weston_log("unexpectedly large timestamp jump (from %u to %u)\n", - spring->timestamp, msec); - spring->timestamp = msec - 1000; + if (timespec_sub_to_msec(time, &spring->timestamp) > 1000) { + weston_log("unexpectedly large timestamp jump " + "(from %" PRId64 " to %" PRId64 ")\n", + timespec_to_msec(&spring->timestamp), + timespec_to_msec(time)); + timespec_add_msec(&spring->timestamp, time, -1000); } step = 0.01; - while (4 < msec - spring->timestamp) { + while (4 < timespec_sub_to_msec(time, &spring->timestamp)) { current = spring->current; v = current - spring->previous; force = spring->k * (spring->target - current) / 10.0 + @@ -108,7 +112,7 @@ weston_spring_update(struct weston_spring *spring, uint32_t msec) break; } - spring->timestamp += 4; + timespec_add_msec(&spring->timestamp, &spring->timestamp, 4); } } @@ -161,7 +165,8 @@ handle_animation_view_destroy(struct wl_listener *listener, void *data) static void weston_view_animation_frame(struct weston_animation *base, - struct weston_output *output, uint32_t msecs) + struct weston_output *output, + const struct timespec *time) { struct weston_view_animation *animation = container_of(base, @@ -170,9 +175,9 @@ weston_view_animation_frame(struct weston_animation *base, animation->view->surface->compositor; if (base->frame_counter <= 1) - animation->spring.timestamp = msecs; + animation->spring.timestamp = *time; - weston_spring_update(&animation->spring, msecs); + weston_spring_update(&animation->spring, time); if (weston_spring_done(&animation->spring)) { weston_view_schedule_repaint(animation->view); @@ -254,8 +259,10 @@ weston_view_animation_create(struct weston_view *view, static void weston_view_animation_run(struct weston_view_animation *animation) { + struct timespec zero_time = { 0 }; + animation->animation.frame_counter = 0; - weston_view_animation_frame(&animation->animation, NULL, 0); + weston_view_animation_frame(&animation->animation, NULL, &zero_time); } static void diff --git a/libweston/compositor.c b/libweston/compositor.c index 71a9b38c9..037b4b5f1 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2292,12 +2292,15 @@ weston_output_repaint(struct weston_output *output, void *repaint_data) struct wl_list frame_callback_list; pixman_region32_t output_damage; int r; + struct timespec frame_time; if (output->destroying) return 0; TL_POINT("core_repaint_begin", TLP_OUTPUT(output), TLP_END); + timespec_from_msec(&frame_time, output->frame_time); + /* Rebuild the surface list and update surface transforms up front. */ weston_compositor_build_view_list(ec); @@ -2352,7 +2355,7 @@ weston_output_repaint(struct weston_output *output, void *repaint_data) wl_list_for_each_safe(animation, next, &output->animation_list, link) { animation->frame_counter++; - animation->frame(animation, output, output->frame_time); + animation->frame(animation, output, &frame_time); } TL_POINT("core_repaint_posted", TLP_OUTPUT(output), TLP_END); diff --git a/libweston/compositor.h b/libweston/compositor.h index 8b2d2b069..23d709ceb 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -101,7 +101,8 @@ struct weston_mode { struct weston_animation { void (*frame)(struct weston_animation *animation, - struct weston_output *output, uint32_t msecs); + struct weston_output *output, + const struct timespec *time); int frame_counter; struct wl_list link; }; @@ -119,7 +120,7 @@ struct weston_spring { double target; double previous; double min, max; - uint32_t timestamp; + struct timespec timestamp; uint32_t clip; }; @@ -1352,7 +1353,7 @@ void weston_spring_init(struct weston_spring *spring, double k, double current, double target); void -weston_spring_update(struct weston_spring *spring, uint32_t msec); +weston_spring_update(struct weston_spring *spring, const struct timespec *time); int weston_spring_done(struct weston_spring *spring); diff --git a/libweston/spring-tool.c b/libweston/spring-tool.c index 9e7c344e7..a6ce055dc 100644 --- a/libweston/spring-tool.c +++ b/libweston/spring-tool.c @@ -24,10 +24,12 @@ */ #include +#include #include "config.h" #include "compositor.h" +#include "shared/timespec-util.h" WL_EXPORT void weston_view_geometry_dirty(struct weston_view *view) @@ -59,17 +61,18 @@ main(int argc, char *argv[]) const double friction = 1400; struct weston_spring spring; - uint32_t time = 0; + struct timespec time = { 0 }; weston_spring_init(&spring, k, current, target); spring.friction = friction; spring.previous = 0.48; - spring.timestamp = 0; + spring.timestamp = (struct timespec) { 0 }; while (!weston_spring_done(&spring)) { - printf("\t%d\t%f\n", time, spring.current); - weston_spring_update(&spring, time); - time += 16; + printf("\t%" PRId64 "\t%f\n", + timespec_to_msec(&time), spring.current); + weston_spring_update(&spring, &time); + timespec_add_msec(&time, &time, 16); } return 0; diff --git a/libweston/zoom.c b/libweston/zoom.c index a1a1ab219..84f1a3204 100644 --- a/libweston/zoom.c +++ b/libweston/zoom.c @@ -36,12 +36,13 @@ static void weston_zoom_frame_z(struct weston_animation *animation, - struct weston_output *output, uint32_t msecs) + struct weston_output *output, + const struct timespec *time) { if (animation->frame_counter <= 1) - output->zoom.spring_z.timestamp = msecs; + output->zoom.spring_z.timestamp = *time; - weston_spring_update(&output->zoom.spring_z, msecs); + weston_spring_update(&output->zoom.spring_z, time); if (output->zoom.spring_z.current > output->zoom.max_level) output->zoom.spring_z.current = output->zoom.max_level; From e6ac2afa5659276e4050352a5d7626c6903fbce6 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 16 Nov 2017 18:20:53 +0200 Subject: [PATCH 0199/1642] libweston: Use struct timespec for the output presentation timestamp Store the output presentation timestamp as struct timespec. This commit is part of a larger effort to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 12 ++++++------ libweston/compositor.h | 2 +- libweston/screenshooter.c | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 037b4b5f1..cfa7eace0 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2292,15 +2292,13 @@ weston_output_repaint(struct weston_output *output, void *repaint_data) struct wl_list frame_callback_list; pixman_region32_t output_damage; int r; - struct timespec frame_time; + uint32_t frame_time_msec; if (output->destroying) return 0; TL_POINT("core_repaint_begin", TLP_OUTPUT(output), TLP_END); - timespec_from_msec(&frame_time, output->frame_time); - /* Rebuild the surface list and update surface transforms up front. */ weston_compositor_build_view_list(ec); @@ -2348,14 +2346,16 @@ weston_output_repaint(struct weston_output *output, void *repaint_data) weston_compositor_repick(ec); + frame_time_msec = timespec_to_msec(&output->frame_time); + wl_list_for_each_safe(cb, cnext, &frame_callback_list, link) { - wl_callback_send_done(cb->resource, output->frame_time); + wl_callback_send_done(cb->resource, frame_time_msec); wl_resource_destroy(cb->resource); } wl_list_for_each_safe(animation, next, &output->animation_list, link) { animation->frame_counter++; - animation->frame(animation, output, &frame_time); + animation->frame(animation, output, &output->frame_time); } TL_POINT("core_repaint_posted", TLP_OUTPUT(output), TLP_END); @@ -2520,7 +2520,7 @@ weston_output_finish_frame(struct weston_output *output, output->msc, presented_flags); - output->frame_time = timespec_to_msec(stamp); + output->frame_time = *stamp; timespec_add_nsec(&output->next_repaint, stamp, refresh_nsec); timespec_add_msec(&output->next_repaint, &output->next_repaint, diff --git a/libweston/compositor.h b/libweston/compositor.h index 23d709ceb..59c349d57 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -193,7 +193,7 @@ struct weston_output { struct wl_signal frame_signal; struct wl_signal destroy_signal; int move_x, move_y; - uint32_t frame_time; /* presentation timestamp in milliseconds */ + struct timespec frame_time; /* presentation timestamp */ uint64_t msc; /* media stream counter */ int disable_planes; int destroying; diff --git a/libweston/screenshooter.c b/libweston/screenshooter.c index 2c5dacc12..f4e3f4def 100644 --- a/libweston/screenshooter.c +++ b/libweston/screenshooter.c @@ -36,6 +36,7 @@ #include "compositor.h" #include "shared/helpers.h" +#include "shared/timespec-util.h" #include "wcap/wcap-decode.h" @@ -259,7 +260,7 @@ weston_recorder_frame_notify(struct wl_listener *listener, void *data) container_of(listener, struct weston_recorder, frame_listener); struct weston_output *output = data; struct weston_compositor *compositor = output->compositor; - uint32_t msecs = output->frame_time; + uint32_t msecs = timespec_to_msec(&output->frame_time); pixman_box32_t *r; pixman_region32_t damage, transformed_damage; int i, j, k, n, width, height, run, stride; From 84b31f895682a2f2627934d79ade15e573583ae9 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 24 Nov 2017 18:01:46 +0200 Subject: [PATCH 0200/1642] libweston: Use struct timespec for motion events Change code related to motion events to use struct timespec to represent time. This commit is part of a larger effort to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- compositor/screen-share.c | 6 +++++- desktop-shell/exposay.c | 3 ++- desktop-shell/shell.c | 12 ++++++++---- ivi-shell/hmi-controller.c | 3 ++- libweston-desktop/seat.c | 2 +- libweston/compositor-rdp.c | 10 +++++++--- libweston/compositor-wayland.c | 5 ++++- libweston/compositor-x11.c | 6 ++++-- libweston/compositor.h | 12 +++++++----- libweston/data-device.c | 8 ++++++-- libweston/input.c | 30 ++++++++++++++++++------------ libweston/libinput-device.c | 19 ++++++++++--------- tests/weston-test.c | 6 +++++- 13 files changed, 79 insertions(+), 43 deletions(-) diff --git a/compositor/screen-share.c b/compositor/screen-share.c index a6f82b19c..c7aad3136 100644 --- a/compositor/screen-share.c +++ b/compositor/screen-share.c @@ -44,6 +44,7 @@ #include "weston.h" #include "shared/helpers.h" #include "shared/os-compatibility.h" +#include "shared/timespec-util.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" struct shared_output { @@ -140,11 +141,14 @@ ss_seat_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { struct ss_seat *seat = data; + struct timespec ts; + + timespec_from_msec(&ts, time); /* No transformation of input position is required here because we are * always receiving the input in the same coordinates as the output. */ - notify_motion_absolute(&seat->base, time, + notify_motion_absolute(&seat->base, &ts, wl_fixed_to_double(x), wl_fixed_to_double(y)); notify_pointer_frame(&seat->base); } diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c index b11a7f794..15b86863a 100644 --- a/desktop-shell/exposay.c +++ b/desktop-shell/exposay.c @@ -349,7 +349,8 @@ exposay_focus(struct weston_pointer_grab *grab) } static void -exposay_motion(struct weston_pointer_grab *grab, uint32_t time, +exposay_motion(struct weston_pointer_grab *grab, + const struct timespec *time, struct weston_pointer_motion_event *event) { struct desktop_shell *shell = diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 553804177..62dfa4508 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -1482,7 +1482,8 @@ constrain_position(struct weston_move_grab *move, int *cx, int *cy) } static void -move_grab_motion(struct weston_pointer_grab *grab, uint32_t time, +move_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, struct weston_pointer_motion_event *event) { struct weston_move_grab *move = (struct weston_move_grab *) grab; @@ -1577,7 +1578,8 @@ struct weston_resize_grab { }; static void -resize_grab_motion(struct weston_pointer_grab *grab, uint32_t time, +resize_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, struct weston_pointer_motion_event *event) { struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; @@ -1767,7 +1769,8 @@ busy_cursor_grab_focus(struct weston_pointer_grab *base) } static void -busy_cursor_grab_motion(struct weston_pointer_grab *grab, uint32_t time, +busy_cursor_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, struct weston_pointer_motion_event *event) { weston_pointer_move(grab->pointer, event); @@ -3443,7 +3446,8 @@ terminate_binding(struct weston_keyboard *keyboard, uint32_t time, } static void -rotate_grab_motion(struct weston_pointer_grab *grab, uint32_t time, +rotate_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, struct weston_pointer_motion_event *event) { struct rotate_grab *rotate = diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index d5403e06c..6713eca3e 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -1534,7 +1534,8 @@ layer_set_pos(struct ivi_layout_layer *layer, wl_fixed_t pos_x, } static void -pointer_move_grab_motion(struct weston_pointer_grab *grab, uint32_t time, +pointer_move_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, struct weston_pointer_motion_event *event) { struct pointer_move_grab *pnt_move_grab = diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c index 963088070..18a329c4c 100644 --- a/libweston-desktop/seat.c +++ b/libweston-desktop/seat.c @@ -108,7 +108,7 @@ weston_desktop_seat_popup_grab_pointer_focus(struct weston_pointer_grab *grab) static void weston_desktop_seat_popup_grab_pointer_motion(struct weston_pointer_grab *grab, - uint32_t time, + const struct timespec *time, struct weston_pointer_motion_event *event) { weston_pointer_send_motion(grab->pointer, time, event); diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 990ddc66d..315d2ade4 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -81,6 +81,7 @@ #endif #include "shared/helpers.h" +#include "shared/timespec-util.h" #include "compositor.h" #include "compositor-rdp.h" #include "pixman-renderer.h" @@ -1029,11 +1030,13 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) struct rdp_output *output; uint32_t button = 0; bool need_frame = false; + struct timespec time; if (flags & PTR_FLAGS_MOVE) { output = peerContext->rdpBackend->output; if (x < output->base.width && y < output->base.height) { - notify_motion_absolute(peerContext->item.seat, weston_compositor_get_time(), + timespec_from_msec(&time, weston_compositor_get_time()); + notify_motion_absolute(peerContext->item.seat, &time, x, y); need_frame = true; } @@ -1088,11 +1091,12 @@ xf_extendedMouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) { RdpPeerContext *peerContext = (RdpPeerContext *)input->context; struct rdp_output *output; + struct timespec time; output = peerContext->rdpBackend->output; if (x < output->base.width && y < output->base.height) { - notify_motion_absolute(peerContext->item.seat, weston_compositor_get_time(), - x, y); + timespec_from_msec(&time, weston_compositor_get_time()); + notify_motion_absolute(peerContext->item.seat, &time, x, y); } FREERDP_CB_RETURN(TRUE); diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index a0d0e62a0..8f26c5450 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -53,6 +53,7 @@ #include "shared/image-loader.h" #include "shared/os-compatibility.h" #include "shared/cairo-util.h" +#include "shared/timespec-util.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "xdg-shell-unstable-v6-client-protocol.h" #include "presentation-time-server-protocol.h" @@ -1589,6 +1590,7 @@ input_handle_motion(void *data, struct wl_pointer *pointer, enum theme_location location; bool want_frame = false; double x, y; + struct timespec ts; if (!input->output) return; @@ -1626,7 +1628,8 @@ input_handle_motion(void *data, struct wl_pointer *pointer, } if (location == THEME_LOCATION_CLIENT_AREA) { - notify_motion_absolute(&input->base, time, x, y); + timespec_from_msec(&ts, time); + notify_motion_absolute(&input->base, &ts, x, y); want_frame = true; } diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 60843ac15..7e24afcb4 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -55,6 +55,7 @@ #include "shared/config-parser.h" #include "shared/helpers.h" #include "shared/image-loader.h" +#include "shared/timespec-util.h" #include "gl-renderer.h" #include "weston-egl-ext.h" #include "pixman-renderer.h" @@ -1242,6 +1243,7 @@ x11_backend_deliver_motion_event(struct x11_backend *b, struct weston_pointer_motion_event motion_event = { 0 }; xcb_motion_notify_event_t *motion_notify = (xcb_motion_notify_event_t *) event; + struct timespec time; if (!b->has_xkb) update_xkb_state_from_core(b, motion_notify->state); @@ -1260,8 +1262,8 @@ x11_backend_deliver_motion_event(struct x11_backend *b, .dy = y - b->prev_y }; - notify_motion(&b->core_seat, weston_compositor_get_time(), - &motion_event); + timespec_from_msec(&time, weston_compositor_get_time()); + notify_motion(&b->core_seat, &time, &motion_event); notify_pointer_frame(&b->core_seat); b->prev_x = x; diff --git a/libweston/compositor.h b/libweston/compositor.h index 59c349d57..971676cf9 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -249,7 +249,7 @@ enum weston_pointer_motion_mask { struct weston_pointer_motion_event { uint32_t mask; - uint64_t time_usec; + struct timespec time; double x; double y; double dx; @@ -268,7 +268,8 @@ struct weston_pointer_axis_event { struct weston_pointer_grab; struct weston_pointer_grab_interface { void (*focus)(struct weston_pointer_grab *grab); - void (*motion)(struct weston_pointer_grab *grab, uint32_t time, + void (*motion)(struct weston_pointer_grab *grab, + const struct timespec *time, struct weston_pointer_motion_event *event); void (*button)(struct weston_pointer_grab *grab, uint32_t time, uint32_t button, uint32_t state); @@ -423,7 +424,8 @@ weston_pointer_create(struct weston_seat *seat); void weston_pointer_destroy(struct weston_pointer *pointer); void -weston_pointer_send_motion(struct weston_pointer *pointer, uint32_t time, +weston_pointer_send_motion(struct weston_pointer *pointer, + const struct timespec *time, struct weston_pointer_motion_event *event); bool weston_pointer_has_focus_resource(struct weston_pointer *pointer); @@ -1363,10 +1365,10 @@ weston_view_activate(struct weston_view *view, uint32_t flags); void -notify_motion(struct weston_seat *seat, uint32_t time, +notify_motion(struct weston_seat *seat, const struct timespec *time, struct weston_pointer_motion_event *event); void -notify_motion_absolute(struct weston_seat *seat, uint32_t time, +notify_motion_absolute(struct weston_seat *seat, const struct timespec *time, double x, double y); void notify_button(struct weston_seat *seat, uint32_t time, int32_t button, diff --git a/libweston/data-device.c b/libweston/data-device.c index cba1e116e..58a440aef 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -34,6 +34,7 @@ #include "compositor.h" #include "shared/helpers.h" +#include "shared/timespec-util.h" struct weston_drag { struct wl_client *client; @@ -575,7 +576,8 @@ drag_grab_focus(struct weston_pointer_grab *grab) } static void -drag_grab_motion(struct weston_pointer_grab *grab, uint32_t time, +drag_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, struct weston_pointer_motion_event *event) { struct weston_pointer_drag *drag = @@ -583,6 +585,7 @@ drag_grab_motion(struct weston_pointer_grab *grab, uint32_t time, struct weston_pointer *pointer = drag->grab.pointer; float fx, fy; wl_fixed_t sx, sy; + uint32_t msecs; weston_pointer_move(pointer, event); @@ -594,11 +597,12 @@ drag_grab_motion(struct weston_pointer_grab *grab, uint32_t time, } if (drag->base.focus_resource) { + msecs = timespec_to_msec(time); weston_view_from_global_fixed(drag->base.focus, pointer->x, pointer->y, &sx, &sy); - wl_data_device_send_motion(drag->base.focus_resource, time, sx, sy); + wl_data_device_send_motion(drag->base.focus_resource, msecs, sx, sy); } } diff --git a/libweston/input.c b/libweston/input.c index 81a94a929..c167b8cea 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -38,6 +38,7 @@ #include "shared/helpers.h" #include "shared/os-compatibility.h" +#include "shared/timespec-util.h" #include "compositor.h" #include "relative-pointer-unstable-v1-server-protocol.h" #include "pointer-constraints-unstable-v1-server-protocol.h" @@ -341,7 +342,7 @@ default_grab_pointer_focus(struct weston_pointer_grab *grab) static void pointer_send_relative_motion(struct weston_pointer *pointer, - uint32_t time, + const struct timespec *time, struct weston_pointer_motion_event *event) { uint64_t time_usec; @@ -359,9 +360,9 @@ pointer_send_relative_motion(struct weston_pointer *pointer, return; resource_list = &pointer->focus_client->relative_pointer_resources; - time_usec = event->time_usec; + time_usec = timespec_to_usec(&event->time); if (time_usec == 0) - time_usec = time * 1000ULL; + time_usec = timespec_to_usec(time); dxf = wl_fixed_from_double(dx); dyf = wl_fixed_from_double(dy); @@ -379,22 +380,26 @@ pointer_send_relative_motion(struct weston_pointer *pointer, } static void -pointer_send_motion(struct weston_pointer *pointer, uint32_t time, +pointer_send_motion(struct weston_pointer *pointer, + const struct timespec *time, wl_fixed_t sx, wl_fixed_t sy) { struct wl_list *resource_list; struct wl_resource *resource; + uint32_t msecs; if (!pointer->focus_client) return; resource_list = &pointer->focus_client->pointer_resources; + msecs = timespec_to_msec(time); wl_resource_for_each(resource, resource_list) - wl_pointer_send_motion(resource, time, sx, sy); + wl_pointer_send_motion(resource, msecs, sx, sy); } WL_EXPORT void -weston_pointer_send_motion(struct weston_pointer *pointer, uint32_t time, +weston_pointer_send_motion(struct weston_pointer *pointer, + const struct timespec *time, struct weston_pointer_motion_event *event) { wl_fixed_t x, y; @@ -418,7 +423,8 @@ weston_pointer_send_motion(struct weston_pointer *pointer, uint32_t time, } static void -default_grab_pointer_motion(struct weston_pointer_grab *grab, uint32_t time, +default_grab_pointer_motion(struct weston_pointer_grab *grab, + const struct timespec *time, struct weston_pointer_motion_event *event) { weston_pointer_send_motion(grab->pointer, time, event); @@ -1551,7 +1557,7 @@ weston_pointer_handle_output_destroy(struct wl_listener *listener, void *data) WL_EXPORT void notify_motion(struct weston_seat *seat, - uint32_t time, + const struct timespec *time, struct weston_pointer_motion_event *event) { struct weston_compositor *ec = seat->compositor; @@ -1598,8 +1604,8 @@ run_modifier_bindings(struct weston_seat *seat, uint32_t old, uint32_t new) } WL_EXPORT void -notify_motion_absolute(struct weston_seat *seat, - uint32_t time, double x, double y) +notify_motion_absolute(struct weston_seat *seat, const struct timespec *time, + double x, double y) { struct weston_compositor *ec = seat->compositor; struct weston_pointer *pointer = weston_seat_get_pointer(seat); @@ -3314,7 +3320,7 @@ locked_pointer_grab_pointer_focus(struct weston_pointer_grab *grab) static void locked_pointer_grab_pointer_motion(struct weston_pointer_grab *grab, - uint32_t time, + const struct timespec *time, struct weston_pointer_motion_event *event) { pointer_send_relative_motion(grab->pointer, time, event); @@ -4291,7 +4297,7 @@ maybe_warp_confined_pointer(struct weston_pointer_constraint *constraint) static void confined_pointer_grab_pointer_motion(struct weston_pointer_grab *grab, - uint32_t time, + const struct timespec *time, struct weston_pointer_motion_event *event) { struct weston_pointer_constraint *constraint = diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index b1d269dbb..24f79b4c8 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -39,6 +39,7 @@ #include "compositor.h" #include "libinput-device.h" #include "shared/helpers.h" +#include "shared/timespec-util.h" void evdev_led_update(struct evdev_device *device, enum weston_led weston_leds) @@ -86,26 +87,25 @@ handle_pointer_motion(struct libinput_device *libinput_device, struct evdev_device *device = libinput_device_get_user_data(libinput_device); struct weston_pointer_motion_event event = { 0 }; - uint64_t time_usec = - libinput_event_pointer_get_time_usec(pointer_event); + struct timespec time; double dx_unaccel, dy_unaccel; + timespec_from_usec(&time, + libinput_event_pointer_get_time_usec(pointer_event)); dx_unaccel = libinput_event_pointer_get_dx_unaccelerated(pointer_event); dy_unaccel = libinput_event_pointer_get_dy_unaccelerated(pointer_event); event = (struct weston_pointer_motion_event) { .mask = WESTON_POINTER_MOTION_REL | WESTON_POINTER_MOTION_REL_UNACCEL, - .time_usec = time_usec, + .time = time, .dx = libinput_event_pointer_get_dx(pointer_event), .dy = libinput_event_pointer_get_dy(pointer_event), .dx_unaccel = dx_unaccel, .dy_unaccel = dy_unaccel, }; - notify_motion(device->seat, - libinput_event_pointer_get_time(pointer_event), - &event); + notify_motion(device->seat, &time, &event); return true; } @@ -118,14 +118,15 @@ handle_pointer_motion_absolute( struct evdev_device *device = libinput_device_get_user_data(libinput_device); struct weston_output *output = device->output; - uint32_t time; + struct timespec time; double x, y; uint32_t width, height; if (!output) return false; - time = libinput_event_pointer_get_time(pointer_event); + timespec_from_usec(&time, + libinput_event_pointer_get_time_usec(pointer_event)); width = device->output->current_mode->width; height = device->output->current_mode->height; @@ -135,7 +136,7 @@ handle_pointer_motion_absolute( height); weston_output_transform_coordinate(device->output, x, y, &x, &y); - notify_motion_absolute(device->seat, time, x, y); + notify_motion_absolute(device->seat, &time, x, y); return true; } diff --git a/tests/weston-test.c b/tests/weston-test.c index 189fcc1be..3f684820f 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -43,6 +43,7 @@ #endif /* ENABLE_EGL */ #include "shared/helpers.h" +#include "shared/timespec-util.h" struct weston_test { struct weston_compositor *compositor; @@ -151,6 +152,7 @@ move_pointer(struct wl_client *client, struct wl_resource *resource, struct weston_seat *seat = get_seat(test); struct weston_pointer *pointer = weston_seat_get_pointer(seat); struct weston_pointer_motion_event event = { 0 }; + struct timespec time; event = (struct weston_pointer_motion_event) { .mask = WESTON_POINTER_MOTION_REL, @@ -158,7 +160,9 @@ move_pointer(struct wl_client *client, struct wl_resource *resource, .dy = wl_fixed_to_double(wl_fixed_from_int(y) - pointer->y), }; - notify_motion(seat, 100, &event); + timespec_from_msec(&time, 100); + + notify_motion(seat, &time, &event); notify_pointer_position(test, resource); } From 215bedc88bf90fb367e086b6fb39f8dbcc7baa81 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 16 Nov 2017 18:20:55 +0200 Subject: [PATCH 0201/1642] libweston: Use struct timespec for button events Change code related to button events to use struct timespec to represent time. This commit is part of a larger effort to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- compositor/screen-share.c | 5 ++++- desktop-shell/exposay.c | 4 ++-- desktop-shell/shell.c | 22 +++++++++++++--------- ivi-shell/hmi-controller.c | 2 +- ivi-shell/ivi-shell.c | 3 ++- libweston-desktop/seat.c | 7 +++++-- libweston/bindings.c | 3 ++- libweston/compositor-rdp.c | 3 ++- libweston/compositor-wayland.c | 4 +++- libweston/compositor-x11.c | 6 ++++-- libweston/compositor.h | 18 +++++++++++------- libweston/data-device.c | 3 ++- libweston/input.c | 18 ++++++++++-------- libweston/libinput-device.c | 7 +++++-- tests/weston-test.c | 6 +++++- 15 files changed, 71 insertions(+), 40 deletions(-) diff --git a/compositor/screen-share.c b/compositor/screen-share.c index c7aad3136..368d0cd6e 100644 --- a/compositor/screen-share.c +++ b/compositor/screen-share.c @@ -159,8 +159,11 @@ ss_seat_handle_button(void *data, struct wl_pointer *pointer, uint32_t state) { struct ss_seat *seat = data; + struct timespec ts; + + timespec_from_msec(&ts, time); - notify_button(&seat->base, time, button, state); + notify_button(&seat->base, &ts, button, state); notify_pointer_frame(&seat->base); } diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c index 15b86863a..3571c5d76 100644 --- a/desktop-shell/exposay.c +++ b/desktop-shell/exposay.c @@ -364,8 +364,8 @@ exposay_motion(struct weston_pointer_grab *grab, } static void -exposay_button(struct weston_pointer_grab *grab, uint32_t time, uint32_t button, - uint32_t state_w) +exposay_button(struct weston_pointer_grab *grab, const struct timespec *time, + uint32_t button, uint32_t state_w) { struct desktop_shell *shell = container_of(grab, struct desktop_shell, exposay.grab_ptr); diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 62dfa4508..2d2a6c8bf 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -1507,7 +1507,7 @@ move_grab_motion(struct weston_pointer_grab *grab, static void move_grab_button(struct weston_pointer_grab *grab, - uint32_t time, uint32_t button, uint32_t state_w) + const struct timespec *time, uint32_t button, uint32_t state_w) { struct shell_grab *shell_grab = container_of(grab, struct shell_grab, grab); @@ -1634,7 +1634,8 @@ resize_grab_motion(struct weston_pointer_grab *grab, static void resize_grab_button(struct weston_pointer_grab *grab, - uint32_t time, uint32_t button, uint32_t state_w) + const struct timespec *time, + uint32_t button, uint32_t state_w) { struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; struct weston_pointer *pointer = grab->pointer; @@ -1778,7 +1779,8 @@ busy_cursor_grab_motion(struct weston_pointer_grab *grab, static void busy_cursor_grab_button(struct weston_pointer_grab *base, - uint32_t time, uint32_t button, uint32_t state) + const struct timespec *time, + uint32_t button, uint32_t state) { struct shell_grab *grab = (struct shell_grab *) base; struct shell_surface *shsurf = grab->shsurf; @@ -3203,7 +3205,7 @@ static const struct weston_desktop_shell_interface desktop_shell_implementation }; static void -move_binding(struct weston_pointer *pointer, uint32_t time, +move_binding(struct weston_pointer *pointer, const struct timespec *time, uint32_t button, void *data) { struct weston_surface *focus; @@ -3295,7 +3297,7 @@ touch_move_binding(struct weston_touch *touch, uint32_t time, void *data) } static void -resize_binding(struct weston_pointer *pointer, uint32_t time, +resize_binding(struct weston_pointer *pointer, const struct timespec *time, uint32_t button, void *data) { struct weston_surface *focus; @@ -3515,7 +3517,8 @@ rotate_grab_motion(struct weston_pointer_grab *grab, static void rotate_grab_button(struct weston_pointer_grab *grab, - uint32_t time, uint32_t button, uint32_t state_w) + const struct timespec *time, + uint32_t button, uint32_t state_w) { struct rotate_grab *rotate = container_of(grab, struct rotate_grab, base.grab); @@ -3593,8 +3596,8 @@ surface_rotate(struct shell_surface *shsurf, struct weston_pointer *pointer) } static void -rotate_binding(struct weston_pointer *pointer, uint32_t time, uint32_t button, - void *data) +rotate_binding(struct weston_pointer *pointer, const struct timespec *time, + uint32_t button, void *data) { struct weston_surface *focus; struct weston_surface *base_surface; @@ -3754,7 +3757,8 @@ activate_binding(struct weston_seat *seat, } static void -click_to_activate_binding(struct weston_pointer *pointer, uint32_t time, +click_to_activate_binding(struct weston_pointer *pointer, + const struct timespec *time, uint32_t button, void *data) { if (pointer->grab != &pointer->default_grab) diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index 6713eca3e..b91b7f2b9 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -1571,7 +1571,7 @@ touch_move_grab_motion(struct weston_touch_grab *grab, uint32_t time, static void pointer_move_workspace_grab_button(struct weston_pointer_grab *grab, - uint32_t time, uint32_t button, + const struct timespec *time, uint32_t button, uint32_t state_w) { if (BTN_LEFT == button && diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 67619b8f2..7eef1cec4 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -450,7 +450,8 @@ activate_binding(struct weston_seat *seat, } static void -click_to_activate_binding(struct weston_pointer *pointer, uint32_t time, +click_to_activate_binding(struct weston_pointer *pointer, + const struct timespec *time, uint32_t button, void *data) { if (pointer->grab != &pointer->default_grab) diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c index 18a329c4c..2c62f4fda 100644 --- a/libweston-desktop/seat.c +++ b/libweston-desktop/seat.c @@ -35,6 +35,7 @@ #include "libweston-desktop.h" #include "internal.h" +#include "shared/timespec-util.h" struct weston_desktop_seat { struct wl_listener seat_destroy_listener; @@ -116,7 +117,8 @@ weston_desktop_seat_popup_grab_pointer_motion(struct weston_pointer_grab *grab, static void weston_desktop_seat_popup_grab_pointer_button(struct weston_pointer_grab *grab, - uint32_t time, uint32_t button, + const struct timespec *time, + uint32_t button, enum wl_pointer_button_state state) { struct weston_desktop_seat *seat = @@ -130,7 +132,8 @@ weston_desktop_seat_popup_grab_pointer_button(struct weston_pointer_grab *grab, if (weston_pointer_has_focus_resource(pointer)) weston_pointer_send_button(pointer, time, button, state); else if (state == WL_POINTER_BUTTON_STATE_RELEASED && - (initial_up || (time - grab->pointer->grab_time) > 500)) + (initial_up || + (timespec_sub_to_msec(time, &grab->pointer->grab_time) > 500))) weston_desktop_seat_popup_grab_end(seat); } diff --git a/libweston/bindings.c b/libweston/bindings.c index 213665224..ae6167431 100644 --- a/libweston/bindings.c +++ b/libweston/bindings.c @@ -349,7 +349,8 @@ weston_compositor_run_modifier_binding(struct weston_compositor *compositor, void weston_compositor_run_button_binding(struct weston_compositor *compositor, struct weston_pointer *pointer, - uint32_t time, uint32_t button, + const struct timespec *time, + uint32_t button, enum wl_pointer_button_state state) { struct weston_binding *b, *tmp; diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 315d2ade4..ad435b140 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -1050,7 +1050,8 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) button = BTN_MIDDLE; if (button) { - notify_button(peerContext->item.seat, weston_compositor_get_time(), button, + timespec_from_msec(&time, weston_compositor_get_time()); + notify_button(peerContext->item.seat, &time, button, (flags & PTR_FLAGS_DOWN) ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED ); need_frame = true; diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 8f26c5450..46f7aba3e 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1644,6 +1644,7 @@ input_handle_button(void *data, struct wl_pointer *pointer, { struct wayland_input *input = data; enum theme_location location; + struct timespec ts; if (!input->output) return; @@ -1682,7 +1683,8 @@ input_handle_button(void *data, struct wl_pointer *pointer, } if (location == THEME_LOCATION_CLIENT_AREA) { - notify_button(&input->base, time, button, state); + timespec_from_msec(&ts, time); + notify_button(&input->base, &ts, button, state); if (input->seat_version < WL_POINTER_FRAME_SINCE_VERSION) notify_pointer_frame(&input->base); } diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 7e24afcb4..32622c061 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -1133,6 +1133,7 @@ x11_backend_deliver_button_event(struct x11_backend *b, struct x11_output *output; struct weston_pointer_axis_event weston_event; bool is_button_pressed = event->response_type == XCB_BUTTON_PRESS; + struct timespec time = { 0 }; assert(event->response_type == XCB_BUTTON_PRESS || event->response_type == XCB_BUTTON_RELEASE); @@ -1227,8 +1228,9 @@ x11_backend_deliver_button_event(struct x11_backend *b, break; } - notify_button(&b->core_seat, - weston_compositor_get_time(), button, + timespec_from_msec(&time, weston_compositor_get_time()); + + notify_button(&b->core_seat, &time, button, is_button_pressed ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED); notify_pointer_frame(&b->core_seat); diff --git a/libweston/compositor.h b/libweston/compositor.h index 971676cf9..cd42006ae 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -272,7 +272,8 @@ struct weston_pointer_grab_interface { const struct timespec *time, struct weston_pointer_motion_event *event); void (*button)(struct weston_pointer_grab *grab, - uint32_t time, uint32_t button, uint32_t state); + const struct timespec *time, + uint32_t button, uint32_t state); void (*axis)(struct weston_pointer_grab *grab, uint32_t time, struct weston_pointer_axis_event *event); @@ -383,7 +384,7 @@ struct weston_pointer { wl_fixed_t grab_x, grab_y; uint32_t grab_button; uint32_t grab_serial; - uint32_t grab_time; + struct timespec grab_time; wl_fixed_t x, y; wl_fixed_t sx, sy; @@ -431,7 +432,8 @@ bool weston_pointer_has_focus_resource(struct weston_pointer *pointer); void weston_pointer_send_button(struct weston_pointer *pointer, - uint32_t time, uint32_t button, uint32_t state_w); + const struct timespec *time, + uint32_t button, uint32_t state_w); void weston_pointer_send_axis(struct weston_pointer *pointer, uint32_t time, @@ -1371,8 +1373,8 @@ void notify_motion_absolute(struct weston_seat *seat, const struct timespec *time, double x, double y); void -notify_button(struct weston_seat *seat, uint32_t time, int32_t button, - enum wl_pointer_button_state state); +notify_button(struct weston_seat *seat, const struct timespec *time, + int32_t button, enum wl_pointer_button_state state); void notify_axis(struct weston_seat *seat, uint32_t time, struct weston_pointer_axis_event *event); @@ -1492,7 +1494,8 @@ weston_compositor_add_modifier_binding(struct weston_compositor *compositor, void *data); typedef void (*weston_button_binding_handler_t)(struct weston_pointer *pointer, - uint32_t time, uint32_t button, + const struct timespec *time, + uint32_t button, void *data); struct weston_binding * weston_compositor_add_button_binding(struct weston_compositor *compositor, @@ -1549,7 +1552,8 @@ weston_compositor_run_modifier_binding(struct weston_compositor *compositor, enum wl_keyboard_key_state state); void weston_compositor_run_button_binding(struct weston_compositor *compositor, - struct weston_pointer *pointer, uint32_t time, + struct weston_pointer *pointer, + const struct timespec *time, uint32_t button, enum wl_pointer_button_state value); void diff --git a/libweston/data-device.c b/libweston/data-device.c index 58a440aef..20de9b8ae 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -638,7 +638,8 @@ data_device_end_pointer_drag_grab(struct weston_pointer_drag *drag) static void drag_grab_button(struct weston_pointer_grab *grab, - uint32_t time, uint32_t button, uint32_t state_w) + const struct timespec *time, + uint32_t button, uint32_t state_w) { struct weston_pointer_drag *drag = container_of(grab, struct weston_pointer_drag, grab); diff --git a/libweston/input.c b/libweston/input.c index c167b8cea..7f7893332 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -460,26 +460,28 @@ weston_pointer_has_focus_resource(struct weston_pointer *pointer) */ WL_EXPORT void weston_pointer_send_button(struct weston_pointer *pointer, - uint32_t time, uint32_t button, + const struct timespec *time, uint32_t button, enum wl_pointer_button_state state) { struct wl_display *display = pointer->seat->compositor->wl_display; struct wl_list *resource_list; struct wl_resource *resource; uint32_t serial; + uint32_t msecs; if (!weston_pointer_has_focus_resource(pointer)) return; resource_list = &pointer->focus_client->pointer_resources; serial = wl_display_next_serial(display); + msecs = timespec_to_msec(time); wl_resource_for_each(resource, resource_list) - wl_pointer_send_button(resource, serial, time, button, state); + wl_pointer_send_button(resource, serial, msecs, button, state); } static void default_grab_pointer_button(struct weston_pointer_grab *grab, - uint32_t time, uint32_t button, + const struct timespec *time, uint32_t button, enum wl_pointer_button_state state) { struct weston_pointer *pointer = grab->pointer; @@ -1652,8 +1654,8 @@ weston_view_activate(struct weston_view *view, } WL_EXPORT void -notify_button(struct weston_seat *seat, uint32_t time, int32_t button, - enum wl_pointer_button_state state) +notify_button(struct weston_seat *seat, const struct timespec *time, + int32_t button, enum wl_pointer_button_state state) { struct weston_compositor *compositor = seat->compositor; struct weston_pointer *pointer = weston_seat_get_pointer(seat); @@ -1662,7 +1664,7 @@ notify_button(struct weston_seat *seat, uint32_t time, int32_t button, weston_compositor_idle_inhibit(compositor); if (pointer->button_count == 0) { pointer->grab_button = button; - pointer->grab_time = time; + pointer->grab_time = *time; pointer->grab_x = pointer->x; pointer->grab_y = pointer->y; } @@ -3328,7 +3330,7 @@ locked_pointer_grab_pointer_motion(struct weston_pointer_grab *grab, static void locked_pointer_grab_pointer_button(struct weston_pointer_grab *grab, - uint32_t time, + const struct timespec *time, uint32_t button, uint32_t state_w) { @@ -4336,7 +4338,7 @@ confined_pointer_grab_pointer_motion(struct weston_pointer_grab *grab, static void confined_pointer_grab_pointer_button(struct weston_pointer_grab *grab, - uint32_t time, + const struct timespec *time, uint32_t button, uint32_t state_w) { diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index 24f79b4c8..e2f204680 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -151,6 +151,7 @@ handle_pointer_button(struct libinput_device *libinput_device, libinput_event_pointer_get_button_state(pointer_event); int seat_button_count = libinput_event_pointer_get_seat_button_count(pointer_event); + struct timespec time; /* Ignore button events that are not seat wide state changes. */ if ((button_state == LIBINPUT_BUTTON_STATE_PRESSED && @@ -159,8 +160,10 @@ handle_pointer_button(struct libinput_device *libinput_device, seat_button_count != 0)) return false; - notify_button(device->seat, - libinput_event_pointer_get_time(pointer_event), + timespec_from_usec(&time, + libinput_event_pointer_get_time_usec(pointer_event)); + + notify_button(device->seat, &time, libinput_event_pointer_get_button(pointer_event), button_state); diff --git a/tests/weston-test.c b/tests/weston-test.c index 3f684820f..306757a01 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -171,10 +171,14 @@ static void send_button(struct wl_client *client, struct wl_resource *resource, int32_t button, uint32_t state) { + struct timespec time; + struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat = get_seat(test); - notify_button(seat, 100, button, state); + timespec_from_msec(&time, 100); + + notify_button(seat, &time, button, state); } static void From 80321942e769f89e76f5372d4283aaad8fb4bd9e Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 16 Nov 2017 18:20:56 +0200 Subject: [PATCH 0202/1642] libweston: Use struct timespec for axis events Change code related to axis events to use struct timespec to represent time. This commit is part of a larger effort to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- compositor/screen-share.c | 5 ++++- desktop-shell/exposay.c | 3 ++- desktop-shell/shell.c | 18 ++++++++++++------ ivi-shell/hmi-controller.c | 2 +- libweston-desktop/seat.c | 2 +- libweston/bindings.c | 2 +- libweston/compositor-rdp.c | 5 +++-- libweston/compositor-wayland.c | 10 ++++++++-- libweston/compositor-x11.c | 20 ++++++++------------ libweston/compositor.h | 11 ++++++----- libweston/data-device.c | 3 ++- libweston/input.c | 16 +++++++++------- libweston/libinput-device.c | 12 ++++++------ 13 files changed, 63 insertions(+), 46 deletions(-) diff --git a/compositor/screen-share.c b/compositor/screen-share.c index 368d0cd6e..8b2decfda 100644 --- a/compositor/screen-share.c +++ b/compositor/screen-share.c @@ -173,12 +173,15 @@ ss_seat_handle_axis(void *data, struct wl_pointer *pointer, { struct ss_seat *seat = data; struct weston_pointer_axis_event weston_event; + struct timespec ts; weston_event.axis = axis; weston_event.value = wl_fixed_to_double(value); weston_event.has_discrete = false; - notify_axis(&seat->base, time, &weston_event); + timespec_from_msec(&ts, time); + + notify_axis(&seat->base, &ts, &weston_event); notify_pointer_frame(&seat->base); } diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c index 3571c5d76..5b23adf76 100644 --- a/desktop-shell/exposay.c +++ b/desktop-shell/exposay.c @@ -390,7 +390,8 @@ exposay_button(struct weston_pointer_grab *grab, const struct timespec *time, static void exposay_axis(struct weston_pointer_grab *grab, - uint32_t time, struct weston_pointer_axis_event *event) + const struct timespec *time, + struct weston_pointer_axis_event *event) { } diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 2d2a6c8bf..5f6c6d198 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -1431,7 +1431,8 @@ noop_grab_focus(struct weston_pointer_grab *grab) static void noop_grab_axis(struct weston_pointer_grab *grab, - uint32_t time, struct weston_pointer_axis_event *event) + const struct timespec *time, + struct weston_pointer_axis_event *event) { } @@ -3344,7 +3345,8 @@ resize_binding(struct weston_pointer *pointer, const struct timespec *time, } static void -surface_opacity_binding(struct weston_pointer *pointer, uint32_t time, +surface_opacity_binding(struct weston_pointer *pointer, + const struct timespec *time, struct weston_pointer_axis_event *event, void *data) { @@ -3374,8 +3376,8 @@ surface_opacity_binding(struct weston_pointer *pointer, uint32_t time, } static void -do_zoom(struct weston_seat *seat, uint32_t time, uint32_t key, uint32_t axis, - double value) +do_zoom(struct weston_seat *seat, const struct timespec *time, uint32_t key, + uint32_t axis, double value) { struct weston_compositor *compositor = seat->compositor; struct weston_pointer *pointer = weston_seat_get_pointer(seat); @@ -3424,7 +3426,7 @@ do_zoom(struct weston_seat *seat, uint32_t time, uint32_t key, uint32_t axis, } static void -zoom_axis_binding(struct weston_pointer *pointer, uint32_t time, +zoom_axis_binding(struct weston_pointer *pointer, const struct timespec *time, struct weston_pointer_axis_event *event, void *data) { @@ -3435,7 +3437,11 @@ static void zoom_key_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, void *data) { - do_zoom(keyboard->seat, time, key, 0, 0); + struct timespec ts; + + timespec_from_msec(&ts, time); + + do_zoom(keyboard->seat, &ts, key, 0, 0); } static void diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index b91b7f2b9..d61e26b56 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -1461,7 +1461,7 @@ pointer_noop_grab_focus(struct weston_pointer_grab *grab) static void pointer_default_grab_axis(struct weston_pointer_grab *grab, - uint32_t time, + const struct timespec *time, struct weston_pointer_axis_event *event) { weston_pointer_send_axis(grab->pointer, time, event); diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c index 2c62f4fda..150229f80 100644 --- a/libweston-desktop/seat.c +++ b/libweston-desktop/seat.c @@ -139,7 +139,7 @@ weston_desktop_seat_popup_grab_pointer_button(struct weston_pointer_grab *grab, static void weston_desktop_seat_popup_grab_pointer_axis(struct weston_pointer_grab *grab, - uint32_t time, + const struct timespec *time, struct weston_pointer_axis_event *event) { weston_pointer_send_axis(grab->pointer, time, event); diff --git a/libweston/bindings.c b/libweston/bindings.c index ae6167431..82a56f4a3 100644 --- a/libweston/bindings.c +++ b/libweston/bindings.c @@ -392,7 +392,7 @@ weston_compositor_run_touch_binding(struct weston_compositor *compositor, int weston_compositor_run_axis_binding(struct weston_compositor *compositor, struct weston_pointer *pointer, - uint32_t time, + const struct timespec *time, struct weston_pointer_axis_event *event) { struct weston_binding *b, *tmp; diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index ad435b140..05e11ee85 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -1076,8 +1076,9 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) weston_event.discrete = (int)value; weston_event.has_discrete = true; - notify_axis(peerContext->item.seat, weston_compositor_get_time(), - &weston_event); + timespec_from_msec(&time, weston_compositor_get_time()); + + notify_axis(peerContext->item.seat, &time, &weston_event); need_frame = true; } diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 46f7aba3e..6a1a50f2c 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1696,6 +1696,7 @@ input_handle_axis(void *data, struct wl_pointer *pointer, { struct wayland_input *input = data; struct weston_pointer_axis_event weston_event; + struct timespec ts; weston_event.axis = axis; weston_event.value = wl_fixed_to_double(value); @@ -1712,7 +1713,9 @@ input_handle_axis(void *data, struct wl_pointer *pointer, input->horiz.has_discrete = false; } - notify_axis(&input->base, time, &weston_event); + timespec_from_msec(&ts, time); + + notify_axis(&input->base, &ts, &weston_event); if (input->seat_version < WL_POINTER_FRAME_SINCE_VERSION) notify_pointer_frame(&input->base); @@ -1741,11 +1744,14 @@ input_handle_axis_stop(void *data, struct wl_pointer *pointer, { struct wayland_input *input = data; struct weston_pointer_axis_event weston_event; + struct timespec ts; weston_event.axis = axis; weston_event.value = 0; - notify_axis(&input->base, time, &weston_event); + timespec_from_msec(&ts, time); + + notify_axis(&input->base, &ts, &weston_event); } static void diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 32622c061..13643a118 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -1178,9 +1178,8 @@ x11_backend_deliver_button_event(struct x11_backend *b, weston_event.has_discrete = true; weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; - notify_axis(&b->core_seat, - weston_compositor_get_time(), - &weston_event); + timespec_from_msec(&time, weston_compositor_get_time()); + notify_axis(&b->core_seat, &time, &weston_event); notify_pointer_frame(&b->core_seat); } return; @@ -1191,9 +1190,8 @@ x11_backend_deliver_button_event(struct x11_backend *b, weston_event.has_discrete = true; weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; - notify_axis(&b->core_seat, - weston_compositor_get_time(), - &weston_event); + timespec_from_msec(&time, weston_compositor_get_time()); + notify_axis(&b->core_seat, &time, &weston_event); notify_pointer_frame(&b->core_seat); } return; @@ -1204,9 +1202,8 @@ x11_backend_deliver_button_event(struct x11_backend *b, weston_event.has_discrete = true; weston_event.axis = WL_POINTER_AXIS_HORIZONTAL_SCROLL; - notify_axis(&b->core_seat, - weston_compositor_get_time(), - &weston_event); + timespec_from_msec(&time, weston_compositor_get_time()); + notify_axis(&b->core_seat, &time, &weston_event); notify_pointer_frame(&b->core_seat); } return; @@ -1217,9 +1214,8 @@ x11_backend_deliver_button_event(struct x11_backend *b, weston_event.has_discrete = true; weston_event.axis = WL_POINTER_AXIS_HORIZONTAL_SCROLL; - notify_axis(&b->core_seat, - weston_compositor_get_time(), - &weston_event); + timespec_from_msec(&time, weston_compositor_get_time()); + notify_axis(&b->core_seat, &time, &weston_event); notify_pointer_frame(&b->core_seat); } return; diff --git a/libweston/compositor.h b/libweston/compositor.h index cd42006ae..ec76248e5 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -275,7 +275,7 @@ struct weston_pointer_grab_interface { const struct timespec *time, uint32_t button, uint32_t state); void (*axis)(struct weston_pointer_grab *grab, - uint32_t time, + const struct timespec *time, struct weston_pointer_axis_event *event); void (*axis_source)(struct weston_pointer_grab *grab, uint32_t source); void (*frame)(struct weston_pointer_grab *grab); @@ -436,7 +436,7 @@ weston_pointer_send_button(struct weston_pointer *pointer, uint32_t button, uint32_t state_w); void weston_pointer_send_axis(struct weston_pointer *pointer, - uint32_t time, + const struct timespec *time, struct weston_pointer_axis_event *event); void weston_pointer_send_axis_source(struct weston_pointer *pointer, @@ -1376,7 +1376,7 @@ void notify_button(struct weston_seat *seat, const struct timespec *time, int32_t button, enum wl_pointer_button_state state); void -notify_axis(struct weston_seat *seat, uint32_t time, +notify_axis(struct weston_seat *seat, const struct timespec *time, struct weston_pointer_axis_event *event); void notify_axis_source(struct weston_seat *seat, uint32_t source); @@ -1514,7 +1514,7 @@ weston_compositor_add_touch_binding(struct weston_compositor *compositor, void *data); typedef void (*weston_axis_binding_handler_t)(struct weston_pointer *pointer, - uint32_t time, + const struct timespec *time, struct weston_pointer_axis_event *event, void *data); struct weston_binding * @@ -1562,7 +1562,8 @@ weston_compositor_run_touch_binding(struct weston_compositor *compositor, int touch_type); int weston_compositor_run_axis_binding(struct weston_compositor *compositor, - struct weston_pointer *pointer, uint32_t time, + struct weston_pointer *pointer, + const struct timespec *time, struct weston_pointer_axis_event *event); int weston_compositor_run_debug_binding(struct weston_compositor *compositor, diff --git a/libweston/data-device.c b/libweston/data-device.c index 20de9b8ae..26898aa60 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -680,7 +680,8 @@ drag_grab_button(struct weston_pointer_grab *grab, static void drag_grab_axis(struct weston_pointer_grab *grab, - uint32_t time, struct weston_pointer_axis_event *event) + const struct timespec *time, + struct weston_pointer_axis_event *event) { } diff --git a/libweston/input.c b/libweston/input.c index 7f7893332..877b0b83c 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -514,16 +514,18 @@ default_grab_pointer_button(struct weston_pointer_grab *grab, */ WL_EXPORT void weston_pointer_send_axis(struct weston_pointer *pointer, - uint32_t time, + const struct timespec *time, struct weston_pointer_axis_event *event) { struct wl_resource *resource; struct wl_list *resource_list; + uint32_t msecs; if (!weston_pointer_has_focus_resource(pointer)) return; resource_list = &pointer->focus_client->pointer_resources; + msecs = timespec_to_msec(time); wl_resource_for_each(resource, resource_list) { if (event->has_discrete && wl_resource_get_version(resource) >= @@ -532,12 +534,12 @@ weston_pointer_send_axis(struct weston_pointer *pointer, event->discrete); if (event->value) - wl_pointer_send_axis(resource, time, + wl_pointer_send_axis(resource, msecs, event->axis, wl_fixed_from_double(event->value)); else if (wl_resource_get_version(resource) >= WL_POINTER_AXIS_STOP_SINCE_VERSION) - wl_pointer_send_axis_stop(resource, time, + wl_pointer_send_axis_stop(resource, msecs, event->axis); } } @@ -603,7 +605,7 @@ weston_pointer_send_frame(struct weston_pointer *pointer) static void default_grab_pointer_axis(struct weston_pointer_grab *grab, - uint32_t time, + const struct timespec *time, struct weston_pointer_axis_event *event) { weston_pointer_send_axis(grab->pointer, time, event); @@ -1685,7 +1687,7 @@ notify_button(struct weston_seat *seat, const struct timespec *time, } WL_EXPORT void -notify_axis(struct weston_seat *seat, uint32_t time, +notify_axis(struct weston_seat *seat, const struct timespec *time, struct weston_pointer_axis_event *event) { struct weston_compositor *compositor = seat->compositor; @@ -3339,7 +3341,7 @@ locked_pointer_grab_pointer_button(struct weston_pointer_grab *grab, static void locked_pointer_grab_pointer_axis(struct weston_pointer_grab *grab, - uint32_t time, + const struct timespec *time, struct weston_pointer_axis_event *event) { weston_pointer_send_axis(grab->pointer, time, event); @@ -4347,7 +4349,7 @@ confined_pointer_grab_pointer_button(struct weston_pointer_grab *grab, static void confined_pointer_grab_pointer_axis(struct weston_pointer_grab *grab, - uint32_t time, + const struct timespec *time, struct weston_pointer_axis_event *event) { weston_pointer_send_axis(grab->pointer, time, event); diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index e2f204680..4d8cf2e6e 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -230,6 +230,7 @@ handle_pointer_axis(struct libinput_device *libinput_device, enum libinput_pointer_axis_source source; uint32_t wl_axis_source; bool has_vert, has_horiz; + struct timespec time; has_vert = libinput_event_pointer_has_axis(pointer_event, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); @@ -260,6 +261,9 @@ handle_pointer_axis(struct libinput_device *libinput_device, notify_axis_source(device->seat, wl_axis_source); + timespec_from_usec(&time, + libinput_event_pointer_get_time_usec(pointer_event)); + if (has_vert) { axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL; vert_discrete = get_axis_discrete(pointer_event, axis); @@ -270,9 +274,7 @@ handle_pointer_axis(struct libinput_device *libinput_device, weston_event.discrete = vert_discrete; weston_event.has_discrete = (vert_discrete != 0); - notify_axis(device->seat, - libinput_event_pointer_get_time(pointer_event), - &weston_event); + notify_axis(device->seat, &time, &weston_event); } if (has_horiz) { @@ -285,9 +287,7 @@ handle_pointer_axis(struct libinput_device *libinput_device, weston_event.discrete = horiz_discrete; weston_event.has_discrete = (horiz_discrete != 0); - notify_axis(device->seat, - libinput_event_pointer_get_time(pointer_event), - &weston_event); + notify_axis(device->seat, &time, &weston_event); } return true; From 47e79c860b3bd90de757230ff7db4822d95b9113 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 16 Nov 2017 18:20:57 +0200 Subject: [PATCH 0203/1642] libweston: Use struct timespec for key events Change code related to key events to use struct timespec to represent time. This commit is part of a larger effort to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- compositor/screen-share.c | 8 +++--- compositor/text-backend.c | 13 ++++++--- compositor/weston-screenshooter.c | 6 ++--- desktop-shell/exposay.c | 4 +-- desktop-shell/shell.c | 44 +++++++++++++++---------------- ivi-shell/ivi-shell.c | 2 +- libweston-desktop/seat.c | 3 ++- libweston/bindings.c | 24 ++++++++++------- libweston/compositor-drm.c | 16 +++++------ libweston/compositor-rdp.c | 4 ++- libweston/compositor-wayland.c | 9 ++++--- libweston/compositor-x11.c | 14 +++++++--- libweston/compositor.c | 5 ++-- libweston/compositor.h | 18 +++++++------ libweston/data-device.c | 2 +- libweston/gl-renderer.c | 6 +++-- libweston/input.c | 13 +++++---- libweston/launcher-util.c | 2 +- libweston/libinput-device.c | 7 +++-- libweston/pixman-renderer.c | 4 +-- tests/surface-screenshot.c | 4 +-- tests/weston-test.c | 5 +++- 22 files changed, 126 insertions(+), 87 deletions(-) diff --git a/compositor/screen-share.c b/compositor/screen-share.c index 8b2decfda..33de2b1f1 100644 --- a/compositor/screen-share.c +++ b/compositor/screen-share.c @@ -277,9 +277,11 @@ ss_seat_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t key, uint32_t state) { struct ss_seat *seat = data; + struct timespec ts; + timespec_from_msec(&ts, time); seat->key_serial = serial; - notify_key(&seat->base, time, key, + notify_key(&seat->base, &ts, key, state ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED, seat->keyboard_state_update); @@ -1092,8 +1094,8 @@ weston_output_find(struct weston_compositor *c, int32_t x, int32_t y) } static void -share_output_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, - void *data) +share_output_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) { struct weston_output *output; struct weston_pointer *pointer; diff --git a/compositor/text-backend.c b/compositor/text-backend.c index bf5c45cc9..5f6b5d800 100644 --- a/compositor/text-backend.c +++ b/compositor/text-backend.c @@ -38,6 +38,7 @@ #include "text-input-unstable-v1-server-protocol.h" #include "input-method-unstable-v1-server-protocol.h" #include "shared/helpers.h" +#include "shared/timespec-util.h" struct text_input_manager; struct input_method; @@ -607,11 +608,13 @@ unbind_keyboard(struct wl_resource *resource) static void input_method_context_grab_key(struct weston_keyboard_grab *grab, - uint32_t time, uint32_t key, uint32_t state_w) + const struct timespec *time, uint32_t key, + uint32_t state_w) { struct weston_keyboard *keyboard = grab->keyboard; struct wl_display *display; uint32_t serial; + uint32_t msecs; if (!keyboard->input_method_resource) return; @@ -619,8 +622,9 @@ input_method_context_grab_key(struct weston_keyboard_grab *grab, display = wl_client_get_display( wl_resource_get_client(keyboard->input_method_resource)); serial = wl_display_next_serial(display); + msecs = timespec_to_msec(time); wl_keyboard_send_key(keyboard->input_method_resource, - serial, time, key, state_w); + serial, msecs, key, state_w); } static void @@ -693,8 +697,11 @@ input_method_context_key(struct wl_client *client, struct weston_seat *seat = context->input_method->seat; struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); struct weston_keyboard_grab *default_grab = &keyboard->default_grab; + struct timespec ts; - default_grab->interface->key(default_grab, time, key, state_w); + timespec_from_msec(&ts, time); + + default_grab->interface->key(default_grab, &ts, key, state_w); } static void diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c index f874c3eb2..f0bc0e1e0 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -112,8 +112,8 @@ screenshooter_sigchld(struct weston_process *process, int status) } static void -screenshooter_binding(struct weston_keyboard *keyboard, uint32_t time, - uint32_t key, void *data) +screenshooter_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) { struct screenshooter *shooter = data; char *screenshooter_exe; @@ -135,7 +135,7 @@ screenshooter_binding(struct weston_keyboard *keyboard, uint32_t time, } static void -recorder_binding(struct weston_keyboard *keyboard, uint32_t time, +recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct weston_compositor *ec = keyboard->seat->compositor; diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c index 5b23adf76..9fd438342 100644 --- a/desktop-shell/exposay.c +++ b/desktop-shell/exposay.c @@ -442,8 +442,8 @@ exposay_maybe_move(struct desktop_shell *shell, int row, int column) } static void -exposay_key(struct weston_keyboard_grab *grab, uint32_t time, uint32_t key, - uint32_t state_w) +exposay_key(struct weston_keyboard_grab *grab, const struct timespec *time, + uint32_t key, uint32_t state_w) { struct weston_seat *seat = grab->keyboard->seat; struct desktop_shell *shell = diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 5f6c6d198..b8f00eb12 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -3232,7 +3232,7 @@ move_binding(struct weston_pointer *pointer, const struct timespec *time, } static void -maximize_binding(struct weston_keyboard *keyboard, uint32_t time, +maximize_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t button, void *data) { struct weston_surface *focus = keyboard->focus; @@ -3251,8 +3251,8 @@ maximize_binding(struct weston_keyboard *keyboard, uint32_t time, } static void -fullscreen_binding(struct weston_keyboard *keyboard, uint32_t time, - uint32_t button, void *data) +fullscreen_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t button, void *data) { struct weston_surface *focus = keyboard->focus; struct weston_surface *surface; @@ -3434,18 +3434,14 @@ zoom_axis_binding(struct weston_pointer *pointer, const struct timespec *time, } static void -zoom_key_binding(struct weston_keyboard *keyboard, uint32_t time, +zoom_key_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { - struct timespec ts; - - timespec_from_msec(&ts, time); - - do_zoom(keyboard->seat, &ts, key, 0, 0); + do_zoom(keyboard->seat, time, key, 0, 0); } static void -terminate_binding(struct weston_keyboard *keyboard, uint32_t time, +terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct weston_compositor *compositor = data; @@ -4421,7 +4417,7 @@ switcher_destroy(struct switcher *switcher) static void switcher_key(struct weston_keyboard_grab *grab, - uint32_t time, uint32_t key, uint32_t state_w) + const struct timespec *time, uint32_t key, uint32_t state_w) { struct switcher *switcher = container_of(grab, struct switcher, grab); enum wl_keyboard_key_state state = state_w; @@ -4457,7 +4453,7 @@ static const struct weston_keyboard_grab_interface switcher_grab = { }; static void -switcher_binding(struct weston_keyboard *keyboard, uint32_t time, +switcher_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct desktop_shell *shell = data; @@ -4478,7 +4474,7 @@ switcher_binding(struct weston_keyboard *keyboard, uint32_t time, } static void -backlight_binding(struct weston_keyboard *keyboard, uint32_t time, +backlight_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct weston_compositor *compositor = data; @@ -4511,8 +4507,8 @@ backlight_binding(struct weston_keyboard *keyboard, uint32_t time, } static void -force_kill_binding(struct weston_keyboard *keyboard, uint32_t time, - uint32_t key, void *data) +force_kill_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) { struct weston_surface *focus_surface; struct wl_client *client; @@ -4538,8 +4534,8 @@ force_kill_binding(struct weston_keyboard *keyboard, uint32_t time, } static void -workspace_up_binding(struct weston_keyboard *keyboard, uint32_t time, - uint32_t key, void *data) +workspace_up_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) { struct desktop_shell *shell = data; unsigned int new_index = shell->workspaces.current; @@ -4553,8 +4549,8 @@ workspace_up_binding(struct weston_keyboard *keyboard, uint32_t time, } static void -workspace_down_binding(struct weston_keyboard *keyboard, uint32_t time, - uint32_t key, void *data) +workspace_down_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) { struct desktop_shell *shell = data; unsigned int new_index = shell->workspaces.current; @@ -4568,8 +4564,8 @@ workspace_down_binding(struct weston_keyboard *keyboard, uint32_t time, } static void -workspace_f_binding(struct weston_keyboard *keyboard, uint32_t time, - uint32_t key, void *data) +workspace_f_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) { struct desktop_shell *shell = data; unsigned int new_index; @@ -4585,7 +4581,8 @@ workspace_f_binding(struct weston_keyboard *keyboard, uint32_t time, static void workspace_move_surface_up_binding(struct weston_keyboard *keyboard, - uint32_t time, uint32_t key, void *data) + const struct timespec *time, uint32_t key, + void *data) { struct desktop_shell *shell = data; unsigned int new_index = shell->workspaces.current; @@ -4601,7 +4598,8 @@ workspace_move_surface_up_binding(struct weston_keyboard *keyboard, static void workspace_move_surface_down_binding(struct weston_keyboard *keyboard, - uint32_t time, uint32_t key, void *data) + const struct timespec *time, uint32_t key, + void *data) { struct desktop_shell *shell = data; unsigned int new_index = shell->workspaces.current; diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 7eef1cec4..e675a3bd1 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -376,7 +376,7 @@ shell_destroy(struct wl_listener *listener, void *data) } static void -terminate_binding(struct weston_keyboard *keyboard, uint32_t time, +terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct weston_compositor *compositor = data; diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c index 150229f80..bba2605f2 100644 --- a/libweston-desktop/seat.c +++ b/libweston-desktop/seat.c @@ -54,7 +54,8 @@ static void weston_desktop_seat_popup_grab_end(struct weston_desktop_seat *seat) static void weston_desktop_seat_popup_grab_keyboard_key(struct weston_keyboard_grab *grab, - uint32_t time, uint32_t key, + const struct timespec *time, + uint32_t key, enum wl_keyboard_key_state state) { weston_keyboard_send_key(grab->keyboard, time, key, state); diff --git a/libweston/bindings.c b/libweston/bindings.c index 82a56f4a3..79c043e9b 100644 --- a/libweston/bindings.c +++ b/libweston/bindings.c @@ -31,6 +31,7 @@ #include "compositor.h" #include "shared/helpers.h" +#include "shared/timespec-util.h" struct weston_binding { uint32_t key; @@ -192,7 +193,7 @@ struct binding_keyboard_grab { static void binding_key(struct weston_keyboard_grab *grab, - uint32_t time, uint32_t key, uint32_t state_w) + const struct timespec *time, uint32_t key, uint32_t state_w) { struct binding_keyboard_grab *b = container_of(grab, struct binding_keyboard_grab, grab); @@ -201,6 +202,7 @@ binding_key(struct weston_keyboard_grab *grab, uint32_t serial; struct weston_keyboard *keyboard = grab->keyboard; struct wl_display *display = keyboard->seat->compositor->wl_display; + uint32_t msecs; if (key == b->key) { if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { @@ -215,10 +217,11 @@ binding_key(struct weston_keyboard_grab *grab, } if (!wl_list_empty(&keyboard->focus_resource_list)) { serial = wl_display_next_serial(display); + msecs = timespec_to_msec(time); wl_resource_for_each(resource, &keyboard->focus_resource_list) { wl_keyboard_send_key(resource, serial, - time, + msecs, key, state); } @@ -255,8 +258,9 @@ static const struct weston_keyboard_grab_interface binding_grab = { }; static void -install_binding_grab(struct weston_keyboard *keyboard, uint32_t time, - uint32_t key, struct weston_surface *focus) +install_binding_grab(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, + struct weston_surface *focus) { struct binding_keyboard_grab *grab; @@ -282,7 +286,7 @@ install_binding_grab(struct weston_keyboard *keyboard, uint32_t time, void weston_compositor_run_key_binding(struct weston_compositor *compositor, struct weston_keyboard *keyboard, - uint32_t time, uint32_t key, + const struct timespec *time, uint32_t key, enum wl_keyboard_key_state state) { struct weston_binding *b, *tmp; @@ -416,7 +420,7 @@ weston_compositor_run_axis_binding(struct weston_compositor *compositor, int weston_compositor_run_debug_binding(struct weston_compositor *compositor, struct weston_keyboard *keyboard, - uint32_t time, uint32_t key, + const struct timespec *time, uint32_t key, enum wl_keyboard_key_state state) { weston_key_binding_handler_t handler; @@ -443,7 +447,7 @@ struct debug_binding_grab { }; static void -debug_binding_key(struct weston_keyboard_grab *grab, uint32_t time, +debug_binding_key(struct weston_keyboard_grab *grab, const struct timespec *time, uint32_t key, uint32_t state) { struct debug_binding_grab *db = (struct debug_binding_grab *) grab; @@ -455,6 +459,7 @@ debug_binding_key(struct weston_keyboard_grab *grab, uint32_t time, int check_binding = 1; int i; struct wl_list *resource_list; + uint32_t msecs; if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { /* Do not run bindings on key releases */ @@ -503,8 +508,9 @@ debug_binding_key(struct weston_keyboard_grab *grab, uint32_t time, if (send) { serial = wl_display_next_serial(display); resource_list = &grab->keyboard->focus_resource_list; + msecs = timespec_to_msec(time); wl_resource_for_each(resource, resource_list) { - wl_keyboard_send_key(resource, serial, time, key, state); + wl_keyboard_send_key(resource, serial, msecs, key, state); } } @@ -548,7 +554,7 @@ struct weston_keyboard_grab_interface debug_binding_keyboard_grab = { }; static void -debug_binding(struct weston_keyboard *keyboard, uint32_t time, +debug_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct debug_binding_grab *grab; diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index b641d61e3..0b243577f 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3804,8 +3804,8 @@ find_primary_gpu(struct drm_backend *b, const char *seat) } static void -planes_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, - void *data) +planes_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) { struct drm_backend *b = data; @@ -3885,8 +3885,8 @@ create_recorder(struct drm_backend *b, int width, int height, } static void -recorder_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, - void *data) +recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) { struct drm_backend *b = data; struct drm_output *output; @@ -3927,8 +3927,8 @@ recorder_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, } #else static void -recorder_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, - void *data) +recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) { weston_log("Compiled without libva support\n"); } @@ -3979,8 +3979,8 @@ switch_to_gl_renderer(struct drm_backend *b) } static void -renderer_switch_binding(struct weston_keyboard *keyboard, uint32_t time, - uint32_t key, void *data) +renderer_switch_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) { struct drm_backend *b = to_drm_backend(keyboard->seat->compositor); diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 05e11ee85..3b984e416 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -1135,6 +1135,7 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) enum wl_keyboard_key_state keyState; RdpPeerContext *peerContext = (RdpPeerContext *)input->context; int notify = 0; + struct timespec time; if (!(peerContext->item.flags & RDP_PEER_ACTIVATED)) FREERDP_CB_RETURN(TRUE); @@ -1160,7 +1161,8 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) /*weston_log("code=%x ext=%d vk_code=%x scan_code=%x\n", code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, vk_code, scan_code);*/ - notify_key(peerContext->item.seat, weston_compositor_get_time(), + timespec_from_msec(&time, weston_compositor_get_time()); + notify_key(peerContext->item.seat, &time, scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); } diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 6a1a50f2c..7b96b7bee 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1905,9 +1905,12 @@ input_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { struct wayland_input *input = data; + struct timespec ts; + + timespec_from_msec(&ts, time); input->key_serial = serial; - notify_key(&input->base, time, key, + notify_key(&input->base, &ts, key, state ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED, input->keyboard_state_update); @@ -2515,8 +2518,8 @@ create_cursor(struct wayland_backend *b, } static void -fullscreen_binding(struct weston_keyboard *keyboard, uint32_t time, - uint32_t key, void *data) +fullscreen_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) { struct wayland_backend *b = data; struct wayland_input *input = NULL; diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 13643a118..edaa53ef9 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -1325,6 +1325,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) uint32_t i, set; uint8_t response_type; int count; + struct timespec time; prev = NULL; count = 0; @@ -1351,8 +1352,10 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) * and fall through and handle the new * event below. */ update_xkb_state_from_core(b, key_release->state); + timespec_from_msec(&time, + weston_compositor_get_time()); notify_key(&b->core_seat, - weston_compositor_get_time(), + &time, key_release->detail - 8, WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_AUTOMATIC); @@ -1395,8 +1398,9 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) key_press = (xcb_key_press_event_t *) event; if (!b->has_xkb) update_xkb_state_from_core(b, key_press->state); + timespec_from_msec(&time, weston_compositor_get_time()); notify_key(&b->core_seat, - weston_compositor_get_time(), + &time, key_press->detail - 8, WL_KEYBOARD_KEY_STATE_PRESSED, b->has_xkb ? STATE_UPDATE_NONE : @@ -1410,8 +1414,9 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) break; } key_release = (xcb_key_press_event_t *) event; + timespec_from_msec(&time, weston_compositor_get_time()); notify_key(&b->core_seat, - weston_compositor_get_time(), + &time, key_release->detail - 8, WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_NONE); @@ -1517,8 +1522,9 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) case XCB_KEY_RELEASE: key_release = (xcb_key_press_event_t *) prev; update_xkb_state_from_core(b, key_release->state); + timespec_from_msec(&time, weston_compositor_get_time()); notify_key(&b->core_seat, - weston_compositor_get_time(), + &time, key_release->detail - 8, WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_AUTOMATIC); diff --git a/libweston/compositor.c b/libweston/compositor.c index cfa7eace0..c130d92ce 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5241,8 +5241,9 @@ weston_environment_get_fd(const char *env) } static void -timeline_key_binding_handler(struct weston_keyboard *keyboard, uint32_t time, - uint32_t key, void *data) +timeline_key_binding_handler(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, + void *data) { struct weston_compositor *compositor = data; diff --git a/libweston/compositor.h b/libweston/compositor.h index ec76248e5..964c32336 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -289,8 +289,8 @@ struct weston_pointer_grab { struct weston_keyboard_grab; struct weston_keyboard_grab_interface { - void (*key)(struct weston_keyboard_grab *grab, uint32_t time, - uint32_t key, uint32_t state); + void (*key)(struct weston_keyboard_grab *grab, + const struct timespec *time, uint32_t key, uint32_t state); void (*modifiers)(struct weston_keyboard_grab *grab, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); @@ -492,7 +492,7 @@ bool weston_keyboard_has_focus_resource(struct weston_keyboard *keyboard); void weston_keyboard_send_key(struct weston_keyboard *keyboard, - uint32_t time, uint32_t key, + const struct timespec *time, uint32_t key, enum wl_keyboard_key_state state); void weston_keyboard_send_modifiers(struct weston_keyboard *keyboard, @@ -583,7 +583,7 @@ struct weston_keyboard { struct weston_keyboard_grab default_grab; uint32_t grab_key; uint32_t grab_serial; - uint32_t grab_time; + struct timespec grab_time; struct wl_array keys; @@ -1385,7 +1385,7 @@ void notify_pointer_frame(struct weston_seat *seat); void -notify_key(struct weston_seat *seat, uint32_t time, uint32_t key, +notify_key(struct weston_seat *seat, const struct timespec *time, uint32_t key, enum wl_keyboard_key_state state, enum weston_key_state_update update_state); void @@ -1475,7 +1475,8 @@ weston_compositor_pick_view(struct weston_compositor *compositor, struct weston_binding; typedef void (*weston_key_binding_handler_t)(struct weston_keyboard *keyboard, - uint32_t time, uint32_t key, + const struct timespec *time, + uint32_t key, void *data); struct weston_binding * weston_compositor_add_key_binding(struct weston_compositor *compositor, @@ -1541,7 +1542,7 @@ weston_binding_list_destroy_all(struct wl_list *list); void weston_compositor_run_key_binding(struct weston_compositor *compositor, struct weston_keyboard *keyboard, - uint32_t time, + const struct timespec *time, uint32_t key, enum wl_keyboard_key_state state); @@ -1567,7 +1568,8 @@ weston_compositor_run_axis_binding(struct weston_compositor *compositor, struct weston_pointer_axis_event *event); int weston_compositor_run_debug_binding(struct weston_compositor *compositor, - struct weston_keyboard *keyboard, uint32_t time, + struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, enum wl_keyboard_key_state state); diff --git a/libweston/data-device.c b/libweston/data-device.c index 26898aa60..674d3a24e 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -824,7 +824,7 @@ static const struct weston_touch_grab_interface touch_drag_grab_interface = { static void drag_grab_keyboard_key(struct weston_keyboard_grab *grab, - uint32_t time, uint32_t key, uint32_t state) + const struct timespec *time, uint32_t key, uint32_t state) { } diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 244ce3099..94d81ef45 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -3534,7 +3534,8 @@ compile_shaders(struct weston_compositor *ec) } static void -fragment_debug_binding(struct weston_keyboard *keyboard, uint32_t time, +fragment_debug_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) { struct weston_compositor *ec = data; @@ -3560,7 +3561,8 @@ fragment_debug_binding(struct weston_keyboard *keyboard, uint32_t time, } static void -fan_debug_repaint_binding(struct weston_keyboard *keyboard, uint32_t time, +fan_debug_repaint_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) { struct weston_compositor *compositor = data; diff --git a/libweston/input.c b/libweston/input.c index 877b0b83c..a1085463a 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -842,26 +842,29 @@ weston_keyboard_has_focus_resource(struct weston_keyboard *keyboard) */ WL_EXPORT void weston_keyboard_send_key(struct weston_keyboard *keyboard, - uint32_t time, uint32_t key, + const struct timespec *time, uint32_t key, enum wl_keyboard_key_state state) { struct wl_resource *resource; struct wl_display *display = keyboard->seat->compositor->wl_display; uint32_t serial; struct wl_list *resource_list; + uint32_t msecs; if (!weston_keyboard_has_focus_resource(keyboard)) return; resource_list = &keyboard->focus_resource_list; serial = wl_display_next_serial(display); + msecs = timespec_to_msec(time); wl_resource_for_each(resource, resource_list) - wl_keyboard_send_key(resource, serial, time, key, state); + wl_keyboard_send_key(resource, serial, msecs, key, state); }; static void default_grab_keyboard_key(struct weston_keyboard_grab *grab, - uint32_t time, uint32_t key, uint32_t state) + const struct timespec *time, uint32_t key, + uint32_t state) { weston_keyboard_send_key(grab->keyboard, time, key, state); } @@ -1944,7 +1947,7 @@ update_keymap(struct weston_seat *seat) } WL_EXPORT void -notify_key(struct weston_seat *seat, uint32_t time, uint32_t key, +notify_key(struct weston_seat *seat, const struct timespec *time, uint32_t key, enum wl_keyboard_key_state state, enum weston_key_state_update update_state) { @@ -1996,7 +1999,7 @@ notify_key(struct weston_seat *seat, uint32_t time, uint32_t key, keyboard->grab_serial = wl_display_get_serial(compositor->wl_display); if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - keyboard->grab_time = time; + keyboard->grab_time = *time; keyboard->grab_key = key; } } diff --git a/libweston/launcher-util.c b/libweston/launcher-util.c index fa3ed13b1..96a0ba6f8 100644 --- a/libweston/launcher-util.c +++ b/libweston/launcher-util.c @@ -95,7 +95,7 @@ weston_launcher_restore(struct weston_launcher *launcher) static void switch_vt_binding(struct weston_keyboard *keyboard, - uint32_t time, uint32_t key, void *data) + const struct timespec *time, uint32_t key, void *data) { struct weston_compositor *compositor = data; struct weston_launcher *launcher = compositor->launcher; diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index 4d8cf2e6e..69844a765 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -66,6 +66,7 @@ handle_keyboard_key(struct libinput_device *libinput_device, libinput_event_keyboard_get_key_state(keyboard_event); int seat_key_count = libinput_event_keyboard_get_seat_key_count(keyboard_event); + struct timespec time; /* Ignore key events that are not seat wide state changes. */ if ((key_state == LIBINPUT_KEY_STATE_PRESSED && @@ -74,8 +75,10 @@ handle_keyboard_key(struct libinput_device *libinput_device, seat_key_count != 0)) return; - notify_key(device->seat, - libinput_event_keyboard_get_time(keyboard_event), + timespec_from_usec(&time, + libinput_event_keyboard_get_time_usec(keyboard_event)); + + notify_key(device->seat, &time, libinput_event_keyboard_get_key(keyboard_event), key_state, STATE_UPDATE_AUTOMATIC); } diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c index 4ba13778f..f7366cf60 100644 --- a/libweston/pixman-renderer.c +++ b/libweston/pixman-renderer.c @@ -804,8 +804,8 @@ pixman_renderer_surface_copy_content(struct weston_surface *surface, } static void -debug_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, - void *data) +debug_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) { struct weston_compositor *ec = data; struct pixman_renderer *pr = (struct pixman_renderer *) ec->renderer; diff --git a/tests/surface-screenshot.c b/tests/surface-screenshot.c index 716eedae6..f5199371a 100644 --- a/tests/surface-screenshot.c +++ b/tests/surface-screenshot.c @@ -133,8 +133,8 @@ unpremultiply_and_swap_a8b8g8r8_to_PAMrgba(void *pixels, size_t size) } static void -trigger_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, - void *data) +trigger_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) { const char *prefix = "surfaceshot-"; const char *suffix = ".pam"; diff --git a/tests/weston-test.c b/tests/weston-test.c index 306757a01..6e7beeb7e 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -210,8 +210,11 @@ send_key(struct wl_client *client, struct wl_resource *resource, { struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat = get_seat(test); + struct timespec time; + + timespec_from_msec(&time, 100); - notify_key(seat, 100, key, state, STATE_UPDATE_AUTOMATIC); + notify_key(seat, &time, key, state, STATE_UPDATE_AUTOMATIC); } static void From 9448deb07341632b5826422f657d44fc792ab83b Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 16 Nov 2017 18:20:58 +0200 Subject: [PATCH 0204/1642] libweston: Use struct timespec for touch down events Change code related to touch down events to use struct timespec to represent time. This commit is part of a larger effort to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- desktop-shell/shell.c | 8 +++++--- ivi-shell/hmi-controller.c | 3 ++- ivi-shell/ivi-shell.c | 3 ++- libweston-desktop/seat.c | 3 ++- libweston/bindings.c | 3 ++- libweston/compositor-wayland.c | 14 +++++++++++--- libweston/compositor.h | 15 ++++++++------- libweston/data-device.c | 5 +++-- libweston/input.c | 22 +++++++++++++--------- libweston/libinput-device.c | 14 +++++++++----- 10 files changed, 57 insertions(+), 33 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index b8f00eb12..30cffed18 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -1331,7 +1331,8 @@ take_surface_to_workspace_by_seat(struct desktop_shell *shell, } static void -touch_move_grab_down(struct weston_touch_grab *grab, uint32_t time, +touch_move_grab_down(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, wl_fixed_t x, wl_fixed_t y) { } @@ -3274,7 +3275,7 @@ fullscreen_binding(struct weston_keyboard *keyboard, } static void -touch_move_binding(struct weston_touch *touch, uint32_t time, void *data) +touch_move_binding(struct weston_touch *touch, const struct timespec *time, void *data) { struct weston_surface *focus; struct weston_surface *surface; @@ -3774,7 +3775,8 @@ click_to_activate_binding(struct weston_pointer *pointer, } static void -touch_to_activate_binding(struct weston_touch *touch, uint32_t time, +touch_to_activate_binding(struct weston_touch *touch, + const struct timespec *time, void *data) { if (touch->grab != &touch->default_grab) diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index d61e26b56..9b99668c1 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -1584,7 +1584,8 @@ pointer_move_workspace_grab_button(struct weston_pointer_grab *grab, } static void -touch_nope_grab_down(struct weston_touch_grab *grab, uint32_t time, +touch_nope_grab_down(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, wl_fixed_t sx, wl_fixed_t sy) { } diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index e675a3bd1..766a1fd1c 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -463,7 +463,8 @@ click_to_activate_binding(struct weston_pointer *pointer, } static void -touch_to_activate_binding(struct weston_touch *touch, uint32_t time, +touch_to_activate_binding(struct weston_touch *touch, + const struct timespec *time, void *data) { if (touch->grab != &touch->default_grab) diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c index bba2605f2..3e044fe11 100644 --- a/libweston-desktop/seat.c +++ b/libweston-desktop/seat.c @@ -180,7 +180,8 @@ static const struct weston_pointer_grab_interface weston_desktop_seat_pointer_po static void weston_desktop_seat_popup_grab_touch_down(struct weston_touch_grab *grab, - uint32_t time, int touch_id, + const struct timespec *time, + int touch_id, wl_fixed_t sx, wl_fixed_t sy) { weston_touch_send_down(grab->touch, time, touch_id, sx, sy); diff --git a/libweston/bindings.c b/libweston/bindings.c index 79c043e9b..d9e280e4d 100644 --- a/libweston/bindings.c +++ b/libweston/bindings.c @@ -377,7 +377,8 @@ weston_compositor_run_button_binding(struct weston_compositor *compositor, void weston_compositor_run_touch_binding(struct weston_compositor *compositor, - struct weston_touch *touch, uint32_t time, + struct weston_touch *touch, + const struct timespec *time, int touch_type) { struct weston_binding *b, *tmp; diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 7b96b7bee..3bdfb03ee 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1974,10 +1974,13 @@ input_handle_touch_down(void *data, struct wl_touch *wl_touch, bool first_touch; int32_t fx, fy; double x, y; + struct timespec ts; x = wl_fixed_to_double(fixed_x); y = wl_fixed_to_double(fixed_y); + timespec_from_msec(&ts, time); + first_touch = (input->touch_points == 0); input->touch_points++; @@ -2015,7 +2018,7 @@ input_handle_touch_down(void *data, struct wl_touch *wl_touch, weston_output_transform_coordinate(&output->base, x, y, &x, &y); - notify_touch(&input->base, time, id, x, y, WL_TOUCH_DOWN); + notify_touch(&input->base, &ts, id, x, y, WL_TOUCH_DOWN); input->touch_active = true; } @@ -2026,6 +2029,9 @@ input_handle_touch_up(void *data, struct wl_touch *wl_touch, struct wayland_input *input = data; struct wayland_output *output = input->touch_focus; bool active = input->touch_active; + struct timespec ts; + + timespec_from_msec(&ts, time); input->touch_points--; if (input->touch_points == 0) { @@ -2053,7 +2059,7 @@ input_handle_touch_up(void *data, struct wl_touch *wl_touch, } if (active) - notify_touch(&input->base, time, id, 0, 0, WL_TOUCH_UP); + notify_touch(&input->base, &ts, id, 0, 0, WL_TOUCH_UP); } static void @@ -2065,9 +2071,11 @@ input_handle_touch_motion(void *data, struct wl_touch *wl_touch, struct wayland_output *output = input->touch_focus; int32_t fx, fy; double x, y; + struct timespec ts; x = wl_fixed_to_double(fixed_x); y = wl_fixed_to_double(fixed_y); + timespec_from_msec(&ts, time); if (!output || !input->touch_active) return; @@ -2080,7 +2088,7 @@ input_handle_touch_motion(void *data, struct wl_touch *wl_touch, weston_output_transform_coordinate(&output->base, x, y, &x, &y); - notify_touch(&input->base, time, id, x, y, WL_TOUCH_MOTION); + notify_touch(&input->base, &ts, id, x, y, WL_TOUCH_MOTION); } static void diff --git a/libweston/compositor.h b/libweston/compositor.h index 964c32336..9aadd83f6 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -305,7 +305,7 @@ struct weston_keyboard_grab { struct weston_touch_grab; struct weston_touch_grab_interface { void (*down)(struct weston_touch_grab *grab, - uint32_t time, + const struct timespec *time, int touch_id, wl_fixed_t sx, wl_fixed_t sy); @@ -412,7 +412,7 @@ struct weston_touch { int grab_touch_id; wl_fixed_t grab_x, grab_y; uint32_t grab_serial; - uint32_t grab_time; + struct timespec grab_time; }; void @@ -516,7 +516,7 @@ weston_touch_end_grab(struct weston_touch *touch); bool weston_touch_has_focus_resource(struct weston_touch *touch); void -weston_touch_send_down(struct weston_touch *touch, uint32_t time, +weston_touch_send_down(struct weston_touch *touch, const struct timespec *time, int touch_id, wl_fixed_t x, wl_fixed_t y); void weston_touch_send_up(struct weston_touch *touch, uint32_t time, int touch_id); @@ -1402,8 +1402,8 @@ void notify_keyboard_focus_out(struct weston_seat *seat); void -notify_touch(struct weston_seat *seat, uint32_t time, int touch_id, - double x, double y, int touch_type); +notify_touch(struct weston_seat *seat, const struct timespec *time, + int touch_id, double x, double y, int touch_type); void notify_touch_frame(struct weston_seat *seat); @@ -1506,7 +1506,7 @@ weston_compositor_add_button_binding(struct weston_compositor *compositor, void *data); typedef void (*weston_touch_binding_handler_t)(struct weston_touch *touch, - uint32_t time, + const struct timespec *time, void *data); struct weston_binding * weston_compositor_add_touch_binding(struct weston_compositor *compositor, @@ -1559,7 +1559,8 @@ weston_compositor_run_button_binding(struct weston_compositor *compositor, enum wl_pointer_button_state value); void weston_compositor_run_touch_binding(struct weston_compositor *compositor, - struct weston_touch *touch, uint32_t time, + struct weston_touch *touch, + const struct timespec *time, int touch_type); int weston_compositor_run_axis_binding(struct weston_compositor *compositor, diff --git a/libweston/data-device.c b/libweston/data-device.c index 674d3a24e..1c7d546c7 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -718,8 +718,9 @@ static const struct weston_pointer_grab_interface pointer_drag_grab_interface = }; static void -drag_grab_touch_down(struct weston_touch_grab *grab, uint32_t time, - int touch_id, wl_fixed_t sx, wl_fixed_t sy) +drag_grab_touch_down(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, + wl_fixed_t sx, wl_fixed_t sy) { } diff --git a/libweston/input.c b/libweston/input.c index a1085463a..73334bf8f 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -670,7 +670,7 @@ weston_touch_has_focus_resource(struct weston_touch *touch) * resources of the client which currently has the surface with touch focus. */ WL_EXPORT void -weston_touch_send_down(struct weston_touch *touch, uint32_t time, +weston_touch_send_down(struct weston_touch *touch, const struct timespec *time, int touch_id, wl_fixed_t x, wl_fixed_t y) { struct wl_display *display = touch->seat->compositor->wl_display; @@ -678,6 +678,7 @@ weston_touch_send_down(struct weston_touch *touch, uint32_t time, struct wl_resource *resource; struct wl_list *resource_list; wl_fixed_t sx, sy; + uint32_t msecs; if (!weston_touch_has_focus_resource(touch)) return; @@ -686,15 +687,17 @@ weston_touch_send_down(struct weston_touch *touch, uint32_t time, resource_list = &touch->focus_resource_list; serial = wl_display_next_serial(display); + msecs = timespec_to_msec(time); wl_resource_for_each(resource, resource_list) - wl_touch_send_down(resource, serial, time, + wl_touch_send_down(resource, serial, msecs, touch->focus->surface->resource, touch_id, sx, sy); } static void -default_grab_touch_down(struct weston_touch_grab *grab, uint32_t time, - int touch_id, wl_fixed_t x, wl_fixed_t y) +default_grab_touch_down(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) { weston_touch_send_down(grab->touch, time, touch_id, x, y); } @@ -2140,8 +2143,8 @@ weston_touch_set_focus(struct weston_touch *touch, struct weston_view *view) * */ WL_EXPORT void -notify_touch(struct weston_seat *seat, uint32_t time, int touch_id, - double double_x, double double_y, int touch_type) +notify_touch(struct weston_seat *seat, const struct timespec *time, + int touch_id, double double_x, double double_y, int touch_type) { struct weston_compositor *ec = seat->compositor; struct weston_touch *touch = weston_seat_get_touch(seat); @@ -2186,7 +2189,7 @@ notify_touch(struct weston_seat *seat, uint32_t time, int touch_id, touch->grab_serial = wl_display_get_serial(ec->wl_display); touch->grab_touch_id = touch_id; - touch->grab_time = time; + touch->grab_time = *time; touch->grab_x = x; touch->grab_y = y; } @@ -2197,7 +2200,8 @@ notify_touch(struct weston_seat *seat, uint32_t time, int touch_id, if (!ev) break; - grab->interface->motion(grab, time, touch_id, x, y); + grab->interface->motion(grab, timespec_to_msec(time), + touch_id, x, y); break; case WL_TOUCH_UP: if (touch->num_tp == 0) { @@ -2211,7 +2215,7 @@ notify_touch(struct weston_seat *seat, uint32_t time, int touch_id, weston_compositor_idle_release(ec); touch->num_tp--; - grab->interface->up(grab, time, touch_id); + grab->interface->up(grab, timespec_to_msec(time), touch_id); if (touch->num_tp == 0) weston_touch_set_focus(touch, NULL); break; diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index 69844a765..1b9fc9c83 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -306,13 +306,14 @@ handle_touch_with_coords(struct libinput_device *libinput_device, double x; double y; uint32_t width, height; - uint32_t time; + struct timespec time; int32_t slot; if (!device->output) return; - time = libinput_event_touch_get_time(touch_event); + timespec_from_usec(&time, + libinput_event_touch_get_time_usec(touch_event)); slot = libinput_event_touch_get_seat_slot(touch_event); width = device->output->current_mode->width; @@ -323,7 +324,7 @@ handle_touch_with_coords(struct libinput_device *libinput_device, weston_output_transform_coordinate(device->output, x, y, &x, &y); - notify_touch(device->seat, time, slot, x, y, touch_type); + notify_touch(device->seat, &time, slot, x, y, touch_type); } static void @@ -346,10 +347,13 @@ handle_touch_up(struct libinput_device *libinput_device, { struct evdev_device *device = libinput_device_get_user_data(libinput_device); - uint32_t time = libinput_event_touch_get_time(touch_event); + struct timespec time; int32_t slot = libinput_event_touch_get_seat_slot(touch_event); - notify_touch(device->seat, time, slot, 0, 0, WL_TOUCH_UP); + timespec_from_usec(&time, + libinput_event_touch_get_time_usec(touch_event)); + + notify_touch(device->seat, &time, slot, 0, 0, WL_TOUCH_UP); } static void From 27a51b83e56ac04ffd17b6220db3c2a46e9b09ae Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 16 Nov 2017 18:20:59 +0200 Subject: [PATCH 0205/1642] libweston: Use struct timespec for touch up events Change code related to touch up events to use struct timespec to represent time. This commit is part of a larger effort to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- desktop-shell/shell.c | 3 ++- ivi-shell/hmi-controller.c | 3 ++- libweston-desktop/seat.c | 3 ++- libweston/compositor.h | 5 +++-- libweston/data-device.c | 2 +- libweston/input.c | 11 +++++++---- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 30cffed18..5823a481a 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -1338,7 +1338,8 @@ touch_move_grab_down(struct weston_touch_grab *grab, } static void -touch_move_grab_up(struct weston_touch_grab *grab, uint32_t time, int touch_id) +touch_move_grab_up(struct weston_touch_grab *grab, const struct timespec *time, + int touch_id) { struct weston_touch_move_grab *move = (struct weston_touch_move_grab *) container_of( diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index 9b99668c1..f9500ef57 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -1591,7 +1591,8 @@ touch_nope_grab_down(struct weston_touch_grab *grab, } static void -touch_move_workspace_grab_up(struct weston_touch_grab *grab, uint32_t time, +touch_move_workspace_grab_up(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id) { struct touch_move_grab *tch_move_grab = (struct touch_move_grab *)grab; diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c index 3e044fe11..e160fd184 100644 --- a/libweston-desktop/seat.c +++ b/libweston-desktop/seat.c @@ -189,7 +189,8 @@ weston_desktop_seat_popup_grab_touch_down(struct weston_touch_grab *grab, static void weston_desktop_seat_popup_grab_touch_up(struct weston_touch_grab *grab, - uint32_t time, int touch_id) + const struct timespec *time, + int touch_id) { weston_touch_send_up(grab->touch, time, touch_id); } diff --git a/libweston/compositor.h b/libweston/compositor.h index 9aadd83f6..40d192eee 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -310,7 +310,7 @@ struct weston_touch_grab_interface { wl_fixed_t sx, wl_fixed_t sy); void (*up)(struct weston_touch_grab *grab, - uint32_t time, + const struct timespec *time, int touch_id); void (*motion)(struct weston_touch_grab *grab, uint32_t time, @@ -519,7 +519,8 @@ void weston_touch_send_down(struct weston_touch *touch, const struct timespec *time, int touch_id, wl_fixed_t x, wl_fixed_t y); void -weston_touch_send_up(struct weston_touch *touch, uint32_t time, int touch_id); +weston_touch_send_up(struct weston_touch *touch, const struct timespec *time, + int touch_id); void weston_touch_send_motion(struct weston_touch *touch, uint32_t time, int touch_id, wl_fixed_t x, wl_fixed_t y); diff --git a/libweston/data-device.c b/libweston/data-device.c index 1c7d546c7..5821386e4 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -738,7 +738,7 @@ data_device_end_touch_drag_grab(struct weston_touch_drag *drag) static void drag_grab_touch_up(struct weston_touch_grab *grab, - uint32_t time, int touch_id) + const struct timespec *time, int touch_id) { struct weston_touch_drag *touch_drag = container_of(grab, struct weston_touch_drag, grab); diff --git a/libweston/input.c b/libweston/input.c index 73334bf8f..996c00f75 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -713,25 +713,28 @@ default_grab_touch_down(struct weston_touch_grab *grab, * resources of the client which currently has the surface with touch focus. */ WL_EXPORT void -weston_touch_send_up(struct weston_touch *touch, uint32_t time, int touch_id) +weston_touch_send_up(struct weston_touch *touch, const struct timespec *time, + int touch_id) { struct wl_display *display = touch->seat->compositor->wl_display; uint32_t serial; struct wl_resource *resource; struct wl_list *resource_list; + uint32_t msecs; if (!weston_touch_has_focus_resource(touch)) return; resource_list = &touch->focus_resource_list; serial = wl_display_next_serial(display); + msecs = timespec_to_msec(time); wl_resource_for_each(resource, resource_list) - wl_touch_send_up(resource, serial, time, touch_id); + wl_touch_send_up(resource, serial, msecs, touch_id); } static void default_grab_touch_up(struct weston_touch_grab *grab, - uint32_t time, int touch_id) + const struct timespec *time, int touch_id) { weston_touch_send_up(grab->touch, time, touch_id); } @@ -2215,7 +2218,7 @@ notify_touch(struct weston_seat *seat, const struct timespec *time, weston_compositor_idle_release(ec); touch->num_tp--; - grab->interface->up(grab, timespec_to_msec(time), touch_id); + grab->interface->up(grab, time, touch_id); if (touch->num_tp == 0) weston_touch_set_focus(touch, NULL); break; From 7d2abcf6c8e7bf0cb137febfa43936ba9877c465 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 16 Nov 2017 18:21:00 +0200 Subject: [PATCH 0206/1642] libweston: Use struct timespec for touch motion events Change code related to touch motion events to use struct timespec to represent time. This commit is part of a larger effort to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- desktop-shell/shell.c | 5 +++-- ivi-shell/hmi-controller.c | 5 +++-- libweston-desktop/seat.c | 3 ++- libweston/compositor.h | 7 ++++--- libweston/data-device.c | 11 +++++++---- libweston/input.c | 17 ++++++++++------- 6 files changed, 29 insertions(+), 19 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 5823a481a..564cbb587 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -1355,8 +1355,9 @@ touch_move_grab_up(struct weston_touch_grab *grab, const struct timespec *time, } static void -touch_move_grab_motion(struct weston_touch_grab *grab, uint32_t time, - int touch_id, wl_fixed_t x, wl_fixed_t y) +touch_move_grab_motion(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) { struct weston_touch_move_grab *move = (struct weston_touch_move_grab *) grab; struct shell_surface *shsurf = move->base.shsurf; diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index f9500ef57..5a2ff78c9 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -1551,8 +1551,9 @@ pointer_move_grab_motion(struct weston_pointer_grab *grab, } static void -touch_move_grab_motion(struct weston_touch_grab *grab, uint32_t time, - int touch_id, wl_fixed_t x, wl_fixed_t y) +touch_move_grab_motion(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) { struct touch_move_grab *tch_move_grab = (struct touch_move_grab *)grab; diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c index e160fd184..382b9e413 100644 --- a/libweston-desktop/seat.c +++ b/libweston-desktop/seat.c @@ -197,7 +197,8 @@ weston_desktop_seat_popup_grab_touch_up(struct weston_touch_grab *grab, static void weston_desktop_seat_popup_grab_touch_motion(struct weston_touch_grab *grab, - uint32_t time, int touch_id, + const struct timespec *time, + int touch_id, wl_fixed_t sx, wl_fixed_t sy) { weston_touch_send_motion(grab->touch, time, touch_id, sx, sy); diff --git a/libweston/compositor.h b/libweston/compositor.h index 40d192eee..5eff0262e 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -313,7 +313,7 @@ struct weston_touch_grab_interface { const struct timespec *time, int touch_id); void (*motion)(struct weston_touch_grab *grab, - uint32_t time, + const struct timespec *time, int touch_id, wl_fixed_t sx, wl_fixed_t sy); @@ -522,8 +522,9 @@ void weston_touch_send_up(struct weston_touch *touch, const struct timespec *time, int touch_id); void -weston_touch_send_motion(struct weston_touch *touch, uint32_t time, - int touch_id, wl_fixed_t x, wl_fixed_t y); +weston_touch_send_motion(struct weston_touch *touch, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y); void weston_touch_send_frame(struct weston_touch *touch); diff --git a/libweston/data-device.c b/libweston/data-device.c index 5821386e4..b4bb4b37f 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -770,14 +770,16 @@ drag_grab_touch_focus(struct weston_touch_drag *drag) } static void -drag_grab_touch_motion(struct weston_touch_grab *grab, uint32_t time, - int touch_id, wl_fixed_t x, wl_fixed_t y) +drag_grab_touch_motion(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, wl_fixed_t x, wl_fixed_t y) { struct weston_touch_drag *touch_drag = container_of(grab, struct weston_touch_drag, grab); struct weston_touch *touch = grab->touch; wl_fixed_t view_x, view_y; float fx, fy; + uint32_t msecs; if (touch_id != touch->grab_touch_id) return; @@ -791,11 +793,12 @@ drag_grab_touch_motion(struct weston_touch_grab *grab, uint32_t time, } if (touch_drag->base.focus_resource) { + msecs = timespec_to_msec(time); weston_view_from_global_fixed(touch_drag->base.focus, touch->grab_x, touch->grab_y, &view_x, &view_y); - wl_data_device_send_motion(touch_drag->base.focus_resource, time, - view_x, view_y); + wl_data_device_send_motion(touch_drag->base.focus_resource, + msecs, view_x, view_y); } } diff --git a/libweston/input.c b/libweston/input.c index 996c00f75..0a694d138 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -752,12 +752,14 @@ default_grab_touch_up(struct weston_touch_grab *grab, * resources of the client which currently has the surface with touch focus. */ WL_EXPORT void -weston_touch_send_motion(struct weston_touch *touch, uint32_t time, - int touch_id, wl_fixed_t x, wl_fixed_t y) +weston_touch_send_motion(struct weston_touch *touch, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) { struct wl_resource *resource; struct wl_list *resource_list; wl_fixed_t sx, sy; + uint32_t msecs; if (!weston_touch_has_focus_resource(touch)) return; @@ -765,15 +767,17 @@ weston_touch_send_motion(struct weston_touch *touch, uint32_t time, weston_view_from_global_fixed(touch->focus, x, y, &sx, &sy); resource_list = &touch->focus_resource_list; + msecs = timespec_to_msec(time); wl_resource_for_each(resource, resource_list) { - wl_touch_send_motion(resource, time, + wl_touch_send_motion(resource, msecs, touch_id, sx, sy); } } static void -default_grab_touch_motion(struct weston_touch_grab *grab, uint32_t time, - int touch_id, wl_fixed_t x, wl_fixed_t y) +default_grab_touch_motion(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) { weston_touch_send_motion(grab->touch, time, touch_id, x, y); } @@ -2203,8 +2207,7 @@ notify_touch(struct weston_seat *seat, const struct timespec *time, if (!ev) break; - grab->interface->motion(grab, timespec_to_msec(time), - touch_id, x, y); + grab->interface->motion(grab, time, touch_id, x, y); break; case WL_TOUCH_UP: if (touch->num_tp == 0) { From 409b01fd6dad591b42e4f43c37b4e8c833422968 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 16 Nov 2017 18:21:01 +0200 Subject: [PATCH 0207/1642] libweston: Use struct timespec for compositor time Change weston_compositor_get_time to return the current compositor time as a struct timespec. Also, use clock_gettime (with CLOCK_REALTIME) to get the time, since it's equivalent to the currently used gettimeofday call, but returns the data directly in a struct timespec. This commit is part of a larger effort to transition the Weston codebase to struct timespec. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- compositor/text-backend.c | 11 +++++++---- desktop-shell/shell.c | 8 ++++---- desktop-shell/shell.h | 2 +- libweston/compositor-rdp.c | 10 +++++----- libweston/compositor-x11.c | 21 ++++++++++----------- libweston/compositor.c | 10 +++------- libweston/compositor.h | 4 ++-- 7 files changed, 32 insertions(+), 34 deletions(-) diff --git a/compositor/text-backend.c b/compositor/text-backend.c index 5f6b5d800..e10f95764 100644 --- a/compositor/text-backend.c +++ b/compositor/text-backend.c @@ -106,7 +106,7 @@ struct text_backend { struct wl_client *client; unsigned deathcount; - uint32_t deathstamp; + struct timespec deathstamp; } input_method; struct wl_listener client_listener; @@ -938,11 +938,14 @@ static void launch_input_method(struct text_backend *text_backend); static void respawn_input_method_process(struct text_backend *text_backend) { - uint32_t time; + struct timespec time; + int64_t tdiff; /* if input_method dies more than 5 times in 10 seconds, give up */ - time = weston_compositor_get_time(); - if (time - text_backend->input_method.deathstamp > 10000) { + weston_compositor_get_time(&time); + tdiff = timespec_sub_to_msec(&time, + &text_backend->input_method.deathstamp); + if (tdiff > 10000) { text_backend->input_method.deathstamp = time; text_backend->input_method.deathcount = 0; } diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 564cbb587..a0070a042 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -4209,11 +4209,11 @@ static void launch_desktop_shell_process(void *data); static void respawn_desktop_shell_process(struct desktop_shell *shell) { - uint32_t time; + struct timespec time; /* if desktop-shell dies more than 5 times in 30 seconds, give up */ - time = weston_compositor_get_time(); - if (time - shell->child.deathstamp > 30000) { + weston_compositor_get_time(&time); + if (timespec_sub_to_msec(&time, &shell->child.deathstamp) > 30000) { shell->child.deathstamp = time; shell->child.deathcount = 0; } @@ -5043,7 +5043,7 @@ wet_shell_init(struct weston_compositor *ec, shell, bind_desktop_shell) == NULL) return -1; - shell->child.deathstamp = weston_compositor_get_time(); + weston_compositor_get_time(&shell->child.deathstamp); shell->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP; diff --git a/desktop-shell/shell.h b/desktop-shell/shell.h index 0ff737bbc..fb8c2bf0b 100644 --- a/desktop-shell/shell.h +++ b/desktop-shell/shell.h @@ -161,7 +161,7 @@ struct desktop_shell { struct wl_listener client_destroy_listener; unsigned deathcount; - uint32_t deathstamp; + struct timespec deathstamp; } child; bool locked; diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 3b984e416..982867372 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -1035,7 +1035,7 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) if (flags & PTR_FLAGS_MOVE) { output = peerContext->rdpBackend->output; if (x < output->base.width && y < output->base.height) { - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_motion_absolute(peerContext->item.seat, &time, x, y); need_frame = true; @@ -1050,7 +1050,7 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) button = BTN_MIDDLE; if (button) { - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_button(peerContext->item.seat, &time, button, (flags & PTR_FLAGS_DOWN) ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED ); @@ -1076,7 +1076,7 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) weston_event.discrete = (int)value; weston_event.has_discrete = true; - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_axis(peerContext->item.seat, &time, &weston_event); need_frame = true; @@ -1097,7 +1097,7 @@ xf_extendedMouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) output = peerContext->rdpBackend->output; if (x < output->base.width && y < output->base.height) { - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_motion_absolute(peerContext->item.seat, &time, x, y); } @@ -1161,7 +1161,7 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) /*weston_log("code=%x ext=%d vk_code=%x scan_code=%x\n", code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, vk_code, scan_code);*/ - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_key(peerContext->item.seat, &time, scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); } diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index edaa53ef9..1d4108645 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -1178,7 +1178,7 @@ x11_backend_deliver_button_event(struct x11_backend *b, weston_event.has_discrete = true; weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_axis(&b->core_seat, &time, &weston_event); notify_pointer_frame(&b->core_seat); } @@ -1190,7 +1190,7 @@ x11_backend_deliver_button_event(struct x11_backend *b, weston_event.has_discrete = true; weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_axis(&b->core_seat, &time, &weston_event); notify_pointer_frame(&b->core_seat); } @@ -1202,7 +1202,7 @@ x11_backend_deliver_button_event(struct x11_backend *b, weston_event.has_discrete = true; weston_event.axis = WL_POINTER_AXIS_HORIZONTAL_SCROLL; - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_axis(&b->core_seat, &time, &weston_event); notify_pointer_frame(&b->core_seat); } @@ -1214,7 +1214,7 @@ x11_backend_deliver_button_event(struct x11_backend *b, weston_event.has_discrete = true; weston_event.axis = WL_POINTER_AXIS_HORIZONTAL_SCROLL; - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_axis(&b->core_seat, &time, &weston_event); notify_pointer_frame(&b->core_seat); } @@ -1224,7 +1224,7 @@ x11_backend_deliver_button_event(struct x11_backend *b, break; } - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_button(&b->core_seat, &time, button, is_button_pressed ? WL_POINTER_BUTTON_STATE_PRESSED : @@ -1260,7 +1260,7 @@ x11_backend_deliver_motion_event(struct x11_backend *b, .dy = y - b->prev_y }; - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_motion(&b->core_seat, &time, &motion_event); notify_pointer_frame(&b->core_seat); @@ -1352,8 +1352,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) * and fall through and handle the new * event below. */ update_xkb_state_from_core(b, key_release->state); - timespec_from_msec(&time, - weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_key(&b->core_seat, &time, key_release->detail - 8, @@ -1398,7 +1397,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) key_press = (xcb_key_press_event_t *) event; if (!b->has_xkb) update_xkb_state_from_core(b, key_press->state); - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_key(&b->core_seat, &time, key_press->detail - 8, @@ -1414,7 +1413,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) break; } key_release = (xcb_key_press_event_t *) event; - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_key(&b->core_seat, &time, key_release->detail - 8, @@ -1522,7 +1521,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) case XCB_KEY_RELEASE: key_release = (xcb_key_press_event_t *) prev; update_xkb_state_from_core(b, key_release->state); - timespec_from_msec(&time, weston_compositor_get_time()); + weston_compositor_get_time(&time); notify_key(&b->core_seat, &time, key_release->detail - 8, diff --git a/libweston/compositor.c b/libweston/compositor.c index c130d92ce..7d7a17edf 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -1721,14 +1721,10 @@ weston_surface_update_size(struct weston_surface *surface) surface_set_size(surface, width, height); } -WL_EXPORT uint32_t -weston_compositor_get_time(void) +WL_EXPORT void +weston_compositor_get_time(struct timespec *time) { - struct timeval tv; - - gettimeofday(&tv, NULL); - - return tv.tv_sec * 1000 + tv.tv_usec / 1000; + clock_gettime(CLOCK_REALTIME, time); } WL_EXPORT struct weston_view * diff --git a/libweston/compositor.h b/libweston/compositor.h index 5eff0262e..97f9a2ffb 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1674,8 +1674,8 @@ void weston_buffer_reference(struct weston_buffer_reference *ref, struct weston_buffer *buffer); -uint32_t -weston_compositor_get_time(void); +void +weston_compositor_get_time(struct timespec *time); void weston_compositor_destroy(struct weston_compositor *ec); From 3baf9ce7e80da5fc15dbc7b413f99081f344aaa9 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 11 Oct 2017 15:17:47 +0300 Subject: [PATCH 0208/1642] weston: arm SEGV handler earlier It is useful to print the backtrace regardless of whether we have a compositor and a backend initialized yet. Move catch_signals() to the earliest point in main() and protect the SEGV handler from dereferencing NULL when we don't yet have a compositor or a backend. The SEGV handler uses weston_log(), so cannot move catch_signals() any earlier. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic Reviewed-by: Daniel Stone --- compositor/main.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 9e4451e5f..9f00ee3af 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -656,7 +656,8 @@ on_caught_signal(int s, siginfo_t *siginfo, void *context) print_backtrace(); - segv_compositor->backend->restore(segv_compositor); + if (segv_compositor && segv_compositor->backend) + segv_compositor->backend->restore(segv_compositor); raise(SIGTRAP); } @@ -1824,6 +1825,8 @@ int main(int argc, char *argv[]) weston_log_set_handler(vlog, vlog_continue); weston_log_file_open(log); + catch_signals(); + weston_log("%s\n" STAMP_SPACE "%s\n" STAMP_SPACE "Bug reports to: %s\n" @@ -1872,6 +1875,7 @@ int main(int argc, char *argv[]) weston_log("fatal: failed to create compositor\n"); goto out; } + segv_compositor = ec; if (weston_compositor_init_config(ec, config) < 0) goto out; @@ -1887,9 +1891,6 @@ int main(int argc, char *argv[]) weston_pending_output_coldplug(ec); - catch_signals(); - segv_compositor = ec; - if (idle_time < 0) weston_config_section_get_int(section, "idle-time", &idle_time, -1); if (idle_time < 0) From 1fdeb68013ae3c716e32dadbf9269027836ff732 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 1 Nov 2017 10:26:46 +0200 Subject: [PATCH 0209/1642] weston: add wait-for-debugger option When you need to start Weston via weston-launch, systemd unit, or any other runner, it is annoying to try to get in with a debugger, especially if the thing you are interested in happens at start-up. To make it easy, a new option is introduced. The new option, implemented both as a command line option and a weston.ini option, raises SIGSTOP early in the start-up, before the weston_compositor has been created. This allows one to attach a debugger at a known point in execution, and resume execution with SIGCONT. Signed-off-by: Pekka Paalanen Acked-by: Daniel Stone Reviewed-by: Quentin Glidic Reviewed-by: Ian Ray --- compositor/main.c | 13 +++++++++++++ man/weston.ini.man | 8 ++++++++ man/weston.man | 8 ++++++++ 3 files changed, 29 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index 9f00ee3af..32fb33e80 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -557,6 +557,7 @@ usage(int error_code) " --log=FILE\t\tLog to the given file\n" " -c, --config=FILE\tConfig file to load, defaults to weston.ini\n" " --no-config\t\tDo not read weston.ini\n" + " --wait-for-debugger\tRaise SIGSTOP on start-up\n" " -h, --help\t\tThis help message\n\n"); #if defined(BUILD_DRM_COMPOSITOR) @@ -1787,6 +1788,7 @@ int main(int argc, char *argv[]) struct weston_seat *seat; struct wet_compositor user_data; int require_input; + int32_t wait_for_debugger = 0; const struct weston_option core_options[] = { { WESTON_OPTION_STRING, "backend", 'B', &backend }, @@ -1800,6 +1802,7 @@ int main(int argc, char *argv[]) { WESTON_OPTION_BOOLEAN, "version", 0, &version }, { WESTON_OPTION_BOOLEAN, "no-config", 0, &noconfig }, { WESTON_OPTION_STRING, "config", 'c', &config_file }, + { WESTON_OPTION_BOOLEAN, "wait-for-debugger", 0, &wait_for_debugger }, }; if (os_fd_set_cloexec(fileno(stdin))) { @@ -1863,6 +1866,16 @@ int main(int argc, char *argv[]) section = weston_config_get_section(config, "core", NULL, NULL); + if (!wait_for_debugger) + weston_config_section_get_bool(section, "wait-for-debugger", + &wait_for_debugger, 0); + if (wait_for_debugger) { + weston_log("Weston PID is %ld - " + "waiting for debugger, send SIGCONT to continue...\n", + (long)getpid()); + raise(SIGSTOP); + } + if (!backend) { weston_config_section_get_string(section, "backend", &backend, NULL); diff --git a/man/weston.ini.man b/man/weston.ini.man index 4cfefc913..f237fd609 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -181,6 +181,14 @@ require an input device for launch sets Weston's pageflip timeout in milliseconds. This sets a timer to exit gracefully with a log message and an exit code of 1 in case the DRM driver is non-responsive. Setting it to 0 disables this feature. +.TP 7 +.BI "wait-for-debugger=" true +Raises SIGSTOP before initializing the compositor. This allows the user to +attach with a debugger and continue execution by sending SIGCONT. This is +useful for debugging a crash on start-up when it would be inconvenient to +launch weston directly from a debugger. Boolean, defaults to +.BR false . +There is also a command line option to do the same. .SH "LIBINPUT SECTION" The diff --git a/man/weston.man b/man/weston.man index face22989..33f0a0b2c 100644 --- a/man/weston.man +++ b/man/weston.man @@ -165,6 +165,14 @@ Weston will export .B WAYLAND_DISPLAY with this value in the environment for all child processes to allow them to connect to the right server automatically. +.TP +\fB\-\-wait-for-debugger\fR +Raises SIGSTOP before initializing the compositor. This allows the user to +attach with a debugger and continue execution by sending SIGCONT. This is +useful for debugging a crash on start-up when it would be inconvenient to +launch weston directly from a debugger. There is also a +.IR weston.ini " option to do the same." +. .SS DRM backend options: See .BR weston-drm (7). From 5949e48d1dc2de76c9f0daaec6a7ce20c2b89dab Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 1 Nov 2017 12:12:17 +0200 Subject: [PATCH 0210/1642] weston-launch: quit if -t without -u setup_tty() function uses the tty argument for choosing the tty/VT only if wl->new_user (the -u option) is given. If the tty option is given without -u, it will only be used for misleading error messages. To make it clear to the user that -t without -u does not work the way one might think, let weston-launch exit with an error in that case. Signed-off-by: Pekka Paalanen Acked-by: Daniel Stone Reviewed-by: Quentin Glidic --- libweston/weston-launch.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index aa7e07118..dcd31e1a1 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -722,6 +722,9 @@ main(int argc, char *argv[]) if ((argc - optind) > (MAX_ARGV_SIZE - 6)) error(1, E2BIG, "Too many arguments to pass to weston"); + if (tty && !wl.new_user) + error(1, 0, "-t/--tty option requires -u/--user option as well"); + if (wl.new_user) wl.pw = getpwnam(wl.new_user); else From 625f56f06250aa70f74b9c99a9d47d6009dda76d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 1 Nov 2017 13:20:03 +0200 Subject: [PATCH 0211/1642] weston-launch: fix -t option parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix an issue introduced in: commit ab4999492ce630e6bb1c93215fc052c2c29913bd Author: Kristian Høgsberg Date: Fri Jul 19 21:26:24 2013 -0700 weston-launch: Drop sleep_fork option where the option string accidentally became "t::". That causes $ weston-lauch -t /dev/tty4 to be parsed incorrectly, as if -t option had no argument and the tty path gets passed to weston which errors out because of it. This patch fixes the above to work as expected. Signed-off-by: Pekka Paalanen Acked-by: Daniel Stone Reviewed-by: Quentin Glidic --- libweston/weston-launch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index dcd31e1a1..1415e72db 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -698,7 +698,7 @@ main(int argc, char *argv[]) memset(&wl, 0, sizeof wl); - while ((c = getopt_long(argc, argv, "u:t::vh", opts, &i)) != -1) { + while ((c = getopt_long(argc, argv, "u:t:vh", opts, &i)) != -1) { switch (c) { case 'u': wl.new_user = optarg; From 1a2adfedea74ae264eca7d4b8cbe6d1f04e552bf Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 1 Nov 2017 12:20:24 +0200 Subject: [PATCH 0212/1642] weston-launch: improve help text for -u and -t Explain that -u requires root and -t requires -u. Most importantly, document in what format does -t expect the tty to be given. It has been confusing, because Weston's --tty option takes an integer, weston-launch takes a full device path. Signed-off-by: Pekka Paalanen Acked-by: Daniel Stone Reviewed-by: Quentin Glidic --- libweston/weston-launch.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index 1415e72db..c8e1513dd 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -676,8 +676,10 @@ static void help(const char *name) { fprintf(stderr, "Usage: %s [args...] [-- [weston args..]]\n", name); - fprintf(stderr, " -u, --user Start session as specified username\n"); - fprintf(stderr, " -t, --tty Start session on alternative tty\n"); + fprintf(stderr, " -u, --user Start session as specified username,\n" + " e.g. -u joe, requires root.\n"); + fprintf(stderr, " -t, --tty Start session on alternative tty,\n" + " e.g. -t /dev/tty4, requires -u option.\n"); fprintf(stderr, " -v, --verbose Be verbose\n"); fprintf(stderr, " -h, --help Display this help message\n"); } From a453f4d564d8228fb43d93a3740ade755eb8ebc2 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 31 Oct 2017 10:19:48 +0200 Subject: [PATCH 0213/1642] compositor-drm, compositor-fbdev: stop suggesting root Stop suggesting to run Weston as root, it is only meant for debugging. Instead, mention the two supported ways to run Weston on DRM and fbdev: weston-launch helper and logind service. Cc: "Ucan, Emre (ADITG/ESB)" Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic [Pekka: added forgotten "using" word.] --- libweston/compositor-drm.c | 5 +++-- libweston/compositor-fbdev.c | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 0b243577f..1b4db5136 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4039,8 +4039,9 @@ drm_backend_create(struct weston_compositor *compositor, compositor->launcher = weston_launcher_connect(compositor, config->tty, seat_id, true); if (compositor->launcher == NULL) { - weston_log("fatal: drm backend should be run " - "using weston-launch binary or as root\n"); + weston_log("fatal: drm backend should be run using " + "weston-launch binary, or your system should " + "provide the logind D-Bus API.\n"); goto err_compositor; } diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 4b3605cf7..fbab634b3 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -736,8 +736,9 @@ fbdev_backend_create(struct weston_compositor *compositor, compositor->launcher = weston_launcher_connect(compositor, param->tty, "seat0", false); if (!compositor->launcher) { - weston_log("fatal: fbdev backend should be run " - "using weston-launch binary or as root\n"); + weston_log("fatal: fbdev backend should be run using " + "weston-launch binary, or your system should " + "provide the logind D-Bus API.\n"); goto out_udev; } From 68f8e47a56dc59392b848537b9db7e0c01f3cab0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 31 Oct 2017 11:29:25 +0200 Subject: [PATCH 0214/1642] man: mention logind for launching Logind has been long supported as means to launch Weston without weston-launch. It's good to note it in the manual. Signed-off-by: Pekka Paalanen Reviewed-by: Quentin Glidic --- man/weston.man | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/man/weston.man b/man/weston.man index 33f0a0b2c..cd53df69c 100644 --- a/man/weston.man +++ b/man/weston.man @@ -20,9 +20,12 @@ shell. When weston is started as the first windowing system (i.e. not under X nor under another Wayland server), it should be done with the command .B weston-launch -to set up proper privileged access to devices. +to set up proper privileged access to devices. If your system supports +the logind D-Bus API and the support has been built into weston as well, +it is possible to start weston with just +.BR weston . -Weston also supports X clients via +Weston also supports X clients via .BR XWayland ", see below." . .\" *************************************************************** @@ -106,7 +109,7 @@ Load .I backend.so instead of the default backend. The file is searched for in .IR "__weston_modules_dir__" , -or you can pass an absolute path. The default backend is +or you can pass an absolute path. The default backend is .I __weston_native_backend__ unless the environment suggests otherwise, see .IR DISPLAY " and " WAYLAND_DISPLAY . From dd61625d18b85ec63495b2e23c6c1191d91e09c7 Mon Sep 17 00:00:00 2001 From: Joshua Watt Date: Sat, 24 Jun 2017 16:03:41 -0500 Subject: [PATCH 0215/1642] text-backend: Allow client hiding of input panel Previously, the hide_input_panel and show_input_panel messages for the text input protocol were limited to specific cases, such as showing the panel on activation, or making the panel visible after activation. Now, clients are allowed to toggle the panel visiblity at will as long as they are the currently active client Signed-off-by: Joshua Watt Tested-by: Silvan Jegen Reviewed-by: Jan Arne Petersen --- compositor/text-backend.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/compositor/text-backend.c b/compositor/text-backend.c index e10f95764..e6ee249cb 100644 --- a/compositor/text-backend.c +++ b/compositor/text-backend.c @@ -65,7 +65,7 @@ struct text_input_manager { struct wl_global *text_input_manager_global; struct wl_listener destroy_listener; - struct text_input *current_panel; + struct text_input *current_text_input; struct weston_compositor *ec; }; @@ -141,11 +141,15 @@ deactivate_input_method(struct input_method *input_method) input_method->context = NULL; if (wl_list_empty(&text_input->input_methods) && - text_input->input_panel_visible) { + text_input->input_panel_visible && + text_input->manager->current_text_input == text_input) { wl_signal_emit(&ec->hide_input_panel_signal, ec); text_input->input_panel_visible = false; - text_input->manager->current_panel = NULL; } + + if (text_input->manager->current_text_input == text_input) + text_input->manager->current_text_input = NULL; + zwp_text_input_v1_send_leave(text_input->resource); } @@ -207,12 +211,11 @@ text_input_activate(struct wl_client *client, input_method_context_create(text_input, input_method); - current = text_input->manager->current_panel; + current = text_input->manager->current_text_input; if (current && current != text_input) { current->input_panel_visible = false; wl_signal_emit(&ec->hide_input_panel_signal, ec); - text_input->manager->current_panel = NULL; } if (text_input->input_panel_visible) { @@ -220,8 +223,8 @@ text_input_activate(struct wl_client *client, text_input->surface); wl_signal_emit(&ec->update_input_panel_signal, &text_input->cursor_rectangle); - text_input->manager->current_panel = text_input; } + text_input->manager->current_text_input = text_input; zwp_text_input_v1_send_enter(text_input->resource, text_input->surface->resource); @@ -336,7 +339,8 @@ text_input_show_input_panel(struct wl_client *client, text_input->input_panel_visible = true; - if (!wl_list_empty(&text_input->input_methods)) { + if (!wl_list_empty(&text_input->input_methods) && + text_input == text_input->manager->current_text_input) { wl_signal_emit(&ec->show_input_panel_signal, text_input->surface); wl_signal_emit(&ec->update_input_panel_signal, @@ -354,10 +358,8 @@ text_input_hide_input_panel(struct wl_client *client, text_input->input_panel_visible = false; if (!wl_list_empty(&text_input->input_methods) && - text_input == text_input->manager->current_panel) { - text_input->manager->current_panel = NULL; + text_input == text_input->manager->current_text_input) wl_signal_emit(&ec->hide_input_panel_signal, ec); - } } static void From 8093fd5db2e31230cd4ef18dfb9bb9df5cf6a8ff Mon Sep 17 00:00:00 2001 From: Joshua Watt Date: Sat, 24 Jun 2017 16:03:42 -0500 Subject: [PATCH 0216/1642] clients/editor: Toggle panel visibility on click If the --click-to-show option is specified, clicking an input field will toggle the input panel visiblity Signed-off-by: Joshua Watt Tested-by: Silvan Jegen Reviewed-by: Jan Arne Petersen --- clients/editor.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/clients/editor.c b/clients/editor.c index b63c56284..78d10d2c1 100644 --- a/clients/editor.c +++ b/clients/editor.c @@ -49,6 +49,7 @@ struct text_entry { struct window *window; char *text; int active; + bool panel_visible; uint32_t cursor; uint32_t anchor; struct { @@ -499,8 +500,10 @@ text_input_leave(void *data, text_entry_commit_and_reset(entry); entry->active--; - if (!entry->active) + if (!entry->active) { zwp_text_input_v1_hide_input_panel(text_input); + entry->panel_visible = false; + } widget_schedule_redraw(entry->widget); } @@ -699,6 +702,7 @@ text_entry_create(struct editor *editor, const char *text) entry->window = editor->window; entry->text = strdup(text); entry->active = 0; + entry->panel_visible = false; entry->cursor = strlen(text); entry->anchor = entry->cursor; entry->text_input = @@ -787,7 +791,12 @@ text_entry_activate(struct text_entry *entry, struct wl_surface *surface = window_get_wl_surface(entry->window); if (entry->click_to_show && entry->active) { - zwp_text_input_v1_show_input_panel(entry->text_input); + entry->panel_visible = !entry->panel_visible; + + if (entry->panel_visible) + zwp_text_input_v1_show_input_panel(entry->text_input); + else + zwp_text_input_v1_hide_input_panel(entry->text_input); return; } From b040974398e708101a08f4aa5f0a7177a267ef1c Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 5 Oct 2017 15:31:26 +0100 Subject: [PATCH 0217/1642] gl-renderer: Set pitch correctly for subsampled textures zwp_linux_dmabuf_v1 allows clients to pass an explicit pitch for each plane, but wl_shm only takes a single pitch parameter; the pitch for secondary planes must be inferred. Multi-plane sub-sampled textures have partial width/height, e.g. YUV420/I420 has a full-size Y plane, followed by a half-width/height U plane, and a half-width/height V plane. GStreamer's waylandsink - the only user of wl_shm YUV formats - expects the implementation to follow the example of Xv and implicitly divide the pitch for secondary planes by the subsampling factor. gl-renderer was not doing this, and instead just using the (larger) stride provided by the client for all planes in the buffer. Fix gl-renderer to divide pitch by the subsampling factor when uploading from subsampled SHM buffers into GL textures, also dividing co-ordinates when doing offset partial uploads. Tested with: $ gst-launch-1.0 videotestsrc ! waylandsink Signed-off-by: Daniel Stone Reported-by: Fabien Lahoudere Tested-by: Fabien Lahoudere Reviewed-by: Arnaud Vrac Acked-by: Vincent ABRIOU Acked-by: Nicolas Dufresne Acked-by: Pekka Paalanen Fixes: fdeefe42418 ("gl-renderer: add support of WL_SHM_FORMAT_YUV420") Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=103063 --- libweston/gl-renderer.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 94d81ef45..bd7bb2aab 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -1445,14 +1445,14 @@ gl_renderer_flush_damage(struct weston_surface *surface) goto done; } - glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, gs->pitch); - if (gs->needs_full_upload) { glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); wl_shm_buffer_begin_access(buffer->shm_buffer); for (j = 0; j < gs->num_textures; j++) { glBindTexture(GL_TEXTURE_2D, gs->textures[j]); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, + gs->pitch / gs->hsub[j]); glTexImage2D(GL_TEXTURE_2D, 0, gs->gl_format[j], gs->pitch / gs->hsub[j], @@ -1473,10 +1473,14 @@ gl_renderer_flush_damage(struct weston_surface *surface) r = weston_surface_to_buffer_rect(surface, rectangles[i]); - glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, r.x1); - glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, r.y1); for (j = 0; j < gs->num_textures; j++) { glBindTexture(GL_TEXTURE_2D, gs->textures[j]); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, + gs->pitch / gs->hsub[j]); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, + r.x1 / gs->hsub[j]); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, + r.y1 / gs->hsub[j]); glTexSubImage2D(GL_TEXTURE_2D, 0, r.x1 / gs->hsub[j], r.y1 / gs->vsub[j], From 0f4dbe72d36106027b448c31f45975c8c503bd15 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 14 Apr 2017 19:48:06 +0100 Subject: [PATCH 0218/1642] tests: Add one more indentation level to some macros This is a preparatory patch for the next one. Signed-off-by: Emmanuel Gil Peyrot Reviewed-by: Daniel Stone --- tests/weston-test-runner.h | 44 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/weston-test-runner.h b/tests/weston-test-runner.h index 21a059d63..b9f966e0d 100644 --- a/tests/weston-test-runner.h +++ b/tests/weston-test-runner.h @@ -46,34 +46,34 @@ struct weston_test { int must_fail; } __attribute__ ((aligned (32))); -#define TEST_BEGIN(name, arg) \ +#define TEST_BEGIN(name, arg) \ static void name(arg) -#define TEST_COMMON(func, name, ret, data, size, n_elem) \ - static void func(void *); \ - \ - const struct weston_test test##name \ - __attribute__ ((section ("test_section"))) = \ - { \ - #name, func, data, size, n_elem, ret \ +#define TEST_COMMON(func, name, ret, data, size, n_elem) \ + static void func(void *); \ + \ + const struct weston_test test##name \ + __attribute__ ((section ("test_section"))) = \ + { \ + #name, func, data, size, n_elem, ret \ }; -#define NO_ARG_TEST(name, ret) \ - TEST_COMMON(wrap##name, name, ret, NULL, 0, 1) \ - static void name(void); \ - static void wrap##name(void *data) \ - { \ - (void) data; \ - name(); \ - } \ - \ +#define NO_ARG_TEST(name, ret) \ + TEST_COMMON(wrap##name, name, ret, NULL, 0, 1) \ + static void name(void); \ + static void wrap##name(void *data) \ + { \ + (void) data; \ + name(); \ + } \ + \ TEST_BEGIN(name, void) -#define ARG_TEST(name, ret, test_data) \ - TEST_COMMON(name, name, ret, test_data, \ - sizeof(test_data[0]), \ - ARRAY_LENGTH(test_data)) \ - TEST_BEGIN(name, void *data) \ +#define ARG_TEST(name, ret, test_data) \ + TEST_COMMON(name, name, ret, test_data, \ + sizeof(test_data[0]), \ + ARRAY_LENGTH(test_data)) \ + TEST_BEGIN(name, void *data) \ #define TEST(name) NO_ARG_TEST(name, 0) #define FAIL_TEST(name) NO_ARG_TEST(name, 1) From 7092090de9f0f619272e52f65decdce86bc532f9 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 14 Apr 2017 19:48:07 +0100 Subject: [PATCH 0219/1642] =?UTF-8?q?tests:=20Mark=20tests=20as=20used=20s?= =?UTF-8?q?o=20they=20don=E2=80=99t=20get=20removed=20at=20link=20time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this attribute, the test macros were making Weston fail to build with LTO enabled. Signed-off-by: Markus Trippelsdorf Signed-off-by: Emmanuel Gil Peyrot Tested-by: Emmanuel Gil Peyrot Reviewed-by: Daniel Stone --- tests/weston-test-runner.h | 2 +- tools/zunitc/inc/zunitc/zunitc.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/weston-test-runner.h b/tests/weston-test-runner.h index b9f966e0d..eb9a488a1 100644 --- a/tests/weston-test-runner.h +++ b/tests/weston-test-runner.h @@ -53,7 +53,7 @@ struct weston_test { static void func(void *); \ \ const struct weston_test test##name \ - __attribute__ ((section ("test_section"))) = \ + __attribute__ ((used, section ("test_section"))) = \ { \ #name, func, data, size, n_elem, ret \ }; diff --git a/tools/zunitc/inc/zunitc/zunitc.h b/tools/zunitc/inc/zunitc/zunitc.h index 6ac6f3918..16b211ba1 100644 --- a/tools/zunitc/inc/zunitc/zunitc.h +++ b/tools/zunitc/inc/zunitc/zunitc.h @@ -268,7 +268,7 @@ zuc_set_output_junit(bool enable); static void zuctest_##tcase##_##test(void); \ \ const struct zuc_registration zzz_##tcase##_##test \ - __attribute__ ((section ("zuc_tsect"))) = \ + __attribute__ ((used, section ("zuc_tsect"))) = \ { \ #tcase, #test, 0, \ zuctest_##tcase##_##test, \ @@ -298,7 +298,7 @@ zuc_set_output_junit(bool enable); static void zuctest_##tcase##_##test(void *param); \ \ const struct zuc_registration zzz_##tcase##_##test \ - __attribute__ ((section ("zuc_tsect"))) = \ + __attribute__ ((used, section ("zuc_tsect"))) = \ { \ #tcase, #test, &tcase, \ 0, \ From 55cdf69b4b70f9d954d71187a9dd85f26633befe Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Tue, 3 Oct 2017 14:36:21 +0100 Subject: [PATCH 0220/1642] weston-info: Add support for zwp_linux_dmabuf_v1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This now prints each (format, modifier) tuple, to show which ones the compositor sends to its clients. It is only implemented for version 3+, since I didn’t have any compositor implementing previous versions, and the old `format` event is deprecated anyway. Signed-off-by: Emmanuel Gil Peyrot Reviewed-by: Daniel Stone --- Makefile.am | 4 +- clients/weston-info.c | 92 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 9d99c6949..e7e6a0ed4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -835,7 +835,9 @@ weston_info_SOURCES = \ shared/helpers.h nodist_weston_info_SOURCES = \ protocol/presentation-time-protocol.c \ - protocol/presentation-time-client-protocol.h + protocol/presentation-time-client-protocol.h \ + protocol/linux-dmabuf-unstable-v1-protocol.c \ + protocol/linux-dmabuf-unstable-v1-client-protocol.h weston_info_LDADD = $(WESTON_INFO_LIBS) libshared.la weston_info_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) diff --git a/clients/weston-info.c b/clients/weston-info.c index c07134d16..386bd412b 100644 --- a/clients/weston-info.c +++ b/clients/weston-info.c @@ -40,6 +40,7 @@ #include "shared/xalloc.h" #include "shared/zalloc.h" #include "presentation-time-client-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" typedef void (*print_info_t)(void *info); typedef void (*destroy_info_t)(void *info); @@ -96,6 +97,20 @@ struct shm_info { struct wl_list formats; }; +struct linux_dmabuf_modifier { + struct wl_list link; + + uint32_t format; + uint64_t modifier; +}; + +struct linux_dmabuf_info { + struct global_info global; + struct zwp_linux_dmabuf_v1 *dmabuf; + + struct wl_list modifiers; +}; + struct seat_info { struct global_info global; struct wl_seat *seat; @@ -296,6 +311,25 @@ print_shm_info(void *data) printf("\n"); } +static void +print_linux_dmabuf_info(void *data) +{ + char str[5]; + struct linux_dmabuf_info *dmabuf = data; + struct linux_dmabuf_modifier *modifier; + + print_global_info(data); + + printf("\tformats:"); + + wl_list_for_each(modifier, &dmabuf->modifiers, link) { + fourcc2str(modifier->format, str, sizeof(str)); + printf("\n\t'%s'(0x%08x), modifier: 0x%016"PRIx64, str, modifier->format, modifier->modifier); + } + + printf("\n"); +} + static void print_seat_info(void *data) { @@ -491,6 +525,62 @@ add_shm_info(struct weston_info *info, uint32_t id, uint32_t version) info->roundtrip_needed = true; } +static void +linux_dmabuf_handle_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format) +{ + /* This is a deprecated event, don’t use it. */ +} + +static void +linux_dmabuf_handle_modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) +{ + struct linux_dmabuf_info *dmabuf = data; + struct linux_dmabuf_modifier *linux_dmabuf_modifier = xzalloc(sizeof *linux_dmabuf_modifier); + + wl_list_insert(&dmabuf->modifiers, &linux_dmabuf_modifier->link); + linux_dmabuf_modifier->format = format; + linux_dmabuf_modifier->modifier = ((uint64_t)modifier_hi) << 32 | modifier_lo; +} + +static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_listener = { + linux_dmabuf_handle_format, + linux_dmabuf_handle_modifier, +}; + +static void +destroy_linux_dmabuf_info(void *data) +{ + struct linux_dmabuf_info *dmabuf = data; + struct linux_dmabuf_modifier *modifier, *tmp; + + wl_list_for_each_safe(modifier, tmp, &dmabuf->modifiers, link) { + wl_list_remove(&modifier->link); + free(modifier); + } + + zwp_linux_dmabuf_v1_destroy(dmabuf->dmabuf); +} + +static void +add_linux_dmabuf_info(struct weston_info *info, uint32_t id, uint32_t version) +{ + struct linux_dmabuf_info *dmabuf = xzalloc(sizeof *dmabuf); + + init_global_info(info, &dmabuf->global, id, "zwp_linux_dmabuf_v1", version); + dmabuf->global.print = print_linux_dmabuf_info; + dmabuf->global.destroy = destroy_linux_dmabuf_info; + + wl_list_init(&dmabuf->modifiers); + + if (version >= 3) { + dmabuf->dmabuf = wl_registry_bind(info->registry, + id, &zwp_linux_dmabuf_v1_interface, 3); + zwp_linux_dmabuf_v1_add_listener(dmabuf->dmabuf, &linux_dmabuf_listener, dmabuf); + + info->roundtrip_needed = true; + } +} + static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, @@ -688,6 +778,8 @@ global_handler(void *data, struct wl_registry *registry, uint32_t id, add_seat_info(info, id, version); else if (!strcmp(interface, "wl_shm")) add_shm_info(info, id, version); + else if (!strcmp(interface, "zwp_linux_dmabuf_v1")) + add_linux_dmabuf_info(info, id, version); else if (!strcmp(interface, "wl_output")) add_output_info(info, id, version); else if (!strcmp(interface, wp_presentation_interface.name)) From 11b6242ba9a0e4b680bce82243ed1bc94b1987c4 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 20 Apr 2017 14:31:36 -0500 Subject: [PATCH 0221/1642] clients: Don't crash when compositor doesn't support drag and drop display_create_data_source() can return NULL when there's no data device manager present. Instead of carrying on blindly, test its return value. Signed-off-by: Derek Foreman Reviewed-by: Daniel Stone --- clients/editor.c | 3 +++ clients/terminal.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/clients/editor.c b/clients/editor.c index 78d10d2c1..8f9eb6322 100644 --- a/clients/editor.c +++ b/clients/editor.c @@ -639,6 +639,9 @@ editor_copy_cut(struct editor *editor, struct input *input, bool cut) editor->selection = display_create_data_source(editor->display); + if (!editor->selection) + return; + wl_data_source_offer(editor->selection, "text/plain;charset=utf-8"); wl_data_source_add_listener(editor->selection, diff --git a/clients/terminal.c b/clients/terminal.c index 274ced095..16a449540 100644 --- a/clients/terminal.c +++ b/clients/terminal.c @@ -2264,6 +2264,9 @@ terminal_copy(struct terminal *terminal, struct input *input) { terminal->selection = display_create_data_source(terminal->display); + if (!terminal->selection) + return; + wl_data_source_offer(terminal->selection, "text/plain;charset=utf-8"); wl_data_source_add_listener(terminal->selection, From bd9069ffadbac80d36a442dd903492501c0c8f63 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 20 Apr 2017 14:31:37 -0500 Subject: [PATCH 0222/1642] dnd: Abort with an error message if compositor doesn't support drag and drop This test isn't particularly useful when the compositor doesn't support drag and drop - so bail if we fail to create a data source. Signed-off-by: Derek Foreman Reviewed-by: Daniel Stone --- clients/dnd.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clients/dnd.c b/clients/dnd.c index 41e532ef8..ec706bffb 100644 --- a/clients/dnd.c +++ b/clients/dnd.c @@ -534,6 +534,10 @@ create_drag_source(struct dnd *dnd, } else { dnd_drag->data_source = display_create_data_source(dnd->display); + if (!dnd_drag->data_source) { + fprintf(stderr, "No data device manager\n"); + abort(); + } wl_data_source_add_listener(dnd_drag->data_source, &data_source_listener, dnd_drag); From 8c919b488c7dac95e5fe1e81c0be28634e8291d7 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 1 Dec 2017 19:28:52 +0200 Subject: [PATCH 0223/1642] tests: Fix integer overflows on 32-bit systems Ensure that the integer type used in expressions involving multiplication with NSEC_PER_SEC is large enough to avoid overflows on 32-bit systems. In the expressions fixed by this patch a 64-bit type (long long) is required. Signed-off-by: Alexandros Frantzis Reviewed-by: Daniel Stone --- tests/timespec-test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/timespec-test.c b/tests/timespec-test.c index f127bceeb..31a6f1467 100644 --- a/tests/timespec-test.c +++ b/tests/timespec-test.c @@ -160,7 +160,7 @@ ZUC_TEST(timespec_test, timespec_sub_to_nsec) a.tv_nsec = 1; b.tv_sec = 1; b.tv_nsec = 2; - ZUC_ASSERT_EQ((999L * NSEC_PER_SEC) - 1, timespec_sub_to_nsec(&a, &b)); + ZUC_ASSERT_EQ((999LL * NSEC_PER_SEC) - 1, timespec_sub_to_nsec(&a, &b)); } ZUC_TEST(timespec_test, timespec_sub_to_msec) @@ -190,7 +190,7 @@ ZUC_TEST(timespec_test, timespec_from_nsec) ZUC_ASSERT_EQ(1, a.tv_sec); ZUC_ASSERT_EQ(0, a.tv_nsec); - timespec_from_nsec(&a, (5L * NSEC_PER_SEC) + 1); + timespec_from_nsec(&a, (5LL * NSEC_PER_SEC) + 1); ZUC_ASSERT_EQ(5, a.tv_sec); ZUC_ASSERT_EQ(1, a.tv_nsec); } From d4512f6aa1a8db0b811918593f21e7877041f0c0 Mon Sep 17 00:00:00 2001 From: Valery Kartel Date: Wed, 9 Aug 2017 14:27:29 +0300 Subject: [PATCH 0224/1642] weston: added missing header time.h without it I can't built weston on alpinelinux Reviewed-by: Daniel Stone --- tests/timespec-test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/timespec-test.c b/tests/timespec-test.c index 31a6f1467..f10ed76cb 100644 --- a/tests/timespec-test.c +++ b/tests/timespec-test.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "timespec-util.h" From df573031d0ba0a810d84784002d18453b0d9b2eb Mon Sep 17 00:00:00 2001 From: Matt Hoosier Date: Thu, 24 Aug 2017 09:24:20 -0500 Subject: [PATCH 0225/1642] compositor-drm: fix z-order inversion in plane assignment As discussed in the following thread: https://lists.freedesktop.org/archives/wayland-devel/2017-August/034755.html the existing plane assignment in the DRM backend is vulnerable to accidental masking of the intended fullscreen surface. This change adds a simple stateful memory to the plane assignment algorithm to prevent that. Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 1b4db5136..b77209c41 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1869,6 +1869,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) struct weston_view *ev, *next; pixman_region32_t overlap, surface_overlap; struct weston_plane *primary, *next_plane; + bool picked_scanout = false; /* * Find a surface for each sprite in the output using some heuristics: @@ -1915,14 +1916,23 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) &ev->transform.boundingbox); next_plane = NULL; - if (pixman_region32_not_empty(&surface_overlap)) + if (pixman_region32_not_empty(&surface_overlap) || picked_scanout) next_plane = primary; if (next_plane == NULL) next_plane = drm_output_prepare_cursor_view(output, ev); - if (next_plane == NULL) + + /* If a higher-stacked view already got assigned to scanout, it's incorrect to + * assign a subsequent (lower-stacked) view to scanout. + */ + if (next_plane == NULL) { next_plane = drm_output_prepare_scanout_view(output, ev); + if (next_plane) + picked_scanout = true; + } + if (next_plane == NULL) next_plane = drm_output_prepare_overlay_view(output, ev); + if (next_plane == NULL) next_plane = primary; From bcfe397df2387e3f23d9d1a85cebc6709cba5571 Mon Sep 17 00:00:00 2001 From: Emil Velikov Date: Mon, 31 Jul 2017 20:45:20 +0100 Subject: [PATCH 0226/1642] gl-renderer: remove unneeded cast The variable num is of EGLint type. Signed-off-by: Emil Velikov Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index bd7bb2aab..6fd3641f2 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -2090,8 +2090,7 @@ gl_renderer_query_dmabuf_formats(struct weston_compositor *wc, *num_formats = 0; return false; } - if (!gr->query_dmabuf_formats(gr->egl_display, num, *formats, - (EGLint*) &num)) { + if (!gr->query_dmabuf_formats(gr->egl_display, num, *formats, &num)) { *num_formats = 0; free(*formats); return false; From dc4f9deaee8f220ef7cb70ced4f18b39bb22bb10 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 8 Feb 2017 15:05:54 -0600 Subject: [PATCH 0227/1642] weston-resizor: Don't add new frame callbacks every click The frame callback added on button click re-adds itself when done, so adding a new one every click resulted in an ever increasing number of callbacks. Signed-off-by: Derek Foreman Acked-by: Daniel Stone --- clients/resizor.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clients/resizor.c b/clients/resizor.c index 5d342e1fc..600452adc 100644 --- a/clients/resizor.c +++ b/clients/resizor.c @@ -53,6 +53,7 @@ struct resizor { struct spring height; struct wl_callback *frame_callback; bool pointer_locked; + bool locked_frame_callback_registered; struct input *locked_input; float pointer_x; float pointer_y; @@ -330,11 +331,15 @@ button_handler(struct widget *widget, handle_pointer_unlocked); resizor->locked_input = input; + if (resizor->locked_frame_callback_registered) + return; + surface = window_get_wl_surface(resizor->window); callback = wl_surface_frame(surface); wl_callback_add_listener(callback, &locked_pointer_frame_listener, resizor); + resizor->locked_frame_callback_registered = true; } else if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_RELEASED) { input_set_pointer_image(input, CURSOR_LEFT_PTR); From 58e056ab2d715113a8893ec16ed5c93ca72c68bd Mon Sep 17 00:00:00 2001 From: Eric Engestrom Date: Wed, 24 May 2017 21:23:14 +0100 Subject: [PATCH 0228/1642] config-parser: fix `short_name` type This field is populated with chars, compared to chars and printed as a char. It should probably be a char. Signed-off-by: Eric Engestrom Reviewed-by: Daniel Stone --- shared/config-parser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/config-parser.h b/shared/config-parser.h index f18d2c0b1..af3f66a24 100644 --- a/shared/config-parser.h +++ b/shared/config-parser.h @@ -64,7 +64,7 @@ enum weston_option_type { struct weston_option { enum weston_option_type type; const char *name; - int short_name; + char short_name; void *data; }; From 0c30fa5503d8d3508704c513a0ce3c24f764e103 Mon Sep 17 00:00:00 2001 From: Eric Engestrom Date: Wed, 24 May 2017 21:23:15 +0100 Subject: [PATCH 0229/1642] option-parser: replace int/0/1 with bool/false/true These are already used as bools by all callers, let's make that official. Signed-off-by: Eric Engestrom Reviewed-by: Daniel Stone --- shared/option-parser.c | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/shared/option-parser.c b/shared/option-parser.c index e8d9b3b72..0f93464c6 100644 --- a/shared/option-parser.c +++ b/shared/option-parser.c @@ -25,6 +25,7 @@ #include "config.h" +#include #include #include #include @@ -35,7 +36,7 @@ #include "config-parser.h" #include "string-helpers.h" -static int +static bool handle_option(const struct weston_option *option, char *value) { char* p; @@ -43,23 +44,23 @@ handle_option(const struct weston_option *option, char *value) switch (option->type) { case WESTON_OPTION_INTEGER: if (!safe_strtoint(value, option->data)) - return 0; - return 1; + return false; + return true; case WESTON_OPTION_UNSIGNED_INTEGER: errno = 0; * (uint32_t *) option->data = strtoul(value, &p, 10); if (errno != 0 || p == value || *p != '\0') - return 0; - return 1; + return false; + return true; case WESTON_OPTION_STRING: * (char **) option->data = strdup(value); - return 1; + return true; default: assert(0); } } -static int +static bool long_option(const struct weston_option *options, int count, char *arg) { int k, len; @@ -76,17 +77,17 @@ long_option(const struct weston_option *options, int count, char *arg) if (!arg[len + 2]) { * (int32_t *) options[k].data = 1; - return 1; + return true; } } else if (arg[len+2] == '=') { return handle_option(options + k, arg + len + 3); } } - return 0; + return false; } -static int +static bool long_option_with_arg(const struct weston_option *options, int count, char *arg, char *param) { @@ -108,16 +109,16 @@ long_option_with_arg(const struct weston_option *options, int count, char *arg, return handle_option(options + k, param); } - return 0; + return false; } -static int +static bool short_option(const struct weston_option *options, int count, char *arg) { int k; if (!arg[1]) - return 0; + return false; for (k = 0; k < count; k++) { if (options[k].short_name != arg[1]) @@ -127,25 +128,25 @@ short_option(const struct weston_option *options, int count, char *arg) if (!arg[2]) { * (int32_t *) options[k].data = 1; - return 1; + return true; } } else if (arg[2]) { return handle_option(options + k, arg + 2); } else { - return 0; + return false; } } - return 0; + return false; } -static int +static bool short_option_with_arg(const struct weston_option *options, int count, char *arg, char *param) { int k; if (!arg[1]) - return 0; + return false; for (k = 0; k < count; k++) { if (options[k].short_name != arg[1]) @@ -157,7 +158,7 @@ short_option_with_arg(const struct weston_option *options, int count, char *arg, return handle_option(options + k, param); } - return 0; + return false; } int From 43c5a65b034a243700cf9c5bfbe6bcefb15f1161 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Wed, 31 May 2017 22:17:50 +0100 Subject: [PATCH 0230/1642] configure.ac: use AC_HEADER_MAJOR to detect major()/minor() This change slightly updates c4d7f66c12853b9575366dd9f4a7960ec5694934 which added inclusion. Autoconf has AC_HEADER_MAJOR to find out which header defines reqiured macros: https://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Particular-Headers.html This change should increase portability across other libcs. Bug: https://bugs.gentoo.org/610652 Signed-off-by: Sergei Trofimovich Reviewed-by: Daniel Stone --- configure.ac | 1 + libweston/launcher-direct.c | 9 ++++++++- libweston/launcher-logind.c | 9 ++++++++- libweston/launcher-weston-launch.c | 8 +++++++- libweston/weston-launch.c | 9 ++++++++- 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index d6a9cbd2a..5c08b2711 100644 --- a/configure.ac +++ b/configure.ac @@ -34,6 +34,7 @@ AC_CONFIG_MACRO_DIR([m4]) AC_USE_SYSTEM_EXTENSIONS AC_SYS_LARGEFILE +AC_HEADER_MAJOR AM_INIT_AUTOMAKE([1.11 parallel-tests foreign no-dist-gzip dist-xz color-tests subdir-objects]) diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c index a5d3ee532..b4ca609a9 100644 --- a/libweston/launcher-direct.c +++ b/libweston/launcher-direct.c @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -47,6 +46,14 @@ #define KDSKBMUTE 0x4B51 #endif +/* major()/minor() */ +#ifdef MAJOR_IN_MKDEV +#include +#endif +#ifdef MAJOR_IN_SYSMACROS +#include +#endif + #ifdef BUILD_DRM_COMPOSITOR #include diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c index a069bd4f2..21d8c37b8 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -35,7 +35,6 @@ #include #include #include -#include #include #include @@ -45,6 +44,14 @@ #define DRM_MAJOR 226 +/* major()/minor() */ +#ifdef MAJOR_IN_MKDEV +#include +#endif +#ifdef MAJOR_IN_SYSMACROS +#include +#endif + struct launcher_logind { struct weston_launcher base; struct weston_compositor *compositor; diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c index 97da18c53..3d3b4d2f6 100644 --- a/libweston/launcher-weston-launch.c +++ b/libweston/launcher-weston-launch.c @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -75,6 +74,13 @@ drmSetMaster(int drm_fd) #endif +/* major()/minor() */ +#ifdef MAJOR_IN_MKDEV +#include +#endif +#ifdef MAJOR_IN_SYSMACROS +#include +#endif union cmsg_data { unsigned char b[4]; int fd; }; diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index c8e1513dd..1adcf21a9 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -42,7 +42,6 @@ #include #include #include -#include #include #include #include @@ -93,6 +92,14 @@ drmSetMaster(int drm_fd) #endif +/* major()/minor() */ +#ifdef MAJOR_IN_MKDEV +# include +#endif +#ifdef MAJOR_IN_SYSMACROS +# include +#endif + struct weston_launch { struct pam_conv pc; pam_handle_t *ph; From 7861fa91517aea77e57f8aed5f03e8bc531eff14 Mon Sep 17 00:00:00 2001 From: Tomohiro Komagata Date: Mon, 4 Dec 2017 19:40:31 +0000 Subject: [PATCH 0231/1642] clients: simple-egl: Restore window size when un-maximized The window position was correct but the window size was wrong when simple-egl returns from maximized window to un-maximized. Its size should be restored to original size. Signed-off-by: Tomohiro Komagata Reviewed-by: Daniel Stone --- clients/simple-egl.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/clients/simple-egl.c b/clients/simple-egl.c index dad0f09b3..a1e57aef5 100644 --- a/clients/simple-egl.c +++ b/clients/simple-egl.c @@ -100,7 +100,7 @@ struct window { struct ivi_surface *ivi_surface; EGLSurface egl_surface; struct wl_callback *callback; - int fullscreen, opaque, buffer_size, frame_sync, delay; + int fullscreen, maximized, opaque, buffer_size, frame_sync, delay; bool wait_for_configure; }; @@ -317,23 +317,27 @@ handle_toplevel_configure(void *data, struct zxdg_toplevel_v6 *toplevel, uint32_t *p; window->fullscreen = 0; + window->maximized = 0; wl_array_for_each(p, states) { uint32_t state = *p; switch (state) { case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN: window->fullscreen = 1; break; + case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED: + window->maximized = 1; + break; } } if (width > 0 && height > 0) { - if (!window->fullscreen) { + if (!window->fullscreen && !window->maximized) { window->window_size.width = width; window->window_size.height = height; } window->geometry.width = width; window->geometry.height = height; - } else if (!window->fullscreen) { + } else if (!window->fullscreen && !window->maximized) { window->geometry = window->window_size; } From b9906aaeadabd3b7013b5984104ee42ee0c43195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Krezovi=C4=87?= Date: Sat, 29 Oct 2016 00:26:46 +0200 Subject: [PATCH 0232/1642] compositor-x11: Implement mode switching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Armin Krezović Reviewed-by: Daniel Stone --- libweston/compositor-x11.c | 137 ++++++++++++++++++++++++++++++++++--- 1 file changed, 129 insertions(+), 8 deletions(-) diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 1d4108645..fd9485406 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -65,6 +65,12 @@ #define DEFAULT_AXIS_STEP_DISTANCE 10 +#define WINDOW_MIN_WIDTH 128 +#define WINDOW_MIN_HEIGHT 128 + +#define WINDOW_MAX_WIDTH 8192 +#define WINDOW_MAX_HEIGHT 8192 + struct x11_backend { struct weston_backend base; struct weston_compositor *compositor; @@ -114,6 +120,7 @@ struct x11_output { xcb_window_t window; struct weston_mode mode; + struct weston_mode native; struct wl_event_source *finish_frame_timer; xcb_gc_t gc; @@ -123,6 +130,8 @@ struct x11_output { void *buf; uint8_t depth; int32_t scale; + bool resize_pending; + bool window_resized; }; struct window_delete_data { @@ -772,6 +781,89 @@ x11_output_init_shm(struct x11_backend *b, struct x11_output *output, return 0; } +static int +x11_output_switch_mode(struct weston_output *base, struct weston_mode *mode) +{ + struct x11_backend *b; + struct x11_output *output; + static uint32_t values[2]; + int ret; + + if (base == NULL) { + weston_log("output is NULL.\n"); + return -1; + } + + if (mode == NULL) { + weston_log("mode is NULL.\n"); + return -1; + } + + b = to_x11_backend(base->compositor); + output = to_x11_output(base); + + if (mode->width == output->mode.width && + mode->height == output->mode.height) + return 0; + + if (mode->width < WINDOW_MIN_WIDTH || mode->width > WINDOW_MAX_WIDTH) + return -1; + + if (mode->height < WINDOW_MIN_HEIGHT || mode->height > WINDOW_MAX_HEIGHT) + return -1; + + /* xcb_configure_window will create an event, and we could end up + being called twice */ + output->resize_pending = true; + + /* window could've been resized by the user, so don't do it twice */ + if (!output->window_resized) { + values[0] = mode->width; + values[1] = mode->height; + xcb_configure_window(b->conn, output->window, XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, values); + } + + output->mode.width = mode->width; + output->mode.height = mode->height; + + if (b->use_pixman) { + pixman_renderer_output_destroy(&output->base); + x11_output_deinit_shm(b, output); + + if (x11_output_init_shm(b, output, + output->base.current_mode->width, + output->base.current_mode->height) < 0) { + weston_log("Failed to initialize SHM for the X11 output\n"); + return -1; + } + + if (pixman_renderer_output_create(&output->base) < 0) { + weston_log("Failed to create pixman renderer for output\n"); + x11_output_deinit_shm(b, output); + return -1; + } + } else { + Window xid = (Window) output->window; + + gl_renderer->output_destroy(&output->base); + + ret = gl_renderer->output_window_create(&output->base, + (EGLNativeWindowType) output->window, + &xid, + gl_renderer->opaque_attribs, + NULL, + 0); + if (ret < 0) + return -1; + } + + output->resize_pending = false; + output->window_resized = false; + + return 0; +} + static int x11_output_disable(struct weston_output *base) { @@ -864,14 +956,13 @@ x11_output_enable(struct weston_output *base) XCB_ATOM_ATOM, 32, ARRAY_LENGTH(atom_list), atom_list); } else { - /* Don't resize me. */ memset(&normal_hints, 0, sizeof normal_hints); normal_hints.flags = WM_NORMAL_HINTS_MAX_SIZE | WM_NORMAL_HINTS_MIN_SIZE; - normal_hints.min_width = output->base.current_mode->width; - normal_hints.min_height = output->base.current_mode->height; - normal_hints.max_width = output->base.current_mode->width; - normal_hints.max_height = output->base.current_mode->height; + normal_hints.min_width = WINDOW_MIN_WIDTH; + normal_hints.min_height = WINDOW_MIN_HEIGHT; + normal_hints.max_width = WINDOW_MAX_WIDTH; + normal_hints.max_height = WINDOW_MAX_HEIGHT; xcb_change_property(b->conn, XCB_PROP_MODE_REPLACE, output->window, b->atom.wm_normal_hints, b->atom.wm_size_hints, 32, @@ -945,7 +1036,7 @@ x11_output_enable(struct weston_output *base) output->base.assign_planes = NULL; output->base.set_backlight = NULL; output->base.set_dpms = NULL; - output->base.switch_mode = NULL; + output->base.switch_mode = x11_output_switch_mode; loop = wl_display_get_event_loop(b->compositor->wl_display); output->finish_frame_timer = @@ -978,13 +1069,13 @@ x11_output_set_size(struct weston_output *base, int width, int height) /* Make sure we have scale set. */ assert(output->base.scale); - if (width < 1) { + if (width < WINDOW_MIN_WIDTH) { weston_log("Invalid width \"%d\" for output %s\n", width, output->base.name); return -1; } - if (height < 1) { + if (height < WINDOW_MIN_HEIGHT) { weston_log("Invalid height \"%d\" for output %s\n", height, output->base.name); return -1; @@ -999,6 +1090,7 @@ x11_output_set_size(struct weston_output *base, int width, int height) output->mode.width = output_width; output->mode.height = output_height; output->mode.refresh = 60000; + output->native = output->mode; output->scale = output->base.scale; wl_list_insert(&output->base.mode_list, &output->mode.link); @@ -1006,6 +1098,9 @@ x11_output_set_size(struct weston_output *base, int width, int height) output->base.make = "weston-X11"; output->base.model = "none"; + output->base.native_mode = &output->native; + output->base.native_scale = output->base.scale; + output->base.mm_width = width * b->screen->width_in_millimeters / b->screen->width_in_pixels; output->base.mm_height = height * b->screen->height_in_millimeters / @@ -1319,6 +1414,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) xcb_keymap_notify_event_t *keymap_notify; xcb_focus_in_event_t *focus_in; xcb_expose_event_t *expose; + xcb_configure_notify_event_t *configure; xcb_atom_t atom; xcb_window_t window; uint32_t *k; @@ -1474,6 +1570,31 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) } break; + case XCB_CONFIGURE_NOTIFY: + configure = (struct xcb_configure_notify_event_t *) event; + struct x11_output *output = + x11_backend_find_output(b, configure->window); + + if (!output || output->resize_pending) + break; + + struct weston_mode mode = output->mode; + + if (mode.width == configure->width && + mode.height == configure->height) + break; + + output->window_resized = true; + + mode.width = configure->width; + mode.height = configure->height; + + if (weston_output_mode_set_native(&output->base, + &mode, output->scale) < 0) + weston_log("Mode switch failed\n"); + + break; + case XCB_FOCUS_IN: focus_in = (xcb_focus_in_event_t *) event; if (focus_in->mode == XCB_NOTIFY_MODE_WHILE_GRABBED) From 85d55540cb64bf97a08b40f79dc66843f8295d3b Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Fri, 21 Jul 2017 14:02:40 +0200 Subject: [PATCH 0233/1642] input: Do not override keyboard focus on restore If we start a special (grabbing) client when Weston is unfocused, it would lose focus when coming back to Weston. Signed-off-by: Quentin Glidic Reviewed-by: Daniel Stone --- libweston/input.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/libweston/input.c b/libweston/input.c index 0a694d138..94a3423a9 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -1336,6 +1336,7 @@ WL_EXPORT void weston_keyboard_set_focus(struct weston_keyboard *keyboard, struct weston_surface *surface) { + struct weston_seat *seat = keyboard->seat; struct wl_resource *resource; struct wl_display *display = keyboard->seat->compositor->wl_display; uint32_t serial; @@ -1369,6 +1370,11 @@ weston_keyboard_set_focus(struct weston_keyboard *keyboard, keyboard->focus_serial = serial; } + if (seat->saved_kbd_focus) { + wl_list_remove(&seat->saved_kbd_focus_listener.link); + seat->saved_kbd_focus = NULL; + } + wl_list_remove(&keyboard->focus_resource_listener.link); wl_list_init(&keyboard->focus_resource_listener.link); if (surface && surface->resource) @@ -2060,11 +2066,8 @@ notify_keyboard_focus_in(struct weston_seat *seat, struct wl_array *keys, } surface = seat->saved_kbd_focus; - if (surface) { - wl_list_remove(&seat->saved_kbd_focus_listener.link); weston_keyboard_set_focus(keyboard, surface); - seat->saved_kbd_focus = NULL; } } @@ -2074,6 +2077,7 @@ notify_keyboard_focus_out(struct weston_seat *seat) struct weston_compositor *compositor = seat->compositor; struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_surface *focus = keyboard->focus; uint32_t *k, serial; serial = wl_display_next_serial(compositor->wl_display); @@ -2085,18 +2089,18 @@ notify_keyboard_focus_out(struct weston_seat *seat) seat->modifier_state = 0; - if (keyboard->focus) { - seat->saved_kbd_focus = keyboard->focus; - seat->saved_kbd_focus_listener.notify = - destroy_device_saved_kbd_focus; - wl_signal_add(&keyboard->focus->destroy_signal, - &seat->saved_kbd_focus_listener); - } - weston_keyboard_set_focus(keyboard, NULL); weston_keyboard_cancel_grab(keyboard); if (pointer) weston_pointer_cancel_grab(pointer); + + if (focus) { + seat->saved_kbd_focus = focus; + seat->saved_kbd_focus_listener.notify = + destroy_device_saved_kbd_focus; + wl_signal_add(&focus->destroy_signal, + &seat->saved_kbd_focus_listener); + } } WL_EXPORT void From 5c3f3575d56702c71b44b612eb3a8cdf8f674f1a Mon Sep 17 00:00:00 2001 From: Emilio Pozuelo Monfort Date: Fri, 3 Feb 2017 16:10:37 +0100 Subject: [PATCH 0234/1642] tests: add a create_test_surface function This doesn't attach a buffer to the surface. This is needed for the next commit, where we have a test case with a surface that doesn't have a buffer attached. Signed-off-by: Emilio Pozuelo Monfort Reviewed-by: Pekka Paalanen Reviewed-by: Daniel Stone --- tests/weston-test-client-helper.c | 29 ++++++++++++++++++++--------- tests/weston-test-client-helper.h | 3 +++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index fd6d5c840..ee508452e 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -864,18 +864,13 @@ create_client(void) return client; } -struct client * -create_client_and_test_surface(int x, int y, int width, int height) +struct surface * +create_test_surface(struct client *client) { - struct client *client; struct surface *surface; - pixman_color_t color = { 16384, 16384, 16384, 16384 }; /* uint16_t */ - pixman_image_t *solid; - - client = create_client(); - /* initialize the client surface */ surface = xzalloc(sizeof *surface); + surface->wl_surface = wl_compositor_create_surface(client->wl_compositor); assert(surface->wl_surface); @@ -883,9 +878,25 @@ create_client_and_test_surface(int x, int y, int width, int height) wl_surface_add_listener(surface->wl_surface, &surface_listener, surface); - client->surface = surface; wl_surface_set_user_data(surface->wl_surface, surface); + return surface; +} + +struct client * +create_client_and_test_surface(int x, int y, int width, int height) +{ + struct client *client; + struct surface *surface; + pixman_color_t color = { 16384, 16384, 16384, 16384 }; /* uint16_t */ + pixman_image_t *solid; + + client = create_client(); + + /* initialize the client surface */ + surface = create_test_surface(client); + client->surface = surface; + surface->width = width; surface->height = height; surface->buffer = create_shm_buffer_a8r8g8b8(client, width, height); diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index a288af7ea..880f47a63 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -155,6 +155,9 @@ struct rectangle { struct client * create_client(void); +struct surface * +create_test_surface(struct client *client); + struct client * create_client_and_test_surface(int x, int y, int width, int height); From 927d9e23fadb8babaf8ee93b215a25c448c5f6f2 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 6 Oct 2017 14:37:44 -0500 Subject: [PATCH 0235/1642] desktop-shell: refactor maximized size calculation into its own function We need to calculate maximized size to resolve a bug with unsetting fullscreen, might as well share the code. Signed-off-by: Derek Foreman Reviewed-by: Daniel Stone --- desktop-shell/shell.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index a0070a042..8c9b59aed 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2584,6 +2584,19 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, } } +static void +get_maximized_size(struct shell_surface *shsurf, int32_t *width, int32_t *height) +{ + struct desktop_shell *shell; + pixman_rectangle32_t area; + + shell = shell_surface_get_shell(shsurf); + get_output_work_area(shell, shsurf->output, &area); + + *width = area.width; + *height = area.height; +} + static void set_fullscreen(struct shell_surface *shsurf, bool fullscreen, struct weston_output *output) @@ -2689,8 +2702,6 @@ set_maximized(struct shell_surface *shsurf, bool maximized) if (maximized) { struct weston_output *output; - struct desktop_shell *shell; - pixman_rectangle32_t area; if (!weston_surface_is_mapped(surface)) output = get_focused_output(surface->compositor); @@ -2699,11 +2710,7 @@ set_maximized(struct shell_surface *shsurf, bool maximized) shell_surface_set_output(shsurf, output); - shell = shell_surface_get_shell(shsurf); - get_output_work_area(shell, shsurf->output, &area); - - width = area.width; - height = area.height; + get_maximized_size(shsurf, &width, &height); } weston_desktop_surface_set_maximized(desktop_surface, maximized); weston_desktop_surface_set_size(desktop_surface, width, height); From e1af3d8d9d2243c9a1ec06549c2e283f91ea6487 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 6 Oct 2017 14:37:45 -0500 Subject: [PATCH 0236/1642] desktop-shell: Handle the fullscreen to maximized case safely When a client transitions from maximized to fullscreen to maximized (run weston-terminal, maximize it, hit f11 twice) we're sending size 0,0 for the unfullscreen configure, which still has maximized set. This results in clients correctly picking any size they like, and weston disconnecting them for it. Instead, pass the correct maximized size. Signed-off-by: Derek Foreman Reviewed-by: Daniel Stone --- desktop-shell/shell.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 8c9b59aed..a2a93e2f8 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2618,6 +2618,8 @@ set_fullscreen(struct shell_surface *shsurf, bool fullscreen, width = shsurf->output->width; height = shsurf->output->height; + } else if (weston_desktop_surface_get_maximized(desktop_surface)) { + get_maximized_size(shsurf, &width, &height); } weston_desktop_surface_set_fullscreen(desktop_surface, fullscreen); weston_desktop_surface_set_size(desktop_surface, width, height); From ef82bdfdd742eda36ecd66da3aefde91a914731a Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Sat, 24 Jun 2017 17:53:02 +0000 Subject: [PATCH 0237/1642] xwm: Handle third data entry in client messages A single client message can be used to modify two properties at once. That's why when processing such messages we have to check both the second and the third data entry for states that we must handle. Signed-off-by: Ilia Bozhinov Reviewed-by: Daniel Stone --- xwayland/window-manager.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 3e8c4c7c5..c4ca0c60a 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -1619,13 +1619,15 @@ weston_wm_window_handle_state(struct weston_wm_window *window, struct weston_wm *wm = window->wm; const struct weston_desktop_xwayland_interface *xwayland_interface = wm->server->compositor->xwayland_interface; - uint32_t action, property; + uint32_t action, property1, property2; int maximized = weston_wm_window_is_maximized(window); action = client_message->data.data32[0]; - property = client_message->data.data32[1]; + property1 = client_message->data.data32[1]; + property2 = client_message->data.data32[2]; - if (property == wm->atom.net_wm_state_fullscreen && + if ((property1 == wm->atom.net_wm_state_fullscreen || + property2 == wm->atom.net_wm_state_fullscreen) && update_state(action, &window->fullscreen)) { weston_wm_window_set_net_wm_state(window); if (window->fullscreen) { @@ -1640,10 +1642,12 @@ weston_wm_window_handle_state(struct weston_wm_window *window, weston_wm_window_set_toplevel(window); } } else { - if (property == wm->atom.net_wm_state_maximized_vert && + if ((property1 == wm->atom.net_wm_state_maximized_vert || + property2 == wm->atom.net_wm_state_maximized_vert) && update_state(action, &window->maximized_vert)) weston_wm_window_set_net_wm_state(window); - if (property == wm->atom.net_wm_state_maximized_horz && + if ((property1 == wm->atom.net_wm_state_maximized_horz || + property2 == wm->atom.net_wm_state_maximized_horz) && update_state(action, &window->maximized_horz)) weston_wm_window_set_net_wm_state(window); From 3f53d9179bcdb11d053527336ac4a49f274bc8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= Date: Mon, 13 Nov 2017 16:20:52 -0500 Subject: [PATCH 0238/1642] xwm: Only send configure a window if the new size is different MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If we configure a window with the same size and wait for the sync alarm to go off, the resizing is gonna block. The event is only handled is the size actually changed. Signed-off-by: Louis-Francis Ratté-Boulianne Reviewed-by: Daniel Stone --- xwayland/window-manager.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index c4ca0c60a..f31e3c739 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2569,6 +2569,7 @@ send_configure(struct weston_surface *surface, int32_t width, int32_t height) struct weston_wm_window *window = get_wm_window(surface); struct weston_wm *wm = window->wm; struct theme *t = window->wm->theme; + int new_width, new_height; int vborder, hborder; if (window->decorate && !window->fullscreen) { @@ -2580,14 +2581,20 @@ send_configure(struct weston_surface *surface, int32_t width, int32_t height) } if (width > hborder) - window->width = width - hborder; + new_width = width - hborder; else - window->width = 1; + new_width = 1; if (height > vborder) - window->height = height - vborder; + new_height = height - vborder; else - window->height = 1; + new_height = 1; + + if (window->width == new_width && window->height == new_height) + return; + + window->width = new_width; + window->height = new_height; if (window->frame) frame_resize_inside(window->frame, window->width, window->height); From e5d655c4840ff96e45e8faead55be944f45c4f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= Date: Mon, 13 Nov 2017 16:20:50 -0500 Subject: [PATCH 0239/1642] xwm: Maximize windows when double-clicking on title bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Louis-Francis Ratté-Boulianne Reviewed-by: Daniel Stone --- xwayland/window-manager.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index f31e3c739..a6b6a3906 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -161,6 +161,8 @@ struct weston_wm_window { struct weston_output_weak_ref legacy_fullscreen_output; int saved_width, saved_height; int decorate; + uint32_t last_button_time; + int did_double; int override_redirect; int fullscreen; int has_alpha; @@ -1930,6 +1932,7 @@ weston_wm_window_close(struct weston_wm_window *window, xcb_timestamp_t time) } } +#define DOUBLE_CLICK_PERIOD 250 static void weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) { @@ -1942,6 +1945,7 @@ weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) enum theme_location location; enum wl_pointer_button_state button_state; uint32_t button_id; + uint32_t double_click = 0; wm_log("XCB_BUTTON_%s (detail %d)\n", button->response_type == XCB_BUTTON_PRESS ? @@ -1962,6 +1966,19 @@ weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) WL_POINTER_BUTTON_STATE_RELEASED; button_id = button->detail == 1 ? BTN_LEFT : BTN_RIGHT; + if (button_state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (button->time - window->last_button_time <= DOUBLE_CLICK_PERIOD) { + double_click = 1; + window->did_double = 1; + } else + window->did_double = 0; + + window->last_button_time = button->time; + } else if (window->did_double == 1) { + double_click = 1; + window->did_double = 0; + } + /* Make sure we're looking at the right location. The frame * could have received a motion event from a pointer from a * different wl_seat, but under X it looks like our core @@ -1969,8 +1986,13 @@ weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) * location before deciding what to do. */ location = frame_pointer_motion(window->frame, NULL, button->event_x, button->event_y); - location = frame_pointer_button(window->frame, NULL, - button_id, button_state); + if (double_click) + location = frame_double_click(window->frame, NULL, + button_id, button_state); + else + location = frame_pointer_button(window->frame, NULL, + button_id, button_state); + if (frame_status(window->frame) & FRAME_STATUS_REPAINT) weston_wm_window_schedule_repaint(window); From 864e39bf96edd86906239f6c079fab2550df41f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= Date: Mon, 13 Nov 2017 16:20:55 -0500 Subject: [PATCH 0240/1642] xwm: Deal with title in a smarter way when there isn't enough space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The title in X11 windows and Wayland application using Weston toy toolkit were placing the title in a very naive fashion. It was only try to center the string in the title bar. This patch: * Makes sure the title isn't renderer underneath buttons; * Move the title to the left if the titlebar isn't large enough; * Clip the end of the title if needed. Signed-off-by: Louis-Francis Ratté-Boulianne Reviewed-by: Daniel Stone --- shared/cairo-util.c | 26 +++++++++++++++----------- shared/cairo-util.h | 4 ++-- shared/frame.c | 10 +++++++++- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/shared/cairo-util.c b/shared/cairo-util.c index 21fcbea52..fd8cc7ce4 100644 --- a/shared/cairo-util.c +++ b/shared/cairo-util.c @@ -454,13 +454,14 @@ theme_destroy(struct theme *t) void theme_render_frame(struct theme *t, cairo_t *cr, int width, int height, - const char *title, struct wl_list *buttons, - uint32_t flags) + const char *title, cairo_rectangle_int_t *title_rect, + struct wl_list *buttons, uint32_t flags) { cairo_text_extents_t extents; cairo_font_extents_t font_extents; cairo_surface_t *source; int x, y, margin, top_margin; + int text_width, text_height; cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 0, 0, 0, 0); @@ -491,11 +492,10 @@ theme_render_frame(struct theme *t, t->width, top_margin); if (title || !wl_list_empty(buttons)) { - cairo_rectangle (cr, margin + t->width, margin, - width - (margin + t->width) * 2, - t->titlebar_height - t->width); - cairo_clip(cr); + cairo_rectangle (cr, title_rect->x, title_rect->y, + title_rect->width, title_rect->height); + cairo_clip(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, @@ -503,11 +503,15 @@ theme_render_frame(struct theme *t, cairo_set_font_size(cr, 14); cairo_text_extents(cr, title, &extents); cairo_font_extents (cr, &font_extents); - x = (width - extents.width) / 2; - y = margin + - (t->titlebar_height - - font_extents.ascent - font_extents.descent) / 2 + - font_extents.ascent; + text_width = extents.width; + text_height = font_extents.descent - font_extents.ascent; + + x = (width - text_width) / 2; + y = margin + (t->titlebar_height - text_height) / 2; + if (x < title_rect->x) + x = title_rect->x; + else if (x + text_width > (title_rect->x + title_rect->width)) + x = (title_rect->x + title_rect->width) - text_width; if (flags & THEME_FRAME_ACTIVE) { cairo_move_to(cr, x + 1, y + 1); diff --git a/shared/cairo-util.h b/shared/cairo-util.h index 84cf005ec..9481e58ca 100644 --- a/shared/cairo-util.h +++ b/shared/cairo-util.h @@ -75,8 +75,8 @@ theme_set_background_source(struct theme *t, cairo_t *cr, uint32_t flags); void theme_render_frame(struct theme *t, cairo_t *cr, int width, int height, - const char *title, struct wl_list *buttons, - uint32_t flags); + const char *title, cairo_rectangle_int_t *title_rect, + struct wl_list *buttons, uint32_t flags); enum theme_location { THEME_LOCATION_INTERIOR = 0, diff --git a/shared/frame.c b/shared/frame.c index eb0cd77a3..5ca7e08b8 100644 --- a/shared/frame.c +++ b/shared/frame.c @@ -98,6 +98,8 @@ struct frame { int opaque_margin; int geometry_dirty; + cairo_rectangle_int_t title_rect; + uint32_t status; struct wl_list buttons; @@ -532,6 +534,11 @@ frame_refresh_geometry(struct frame *frame) } } + frame->title_rect.x = x_l; + frame->title_rect.y = y; + frame->title_rect.width = x_r - x_l; + frame->title_rect.height = titlebar_height; + frame->geometry_dirty = 0; } @@ -938,7 +945,8 @@ frame_repaint(struct frame *frame, cairo_t *cr) cairo_save(cr); theme_render_frame(frame->theme, cr, frame->width, frame->height, - frame->title, &frame->buttons, flags); + frame->title, &frame->title_rect, + &frame->buttons, flags); cairo_restore(cr); wl_list_for_each(button, &frame->buttons, link) From 037f056ec3fc30246dda66e4375d6329e49e4cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= Date: Mon, 13 Nov 2017 16:20:56 -0500 Subject: [PATCH 0241/1642] xwm: Use Pango to draw title string if available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If Weston is built with Pango, use it to render the title for X11 applications and Weston toy toolkit clients. It allows us to ellipsize the title when there isn't enough space to show the whole string. Signed-off-by: Louis-Francis Ratté-Boulianne Reviewed-by: Daniel Stone --- Makefile.am | 2 ++ configure.ac | 3 +++ shared/cairo-util.c | 60 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index e7e6a0ed4..a7ec60b7c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1138,12 +1138,14 @@ libshared_cairo_la_CFLAGS = \ $(COMPOSITOR_CFLAGS) \ $(PIXMAN_CFLAGS) \ $(CAIRO_CFLAGS) \ + $(PANGO_CFLAGS) \ $(PNG_CFLAGS) \ $(WEBP_CFLAGS) libshared_cairo_la_LIBADD = \ $(PIXMAN_LIBS) \ $(CAIRO_LIBS) \ + $(PANGO_LIBS) \ $(PNG_LIBS) \ $(WEBP_LIBS) \ $(JPEG_LIBS) diff --git a/configure.ac b/configure.ac index 5c08b2711..ed4b94fd8 100644 --- a/configure.ac +++ b/configure.ac @@ -448,6 +448,9 @@ if test x$enable_weston_launch = xyes; then fi AM_CONDITIONAL(HAVE_PANGO, test "x$have_pango" = "xyes") +if test "x$have_pango" = "xyes"; then + AC_DEFINE([HAVE_PANGO], [1], [Have pango])] +fi AM_CONDITIONAL(HAVE_CAIRO_GLESV2, [test "x$have_cairo_egl" = "xyes" -a "x$cairo_modules" = "xcairo-glesv2" -a "x$enable_egl" = "xyes"]) diff --git a/shared/cairo-util.c b/shared/cairo-util.c index fd8cc7ce4..d71e0ed48 100644 --- a/shared/cairo-util.c +++ b/shared/cairo-util.c @@ -39,6 +39,10 @@ #include "image-loader.h" #include "config-parser.h" +#ifdef HAVE_PANGO +#include +#endif + void surface_flush_device(cairo_surface_t *surface) { @@ -451,14 +455,42 @@ theme_destroy(struct theme *t) free(t); } +#ifdef HAVE_PANGO +static PangoLayout * +create_layout(cairo_t *cr, const char *title) +{ + PangoLayout *layout; + PangoFontDescription *desc; + + layout = pango_cairo_create_layout(cr); + pango_layout_set_text(layout, title, -1); + desc = pango_font_description_from_string("Sans Bold 10"); + pango_layout_set_font_description(layout, desc); + pango_font_description_free(desc); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); + pango_layout_set_auto_dir (layout, FALSE); + pango_layout_set_single_paragraph_mode (layout, TRUE); + pango_layout_set_width (layout, -1); + + return layout; +} +#endif + +#ifdef HAVE_PANGO +#define SHOW_TEXT(cr) \ + pango_cairo_show_layout(cr, title_layout) +#else +#define SHOW_TEXT(cr) \ + cairo_show_text(cr, title) +#endif + void theme_render_frame(struct theme *t, cairo_t *cr, int width, int height, const char *title, cairo_rectangle_int_t *title_rect, struct wl_list *buttons, uint32_t flags) { - cairo_text_extents_t extents; - cairo_font_extents_t font_extents; cairo_surface_t *source; int x, y, margin, top_margin; int text_width, text_height; @@ -497,6 +529,23 @@ theme_render_frame(struct theme *t, title_rect->width, title_rect->height); cairo_clip(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + +#ifdef HAVE_PANGO + PangoLayout *title_layout; + PangoRectangle logical; + + title_layout = create_layout(cr, title); + + pango_layout_get_pixel_extents (title_layout, NULL, &logical); + text_width = MIN(title_rect->width, logical.width); + text_height = logical.height; + if (text_width < logical.width) + pango_layout_set_width (title_layout, text_width * PANGO_SCALE); + +#else + cairo_text_extents_t extents; + cairo_font_extents_t font_extents; + cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); @@ -505,6 +554,7 @@ theme_render_frame(struct theme *t, cairo_font_extents (cr, &font_extents); text_width = extents.width; text_height = font_extents.descent - font_extents.ascent; +#endif x = (width - text_width) / 2; y = margin + (t->titlebar_height - text_height) / 2; @@ -516,14 +566,14 @@ theme_render_frame(struct theme *t, if (flags & THEME_FRAME_ACTIVE) { cairo_move_to(cr, x + 1, y + 1); cairo_set_source_rgb(cr, 1, 1, 1); - cairo_show_text(cr, title); + SHOW_TEXT(cr); cairo_move_to(cr, x, y); cairo_set_source_rgb(cr, 0, 0, 0); - cairo_show_text(cr, title); + SHOW_TEXT(cr); } else { cairo_move_to(cr, x, y); cairo_set_source_rgb(cr, 0.4, 0.4, 0.4); - cairo_show_text(cr, title); + SHOW_TEXT(cr); } } } From 555c548c7e968588607f39367fff842226c5846b Mon Sep 17 00:00:00 2001 From: Matt Hoosier Date: Tue, 5 Sep 2017 08:05:49 -0500 Subject: [PATCH 0242/1642] libweston-desktop: add signal for title/app-id changes As discussed on https://lists.freedesktop.org/archives/wayland-devel/2017-August/034720.html, it's useful for the shell implementation to know when these change, for example to relay the information on to taskbars or similar. To avoid ABI changes or the need to make the weston_desktop_surface definition public, new functions are introduced for attaching listeners to these signals. Signed-off-by: Matt Hoosier Reviewed-by: Quentin Glidic --- libweston-desktop/libweston-desktop.h | 3 +++ libweston-desktop/surface.c | 22 ++++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/libweston-desktop/libweston-desktop.h b/libweston-desktop/libweston-desktop.h index 03b04c7bc..a0fb9381b 100644 --- a/libweston-desktop/libweston-desktop.h +++ b/libweston-desktop/libweston-desktop.h @@ -164,6 +164,9 @@ weston_desktop_surface_set_size(struct weston_desktop_surface *surface, int32_t width, int32_t height); void weston_desktop_surface_close(struct weston_desktop_surface *surface); +void +weston_desktop_surface_add_metadata_listener(struct weston_desktop_surface *surface, + struct wl_listener *listener); void * weston_desktop_surface_get_user_data(struct weston_desktop_surface *surface); diff --git a/libweston-desktop/surface.c b/libweston-desktop/surface.c index d3be93640..cbfa5ee00 100644 --- a/libweston-desktop/surface.c +++ b/libweston-desktop/surface.c @@ -64,6 +64,7 @@ struct weston_desktop_surface { char *title; char *app_id; pid_t pid; + struct wl_signal metadata_signal; }; struct { struct weston_desktop_surface *parent; @@ -287,6 +288,8 @@ weston_desktop_surface_create(struct weston_desktop *desktop, wl_list_init(&surface->view_list); wl_list_init(&surface->grab_link); + wl_signal_init(&surface->metadata_signal); + return surface; } @@ -511,6 +514,13 @@ weston_desktop_surface_close(struct weston_desktop_surface *surface) surface->implementation_data); } +WL_EXPORT void +weston_desktop_surface_add_metadata_listener(struct weston_desktop_surface *surface, + struct wl_listener *listener) +{ + wl_signal_add(&surface->metadata_signal, listener); +} + struct weston_desktop_surface * weston_desktop_surface_from_client_link(struct wl_list *link) { @@ -679,28 +689,32 @@ void weston_desktop_surface_set_title(struct weston_desktop_surface *surface, const char *title) { - char *tmp; + char *tmp, *old; tmp = strdup(title); if (tmp == NULL) return; - free(surface->title); + old = surface->title; surface->title = tmp; + wl_signal_emit(&surface->metadata_signal, surface); + free(old); } void weston_desktop_surface_set_app_id(struct weston_desktop_surface *surface, const char *app_id) { - char *tmp; + char *tmp, *old; tmp = strdup(app_id); if (tmp == NULL) return; - free(surface->app_id); + old = surface->app_id; surface->app_id = tmp; + wl_signal_emit(&surface->metadata_signal, surface); + free(old); } void From 2006655b31abee042f889dc5221cf2a894669d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 8 Dec 2017 20:46:34 +0100 Subject: [PATCH 0243/1642] configure.ac: drop spurious bracket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise configure output looks like checking for library containing pam_open_session... -lpam ./configure: line 18064: ]: command not found checking for COLORD... yes Signed-off-by: Guido Günther Reviewed-by: Daniel Stone --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index ed4b94fd8..d1b5f4718 100644 --- a/configure.ac +++ b/configure.ac @@ -449,7 +449,7 @@ fi AM_CONDITIONAL(HAVE_PANGO, test "x$have_pango" = "xyes") if test "x$have_pango" = "xyes"; then - AC_DEFINE([HAVE_PANGO], [1], [Have pango])] + AC_DEFINE([HAVE_PANGO], [1], [Have pango]) fi AM_CONDITIONAL(HAVE_CAIRO_GLESV2, From 8e8fa8e885306f32342bfe19256c4df540273fc8 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Fri, 8 Dec 2017 11:10:53 +0100 Subject: [PATCH 0244/1642] libweston-desktop/xwayland: Make sure racy surfaces are properly mapped This fixes a race between Xwayland committing the surface content via the wl_surface, and the XWM setting the role of the surface. We now keep track of the (first) content commit on the surface and forward it to the shell when we finally get the role. There is no need to track later changes, as the only way for Xwayland to unmap a surface is to destroy it. Signed-off-by: Quentin Glidic Reviewed-by: Pekka Paalanen Acked-by: Daniel Stone --- libweston-desktop/xwayland.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index 002e2523c..4b4407b9c 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -61,6 +61,7 @@ struct weston_desktop_xwayland_surface { const struct weston_xwayland_client_interface *client_interface; struct weston_geometry next_geometry; bool has_next_geometry; + bool committed; bool added; enum weston_desktop_xwayland_surface_state state; }; @@ -99,6 +100,14 @@ weston_desktop_xwayland_surface_change_state(struct weston_desktop_xwayland_surf weston_desktop_api_surface_added(surface->desktop, surface->surface); surface->added = true; + if (surface->state == NONE && surface->committed) + /* We had a race, and wl_surface.commit() was + * faster, just fake a commit to map the + * surface */ + weston_desktop_api_committed(surface->desktop, + surface->surface, + 0, 0); + } else if (surface->added) { weston_desktop_api_surface_removed(surface->desktop, surface->surface); @@ -133,6 +142,7 @@ weston_desktop_xwayland_surface_committed(struct weston_desktop_surface *dsurfac struct weston_geometry oldgeom; assert(dsurface == surface->surface); + surface->committed = true; #ifdef WM_DEBUG weston_log("%s: xwayland surface %p\n", __func__, surface); From 49a8d9997b1e65b15bf069c061f366740b2a1222 Mon Sep 17 00:00:00 2001 From: Arnaud Vrac Date: Wed, 29 Nov 2017 15:25:34 +0100 Subject: [PATCH 0245/1642] gl-renderer: fix pixel format used in texture uploads when using R/RG textures In glTexImage2D / glTexSubImage2D calls, the only pixel formats allowed for the GL_R8 and GL_RG internal formats are respectively GL_RED and GL_RG [1]. Make sure we match this requirement, as some drivers will fail with the current code. [1] https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml, Table 2 Signed-off-by: Arnaud Vrac Fixes: 00a03d2f724 ("gl-renderer: add support of WL_SHM_FORMAT_NV12") Reviewed-by: Emil Velikov Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 6fd3641f2..8726c3402 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -1388,6 +1388,18 @@ gl_renderer_read_pixels(struct weston_output *output, return 0; } +static GLenum gl_format_from_internal(GLenum internal_format) +{ + switch (internal_format) { + case GL_R8_EXT: + return GL_RED_EXT; + case GL_RG8_EXT: + return GL_RG_EXT; + default: + return internal_format; + } +} + static void gl_renderer_flush_damage(struct weston_surface *surface) { @@ -1436,7 +1448,7 @@ gl_renderer_flush_damage(struct weston_surface *surface) gs->pitch / gs->hsub[j], buffer->height / gs->vsub[j], 0, - gs->gl_format[j], + gl_format_from_internal(gs->gl_format[j]), gs->gl_pixel_type, data + gs->offset[j]); } @@ -1458,7 +1470,7 @@ gl_renderer_flush_damage(struct weston_surface *surface) gs->pitch / gs->hsub[j], buffer->height / gs->vsub[j], 0, - gs->gl_format[j], + gl_format_from_internal(gs->gl_format[j]), gs->gl_pixel_type, data + gs->offset[j]); } @@ -1486,7 +1498,7 @@ gl_renderer_flush_damage(struct weston_surface *surface) r.y1 / gs->vsub[j], (r.x2 - r.x1) / gs->hsub[j], (r.y2 - r.y1) / gs->vsub[j], - gs->gl_format[j], + gl_format_from_internal(gs->gl_format[j]), gs->gl_pixel_type, data + gs->offset[j]); } From 340d25b76f918d21d1ce75beb62be5686f26f8d3 Mon Sep 17 00:00:00 2001 From: Arnaud Vrac Date: Wed, 29 Nov 2017 15:25:35 +0100 Subject: [PATCH 0246/1642] gl-renderer: use correct pixel shader for NV12 format uploaded to RG texture Signed-off-by: Arnaud Vrac Fixes: 00a03d2f724 ("gl-renderer: add support of WL_SHM_FORMAT_NV12") Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 8726c3402..5a4b83cd9 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -1597,7 +1597,6 @@ gl_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer, } break; case WL_SHM_FORMAT_NV12: - gs->shader = &gr->texture_shader_y_xuxv; pitch = wl_shm_buffer_get_stride(shm_buffer); gl_pixel_type = GL_UNSIGNED_BYTE; num_planes = 2; @@ -1606,9 +1605,11 @@ gl_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer, gs->hsub[1] = 2; gs->vsub[1] = 2; if (gr->has_gl_texture_rg) { + gs->shader = &gr->texture_shader_y_uv; gl_format[0] = GL_R8_EXT; gl_format[1] = GL_RG8_EXT; } else { + gs->shader = &gr->texture_shader_y_xuxv; gl_format[0] = GL_LUMINANCE; gl_format[1] = GL_LUMINANCE_ALPHA; } From fbf165f5e89576730eed4a7e3979100311c4f0f8 Mon Sep 17 00:00:00 2001 From: Emil Velikov Date: Tue, 5 Dec 2017 14:26:16 +0000 Subject: [PATCH 0247/1642] libweston: drop return type from ::query_dmabuf_{formats, modifiers} Nobody checks for the bool returned by these functions. At the same time: a) the functions set the respective num_foo to zero on error and b) callers honour that variable. Just drop the return type - it's useless. Note: this is an ABI break. Signed-off-by: Emil Velikov Reviewed-by: Daniel Stone --- libweston/compositor.h | 6 ++++-- libweston/gl-renderer.c | 18 ++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libweston/compositor.h b/libweston/compositor.h index 97f9a2ffb..dffcba899 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -755,10 +755,12 @@ struct weston_renderer { bool (*import_dmabuf)(struct weston_compositor *ec, struct linux_dmabuf_buffer *buffer); - bool (*query_dmabuf_formats)(struct weston_compositor *ec, + /** On error sets num_formats to zero */ + void (*query_dmabuf_formats)(struct weston_compositor *ec, int **formats, int *num_formats); - bool (*query_dmabuf_modifiers)(struct weston_compositor *ec, + /** On error sets num_modifiers to zero */ + void (*query_dmabuf_modifiers)(struct weston_compositor *ec, int format, uint64_t **modifiers, int *num_modifiers); }; diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 5a4b83cd9..abf556f03 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -2083,7 +2083,7 @@ import_dmabuf(struct gl_renderer *gr, return image; } -static bool +static void gl_renderer_query_dmabuf_formats(struct weston_compositor *wc, int **formats, int *num_formats) { @@ -2095,25 +2095,24 @@ gl_renderer_query_dmabuf_formats(struct weston_compositor *wc, if (!gr->has_dmabuf_import_modifiers || !gr->query_dmabuf_formats(gr->egl_display, 0, NULL, &num)) { *num_formats = 0; - return false; + return; } *formats = calloc(num, sizeof(int)); if (*formats == NULL) { *num_formats = 0; - return false; + return; } if (!gr->query_dmabuf_formats(gr->egl_display, num, *formats, &num)) { *num_formats = 0; free(*formats); - return false; + return; } *num_formats = num; - return true; } -static bool +static void gl_renderer_query_dmabuf_modifiers(struct weston_compositor *wc, int format, uint64_t **modifiers, int *num_modifiers) @@ -2127,23 +2126,22 @@ gl_renderer_query_dmabuf_modifiers(struct weston_compositor *wc, int format, !gr->query_dmabuf_modifiers(gr->egl_display, format, 0, NULL, NULL, &num)) { *num_modifiers = 0; - return false; + return; } *modifiers = calloc(num, sizeof(uint64_t)); if (*modifiers == NULL) { *num_modifiers = 0; - return false; + return; } if (!gr->query_dmabuf_modifiers(gr->egl_display, format, num, *modifiers, NULL, &num)) { *num_modifiers = 0; free(*modifiers); - return false; + return; } *num_modifiers = num; - return true; } static bool From 5d6acf8568412a9429018d20382979aedd984140 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Mon, 4 Dec 2017 15:34:03 +0200 Subject: [PATCH 0248/1642] tests: Move wl_pointer tests to their own file Move wl_pointer tests from event-test.c to their own pointer-test.c file. This move makes the test organization clearer and more consistent, and will make addition of further pointer tests easier. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- Makefile.am | 8 +- tests/button-test.c | 61 --------- tests/event-test.c | 256 ---------------------------------- tests/pointer-test.c | 318 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 322 insertions(+), 321 deletions(-) delete mode 100644 tests/button-test.c create mode 100644 tests/pointer-test.c diff --git a/Makefile.am b/Makefile.am index a7ec60b7c..7adc62549 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1226,7 +1226,7 @@ weston_tests = \ bad_buffer.weston \ keyboard.weston \ event.weston \ - button.weston \ + pointer.weston \ text.weston \ presentation.weston \ viewporter.weston \ @@ -1383,9 +1383,9 @@ event_weston_SOURCES = tests/event-test.c event_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) event_weston_LDADD = libtest-client.la -button_weston_SOURCES = tests/button-test.c -button_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -button_weston_LDADD = libtest-client.la +pointer_weston_SOURCES = tests/pointer-test.c +pointer_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) +pointer_weston_LDADD = libtest-client.la devices_weston_SOURCES = tests/devices-test.c devices_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) diff --git a/tests/button-test.c b/tests/button-test.c deleted file mode 100644 index afa6320fd..000000000 --- a/tests/button-test.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright © 2012 Intel Corporation - * - * 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 "weston-test-client-helper.h" - -TEST(simple_button_test) -{ - struct client *client; - struct pointer *pointer; - - client = create_client_and_test_surface(100, 100, 100, 100); - assert(client); - - pointer = client->input->pointer; - - assert(pointer->button == 0); - assert(pointer->state == 0); - - weston_test_move_pointer(client->test->weston_test, 150, 150); - client_roundtrip(client); - assert(pointer->x == 50); - assert(pointer->y == 50); - - weston_test_send_button(client->test->weston_test, BTN_LEFT, - WL_POINTER_BUTTON_STATE_PRESSED); - client_roundtrip(client); - assert(pointer->button == BTN_LEFT); - assert(pointer->state == WL_POINTER_BUTTON_STATE_PRESSED); - - weston_test_send_button(client->test->weston_test, BTN_LEFT, - WL_POINTER_BUTTON_STATE_RELEASED); - client_roundtrip(client); - assert(pointer->button == BTN_LEFT); - assert(pointer->state == WL_POINTER_BUTTON_STATE_RELEASED); -} diff --git a/tests/event-test.c b/tests/event-test.c index 64dd7a0c0..c1ba3ac1e 100644 --- a/tests/event-test.c +++ b/tests/event-test.c @@ -28,262 +28,6 @@ #include "weston-test-client-helper.h" -static void -check_pointer(struct client *client, int x, int y) -{ - int sx, sy; - - /* check that the client got the global pointer update */ - assert(client->test->pointer_x == x); - assert(client->test->pointer_y == y); - - /* Does global pointer map onto the surface? */ - if (surface_contains(client->surface, x, y)) { - /* check that the surface has the pointer focus */ - assert(client->input->pointer->focus == client->surface); - - /* - * check that the local surface pointer maps - * to the global pointer. - */ - sx = client->input->pointer->x + client->surface->x; - sy = client->input->pointer->y + client->surface->y; - assert(sx == x); - assert(sy == y); - } else { - /* - * The global pointer does not map onto surface. So - * check that it doesn't have the pointer focus. - */ - assert(client->input->pointer->focus == NULL); - } -} - -static void -check_pointer_move(struct client *client, int x, int y) -{ - weston_test_move_pointer(client->test->weston_test, x, y); - client_roundtrip(client); - check_pointer(client, x, y); -} - -TEST(test_pointer_top_left) -{ - struct client *client; - int x, y; - - client = create_client_and_test_surface(46, 76, 111, 134); - assert(client); - - /* move pointer outside top left */ - x = client->surface->x - 1; - y = client->surface->y - 1; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer on top left */ - x += 1; y += 1; - assert(surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer outside top left */ - x -= 1; y -= 1; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); -} - -TEST(test_pointer_bottom_left) -{ - struct client *client; - int x, y; - - client = create_client_and_test_surface(99, 100, 100, 98); - assert(client); - - /* move pointer outside bottom left */ - x = client->surface->x - 1; - y = client->surface->y + client->surface->height; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer on bottom left */ - x += 1; y -= 1; - assert(surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer outside bottom left */ - x -= 1; y += 1; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); -} - -TEST(test_pointer_top_right) -{ - struct client *client; - int x, y; - - client = create_client_and_test_surface(48, 100, 67, 100); - assert(client); - - /* move pointer outside top right */ - x = client->surface->x + client->surface->width; - y = client->surface->y - 1; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer on top right */ - x -= 1; y += 1; - assert(surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer outside top right */ - x += 1; y -= 1; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); -} - -TEST(test_pointer_bottom_right) -{ - struct client *client; - int x, y; - - client = create_client_and_test_surface(100, 123, 100, 69); - assert(client); - - /* move pointer outside bottom right */ - x = client->surface->x + client->surface->width; - y = client->surface->y + client->surface->height; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer on bottom right */ - x -= 1; y -= 1; - assert(surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer outside bottom right */ - x += 1; y += 1; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); -} - -TEST(test_pointer_top_center) -{ - struct client *client; - int x, y; - - client = create_client_and_test_surface(100, 201, 100, 50); - assert(client); - - /* move pointer outside top center */ - x = client->surface->x + client->surface->width/2; - y = client->surface->y - 1; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer on top center */ - y += 1; - assert(surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer outside top center */ - y -= 1; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); -} - -TEST(test_pointer_bottom_center) -{ - struct client *client; - int x, y; - - client = create_client_and_test_surface(100, 45, 67, 100); - assert(client); - - /* move pointer outside bottom center */ - x = client->surface->x + client->surface->width/2; - y = client->surface->y + client->surface->height; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer on bottom center */ - y -= 1; - assert(surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer outside bottom center */ - y += 1; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); -} - -TEST(test_pointer_left_center) -{ - struct client *client; - int x, y; - - client = create_client_and_test_surface(167, 45, 78, 100); - assert(client); - - /* move pointer outside left center */ - x = client->surface->x - 1; - y = client->surface->y + client->surface->height/2; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer on left center */ - x += 1; - assert(surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer outside left center */ - x -= 1; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); -} - -TEST(test_pointer_right_center) -{ - struct client *client; - int x, y; - - client = create_client_and_test_surface(110, 37, 100, 46); - assert(client); - - /* move pointer outside right center */ - x = client->surface->x + client->surface->width; - y = client->surface->y + client->surface->height/2; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer on right center */ - x -= 1; - assert(surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); - - /* move pointer outside right center */ - x += 1; - assert(!surface_contains(client->surface, x, y)); - check_pointer_move(client, x, y); -} - -TEST(test_pointer_surface_move) -{ - struct client *client; - - client = create_client_and_test_surface(100, 100, 100, 100); - assert(client); - - /* move pointer outside of client */ - assert(!surface_contains(client->surface, 50, 50)); - check_pointer_move(client, 50, 50); - - /* move client center to pointer */ - move_client(client, 0, 0); - assert(surface_contains(client->surface, 50, 50)); - check_pointer(client, 50, 50); -} - static int output_contains_client(struct client *client) { diff --git a/tests/pointer-test.c b/tests/pointer-test.c new file mode 100644 index 000000000..d0b85f5da --- /dev/null +++ b/tests/pointer-test.c @@ -0,0 +1,318 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2013 Collabora, Ltd. + * + * 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 "weston-test-client-helper.h" + +static void +check_pointer(struct client *client, int x, int y) +{ + int sx, sy; + + /* check that the client got the global pointer update */ + assert(client->test->pointer_x == x); + assert(client->test->pointer_y == y); + + /* Does global pointer map onto the surface? */ + if (surface_contains(client->surface, x, y)) { + /* check that the surface has the pointer focus */ + assert(client->input->pointer->focus == client->surface); + + /* + * check that the local surface pointer maps + * to the global pointer. + */ + sx = client->input->pointer->x + client->surface->x; + sy = client->input->pointer->y + client->surface->y; + assert(sx == x); + assert(sy == y); + } else { + /* + * The global pointer does not map onto surface. So + * check that it doesn't have the pointer focus. + */ + assert(client->input->pointer->focus == NULL); + } +} + +static void +check_pointer_move(struct client *client, int x, int y) +{ + weston_test_move_pointer(client->test->weston_test, x, y); + client_roundtrip(client); + check_pointer(client, x, y); +} + +TEST(test_pointer_top_left) +{ + struct client *client; + int x, y; + + client = create_client_and_test_surface(46, 76, 111, 134); + assert(client); + + /* move pointer outside top left */ + x = client->surface->x - 1; + y = client->surface->y - 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on top left */ + x += 1; y += 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside top left */ + x -= 1; y -= 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_bottom_left) +{ + struct client *client; + int x, y; + + client = create_client_and_test_surface(99, 100, 100, 98); + assert(client); + + /* move pointer outside bottom left */ + x = client->surface->x - 1; + y = client->surface->y + client->surface->height; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on bottom left */ + x += 1; y -= 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside bottom left */ + x -= 1; y += 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_top_right) +{ + struct client *client; + int x, y; + + client = create_client_and_test_surface(48, 100, 67, 100); + assert(client); + + /* move pointer outside top right */ + x = client->surface->x + client->surface->width; + y = client->surface->y - 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on top right */ + x -= 1; y += 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside top right */ + x += 1; y -= 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_bottom_right) +{ + struct client *client; + int x, y; + + client = create_client_and_test_surface(100, 123, 100, 69); + assert(client); + + /* move pointer outside bottom right */ + x = client->surface->x + client->surface->width; + y = client->surface->y + client->surface->height; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on bottom right */ + x -= 1; y -= 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside bottom right */ + x += 1; y += 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_top_center) +{ + struct client *client; + int x, y; + + client = create_client_and_test_surface(100, 201, 100, 50); + assert(client); + + /* move pointer outside top center */ + x = client->surface->x + client->surface->width/2; + y = client->surface->y - 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on top center */ + y += 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside top center */ + y -= 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_bottom_center) +{ + struct client *client; + int x, y; + + client = create_client_and_test_surface(100, 45, 67, 100); + assert(client); + + /* move pointer outside bottom center */ + x = client->surface->x + client->surface->width/2; + y = client->surface->y + client->surface->height; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on bottom center */ + y -= 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside bottom center */ + y += 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_left_center) +{ + struct client *client; + int x, y; + + client = create_client_and_test_surface(167, 45, 78, 100); + assert(client); + + /* move pointer outside left center */ + x = client->surface->x - 1; + y = client->surface->y + client->surface->height/2; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on left center */ + x += 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside left center */ + x -= 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_right_center) +{ + struct client *client; + int x, y; + + client = create_client_and_test_surface(110, 37, 100, 46); + assert(client); + + /* move pointer outside right center */ + x = client->surface->x + client->surface->width; + y = client->surface->y + client->surface->height/2; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on right center */ + x -= 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside right center */ + x += 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_surface_move) +{ + struct client *client; + + client = create_client_and_test_surface(100, 100, 100, 100); + assert(client); + + /* move pointer outside of client */ + assert(!surface_contains(client->surface, 50, 50)); + check_pointer_move(client, 50, 50); + + /* move client center to pointer */ + move_client(client, 0, 0); + assert(surface_contains(client->surface, 50, 50)); + check_pointer(client, 50, 50); +} + +TEST(simple_pointer_button_test) +{ + struct client *client; + struct pointer *pointer; + + client = create_client_and_test_surface(100, 100, 100, 100); + assert(client); + + pointer = client->input->pointer; + + assert(pointer->button == 0); + assert(pointer->state == 0); + + weston_test_move_pointer(client->test->weston_test, 150, 150); + client_roundtrip(client); + assert(pointer->x == 50); + assert(pointer->y == 50); + + weston_test_send_button(client->test->weston_test, BTN_LEFT, + WL_POINTER_BUTTON_STATE_PRESSED); + client_roundtrip(client); + assert(pointer->button == BTN_LEFT); + assert(pointer->state == WL_POINTER_BUTTON_STATE_PRESSED); + + weston_test_send_button(client->test->weston_test, BTN_LEFT, + WL_POINTER_BUTTON_STATE_RELEASED); + client_roundtrip(client); + assert(pointer->button == BTN_LEFT); + assert(pointer->state == WL_POINTER_BUTTON_STATE_RELEASED); +} From 903e4450a264500ddd7d58cf15f99e9e78cbab66 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Mon, 4 Dec 2017 15:34:04 +0200 Subject: [PATCH 0249/1642] tests: Use separate test cases for pointer motion and button tests Split pointer motion and pointer button tests so that each test case is more focused and self-contained. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- tests/pointer-test.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/tests/pointer-test.c b/tests/pointer-test.c index d0b85f5da..e0e700e06 100644 --- a/tests/pointer-test.c +++ b/tests/pointer-test.c @@ -69,6 +69,17 @@ check_pointer_move(struct client *client, int x, int y) check_pointer(client, x, y); } +static struct client * +create_client_with_pointer_focus(int x, int y, int w, int h) +{ + struct client *cl = create_client_and_test_surface(x, y, w, h); + assert(cl); + /* Move the pointer inside the surface to ensure that the surface + * has the pointer focus. */ + check_pointer_move(cl, x, y); + return cl; +} + TEST(test_pointer_top_left) { struct client *client; @@ -286,23 +297,26 @@ TEST(test_pointer_surface_move) check_pointer(client, 50, 50); } -TEST(simple_pointer_button_test) +TEST(pointer_motion_events) { - struct client *client; - struct pointer *pointer; - - client = create_client_and_test_surface(100, 100, 100, 100); - assert(client); - - pointer = client->input->pointer; - - assert(pointer->button == 0); - assert(pointer->state == 0); + struct client *client = create_client_with_pointer_focus(100, 100, + 100, 100); + struct pointer *pointer = client->input->pointer; weston_test_move_pointer(client->test->weston_test, 150, 150); client_roundtrip(client); assert(pointer->x == 50); assert(pointer->y == 50); +} + +TEST(pointer_button_events) +{ + struct client *client = create_client_with_pointer_focus(100, 100, + 100, 100); + struct pointer *pointer = client->input->pointer; + + assert(pointer->button == 0); + assert(pointer->state == 0); weston_test_send_button(client->test->weston_test, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); From c83fdaa55568b1177f8f4752a5ce352fd0c32a69 Mon Sep 17 00:00:00 2001 From: David Fort Date: Fri, 15 Dec 2017 15:23:59 +0100 Subject: [PATCH 0250/1642] rdp compositor: add a man page and add links to that page --- Makefile.am | 5 +++ man/weston-rdp.man | 92 ++++++++++++++++++++++++++++++++++++++++++++++ man/weston.man | 13 +++++++ 3 files changed, 110 insertions(+) create mode 100644 man/weston-rdp.man diff --git a/Makefile.am b/Makefile.am index 7adc62549..f67e693d4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1563,6 +1563,10 @@ if ENABLE_DRM_COMPOSITOR man_MANS += weston-drm.7 endif +if ENABLE_RDP_COMPOSITOR +man_MANS += weston-rdp.7 +endif + MAN_SUBSTS = \ -e 's|__weston_native_backend__|$(WESTON_NATIVE_BACKEND)|g' \ -e 's|__weston_modules_dir__|$(pkglibdir)|g' \ @@ -1577,6 +1581,7 @@ SUFFIXES = .1 .5 .7 .man EXTRA_DIST += \ man/weston.man \ man/weston-drm.man \ + man/weston-rdp.man \ man/weston.ini.man CLEANFILES += $(man_MANS) diff --git a/man/weston-rdp.man b/man/weston-rdp.man new file mode 100644 index 000000000..0916c8d74 --- /dev/null +++ b/man/weston-rdp.man @@ -0,0 +1,92 @@ +.TH WESTON-RDP 7 "2017-12-14" "Weston __version__" +.SH NAME +weston-rdp \- the RDP backend for Weston +.SH SYNOPSIS +.B weston --backend=rdp-backend.so +. +.\" *************************************************************** +.SH DESCRIPTION +The RDP backend allows to run a +.B weston +environment without the need of specific graphic hardware, or input devices. Users can interact with +.B weston +only by connecting using the RDP protocol. + +The RDP backend uses FreeRDP to implement the RDP part, it acts as a RDP server +listening for incoming connections. It supports different codecs for encoding the +graphical content. Depending on what is supported by the RDP client, the backend will +encode images using remoteFx codec, NS codec or will fallback to raw bitmapUpdate. + +On the security part, the backend supports RDP security or TLS, keys and certificates +must be provided to the backend depending on which kind of security is requested. The RDP +backend will announce security options based on which files have been given. + +The RDP backend is multi-seat aware, so if two clients connect on the backend, +they will get their own seat. + +.\" *************************************************************** +.SH OPTIONS +. +When the RDP backend is loaded, +.B weston +will understand the following additional command line options. +.TP +.B \-\-address\fR=\fIaddress\fR +The IP address on which the RDP backend will listen for RDP connections. By +default it listens on 0.0.0.0. +.TP +\fB\-\-port\fR=\fIport\fR +The TCP port to listen on for connections, it defaults to 3389. +.TP +\fB\-\-no-clients-resize +By default when a client connects on the RDP backend, it will instruct weston to +resize to the dimensions of the client's announced resolution. When this option is +set, weston will force the client to resize to its own resolution. +.TP +\fB\-\-rdp4\-key\fR=\fIfile\fR +The file containing the RSA key for doing RDP security. As RDP security is known +to be insecure, this option should be avoided in production. +.TP +\fB\-\-rdp\-tls\-key\fR=\fIfile\fR +The file containing the key for doing TLS security. To have TLS security you also need +to ship a file containing a certificate. +.TP +\fB\-\-rdp\-tls\-cert\fR=\fIfile\fR +The file containing the certificate for doing TLS security. To have TLS security you also need +to ship a key file. + + +.\" *************************************************************** +.SH Generating cryptographic material for the RDP backend +. +To generate a key file to use for RDP security, you need the +.BR winpr-makecert +utility shipped with FreeRDP: + +.nf +$ winpr-makecert -rdp -silent -n rdp-security +.fi + +This will create a rdp-security.key file. + + +You can generate a key and certificate file to use with TLS security using a typical +.B openssl +invocations: + +.nf +$ openssl genrsa -out tls.key 2048 +Generating RSA private key, 2048 bit long modulus +[...] +$ openssl req -new -key tls.key -out tls.csr +[...] +$ openssl x509 -req -days 365 -signkey tls.key -in tls.csr -out tls.crt +[...] +.fi + +You will get the tls.key and tls.crt files to use with the RDP backend. +. +.\" *************************************************************** +.SH "SEE ALSO" +.BR weston (1) +.\".BR weston.ini (5) diff --git a/man/weston.man b/man/weston.man index cd53df69c..7c3416c2c 100644 --- a/man/weston.man +++ b/man/weston.man @@ -46,6 +46,13 @@ the parent server. The X11 backend runs on an X server. Each Weston output becomes an X window. This is a cheap way to test multi-monitor support of a Wayland shell, desktop, or applications. +.TP +.I rdp-backend.so +The RDP backend runs in memory without the need of graphical hardware. Access +to the desktop is done by using the RDP protocol. Each connecting +client has its own seat making it a cheap way to test multi-seat support. See +.BR weston-rdp (7), +if installed. . .\" *************************************************************** .SH SHELLS @@ -234,6 +241,11 @@ Use the pixman renderer. By default weston will try to use EGL and GLES2 for rendering. Passing this option will make weston use the pixman library for software compsiting. . +.SS RDP backend options: +See +.BR weston-rdp (7). +. +. .\" *************************************************************** .SH FILES . @@ -349,5 +361,6 @@ weston .\" *************************************************************** .SH "SEE ALSO" .BR weston-drm (7) +.BR weston-rdp (7) .\".BR weston-launch (1), .\".BR weston.ini (5) From 787fa611de1b7c4144b7491260c513a5ec0a4728 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 13 Dec 2017 13:27:53 +0200 Subject: [PATCH 0251/1642] shared: Add timespec_from_proto helper function Add helper function to convert tv_sec_hi, tv_sec_lo, tv_nsec triplets, used for sending high-resolution timestamp data over the wayland protocol, to struct timespec values. Replace existing conversion code with the helper function. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- clients/presentation-shm.c | 9 +-------- shared/timespec-util.h | 15 +++++++++++++++ tests/presentation-test.c | 9 +-------- tests/timespec-test.c | 17 +++++++++++++++++ 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/clients/presentation-shm.c b/clients/presentation-shm.c index c9fb66cca..d6a939e5b 100644 --- a/clients/presentation-shm.c +++ b/clients/presentation-shm.c @@ -39,6 +39,7 @@ #include #include "shared/helpers.h" #include "shared/zalloc.h" +#include "shared/timespec-util.h" #include "shared/os-compatibility.h" #include "presentation-time-client-protocol.h" @@ -383,14 +384,6 @@ timespec_to_ms(const struct timespec *ts) return (uint32_t)ts->tv_sec * 1000 + ts->tv_nsec / 1000000; } -static void -timespec_from_proto(struct timespec *tm, uint32_t tv_sec_hi, - uint32_t tv_sec_lo, uint32_t tv_nsec) -{ - tm->tv_sec = ((uint64_t)tv_sec_hi << 32) + tv_sec_lo; - tm->tv_nsec = tv_nsec; -} - static int timespec_diff_to_usec(const struct timespec *a, const struct timespec *b) { diff --git a/shared/timespec-util.h b/shared/timespec-util.h index f9736c270..5184d2811 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -181,6 +181,21 @@ timespec_from_msec(struct timespec *a, int64_t b) timespec_from_nsec(a, b * 1000000); } +/* Convert protocol data to timespec + * + * \param a[out] timespec + * \param tv_sec_hi the high bytes of seconds part + * \param tv_sec_lo the low bytes of seconds part + * \param tv_nsec the nanoseconds part + */ +static inline void +timespec_from_proto(struct timespec *a, uint32_t tv_sec_hi, + uint32_t tv_sec_lo, uint32_t tv_nsec) +{ + a->tv_sec = ((uint64_t)tv_sec_hi << 32) + tv_sec_lo; + a->tv_nsec = tv_nsec; +} + /* Check if a timespec is zero * * \param a timespec diff --git a/tests/presentation-test.c b/tests/presentation-test.c index f12f8eefc..f6ffe4805 100644 --- a/tests/presentation-test.c +++ b/tests/presentation-test.c @@ -34,6 +34,7 @@ #include "shared/helpers.h" #include "shared/xalloc.h" +#include "shared/timespec-util.h" #include "weston-test-client-helper.h" #include "presentation-time-client-protocol.h" @@ -85,14 +86,6 @@ struct feedback { uint32_t flags; }; -static void -timespec_from_proto(struct timespec *tm, uint32_t tv_sec_hi, - uint32_t tv_sec_lo, uint32_t tv_nsec) -{ - tm->tv_sec = ((uint64_t)tv_sec_hi << 32) + tv_sec_lo; - tm->tv_nsec = tv_nsec; -} - static void feedback_sync_output(void *data, struct wp_presentation_feedback *presentation_feedback, diff --git a/tests/timespec-test.c b/tests/timespec-test.c index f10ed76cb..a4d8dcfbd 100644 --- a/tests/timespec-test.c +++ b/tests/timespec-test.c @@ -238,6 +238,23 @@ ZUC_TEST(timespec_test, timespec_from_msec) ZUC_ASSERT_EQ(1000000, a.tv_nsec); } +ZUC_TEST(timespec_test, timespec_from_proto) +{ + struct timespec a; + + timespec_from_proto(&a, 0, 0, 0); + ZUC_ASSERT_EQ(0, a.tv_sec); + ZUC_ASSERT_EQ(0, a.tv_nsec); + + timespec_from_proto(&a, 0, 1234, 9999); + ZUC_ASSERT_EQ(1234, a.tv_sec); + ZUC_ASSERT_EQ(9999, a.tv_nsec); + + timespec_from_proto(&a, 0x1234, 0x5678, 1); + ZUC_ASSERT_EQ((time_t)0x0000123400005678LL, a.tv_sec); + ZUC_ASSERT_EQ(1, a.tv_nsec); +} + ZUC_TEST(timespec_test, timespec_is_zero) { struct timespec zero = { 0 }; From 10d708d2688f26c17b3f009fcc681174d9877166 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 13 Dec 2017 13:27:54 +0200 Subject: [PATCH 0252/1642] shared: Add timespec_to_proto helper function Add helper function to convert from struct timespec values to tv_sec_hi, tv_sec_lo, tv_nsec triplets used for sending high-resolution timestamp data over the wayland protocol. Replace existing conversion code with the helper function. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 9 +++++---- shared/timespec-util.h | 24 ++++++++++++++++++++++++ tests/timespec-test.c | 29 +++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 7d7a17edf..083664fd8 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -341,7 +341,9 @@ weston_presentation_feedback_present( { struct wl_client *client = wl_resource_get_client(feedback->resource); struct wl_resource *o; - uint64_t secs; + uint32_t tv_sec_hi; + uint32_t tv_sec_lo; + uint32_t tv_nsec; wl_resource_for_each(o, &output->resource_list) { if (wl_resource_get_client(o) != client) @@ -350,10 +352,9 @@ weston_presentation_feedback_present( wp_presentation_feedback_send_sync_output(feedback->resource, o); } - secs = ts->tv_sec; + timespec_to_proto(ts, &tv_sec_hi, &tv_sec_lo, &tv_nsec); wp_presentation_feedback_send_presented(feedback->resource, - secs >> 32, secs & 0xffffffff, - ts->tv_nsec, + tv_sec_hi, tv_sec_lo, tv_nsec, refresh_nsec, seq >> 32, seq & 0xffffffff, flags | feedback->psf_flags); diff --git a/shared/timespec-util.h b/shared/timespec-util.h index 5184d2811..5f4b2b9e0 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -147,6 +147,30 @@ timespec_to_usec(const struct timespec *a) return (int64_t)a->tv_sec * 1000000 + a->tv_nsec / 1000; } +/* Convert timespec to protocol data + * + * \param a timespec + * \param tv_sec_hi[out] the high bytes of the seconds part + * \param tv_sec_lo[out] the low bytes of the seconds part + * \param tv_nsec[out] the nanoseconds part + * + * The input timespec must be normalized (the nanoseconds part should + * be less than 1 second) and non-negative. + */ +static inline void +timespec_to_proto(const struct timespec *a, uint32_t *tv_sec_hi, + uint32_t *tv_sec_lo, uint32_t *tv_nsec) +{ + assert(a->tv_sec >= 0); + assert(a->tv_nsec >= 0 && a->tv_nsec < NSEC_PER_SEC); + + uint64_t sec64 = a->tv_sec; + + *tv_sec_hi = sec64 >> 32; + *tv_sec_lo = sec64 & 0xffffffff; + *tv_nsec = a->tv_nsec; +} + /* Convert nanoseconds to timespec * * \param a timespec diff --git a/tests/timespec-test.c b/tests/timespec-test.c index a4d8dcfbd..54230f89a 100644 --- a/tests/timespec-test.c +++ b/tests/timespec-test.c @@ -79,6 +79,35 @@ ZUC_TEST(timespec_test, timespec_to_msec) ZUC_ASSERT_EQ(timespec_to_msec(&a), (4000ULL) + 4); } +ZUC_TEST(timespec_test, timespec_to_proto) +{ + struct timespec a; + uint32_t tv_sec_hi; + uint32_t tv_sec_lo; + uint32_t tv_nsec; + + a.tv_sec = 0; + a.tv_nsec = 0; + timespec_to_proto(&a, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + ZUC_ASSERT_EQ(0, tv_sec_hi); + ZUC_ASSERT_EQ(0, tv_sec_lo); + ZUC_ASSERT_EQ(0, tv_nsec); + + a.tv_sec = 1234; + a.tv_nsec = NSEC_PER_SEC - 1; + timespec_to_proto(&a, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + ZUC_ASSERT_EQ(0, tv_sec_hi); + ZUC_ASSERT_EQ(1234, tv_sec_lo); + ZUC_ASSERT_EQ(NSEC_PER_SEC - 1, tv_nsec); + + a.tv_sec = (time_t)0x7000123470005678LL; + a.tv_nsec = 1; + timespec_to_proto(&a, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + ZUC_ASSERT_EQ((uint64_t)a.tv_sec >> 32, tv_sec_hi); + ZUC_ASSERT_EQ(0x70005678, tv_sec_lo); + ZUC_ASSERT_EQ(1, tv_nsec); +} + ZUC_TEST(timespec_test, millihz_to_nsec) { ZUC_ASSERT_EQ(millihz_to_nsec(60000), 16666666); From 2180858592bdfa67ed75fb91cde1c18ee17270a2 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 13 Dec 2017 13:27:55 +0200 Subject: [PATCH 0253/1642] tests: Add checks for pointer motion and button event timestamps Enhance the existing pointer motion and button event tests to additionally verify the event timestamps. This requires updating the weston-test protocol to support passing motion and button event timestamps. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- protocol/weston-test.xml | 6 +++++ tests/internal-screenshot-test.c | 2 +- tests/pointer-test.c | 45 ++++++++++++++++++++++++------- tests/subsurface-shot-test.c | 2 +- tests/weston-test-client-helper.c | 6 +++-- tests/weston-test-client-helper.h | 2 ++ tests/weston-test.c | 6 +++-- 7 files changed, 53 insertions(+), 16 deletions(-) diff --git a/protocol/weston-test.xml b/protocol/weston-test.xml index 74a152146..ae3349eda 100644 --- a/protocol/weston-test.xml +++ b/protocol/weston-test.xml @@ -40,10 +40,16 @@ + + + + + + diff --git a/tests/internal-screenshot-test.c b/tests/internal-screenshot-test.c index 3bf9b31b3..2a7424b8b 100644 --- a/tests/internal-screenshot-test.c +++ b/tests/internal-screenshot-test.c @@ -97,7 +97,7 @@ TEST(internal_screenshot) */ /* Move the pointer away from the screenshot area. */ - weston_test_move_pointer(client->test->weston_test, 0, 0); + weston_test_move_pointer(client->test->weston_test, 0, 1, 0, 0, 0); buf = create_shm_buffer_a8r8g8b8(client, 100, 100); draw_stuff(buf->image); diff --git a/tests/pointer-test.c b/tests/pointer-test.c index e0e700e06..61bf83b76 100644 --- a/tests/pointer-test.c +++ b/tests/pointer-test.c @@ -28,8 +28,36 @@ #include +#include "shared/timespec-util.h" #include "weston-test-client-helper.h" +static const struct timespec t0 = { .tv_sec = 0, .tv_nsec = 100000000 }; +static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; +static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; + +static void +send_motion(struct client *client, const struct timespec *time, int x, int y) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + weston_test_move_pointer(client->test->weston_test, tv_sec_hi, tv_sec_lo, + tv_nsec, x, y); + client_roundtrip(client); +} + +static void +send_button(struct client *client, const struct timespec *time, + uint32_t button, uint32_t state) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + weston_test_send_button(client->test->weston_test, tv_sec_hi, tv_sec_lo, + tv_nsec, button, state); + client_roundtrip(client); +} + static void check_pointer(struct client *client, int x, int y) { @@ -64,8 +92,7 @@ check_pointer(struct client *client, int x, int y) static void check_pointer_move(struct client *client, int x, int y) { - weston_test_move_pointer(client->test->weston_test, x, y); - client_roundtrip(client); + send_motion(client, &t0, x, y); check_pointer(client, x, y); } @@ -303,10 +330,10 @@ TEST(pointer_motion_events) 100, 100); struct pointer *pointer = client->input->pointer; - weston_test_move_pointer(client->test->weston_test, 150, 150); - client_roundtrip(client); + send_motion(client, &t1, 150, 150); assert(pointer->x == 50); assert(pointer->y == 50); + assert(pointer->motion_time_msec == timespec_to_msec(&t1)); } TEST(pointer_button_events) @@ -318,15 +345,13 @@ TEST(pointer_button_events) assert(pointer->button == 0); assert(pointer->state == 0); - weston_test_send_button(client->test->weston_test, BTN_LEFT, - WL_POINTER_BUTTON_STATE_PRESSED); - client_roundtrip(client); + send_button(client, &t1, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); assert(pointer->button == BTN_LEFT); assert(pointer->state == WL_POINTER_BUTTON_STATE_PRESSED); + assert(pointer->button_time_msec == timespec_to_msec(&t1)); - weston_test_send_button(client->test->weston_test, BTN_LEFT, - WL_POINTER_BUTTON_STATE_RELEASED); - client_roundtrip(client); + send_button(client, &t2, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); assert(pointer->button == BTN_LEFT); assert(pointer->state == WL_POINTER_BUTTON_STATE_RELEASED); + assert(pointer->button_time_msec == timespec_to_msec(&t2)); } diff --git a/tests/subsurface-shot-test.c b/tests/subsurface-shot-test.c index 10415ec76..e8bab6768 100644 --- a/tests/subsurface-shot-test.c +++ b/tests/subsurface-shot-test.c @@ -200,7 +200,7 @@ TEST(subsurface_z_order) subco = get_subcompositor(client); /* move the pointer clearly away from our screenshooting area */ - weston_test_move_pointer(client->test->weston_test, 2, 30); + weston_test_move_pointer(client->test->weston_test, 0, 1, 0, 2, 30); /* make the parent surface red */ surf[0] = client->surface->wl_surface; diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index ee508452e..203cd4418 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -148,12 +148,13 @@ pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, - uint32_t time, wl_fixed_t x, wl_fixed_t y) + uint32_t time_msec, wl_fixed_t x, wl_fixed_t y) { struct pointer *pointer = data; pointer->x = wl_fixed_to_int(x); pointer->y = wl_fixed_to_int(y); + pointer->motion_time_msec = time_msec; fprintf(stderr, "test-client: got pointer motion %d %d\n", pointer->x, pointer->y); @@ -161,13 +162,14 @@ pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, - uint32_t serial, uint32_t time, uint32_t button, + uint32_t serial, uint32_t time_msec, uint32_t button, uint32_t state) { struct pointer *pointer = data; pointer->button = button; pointer->state = state; + pointer->button_time_msec = time_msec; fprintf(stderr, "test-client: got pointer button %u %u\n", button, state); diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 880f47a63..6f5f9c410 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -90,6 +90,8 @@ struct pointer { int y; uint32_t button; uint32_t state; + uint32_t motion_time_msec; + uint32_t button_time_msec; }; struct keyboard { diff --git a/tests/weston-test.c b/tests/weston-test.c index 6e7beeb7e..1799de920 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -146,6 +146,7 @@ move_surface(struct wl_client *client, struct wl_resource *resource, static void move_pointer(struct wl_client *client, struct wl_resource *resource, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, int32_t x, int32_t y) { struct weston_test *test = wl_resource_get_user_data(resource); @@ -160,7 +161,7 @@ move_pointer(struct wl_client *client, struct wl_resource *resource, .dy = wl_fixed_to_double(wl_fixed_from_int(y) - pointer->y), }; - timespec_from_msec(&time, 100); + timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); notify_motion(seat, &time, &event); @@ -169,6 +170,7 @@ move_pointer(struct wl_client *client, struct wl_resource *resource, static void send_button(struct wl_client *client, struct wl_resource *resource, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, int32_t button, uint32_t state) { struct timespec time; @@ -176,7 +178,7 @@ send_button(struct wl_client *client, struct wl_resource *resource, struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat = get_seat(test); - timespec_from_msec(&time, 100); + timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); notify_button(seat, &time, button, state); } From dae224c6dc8054d1cf3ce4596588f922ff029fd2 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 13 Dec 2017 13:27:57 +0200 Subject: [PATCH 0254/1642] tests: Add test for keyboard key event timestamps Add test to verify that the server correctly sets the timestamps of keyboard key events. This requires updating the weston-test protocol to support passing key event timestamps. simple_keyboard_test now uses the create_client_with_keyboard_focus() helper function which changes the initial state of the surface to be focused. This leads to one additional iteration of the test loop when starting, during which the surface is deactivated, i.e., loses focus. After this initial iteration the test continues as before. Furthermore, simple_keyboard_test now uses the send_key() helper function which performs a roundtrip internally. To account for this, the client_roundtrip() function is now directly called in the loop only when it is still required, i.e., when deactivating the surface. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- protocol/weston-test.xml | 3 ++ tests/keyboard-test.c | 61 ++++++++++++++++++++++++------- tests/weston-test-client-helper.c | 3 +- tests/weston-test-client-helper.h | 1 + tests/weston-test.c | 3 +- 5 files changed, 55 insertions(+), 16 deletions(-) diff --git a/protocol/weston-test.xml b/protocol/weston-test.xml index ae3349eda..4c258f4ba 100644 --- a/protocol/weston-test.xml +++ b/protocol/weston-test.xml @@ -57,6 +57,9 @@ + + + diff --git a/tests/keyboard-test.c b/tests/keyboard-test.c index 6b4ba19db..722bfd327 100644 --- a/tests/keyboard-test.c +++ b/tests/keyboard-test.c @@ -27,21 +27,45 @@ #include +#include "shared/timespec-util.h" #include "weston-test-client-helper.h" +static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; +static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; + +static struct client * +create_client_with_keyboard_focus(void) +{ + struct client *cl = create_client_and_test_surface(10, 10, 1, 1); + assert(cl); + + weston_test_activate_surface(cl->test->weston_test, + cl->surface->wl_surface); + client_roundtrip(cl); + + return cl; +} + +static void +send_key(struct client *client, const struct timespec *time, + uint32_t key, uint32_t state) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + weston_test_send_key(client->test->weston_test, tv_sec_hi, tv_sec_lo, + tv_nsec, key, state); + client_roundtrip(client); +} + TEST(simple_keyboard_test) { - struct client *client; - struct surface *expect_focus = NULL; - struct keyboard *keyboard; + struct client *client = create_client_with_keyboard_focus(); + struct keyboard *keyboard = client->input->keyboard; + struct surface *expect_focus = client->surface; uint32_t expect_key = 0; uint32_t expect_state = 0; - client = create_client_and_test_surface(10, 10, 1, 1); - assert(client); - - keyboard = client->input->keyboard; - while (1) { assert(keyboard->key == expect_key); assert(keyboard->state == expect_state); @@ -49,12 +73,12 @@ TEST(simple_keyboard_test) if (keyboard->state == WL_KEYBOARD_KEY_STATE_PRESSED) { expect_state = WL_KEYBOARD_KEY_STATE_RELEASED; - weston_test_send_key(client->test->weston_test, - expect_key, expect_state); + send_key(client, &t1, expect_key, expect_state); } else if (keyboard->focus) { expect_focus = NULL; weston_test_activate_surface( client->test->weston_test, NULL); + client_roundtrip(client); } else if (expect_key < 10) { expect_key++; expect_focus = client->surface; @@ -62,12 +86,21 @@ TEST(simple_keyboard_test) weston_test_activate_surface( client->test->weston_test, expect_focus->wl_surface); - weston_test_send_key(client->test->weston_test, - expect_key, expect_state); + send_key(client, &t1, expect_key, expect_state); } else { break; } - - client_roundtrip(client); } } + +TEST(keyboard_key_event_time) +{ + struct client *client = create_client_with_keyboard_focus(); + struct keyboard *keyboard = client->input->keyboard; + + send_key(client, &t1, 1, WL_KEYBOARD_KEY_STATE_PRESSED); + assert(keyboard->key_time_msec == timespec_to_msec(&t1)); + + send_key(client, &t2, 1, WL_KEYBOARD_KEY_STATE_RELEASED); + assert(keyboard->key_time_msec == timespec_to_msec(&t2)); +} diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 203cd4418..695f69127 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -262,13 +262,14 @@ keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, - uint32_t serial, uint32_t time, uint32_t key, + uint32_t serial, uint32_t time_msec, uint32_t key, uint32_t state) { struct keyboard *keyboard = data; keyboard->key = key; keyboard->state = state; + keyboard->key_time_msec = time_msec; fprintf(stderr, "test-client: got keyboard key %u %u\n", key, state); } diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 6f5f9c410..214f69597 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -107,6 +107,7 @@ struct keyboard { int rate; int delay; } repeat_info; + uint32_t key_time_msec; }; struct touch { diff --git a/tests/weston-test.c b/tests/weston-test.c index 1799de920..7dd7a1cf6 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -208,13 +208,14 @@ activate_surface(struct wl_client *client, struct wl_resource *resource, static void send_key(struct wl_client *client, struct wl_resource *resource, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, uint32_t key, enum wl_keyboard_key_state state) { struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat = get_seat(test); struct timespec time; - timespec_from_msec(&time, 100); + timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); notify_key(seat, &time, key, state, STATE_UPDATE_AUTOMATIC); } From c20b580b5288d6ee6aa6bed4de165e2c191d3a28 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 13 Dec 2017 13:27:58 +0200 Subject: [PATCH 0255/1642] tests: Add test for touch event timestamps Add test to verify that the server correctly sets the timestamps of touch events. This requires updating the weston-test protocol with a new request for touch events. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- Makefile.am | 7 ++- protocol/weston-test.xml | 9 ++++ tests/touch-test.c | 71 +++++++++++++++++++++++++++++++ tests/weston-test-client-helper.c | 13 ++++-- tests/weston-test-client-helper.h | 3 ++ tests/weston-test.c | 16 +++++++ 6 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 tests/touch-test.c diff --git a/Makefile.am b/Makefile.am index f67e693d4..883249c0b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1233,7 +1233,8 @@ weston_tests = \ roles.weston \ subsurface.weston \ subsurface-shot.weston \ - devices.weston + devices.weston \ + touch.weston ivi_tests = @@ -1428,6 +1429,10 @@ nodist_viewporter_weston_SOURCES = \ viewporter_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) viewporter_weston_LDADD = libtest-client.la +touch_weston_SOURCES = tests/touch-test.c +touch_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) +touch_weston_LDADD = libtest-client.la + if ENABLE_XWAYLAND_TEST weston_tests += xwayland-test.weston xwayland_test_weston_SOURCES = tests/xwayland-test.c diff --git a/protocol/weston-test.xml b/protocol/weston-test.xml index 4c258f4ba..fa3d15e15 100644 --- a/protocol/weston-test.xml +++ b/protocol/weston-test.xml @@ -89,6 +89,15 @@ provided buffer. + + + + + + + + + diff --git a/tests/touch-test.c b/tests/touch-test.c new file mode 100644 index 000000000..9635257f2 --- /dev/null +++ b/tests/touch-test.c @@ -0,0 +1,71 @@ +/* + * Copyright © 2017 Collabora, Ltd. + * + * 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 "shared/timespec-util.h" +#include "weston-test-client-helper.h" +#include "wayland-server-protocol.h" + +static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; +static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; +static const struct timespec t3 = { .tv_sec = 3, .tv_nsec = 3000001 }; + +static struct client * +create_touch_test_client(void) +{ + struct client *cl = create_client_and_test_surface(0, 0, 100, 100); + assert(cl); + return cl; +} + +static void +send_touch(struct client *client, const struct timespec *time, + uint32_t touch_type) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + weston_test_send_touch(client->test->weston_test, tv_sec_hi, tv_sec_lo, + tv_nsec, 1, 1, 1, touch_type); + client_roundtrip(client); +} + +TEST(touch_events) +{ + struct client *client = create_touch_test_client(); + struct touch *touch = client->input->touch; + + send_touch(client, &t1, WL_TOUCH_DOWN); + assert(touch->down_time_msec == timespec_to_msec(&t1)); + + send_touch(client, &t2, WL_TOUCH_MOTION); + assert(touch->motion_time_msec == timespec_to_msec(&t2)); + + send_touch(client, &t3, WL_TOUCH_UP); + assert(touch->up_time_msec == timespec_to_msec(&t3)); +} diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 695f69127..c5a003208 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -315,14 +315,16 @@ static const struct wl_keyboard_listener keyboard_listener = { static void touch_handle_down(void *data, struct wl_touch *wl_touch, - uint32_t serial, uint32_t time, struct wl_surface *surface, - int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) + uint32_t serial, uint32_t time_msec, + struct wl_surface *surface, int32_t id, + wl_fixed_t x_w, wl_fixed_t y_w) { struct touch *touch = data; touch->down_x = wl_fixed_to_int(x_w); touch->down_y = wl_fixed_to_int(y_w); touch->id = id; + touch->down_time_msec = time_msec; fprintf(stderr, "test-client: got touch down %d %d, surf: %p, id: %d\n", touch->down_x, touch->down_y, surface, id); @@ -330,21 +332,24 @@ touch_handle_down(void *data, struct wl_touch *wl_touch, static void touch_handle_up(void *data, struct wl_touch *wl_touch, - uint32_t serial, uint32_t time, int32_t id) + uint32_t serial, uint32_t time_msec, int32_t id) { struct touch *touch = data; touch->up_id = id; + touch->up_time_msec = time_msec; fprintf(stderr, "test-client: got touch up, id: %d\n", id); } static void touch_handle_motion(void *data, struct wl_touch *wl_touch, - uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) + uint32_t time_msec, int32_t id, + wl_fixed_t x_w, wl_fixed_t y_w) { struct touch *touch = data; touch->x = wl_fixed_to_int(x_w); touch->y = wl_fixed_to_int(y_w); + touch->motion_time_msec = time_msec; fprintf(stderr, "test-client: got touch motion, %d %d, id: %d\n", touch->x, touch->y, id); diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 214f69597..86ecb640d 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -120,6 +120,9 @@ struct touch { int up_id; /* id of last wl_touch.up event */ int frame_no; int cancel_no; + uint32_t down_time_msec; + uint32_t up_time_msec; + uint32_t motion_time_msec; }; struct output { diff --git a/tests/weston-test.c b/tests/weston-test.c index 7dd7a1cf6..14030f4ce 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -500,6 +500,21 @@ capture_screenshot(struct wl_client *client, capture_screenshot_done, resource); } +static void +send_touch(struct wl_client *client, struct wl_resource *resource, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, + int32_t touch_id, wl_fixed_t x, wl_fixed_t y, uint32_t touch_type) +{ + struct weston_test *test = wl_resource_get_user_data(resource); + struct weston_seat *seat = get_seat(test); + struct timespec time; + + timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); + + notify_touch(seat, &time, touch_id, wl_fixed_to_double(x), + wl_fixed_to_double(y), touch_type); +} + static const struct weston_test_interface test_implementation = { move_surface, move_pointer, @@ -509,6 +524,7 @@ static const struct weston_test_interface test_implementation = { device_release, device_add, capture_screenshot, + send_touch, }; static void From b0341ae972531a6c3bcf43a7f8b66c44f8bc8e49 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Mon, 18 Dec 2017 12:16:55 +0200 Subject: [PATCH 0256/1642] tests: Add test for pointer axis events Add test to verify the server correctly emits pointer axis events. This requires updating the weston-test protocol with a new request for pointer axis events. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- protocol/weston-test.xml | 7 +++++++ tests/pointer-test.c | 28 ++++++++++++++++++++++++++++ tests/weston-test-client-helper.c | 17 ++++++++++++++--- tests/weston-test-client-helper.h | 4 ++++ tests/weston-test.c | 20 ++++++++++++++++++++ 5 files changed, 73 insertions(+), 3 deletions(-) diff --git a/protocol/weston-test.xml b/protocol/weston-test.xml index fa3d15e15..00b7185dc 100644 --- a/protocol/weston-test.xml +++ b/protocol/weston-test.xml @@ -53,6 +53,13 @@ + + + + + + + diff --git a/tests/pointer-test.c b/tests/pointer-test.c index 61bf83b76..4c438a227 100644 --- a/tests/pointer-test.c +++ b/tests/pointer-test.c @@ -58,6 +58,18 @@ send_button(struct client *client, const struct timespec *time, client_roundtrip(client); } +static void +send_axis(struct client *client, const struct timespec *time, uint32_t axis, + double value) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + weston_test_send_axis(client->test->weston_test, tv_sec_hi, tv_sec_lo, + tv_nsec, axis, wl_fixed_from_double(value)); + client_roundtrip(client); +} + static void check_pointer(struct client *client, int x, int y) { @@ -355,3 +367,19 @@ TEST(pointer_button_events) assert(pointer->state == WL_POINTER_BUTTON_STATE_RELEASED); assert(pointer->button_time_msec == timespec_to_msec(&t2)); } + +TEST(pointer_axis_events) +{ + struct client *client = create_client_with_pointer_focus(100, 100, + 100, 100); + struct pointer *pointer = client->input->pointer; + + send_axis(client, &t1, 1, 1.0); + assert(pointer->axis == 1); + assert(pointer->axis_value == 1.0); + assert(pointer->axis_time_msec == timespec_to_msec(&t1)); + + send_axis(client, &t2, 2, 0.0); + assert(pointer->axis == 2); + assert(pointer->axis_stop_time_msec == timespec_to_msec(&t2)); +} diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index c5a003208..6e0a5246b 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -177,8 +177,14 @@ pointer_handle_button(void *data, struct wl_pointer *wl_pointer, static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, - uint32_t time, uint32_t axis, wl_fixed_t value) + uint32_t time_msec, uint32_t axis, wl_fixed_t value) { + struct pointer *pointer = data; + + pointer->axis = axis; + pointer->axis_value = wl_fixed_to_double(value); + pointer->axis_time_msec = time_msec; + fprintf(stderr, "test-client: got pointer axis %u %f\n", axis, wl_fixed_to_double(value)); } @@ -198,9 +204,14 @@ pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer, static void pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, - uint32_t time, uint32_t axis) + uint32_t time_msec, uint32_t axis) { - fprintf(stderr, "test-client: got pointer axis stop\n"); + struct pointer *pointer = data; + + pointer->axis = axis; + pointer->axis_stop_time_msec = time_msec; + + fprintf(stderr, "test-client: got pointer axis stop %u\n", axis); } static void diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 86ecb640d..09a5df4a0 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -90,8 +90,12 @@ struct pointer { int y; uint32_t button; uint32_t state; + uint32_t axis; + double axis_value; uint32_t motion_time_msec; uint32_t button_time_msec; + uint32_t axis_time_msec; + uint32_t axis_stop_time_msec; }; struct keyboard { diff --git a/tests/weston-test.c b/tests/weston-test.c index 14030f4ce..80b3d65b0 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -183,6 +183,25 @@ send_button(struct wl_client *client, struct wl_resource *resource, notify_button(seat, &time, button, state); } +static void +send_axis(struct wl_client *client, struct wl_resource *resource, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, + uint32_t axis, wl_fixed_t value) +{ + struct weston_test *test = wl_resource_get_user_data(resource); + struct weston_seat *seat = get_seat(test); + struct timespec time; + struct weston_pointer_axis_event axis_event; + + timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); + axis_event.axis = axis; + axis_event.value = wl_fixed_to_double(value); + axis_event.has_discrete = false; + axis_event.discrete = 0; + + notify_axis(seat, &time, &axis_event); +} + static void activate_surface(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource) @@ -519,6 +538,7 @@ static const struct weston_test_interface test_implementation = { move_surface, move_pointer, send_button, + send_axis, activate_surface, send_key, device_release, From b45ed8baf0312915f30aa55b7d380379593edfd5 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 28 Mar 2017 18:04:27 +0300 Subject: [PATCH 0257/1642] compositor-drm: add specific_device configuration option Developers with testing rigs having multiple graphics cards plugged in often want to test things on a specific card. We have ways to choose a card through seat assignments, but configuring that run by run is awkward. Add a new DRM backend option to try to open a specific device, and quit if it fails. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 29 ++++++++++++++++++++++++++++- libweston/compositor-drm.h | 7 +++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index b77209c41..3eda70f30 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3813,6 +3813,30 @@ find_primary_gpu(struct drm_backend *b, const char *seat) return drm_device; } +static struct udev_device * +open_specific_drm_device(struct drm_backend *b, const char *name) +{ + struct udev_device *device; + + device = udev_device_new_from_subsystem_sysname(b->udev, "drm", name); + if (!device) { + weston_log("ERROR: could not open DRM device '%s'\n", name); + return NULL; + } + + if (!drm_device_is_kms(b, device)) { + udev_device_unref(device); + weston_log("ERROR: DRM device '%s' is not a KMS device.\n", name); + return NULL; + } + + /* If we're returning a device to use, we must have an open FD for + * it. */ + assert(b->drm.fd >= 0); + + return device; +} + static void planes_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) @@ -4064,7 +4088,10 @@ drm_backend_create(struct weston_compositor *compositor, b->session_listener.notify = session_notify; wl_signal_add(&compositor->session_signal, &b->session_listener); - drm_device = find_primary_gpu(b, seat_id); + if (config->specific_device) + drm_device = open_specific_drm_device(b, config->specific_device); + else + drm_device = find_primary_gpu(b, seat_id); if (drm_device == NULL) { weston_log("no drm device found\n"); goto err_udev; diff --git a/libweston/compositor-drm.h b/libweston/compositor-drm.h index 8181492fb..68f93eabc 100644 --- a/libweston/compositor-drm.h +++ b/libweston/compositor-drm.h @@ -139,6 +139,13 @@ struct weston_drm_backend_config { * * It is exprimed in milliseconds, 0 means disabled. */ uint32_t pageflip_timeout; + + /** Specific DRM device to open + * + * A DRM device name, like "card0", to open. If NULL, use heuristics + * based on seat names and boot_vga to find the right device. + */ + char *specific_device; }; #ifdef __cplusplus From 8a9c8b08cf7b1bf911af181580b7045cb65af5df Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 28 Mar 2017 18:14:37 +0300 Subject: [PATCH 0258/1642] weston: add --drm-device option for DRM-backend Developers with testing rigs having multiple graphics cards plugged in often want to test things on a specific card. We have ways to choose a card through seat assignments, but configuring that run by run is awkward. Add a command line option for opening a specific DRM device. v2: call it --drm-device instead of --device Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- compositor/main.c | 2 ++ man/weston-drm.man | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index 32fb33e80..7feb4cb0e 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -565,6 +565,7 @@ usage(int error_code) "Options for drm-backend.so:\n\n" " --seat=SEAT\t\tThe seat that weston should run on\n" " --tty=TTY\t\tThe tty to use\n" + " --drm-device=CARD\tThe DRM device to use, e.g. \"card0\".\n" " --use-pixman\t\tUse the pixman (CPU) renderer\n" " --current-mode\tPrefer current KMS mode over EDID preferred mode\n\n"); #endif @@ -1225,6 +1226,7 @@ load_drm_backend(struct weston_compositor *c, const struct weston_option options[] = { { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, + { WESTON_OPTION_STRING, "drm-device", 0, &config.specific_device }, { WESTON_OPTION_BOOLEAN, "current-mode", 0, &wet->drm_use_current_mode }, { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, }; diff --git a/man/weston-drm.man b/man/weston-drm.man index d7fd56142..75d79021c 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -91,6 +91,13 @@ will understand the following additional command line options. By default, use the current video mode of all outputs, instead of switching to the monitor preferred mode. .TP +\fB\-\-drm\-device\fR=\fIcardN\fR +Use the DRM device +.I cardN +instead of the default heuristics based on seat assignments and boot VGA +status. For example, use +.BR card0 . +.TP \fB\-\-seat\fR=\fIseatid\fR Use graphics and input devices designated for seat .I seatid From 14a7e371f57fd8dbdddb869296cdb686f4a0e9bc Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 18 Dec 2017 10:02:58 +0200 Subject: [PATCH 0259/1642] configure: fix sys/sysmacros.h check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch is a copy of https://cgit.freedesktop.org/mesa/drm/commit/?id=7040fea0280bad527ed4b3d5eee7d7bfbf303efc by Adam Jackson. Commit 43c5a65b034a243700cf9c5bfbe6bcefb15f1161 "configure.ac: use AC_HEADER_MAJOR to detect major()/minor()" started using AC_HEADER_MAJOR to detect the header where major() is defined. This caused a regression on systems where glibc is still providing a deprecated definition of major() through sys/types.h, leading to a bunch of compiler warnings: /home/pq/git/weston/libweston/launcher-logind.c: In function ‘launcher_logind_open’: /home/pq/git/weston/libweston/launcher-logind.c:182:13: warning: In the GNU C Library, "major" is defined by . For historical compatibility, it is currently defined by as well, but we plan to remove this soon. To use "major", include directly. If you did not intend to use a system-defined macro "major", you should undefine it after including . fd = launcher_logind_take_device(wl, major(st.st_rdev), The issue has been discussed earlier on https://lists.gnu.org/archive/html/autoconf/2016-09/msg00013.html Work around the issue by causing the warning to trigger a build failure inside AC_HEADER_MAJOR test, so that we get MAJOR_IN_SYSMACROS defined. Cc: Adam Jackson Cc: Emil Velikov Cc: Sergei Trofimovich Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Reviewed-by: Emil Velikov --- configure.ac | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/configure.ac b/configure.ac index d1b5f4718..285c2efdf 100644 --- a/configure.ac +++ b/configure.ac @@ -34,7 +34,11 @@ AC_CONFIG_MACRO_DIR([m4]) AC_USE_SYSTEM_EXTENSIONS AC_SYS_LARGEFILE + +save_CFLAGS="$CFLAGS" +export CFLAGS="$CFLAGS -Werror" AC_HEADER_MAJOR +CFLAGS="$save_CFLAGS" AM_INIT_AUTOMAKE([1.11 parallel-tests foreign no-dist-gzip dist-xz color-tests subdir-objects]) From 0a543996132fb809faa550a3e3914452c0bbda0a Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 7 Jan 2018 12:09:27 +0100 Subject: [PATCH 0260/1642] =?UTF-8?q?Unconditionally=20add=20linux-dmabuf?= =?UTF-8?q?=E2=80=99s=20protocol=20to=20BUILT=5FSOURCES?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was preventing weston-info from building if both weston-simple-dmabuf-drm and weston-simple-dmabuf-v4l2 were disabled at build-time. Signed-off-by: Emmanuel Gil Peyrot Reported-by: Cedric Sodhi Tested-by: Cedric Sodhi Reviewed-by: Quentin Glidic --- Makefile.am | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile.am b/Makefile.am index 883249c0b..64cceec47 100644 --- a/Makefile.am +++ b/Makefile.am @@ -638,7 +638,6 @@ nodist_weston_simple_dmabuf_drm_SOURCES = \ protocol/linux-dmabuf-unstable-v1-client-protocol.h weston_simple_dmabuf_drm_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_DRM_CLIENT_CFLAGS) weston_simple_dmabuf_drm_LDADD = $(SIMPLE_DMABUF_DRM_CLIENT_LIBS) $(LIBDRM_PLATFORM_LIBS) libshared.la -BUILT_SOURCES += protocol/linux-dmabuf-unstable-v1-client-protocol.h endif if BUILD_SIMPLE_DMABUF_V4L_CLIENT @@ -653,7 +652,6 @@ nodist_weston_simple_dmabuf_v4l_SOURCES = \ protocol/linux-dmabuf-unstable-v1-client-protocol.h weston_simple_dmabuf_v4l_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_V4L_CLIENT_CFLAGS) weston_simple_dmabuf_v4l_LDADD = $(SIMPLE_DMABUF_V4L_CLIENT_LIBS) libshared.la -BUILT_SOURCES += protocol/linux-dmabuf-unstable-v1-client-protocol.h endif noinst_LTLIBRARIES += libtoytoolkit.la @@ -894,7 +892,9 @@ BUILT_SOURCES += \ protocol/ivi-hmi-controller-protocol.c \ protocol/ivi-hmi-controller-client-protocol.h \ protocol/ivi-application-protocol.c \ - protocol/ivi-application-client-protocol.h + protocol/ivi-application-client-protocol.h \ + protocol/linux-dmabuf-unstable-v1-protocol.c \ + protocol/linux-dmabuf-unstable-v1-client-protocol.h westondatadir = $(datadir)/weston dist_westondata_DATA = \ From b57c6a0b92d1d8eaf2e43063ad3e3f71ea61afa7 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 5 Oct 2017 16:27:21 +0100 Subject: [PATCH 0261/1642] compositor-drm: Add shutting_down flag Does what it says on the box: is true when the compositor is shutting down. When we begin to use universal planes, we need divergent destruction paths. With universal planes, the drm_planes are created at backend initialisation time, and destroyed with the backend. However, without universal planes, we create per-output drm_planes to hold the primary/scanout and cursor planes, whose lifetime is tied to the output. We will use the new shutting_down flag to determine if output destruction is hot-unplug or compositor shutdown, and make a decision on whether or not to destroy the special planes. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 3eda70f30..6ac9626d9 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -184,6 +184,8 @@ struct drm_backend { int32_t cursor_height; uint32_t pageflip_timeout; + + bool shutting_down; }; struct drm_mode { @@ -3605,6 +3607,8 @@ drm_destroy(struct weston_compositor *ec) wl_event_source_remove(b->udev_drm_source); wl_event_source_remove(b->drm_source); + b->shutting_down = true; + destroy_sprites(b); weston_compositor_shutdown(ec); From 7b2ddacb510aef6cc4c970ad1e930bb4f2d2baa1 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 11 Nov 2016 19:11:49 +0000 Subject: [PATCH 0262/1642] compositor-drm: Introduce drm_output_state structure Currently this doesn't actually really do anything, but will be used in the future to track the state for both modeset and repaint requests. Completion of the request gives us a single request-completion path for both pageflip and vblank events. This merges the timing paths for scanout and plane-but-but-atomic-plane content. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 357 +++++++++++++++++++++++++++++++------ 1 file changed, 300 insertions(+), 57 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 6ac9626d9..e123fe503 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -139,6 +139,22 @@ struct drm_property_info { struct drm_property_enum_info *enum_values; /**< array of enum values */ }; +/** + * Mode for drm_output_state_duplicate. + */ +enum drm_output_state_duplicate_mode { + DRM_OUTPUT_STATE_CLEAR_PLANES, /**< reset all planes to off */ + DRM_OUTPUT_STATE_PRESERVE_PLANES, /**< preserve plane state */ +}; + +/** + * Mode for drm_pending_state_apply and co. + */ +enum drm_state_apply_mode { + DRM_STATE_APPLY_SYNC, /**< state fully processed */ + DRM_STATE_APPLY_ASYNC, /**< state pending event delivery */ +}; + struct drm_backend { struct weston_backend base; struct weston_compositor *compositor; @@ -235,6 +251,23 @@ struct drm_edid { */ struct drm_pending_state { struct drm_backend *backend; + struct wl_list output_list; +}; + +/* + * Output state holds the dynamic state for one Weston output, i.e. a KMS CRTC, + * plus >= 1 each of encoder/connector/plane. Since everything but the planes + * is currently statically assigned per-output, we mainly use this to track + * plane state. + * + * pending_state is set when the output state is owned by a pending_state, + * i.e. when it is being constructed and has not yet been applied. When the + * output state has been applied, the owning pending_state is freed. + */ +struct drm_output_state { + struct drm_pending_state *pending_state; + struct drm_output *output; + struct wl_list link; }; /** @@ -328,6 +361,12 @@ struct drm_output { * repaint is flushed. */ struct drm_fb *fb_pending; + /* The last state submitted to the kernel for this CRTC. */ + struct drm_output_state *state_cur; + /* The previously-submitted state, where the hardware has not + * yet acknowledged completion of state_cur. */ + struct drm_output_state *state_last; + struct drm_fb *dumb[2]; pixman_image_t *image[2]; int current_image; @@ -602,6 +641,9 @@ drm_output_set_cursor(struct drm_output *output); static void drm_output_update_msc(struct drm_output *output, unsigned int seq); +static void +drm_output_destroy(struct weston_output *output_base); + static int drm_plane_crtc_supported(struct drm_output *output, struct drm_plane *plane) { @@ -902,11 +944,69 @@ drm_fb_unref(struct drm_fb *fb) } } -static int -drm_view_transform_supported(struct weston_view *ev) +/** + * Allocate a new, empty drm_output_state. This should not generally be used + * in the repaint cycle; see drm_output_state_duplicate. + */ +static struct drm_output_state * +drm_output_state_alloc(struct drm_output *output, + struct drm_pending_state *pending_state) { - return !ev->transform.enabled || - (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); + struct drm_output_state *state = zalloc(sizeof(*state)); + + assert(state); + state->output = output; + state->pending_state = pending_state; + if (pending_state) + wl_list_insert(&pending_state->output_list, &state->link); + else + wl_list_init(&state->link); + + return state; +} + +/** + * Duplicate an existing drm_output_state into a new one. This is generally + * used during the repaint cycle, to capture the existing state of an output + * and modify it to create a new state to be used. + * + * The mode determines whether the output will be reset to an a blank state, + * or an exact mirror of the current state. + */ +static struct drm_output_state * +drm_output_state_duplicate(struct drm_output_state *src, + struct drm_pending_state *pending_state, + enum drm_output_state_duplicate_mode plane_mode) +{ + struct drm_output_state *dst = malloc(sizeof(*dst)); + + assert(dst); + + /* Copy the whole structure, then individually modify the + * pending_state, as well as the list link into our pending + * state. */ + *dst = *src; + + dst->pending_state = pending_state; + if (pending_state) + wl_list_insert(&pending_state->output_list, &dst->link); + else + wl_list_init(&dst->link); + + return dst; +} + +/** + * Free an unused drm_output_state. + */ +static void +drm_output_state_free(struct drm_output_state *state) +{ + if (!state) + return; + + wl_list_remove(&state->link); + free(state); } /** @@ -928,6 +1028,7 @@ drm_pending_state_alloc(struct drm_backend *backend) return NULL; ret->backend = backend; + wl_list_init(&ret->output_list); return ret; } @@ -935,19 +1036,122 @@ drm_pending_state_alloc(struct drm_backend *backend) /** * Free a drm_pending_state structure * - * Frees a pending_state structure. + * Frees a pending_state structure, as well as any output_states connected + * to this pending state. * * @param pending_state Pending state structure to free */ static void drm_pending_state_free(struct drm_pending_state *pending_state) { + struct drm_output_state *output_state, *tmp; + if (!pending_state) return; + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, + link) { + drm_output_state_free(output_state); + } + free(pending_state); } +/** + * Find an output state in a pending state + * + * Given a pending_state structure, find the output_state for a particular + * output. + * + * @param pending_state Pending state structure to search + * @param output Output to find state for + * @returns Output state if present, or NULL if not + */ +static struct drm_output_state * +drm_pending_state_get_output(struct drm_pending_state *pending_state, + struct drm_output *output) +{ + struct drm_output_state *output_state; + + wl_list_for_each(output_state, &pending_state->output_list, link) { + if (output_state->output == output) + return output_state; + } + + return NULL; +} + +/** + * Mark a drm_output_state (the output's last state) as complete. This handles + * any post-completion actions such as updating the repaint timer, disabling the + * output, and finally freeing the state. + */ +static void +drm_output_update_complete(struct drm_output *output, uint32_t flags, + unsigned int sec, unsigned int usec) +{ + struct timespec ts; + + /* Stop the pageflip timer instead of rearming it here */ + if (output->pageflip_timer) + wl_event_source_timer_update(output->pageflip_timer, 0); + + drm_output_state_free(output->state_last); + output->state_last = NULL; + + if (output->destroy_pending) { + drm_output_destroy(&output->base); + return; + } else if (output->disable_pending) { + weston_output_disable(&output->base); + output->disable_pending = 0; + return; + } + + ts.tv_sec = sec; + ts.tv_nsec = usec * 1000; + weston_output_finish_frame(&output->base, &ts, flags); + + /* We can't call this from frame_notify, because the output's + * repaint needed flag is cleared just after that */ + if (output->recorder) + weston_output_schedule_repaint(&output->base); +} + +/** + * Mark an output state as current on the output, i.e. it has been + * submitted to the kernel. The mode argument determines whether this + * update will be applied synchronously (e.g. when calling drmModeSetCrtc), + * or asynchronously (in which case we wait for events to complete). + */ +static void +drm_output_assign_state(struct drm_output_state *state, + enum drm_state_apply_mode mode) +{ + struct drm_output *output = state->output; + + assert(!output->state_last); + + if (mode == DRM_STATE_APPLY_ASYNC) + output->state_last = output->state_cur; + else + drm_output_state_free(output->state_cur); + + wl_list_remove(&state->link); + wl_list_init(&state->link); + state->pending_state = NULL; + + output->state_cur = state; +} + + +static int +drm_view_transform_supported(struct weston_view *ev) +{ + return !ev->transform.enabled || + (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); +} + static uint32_t drm_output_check_scanout_format(struct drm_output *output, struct weston_surface *es, struct gbm_bo *bo) @@ -979,9 +1183,10 @@ drm_output_check_scanout_format(struct drm_output *output, } static struct weston_plane * -drm_output_prepare_scanout_view(struct drm_output *output, +drm_output_prepare_scanout_view(struct drm_output_state *output_state, struct weston_view *ev) { + struct drm_output *output = output_state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; @@ -1048,8 +1253,9 @@ drm_output_prepare_scanout_view(struct drm_output *output, } static struct drm_fb * -drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) +drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) { + struct drm_output *output = state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); struct gbm_bo *bo; struct drm_fb *ret; @@ -1075,8 +1281,10 @@ drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) } static struct drm_fb * -drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) +drm_output_render_pixman(struct drm_output_state *state, + pixman_region32_t *damage) { + struct drm_output *output = state->output; struct weston_compositor *ec = output->base.compositor; pixman_region32_t total_damage, previous_damage; @@ -1102,8 +1310,9 @@ drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) } static void -drm_output_render(struct drm_output *output, pixman_region32_t *damage) +drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) { + struct drm_output *output = state->output; struct weston_compositor *c = output->base.compositor; struct drm_backend *b = to_drm_backend(c); struct drm_fb *fb; @@ -1114,9 +1323,9 @@ drm_output_render(struct drm_output *output, pixman_region32_t *damage) return; if (b->use_pixman) - fb = drm_output_render_pixman(output, damage); + fb = drm_output_render_pixman(state, damage); else - fb = drm_output_render_gl(output, damage); + fb = drm_output_render_gl(state, damage); if (!fb) return; @@ -1178,6 +1387,8 @@ drm_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, void *repaint_data) { + struct drm_pending_state *pending_state = repaint_data; + struct drm_output_state *state = NULL; struct drm_output *output = to_drm_output(output_base); struct drm_backend *backend = to_drm_backend(output->base.compositor); @@ -1186,7 +1397,18 @@ drm_output_repaint(struct weston_output *output_base, int ret = 0; if (output->disable_pending || output->destroy_pending) - return -1; + goto err; + + assert(!output->state_last); + + /* If planes have been disabled in the core, we might not have + * hit assign_planes at all, so might not have valid output state + * here. */ + state = drm_pending_state_get_output(pending_state, output); + if (!state) + state = drm_output_state_duplicate(output->state_cur, + pending_state, + DRM_OUTPUT_STATE_CLEAR_PLANES); assert(!output->fb_last); @@ -1200,9 +1422,9 @@ drm_output_repaint(struct weston_output *output_base, output->cursor_plane.y = INT32_MIN; } - drm_output_render(output, damage); + drm_output_render(state, damage); if (!output->fb_pending) - return -1; + goto err; mode = container_of(output->base.current_mode, struct drm_mode, base); if (output->state_invalid || !output->fb_current || @@ -1213,7 +1435,7 @@ drm_output_repaint(struct weston_output *output_base, &mode->mode_info); if (ret) { weston_log("set mode failed: %m\n"); - goto err_pageflip; + goto err; } output_base->set_dpms(output_base, WESTON_DPMS_ON); @@ -1224,7 +1446,7 @@ drm_output_repaint(struct weston_output *output_base, output->fb_pending->fb_id, DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { weston_log("queueing pageflip failed: %m\n"); - goto err_pageflip; + goto err; } output->fb_last = output->fb_current; @@ -1292,12 +1514,13 @@ drm_output_repaint(struct weston_output *output_base, return 0; -err_pageflip: +err: output->cursor_view = NULL; if (output->fb_pending) { drm_fb_unref(output->fb_pending); output->fb_pending = NULL; } + drm_output_state_free(state); return -1; } @@ -1306,6 +1529,8 @@ static void drm_output_start_repaint_loop(struct weston_output *output_base) { struct drm_output *output = to_drm_output(output_base); + struct drm_pending_state *pending_state = NULL; + struct drm_output_state *state; struct drm_backend *backend = to_drm_backend(output_base->compositor); uint32_t fb_id; @@ -1366,6 +1591,11 @@ drm_output_start_repaint_loop(struct weston_output *output_base) assert(!output->page_flip_pending); assert(!output->fb_last); + assert(!output->state_last); + + pending_state = drm_pending_state_alloc(backend); + state = drm_output_state_duplicate(output->state_cur, pending_state, + DRM_OUTPUT_STATE_PRESERVE_PLANES); if (drmModePageFlip(backend->drm.fd, output->crtc_id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { @@ -1380,9 +1610,14 @@ drm_output_start_repaint_loop(struct weston_output *output_base) output->fb_last = drm_fb_ref(output->fb_current); output->page_flip_pending = 1; + drm_output_assign_state(state, DRM_STATE_APPLY_ASYNC); + drm_pending_state_free(pending_state); + return; finish_frame: + drm_pending_state_free(pending_state); + /* if we cannot page-flip, immediately finish frame */ weston_output_finish_frame(output_base, NULL, WP_PRESENTATION_FEEDBACK_INVALID); @@ -1405,7 +1640,6 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, { struct drm_plane *s = (struct drm_plane *)data; struct drm_output *output = s->output; - struct timespec ts; uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; @@ -1417,26 +1651,17 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, drm_fb_unref(s->fb_last); s->fb_last = NULL; - if (!output->page_flip_pending && !output->vblank_pending) { - /* Stop the pageflip timer instead of rearming it here */ - if (output->pageflip_timer) - wl_event_source_timer_update(output->pageflip_timer, 0); + if (output->page_flip_pending || output->vblank_pending) + return; - ts.tv_sec = sec; - ts.tv_nsec = usec * 1000; - weston_output_finish_frame(&output->base, &ts, flags); - } + drm_output_update_complete(output, flags, sec, usec); } -static void -drm_output_destroy(struct weston_output *base); - static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { struct drm_output *output = data; - struct timespec ts; uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; @@ -1449,30 +1674,18 @@ page_flip_handler(int fd, unsigned int frame, drm_fb_unref(output->fb_last); output->fb_last = NULL; - if (output->destroy_pending) - drm_output_destroy(&output->base); - else if (output->disable_pending) - weston_output_disable(&output->base); - else if (!output->vblank_pending) { - /* Stop the pageflip timer instead of rearming it here */ - if (output->pageflip_timer) - wl_event_source_timer_update(output->pageflip_timer, 0); - - ts.tv_sec = sec; - ts.tv_nsec = usec * 1000; - weston_output_finish_frame(&output->base, &ts, flags); - - /* We can't call this from frame_notify, because the output's - * repaint needed flag is cleared just after that */ - if (output->recorder) - weston_output_schedule_repaint(&output->base); - } + if (output->vblank_pending) + return; + + drm_output_update_complete(output, flags, sec, usec); } /** * Begin a new repaint cycle * - * Called by the core compositor at the beginning of a repaint cycle. + * Called by the core compositor at the beginning of a repaint cycle. Creates + * a new pending_state structure to own any output state created by individual + * output repaint functions until the repaint is flushed or cancelled. */ static void * drm_repaint_begin(struct weston_compositor *compositor) @@ -1490,13 +1703,22 @@ drm_repaint_begin(struct weston_compositor *compositor) * Flush a repaint set * * Called by the core compositor when a repaint cycle has been completed - * and should be flushed. + * and should be flushed. Frees the pending state, transitioning ownership + * of the output state from the pending state, to the update itself. When + * the update completes (see drm_output_update_complete), the output + * state will be freed. */ static void drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) { struct drm_backend *b = to_drm_backend(compositor); struct drm_pending_state *pending_state = repaint_data; + struct drm_output_state *output_state, *tmp; + + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, + link) { + drm_output_assign_state(output_state, DRM_STATE_APPLY_ASYNC); + } drm_pending_state_free(pending_state); b->repaint_data = NULL; @@ -1548,9 +1770,10 @@ drm_output_check_plane_format(struct drm_plane *p, } static struct weston_plane * -drm_output_prepare_overlay_view(struct drm_output *output, +drm_output_prepare_overlay_view(struct drm_output_state *output_state, struct weston_view *ev) { + struct drm_output *output = output_state->output; struct weston_compositor *ec = output->base.compositor; struct drm_backend *b = to_drm_backend(ec); struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; @@ -1734,9 +1957,10 @@ drm_output_prepare_overlay_view(struct drm_output *output, } static struct weston_plane * -drm_output_prepare_cursor_view(struct drm_output *output, +drm_output_prepare_cursor_view(struct drm_output_state *output_state, struct weston_view *ev) { + struct drm_output *output = output_state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; struct wl_shm_buffer *shmbuf; @@ -1867,12 +2091,19 @@ static void drm_assign_planes(struct weston_output *output_base, void *repaint_data) { struct drm_backend *b = to_drm_backend(output_base->compositor); + struct drm_pending_state *pending_state = repaint_data; struct drm_output *output = to_drm_output(output_base); + struct drm_output_state *state; struct weston_view *ev, *next; pixman_region32_t overlap, surface_overlap; struct weston_plane *primary, *next_plane; bool picked_scanout = false; + assert(!output->state_last); + state = drm_output_state_duplicate(output->state_cur, + pending_state, + DRM_OUTPUT_STATE_CLEAR_PLANES); + /* * Find a surface for each sprite in the output using some heuristics: * 1) size @@ -1921,19 +2152,19 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) if (pixman_region32_not_empty(&surface_overlap) || picked_scanout) next_plane = primary; if (next_plane == NULL) - next_plane = drm_output_prepare_cursor_view(output, ev); + next_plane = drm_output_prepare_cursor_view(state, ev); /* If a higher-stacked view already got assigned to scanout, it's incorrect to * assign a subsequent (lower-stacked) view to scanout. */ if (next_plane == NULL) { - next_plane = drm_output_prepare_scanout_view(output, ev); + next_plane = drm_output_prepare_scanout_view(state, ev); if (next_plane) picked_scanout = true; } if (next_plane == NULL) - next_plane = drm_output_prepare_overlay_view(output, ev); + next_plane = drm_output_prepare_overlay_view(state, ev); if (next_plane == NULL) next_plane = primary; @@ -3260,7 +3491,7 @@ drm_output_destroy(struct weston_output *base) struct drm_mode *drm_mode, *next; drmModeCrtcPtr origcrtc = output->original_crtc; - if (output->page_flip_pending) { + if (output->page_flip_pending || output->vblank_pending) { output->destroy_pending = 1; weston_log("destroy output while page flip pending\n"); return; @@ -3295,6 +3526,9 @@ drm_output_destroy(struct weston_output *base) if (output->backlight) backlight_destroy(output->backlight); + assert(!output->state_last); + drm_output_state_free(output->state_cur); + free(output); } @@ -3304,7 +3538,7 @@ drm_output_disable(struct weston_output *base) struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); - if (output->page_flip_pending) { + if (output->page_flip_pending || output->vblank_pending) { output->disable_pending = 1; return -1; } @@ -3312,12 +3546,19 @@ drm_output_disable(struct weston_output *base) if (output->base.enabled) drm_output_deinit(&output->base); + assert(!output->fb_last); + assert(!output->fb_current); + assert(!output->fb_pending); + output->disable_pending = 0; weston_log("Disabling output %s\n", output->base.name); drmModeSetCrtc(b->drm.fd, output->crtc_id, 0, 0, 0, 0, 0, NULL); + drm_output_state_free(output->state_cur); + output->state_cur = drm_output_state_alloc(output, NULL); + return 0; } @@ -3404,6 +3645,8 @@ create_output_for_connector(struct drm_backend *b, output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) output->base.connection_internal = true; + output->state_cur = drm_output_state_alloc(output, NULL); + output->base.mm_width = output->connector->mmWidth; output->base.mm_height = output->connector->mmHeight; From bc15f684f23bf3c8019bfe541d0ad1f124179ed0 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 14 Nov 2016 16:57:01 +0000 Subject: [PATCH 0263/1642] compositor-drm: Introduce drm_plane_state structure Track dynamic plane state (CRTC, FB, position) in separate structures, rather than as part of the plane. This will make it easier to handle state management later, and much more closely tracks what the kernel does with atomic modesets. The fb_last pointer previously used in drm_plane now becomes part of output->state_last, and is not directly visible from the plane itself. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 348 ++++++++++++++++++++++++++++++------- 1 file changed, 283 insertions(+), 65 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index e123fe503..6ea41ae80 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -268,6 +268,31 @@ struct drm_output_state { struct drm_pending_state *pending_state; struct drm_output *output; struct wl_list link; + struct wl_list plane_list; +}; + +/** + * Plane state holds the dynamic state for a plane: where it is positioned, + * and which buffer it is currently displaying. + * + * The plane state is owned by an output state, except when setting an initial + * state. See drm_output_state for notes on state object lifetime. + */ +struct drm_plane_state { + struct drm_plane *plane; + struct drm_output *output; + struct drm_output_state *output_state; + + struct drm_fb *fb; + + int32_t src_x, src_y; + uint32_t src_w, src_h; + int32_t dest_x, dest_y; + uint32_t dest_w, dest_h; + + bool complete; + + struct wl_list link; /* drm_output_state::plane_list */ }; /** @@ -286,11 +311,8 @@ struct drm_output_state { * are referred to as 'sprites'. */ struct drm_plane { - struct wl_list link; - struct weston_plane base; - struct drm_output *output; struct drm_backend *backend; enum wdrm_plane_type type; @@ -301,19 +323,10 @@ struct drm_plane { struct drm_property_info props[WDRM_PLANE__COUNT]; - /* The last framebuffer submitted to the kernel for this plane. */ - struct drm_fb *fb_current; - /* The previously-submitted framebuffer, where the hardware has not - * yet acknowledged display of fb_current. */ - struct drm_fb *fb_last; - /* Framebuffer we are going to submit to the kernel when the current - * repaint is flushed. */ - struct drm_fb *fb_pending; + /* The last state submitted to the kernel for this plane. */ + struct drm_plane_state *state_cur; - int32_t src_x, src_y; - uint32_t src_w, src_h; - uint32_t dest_x, dest_y; - uint32_t dest_w, dest_h; + struct wl_list link; uint32_t formats[]; }; @@ -944,6 +957,141 @@ drm_fb_unref(struct drm_fb *fb) } } +/** + * Allocate a new, empty, plane state. + */ +static struct drm_plane_state * +drm_plane_state_alloc(struct drm_output_state *state_output, + struct drm_plane *plane) +{ + struct drm_plane_state *state = zalloc(sizeof(*state)); + + assert(state); + state->output_state = state_output; + state->plane = plane; + + /* Here we only add the plane state to the desired link, and not + * set the member. Having an output pointer set means that the + * plane will be displayed on the output; this won't be the case + * when we go to disable a plane. In this case, it must be part of + * the commit (and thus the output state), but the member must be + * NULL, as it will not be on any output when the state takes + * effect. + */ + if (state_output) + wl_list_insert(&state_output->plane_list, &state->link); + else + wl_list_init(&state->link); + + return state; +} + +/** + * Free an existing plane state. As a special case, the state will not + * normally be freed if it is the current state; see drm_plane_set_state. + */ +static void +drm_plane_state_free(struct drm_plane_state *state, bool force) +{ + if (!state) + return; + + wl_list_remove(&state->link); + wl_list_init(&state->link); + state->output_state = NULL; + + if (force || state != state->plane->state_cur) { + drm_fb_unref(state->fb); + free(state); + } +} + +/** + * Duplicate an existing plane state into a new plane state, storing it within + * the given output state. If the output state already contains a plane state + * for the drm_plane referenced by 'src', that plane state is freed first. + */ +static struct drm_plane_state * +drm_plane_state_duplicate(struct drm_output_state *state_output, + struct drm_plane_state *src) +{ + struct drm_plane_state *dst = malloc(sizeof(*dst)); + struct drm_plane_state *old, *tmp; + + assert(src); + assert(dst); + *dst = *src; + wl_list_init(&dst->link); + + wl_list_for_each_safe(old, tmp, &state_output->plane_list, link) { + /* Duplicating a plane state into the same output state, so + * it can replace itself with an identical copy of itself, + * makes no sense. */ + assert(old != src); + if (old->plane == dst->plane) + drm_plane_state_free(old, false); + } + + wl_list_insert(&state_output->plane_list, &dst->link); + if (src->fb) + dst->fb = drm_fb_ref(src->fb); + dst->output_state = state_output; + dst->complete = false; + + return dst; +} + +/** + * Remove a plane state from an output state; if the plane was previously + * enabled, then replace it with a disabling state. This ensures that the + * output state was untouched from it was before the plane state was + * modified by the caller of this function. + * + * This is required as drm_output_state_get_plane may either allocate a + * new plane state, in which case this function will just perform a matching + * drm_plane_state_free, or it may instead repurpose an existing disabling + * state (if the plane was previously active), in which case this function + * will reset it. + */ +static void +drm_plane_state_put_back(struct drm_plane_state *state) +{ + struct drm_output_state *state_output; + struct drm_plane *plane; + + if (!state) + return; + + state_output = state->output_state; + plane = state->plane; + drm_plane_state_free(state, false); + + /* Plane was previously disabled; no need to keep this temporary + * state around. */ + if (!plane->state_cur->fb) + return; + + (void) drm_plane_state_alloc(state_output, plane); +} + +/** + * Return a plane state from a drm_output_state, either existing or + * freshly allocated. + */ +static struct drm_plane_state * +drm_output_state_get_plane(struct drm_output_state *state_output, + struct drm_plane *plane) +{ + struct drm_plane_state *ps; + + wl_list_for_each(ps, &state_output->plane_list, link) { + if (ps->plane == plane) + return ps; + } + + return drm_plane_state_alloc(state_output, plane); +} + /** * Allocate a new, empty drm_output_state. This should not generally be used * in the repaint cycle; see drm_output_state_duplicate. @@ -962,6 +1110,8 @@ drm_output_state_alloc(struct drm_output *output, else wl_list_init(&state->link); + wl_list_init(&state->plane_list); + return state; } @@ -979,6 +1129,7 @@ drm_output_state_duplicate(struct drm_output_state *src, enum drm_output_state_duplicate_mode plane_mode) { struct drm_output_state *dst = malloc(sizeof(*dst)); + struct drm_plane_state *ps; assert(dst); @@ -993,6 +1144,20 @@ drm_output_state_duplicate(struct drm_output_state *src, else wl_list_init(&dst->link); + wl_list_init(&dst->plane_list); + + wl_list_for_each(ps, &src->plane_list, link) { + /* Don't carry planes which are now disabled; these should be + * free for other outputs to reuse. */ + if (!ps->output) + continue; + + if (plane_mode == DRM_OUTPUT_STATE_CLEAR_PLANES) + (void) drm_plane_state_alloc(dst, ps->plane); + else + (void) drm_plane_state_duplicate(dst, ps); + } + return dst; } @@ -1002,10 +1167,16 @@ drm_output_state_duplicate(struct drm_output_state *src, static void drm_output_state_free(struct drm_output_state *state) { + struct drm_plane_state *ps, *next; + if (!state) return; + wl_list_for_each_safe(ps, next, &state->plane_list, link) + drm_plane_state_free(ps, false); + wl_list_remove(&state->link); + free(state); } @@ -1090,12 +1261,16 @@ static void drm_output_update_complete(struct drm_output *output, uint32_t flags, unsigned int sec, unsigned int usec) { + struct drm_plane_state *ps; struct timespec ts; /* Stop the pageflip timer instead of rearming it here */ if (output->pageflip_timer) wl_event_source_timer_update(output->pageflip_timer, 0); + wl_list_for_each(ps, &output->state_cur->plane_list, link) + ps->complete = true; + drm_output_state_free(output->state_last); output->state_last = NULL; @@ -1129,6 +1304,7 @@ drm_output_assign_state(struct drm_output_state *state, enum drm_state_apply_mode mode) { struct drm_output *output = state->output; + struct drm_plane_state *plane_state; assert(!output->state_last); @@ -1142,6 +1318,26 @@ drm_output_assign_state(struct drm_output_state *state, state->pending_state = NULL; output->state_cur = state; + + /* Replace state_cur on each affected plane with the new state, being + * careful to dispose of orphaned (but only orphaned) previous state. + * If the previous state is not orphaned (still has an output_state + * attached), it will be disposed of by freeing the output_state. */ + wl_list_for_each(plane_state, &state->plane_list, link) { + struct drm_plane *plane = plane_state->plane; + + if (plane->state_cur && !plane->state_cur->output_state) + drm_plane_state_free(plane->state_cur, true); + plane->state_cur = plane_state; + + if (mode != DRM_STATE_APPLY_ASYNC) { + plane_state->complete = true; + continue; + } + + if (plane->type == WDRM_PLANE_TYPE_OVERLAY) + output->vblank_pending++; + } } @@ -1392,6 +1588,7 @@ drm_output_repaint(struct weston_output *output_base, struct drm_output *output = to_drm_output(output_base); struct drm_backend *backend = to_drm_backend(output->base.compositor); + struct drm_plane_state *ps; struct drm_plane *p; struct drm_mode *mode; int ret = 0; @@ -1465,29 +1662,33 @@ drm_output_repaint(struct weston_output *output_base, /* * Now, update all the sprite surfaces */ - wl_list_for_each(p, &backend->plane_list, link) { + wl_list_for_each(ps, &state->plane_list, link) { uint32_t flags = 0, fb_id = 0; drmVBlank vbl = { .request.type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT, .request.sequence = 1, }; + p = ps->plane; if (p->type != WDRM_PLANE_TYPE_OVERLAY) continue; - if ((!p->fb_current && !p->fb_pending) || - !drm_plane_crtc_supported(output, p)) - continue; + assert(p->state_cur->complete); + assert(!!p->state_cur->output == !!p->state_cur->fb); + assert(!p->state_cur->output || p->state_cur->output == output); + assert(!ps->complete); + assert(!ps->output || ps->output == output); + assert(!!ps->output == !!ps->fb); - if (p->fb_pending && !backend->sprites_hidden) - fb_id = p->fb_pending->fb_id; + if (ps->fb && !backend->sprites_hidden) + fb_id = ps->fb->fb_id; ret = drmModeSetPlane(backend->drm.fd, p->plane_id, output->crtc_id, fb_id, flags, - p->dest_x, p->dest_y, - p->dest_w, p->dest_h, - p->src_x, p->src_y, - p->src_w, p->src_h); + ps->dest_x, ps->dest_y, + ps->dest_w, ps->dest_h, + ps->src_x, ps->src_y, + ps->src_w, ps->src_h); if (ret) weston_log("setplane failed: %d: %s\n", ret, strerror(errno)); @@ -1498,18 +1699,12 @@ drm_output_repaint(struct weston_output *output_base, * Queue a vblank signal so we know when the surface * becomes active on the display or has been replaced. */ - vbl.request.signal = (unsigned long) p; + vbl.request.signal = (unsigned long) ps; ret = drmWaitVBlank(backend->drm.fd, &vbl); if (ret) { weston_log("vblank event request failed: %d: %s\n", ret, strerror(errno)); } - - p->output = output; - p->fb_last = p->fb_current; - p->fb_current = p->fb_pending; - p->fb_pending = NULL; - output->vblank_pending++; } return 0; @@ -1531,6 +1726,7 @@ drm_output_start_repaint_loop(struct weston_output *output_base) struct drm_output *output = to_drm_output(output_base); struct drm_pending_state *pending_state = NULL; struct drm_output_state *state; + struct drm_plane_state *plane_state; struct drm_backend *backend = to_drm_backend(output_base->compositor); uint32_t fb_id; @@ -1610,6 +1806,17 @@ drm_output_start_repaint_loop(struct weston_output *output_base) output->fb_last = drm_fb_ref(output->fb_current); output->page_flip_pending = 1; + wl_list_for_each(plane_state, &state->plane_list, link) { + if (plane_state->plane->type != WDRM_PLANE_TYPE_OVERLAY) + continue; + + vbl.request.type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT; + vbl.request.type |= drm_waitvblank_pipe(output); + vbl.request.sequence = 1; + vbl.request.signal = (unsigned long) plane_state; + drmWaitVBlank(backend->drm.fd, &vbl); + } + drm_output_assign_state(state, DRM_STATE_APPLY_ASYNC); drm_pending_state_free(pending_state); @@ -1638,8 +1845,9 @@ static void vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { - struct drm_plane *s = (struct drm_plane *)data; - struct drm_output *output = s->output; + struct drm_plane_state *ps = (struct drm_plane_state *) data; + struct drm_output_state *os = ps->output_state; + struct drm_output *output = os->output; uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; @@ -1647,9 +1855,7 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, output->vblank_pending--; assert(output->vblank_pending >= 0); - assert(s->fb_last || s->fb_current); - drm_fb_unref(s->fb_last); - s->fb_last = NULL; + assert(ps->fb); if (output->page_flip_pending || output->vblank_pending) return; @@ -1779,8 +1985,8 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; struct wl_resource *buffer_resource; struct drm_plane *p; + struct drm_plane_state *state = NULL; struct linux_dmabuf_buffer *dmabuf; - int found = 0; struct gbm_bo *bo; pixman_region32_t dest_rect, src_rect; pixman_box32_t *box, tbox; @@ -1821,14 +2027,22 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, if (!drm_plane_crtc_supported(output, p)) continue; - if (!p->fb_pending) { - found = 1; - break; + if (!p->state_cur->complete) + continue; + if (p->state_cur->output && p->state_cur->output != output) + continue; + + state = drm_output_state_get_plane(output_state, p); + if (state->fb) { + state = NULL; + continue; } + + break; } /* No sprites available */ - if (!found) + if (!state) return NULL; if ((dmabuf = linux_dmabuf_buffer_get(buffer_resource))) { @@ -1865,28 +2079,26 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_FD, &gbm_dmabuf, GBM_BO_USE_SCANOUT); #else - return NULL; + goto err; #endif } else { bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, buffer_resource, GBM_BO_USE_SCANOUT); } if (!bo) - return NULL; + goto err; format = drm_output_check_plane_format(p, ev, bo); - if (format == 0) { - gbm_bo_destroy(bo); - return NULL; - } + if (format == 0) + goto err; - p->fb_pending = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); - if (!p->fb_pending) { - gbm_bo_destroy(bo); - return NULL; - } + state->fb = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); + if (!state->fb) + goto err; - drm_fb_set_buffer(p->fb_pending, ev->surface->buffer_ref.buffer); + drm_fb_set_buffer(state->fb, ev->surface->buffer_ref.buffer); + + state->output = output; box = pixman_region32_extents(&ev->transform.boundingbox); p->base.x = box->x1; @@ -1907,10 +2119,10 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, output->base.transform, output->base.current_scale, *box); - p->dest_x = tbox.x1; - p->dest_y = tbox.y1; - p->dest_w = tbox.x2 - tbox.x1; - p->dest_h = tbox.y2 - tbox.y1; + state->dest_x = tbox.x1; + state->dest_y = tbox.y1; + state->dest_w = tbox.x2 - tbox.x1; + state->dest_h = tbox.y2 - tbox.y1; pixman_region32_fini(&dest_rect); pixman_region32_init(&src_rect); @@ -1947,13 +2159,19 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, viewport->buffer.scale, tbox); - p->src_x = tbox.x1 << 8; - p->src_y = tbox.y1 << 8; - p->src_w = (tbox.x2 - tbox.x1) << 8; - p->src_h = (tbox.y2 - tbox.y1) << 8; + state->src_x = tbox.x1 << 8; + state->src_y = tbox.y1 << 8; + state->src_w = (tbox.x2 - tbox.x1) << 8; + state->src_h = (tbox.y2 - tbox.y1) << 8; pixman_region32_fini(&src_rect); return &p->base; + +err: + drm_plane_state_put_back(state); + if (bo) + gbm_bo_destroy(bo); + return NULL; } static struct weston_plane * @@ -2500,6 +2718,8 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) plane->possible_crtcs = kplane->possible_crtcs; plane->plane_id = kplane->plane_id; plane->count_formats = kplane->count_formats; + plane->state_cur = drm_plane_state_alloc(NULL, plane); + plane->state_cur->complete = true; memcpy(plane->formats, kplane->formats, kplane->count_formats * sizeof(kplane->formats[0])); @@ -2537,10 +2757,8 @@ drm_plane_destroy(struct drm_plane *plane) { drmModeSetPlane(plane->backend->drm.fd, plane->plane_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - assert(!plane->fb_last); - assert(!plane->fb_pending); + drm_plane_state_free(plane->state_cur, true); drm_property_info_free(plane->props, WDRM_PLANE__COUNT); - drm_fb_unref(plane->fb_current); weston_plane_release(&plane->base); wl_list_remove(&plane->link); free(plane); From 5ff289a170624abf091b3f45d57ed7dcc93ea463 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Sat, 7 Oct 2017 12:59:02 +0100 Subject: [PATCH 0264/1642] compositor-drm: Introduce drm_plane_is_available Helper for the pattern of checking whether or not a plane can be used on an output during the current repaint cycle. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 6ea41ae80..403438398 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -657,9 +657,25 @@ drm_output_update_msc(struct drm_output *output, unsigned int seq); static void drm_output_destroy(struct weston_output *output_base); -static int -drm_plane_crtc_supported(struct drm_output *output, struct drm_plane *plane) +/** + * Returns true if the plane can be used on the given output for its current + * repaint cycle. + */ +static bool +drm_plane_is_available(struct drm_plane *plane, struct drm_output *output) { + assert(plane->state_cur); + + /* The plane still has a request not yet completed by the kernel. */ + if (!plane->state_cur->complete) + return false; + + /* The plane is still active on another output. */ + if (plane->state_cur->output && plane->state_cur->output != output) + return false; + + /* Check whether the plane can be used with this CRTC; possible_crtcs + * is a bitmask of CRTC indices (pipe), rather than CRTC object ID. */ return !!(plane->possible_crtcs & (1 << output->pipe)); } @@ -2024,12 +2040,7 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, if (p->type != WDRM_PLANE_TYPE_OVERLAY) continue; - if (!drm_plane_crtc_supported(output, p)) - continue; - - if (!p->state_cur->complete) - continue; - if (p->state_cur->output && p->state_cur->output != output) + if (!drm_plane_is_available(p, output)) continue; state = drm_output_state_get_plane(output_state, p); From 2ba17f4de525c5112040ada925098a7da5a58ca2 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 19 May 2015 20:02:41 +0100 Subject: [PATCH 0265/1642] compositor-drm: Use drm_plane for cursor plane Change the type of cursor_plane from a weston_plane (base tracking structure) to a drm_plane (wrapper containing additional DRM-specific details), and make it a dynamically-allocated pointer. Using the standard drm_plane allows us to reuse code which already deals with drm_planes, e.g. a common cleanup function. This patch introduces a 'special plane' helper, creating a drm_plane either from a real KMS plane when using universal planes, or a fake plane otherwise. Without universal planes, the cursor and primary planes are hidden from us; this helper allows us to pretend otherwise. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 459 ++++++++++++++++++++++++++++--------- 1 file changed, 354 insertions(+), 105 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 403438398..31377bd7b 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -355,7 +355,7 @@ struct drm_output { int disable_pending; struct drm_fb *gbm_cursor_fb[2]; - struct weston_plane cursor_plane; + struct drm_plane *cursor_plane; struct weston_view *cursor_view; int current_cursor; @@ -649,7 +649,7 @@ drm_property_info_free(struct drm_property_info *info, int num_props) } static void -drm_output_set_cursor(struct drm_output *output); +drm_output_set_cursor(struct drm_output_state *output_state); static void drm_output_update_msc(struct drm_output *output, unsigned int seq); @@ -1091,12 +1091,11 @@ drm_plane_state_put_back(struct drm_plane_state *state) } /** - * Return a plane state from a drm_output_state, either existing or - * freshly allocated. + * Return a plane state from a drm_output_state. */ static struct drm_plane_state * -drm_output_state_get_plane(struct drm_output_state *state_output, - struct drm_plane *plane) +drm_output_state_get_existing_plane(struct drm_output_state *state_output, + struct drm_plane *plane) { struct drm_plane_state *ps; @@ -1105,6 +1104,23 @@ drm_output_state_get_plane(struct drm_output_state *state_output, return ps; } + return NULL; +} + +/** + * Return a plane state from a drm_output_state, either existing or + * freshly allocated. + */ +static struct drm_plane_state * +drm_output_state_get_plane(struct drm_output_state *state_output, + struct drm_plane *plane) +{ + struct drm_plane_state *ps; + + ps = drm_output_state_get_existing_plane(state_output, plane); + if (ps) + return ps; + return drm_plane_state_alloc(state_output, plane); } @@ -1631,8 +1647,8 @@ drm_output_repaint(struct weston_output *output_base, */ if (output->base.disable_planes) { output->cursor_view = NULL; - output->cursor_plane.x = INT32_MIN; - output->cursor_plane.y = INT32_MIN; + output->cursor_plane->base.x = INT32_MIN; + output->cursor_plane->base.y = INT32_MIN; } drm_output_render(state, damage); @@ -1673,7 +1689,7 @@ drm_output_repaint(struct weston_output *output_base, wl_event_source_timer_update(output->pageflip_timer, backend->pageflip_timeout); - drm_output_set_cursor(output); + drm_output_set_cursor(state); /* * Now, update all the sprite surfaces @@ -2185,20 +2201,66 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, return NULL; } +/** + * Update the image for the current cursor surface + * + * @param b DRM backend structure + * @param bo GBM buffer object to write into + * @param ev View to use for cursor image + */ +static void +cursor_bo_update(struct drm_backend *b, struct gbm_bo *bo, + struct weston_view *ev) +{ + struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; + uint32_t buf[b->cursor_width * b->cursor_height]; + int32_t stride; + uint8_t *s; + int i; + + assert(buffer && buffer->shm_buffer); + assert(buffer->shm_buffer == wl_shm_buffer_get(buffer->resource)); + assert(ev->surface->width <= b->cursor_width); + assert(ev->surface->height <= b->cursor_height); + + memset(buf, 0, sizeof buf); + stride = wl_shm_buffer_get_stride(buffer->shm_buffer); + s = wl_shm_buffer_get_data(buffer->shm_buffer); + + wl_shm_buffer_begin_access(buffer->shm_buffer); + for (i = 0; i < ev->surface->height; i++) + memcpy(buf + i * b->cursor_width, + s + i * stride, + ev->surface->width * 4); + wl_shm_buffer_end_access(buffer->shm_buffer); + + if (gbm_bo_write(bo, buf, sizeof buf) < 0) + weston_log("failed update cursor: %m\n"); +} + static struct weston_plane * drm_output_prepare_cursor_view(struct drm_output_state *output_state, struct weston_view *ev) { struct drm_output *output = output_state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_plane *plane = output->cursor_plane; + struct drm_plane_state *plane_state; struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; struct wl_shm_buffer *shmbuf; + bool needs_update = false; float x, y; + if (!plane) + return NULL; + if (b->cursors_are_broken) return NULL; - if (output->cursor_view) + if (!plane->state_cur->complete) + return NULL; + + if (plane->state_cur->output && plane->state_cur->output != output) return NULL; /* Don't import buffers which span multiple outputs. */ @@ -2231,89 +2293,101 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, ev->surface->height > b->cursor_height) return NULL; - output->cursor_view = ev; - weston_view_to_global_float(ev, 0, 0, &x, &y); - output->cursor_plane.x = x; - output->cursor_plane.y = y; - - return &output->cursor_plane; -} - -/** - * Update the image for the current cursor surface - * - * @param b DRM backend structure - * @param bo GBM buffer object to write into - * @param ev View to use for cursor image - */ -static void -cursor_bo_update(struct drm_backend *b, struct gbm_bo *bo, - struct weston_view *ev) -{ - struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; - uint32_t buf[b->cursor_width * b->cursor_height]; - int32_t stride; - uint8_t *s; - int i; - - assert(buffer && buffer->shm_buffer); - assert(buffer->shm_buffer == wl_shm_buffer_get(buffer->resource)); - assert(ev->surface->width <= b->cursor_width); - assert(ev->surface->height <= b->cursor_height); + plane_state = + drm_output_state_get_plane(output_state, output->cursor_plane); - memset(buf, 0, sizeof buf); - stride = wl_shm_buffer_get_stride(buffer->shm_buffer); - s = wl_shm_buffer_get_data(buffer->shm_buffer); + if (plane_state && plane_state->fb) + return NULL; - wl_shm_buffer_begin_access(buffer->shm_buffer); - for (i = 0; i < ev->surface->height; i++) - memcpy(buf + i * b->cursor_width, - s + i * stride, - ev->surface->width * 4); - wl_shm_buffer_end_access(buffer->shm_buffer); + /* Since we're setting plane state up front, we need to work out + * whether or not we need to upload a new cursor. We can't use the + * plane damage, since the planes haven't actually been calculated + * yet: instead try to figure it out directly. KMS cursor planes are + * pretty unique here, in that they lie partway between a Weston plane + * (direct scanout) and a renderer. */ + if (ev != output->cursor_view || + pixman_region32_not_empty(&ev->surface->damage)) { + output->current_cursor++; + output->current_cursor = + output->current_cursor % + ARRAY_LENGTH(output->gbm_cursor_fb); + needs_update = true; + } - if (gbm_bo_write(bo, buf, sizeof buf) < 0) - weston_log("failed update cursor: %m\n"); + output->cursor_view = ev; + weston_view_to_global_float(ev, 0, 0, &x, &y); + plane->base.x = x; + plane->base.y = y; + + plane_state->fb = + drm_fb_ref(output->gbm_cursor_fb[output->current_cursor]); + plane_state->output = output; + plane_state->src_x = 0; + plane_state->src_y = 0; + plane_state->src_w = b->cursor_width << 16; + plane_state->src_h = b->cursor_height << 16; + plane_state->dest_x = (x - output->base.x) * output->base.current_scale; + plane_state->dest_y = (y - output->base.y) * output->base.current_scale; + plane_state->dest_w = b->cursor_width; + plane_state->dest_h = b->cursor_height; + + if (needs_update) + cursor_bo_update(b, plane_state->fb->bo, ev); + + return &plane->base; } static void -drm_output_set_cursor(struct drm_output *output) +drm_output_set_cursor(struct drm_output_state *output_state) { - struct weston_view *ev = output->cursor_view; + struct drm_output *output = output_state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_plane *plane = output->cursor_plane; + struct drm_plane_state *state; EGLint handle; struct gbm_bo *bo; - float x, y; - if (ev == NULL) { + if (!plane) + return; + + state = drm_output_state_get_existing_plane(output_state, plane); + if (!state) + return; + + if (!state->fb) { + pixman_region32_fini(&plane->base.damage); + pixman_region32_init(&plane->base.damage); drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); return; } - if (pixman_region32_not_empty(&output->cursor_plane.damage)) { - pixman_region32_fini(&output->cursor_plane.damage); - pixman_region32_init(&output->cursor_plane.damage); - output->current_cursor ^= 1; - bo = output->gbm_cursor_fb[output->current_cursor]->bo; + assert(state->fb == output->gbm_cursor_fb[output->current_cursor]); + assert(!plane->state_cur->output || plane->state_cur->output == output); - cursor_bo_update(b, bo, ev); + if (plane->state_cur->fb != state->fb) { + bo = state->fb->bo; handle = gbm_bo_get_handle(bo).s32; if (drmModeSetCursor(b->drm.fd, output->crtc_id, handle, - b->cursor_width, b->cursor_height)) { + b->cursor_width, b->cursor_height)) { weston_log("failed to set cursor: %m\n"); - b->cursors_are_broken = 1; + goto err; } } - x = (output->cursor_plane.x - output->base.x) * - output->base.current_scale; - y = (output->cursor_plane.y - output->base.y) * - output->base.current_scale; + pixman_region32_fini(&plane->base.damage); + pixman_region32_init(&plane->base.damage); - if (drmModeMoveCursor(b->drm.fd, output->crtc_id, x, y)) { + if (drmModeMoveCursor(b->drm.fd, output->crtc_id, + state->dest_x, state->dest_y)) { weston_log("failed to move cursor: %m\n"); - b->cursors_are_broken = 1; + goto err; } + + return; + +err: + b->cursors_are_broken = 1; + drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); } static void @@ -2323,6 +2397,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) struct drm_pending_state *pending_state = repaint_data; struct drm_output *output = to_drm_output(output_base); struct drm_output_state *state; + struct drm_plane_state *plane_state; struct weston_view *ev, *next; pixman_region32_t overlap, surface_overlap; struct weston_plane *primary, *next_plane; @@ -2349,10 +2424,6 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) pixman_region32_init(&overlap); primary = &output_base->compositor->primary_plane; - output->cursor_view = NULL; - output->cursor_plane.x = INT32_MIN; - output->cursor_plane.y = INT32_MIN; - wl_list_for_each_safe(ev, next, &output_base->compositor->view_list, link) { struct weston_surface *es = ev->surface; @@ -2405,7 +2476,8 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) &ev->transform.boundingbox); if (next_plane == primary || - next_plane == &output->cursor_plane) { + (output->cursor_plane && + next_plane == &output->cursor_plane->base)) { /* cursor plane involves a copy */ ev->psf_flags = 0; } else { @@ -2418,6 +2490,19 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) pixman_region32_fini(&surface_overlap); } pixman_region32_fini(&overlap); + + /* We rely on output->cursor_view being both an accurate reflection of + * the cursor plane's state, but also being maintained across repaints + * to avoid unnecessary damage uploads, per the comment in + * drm_output_prepare_cursor_view. In the event that we go from having + * a cursor view to not having a cursor view, we need to clear it. */ + if (output->cursor_view) { + plane_state = + drm_output_state_get_existing_plane(state, + output->cursor_plane); + if (!plane_state || !plane_state->fb) + output->cursor_view = NULL; + } } /** @@ -2685,19 +2770,30 @@ init_pixman(struct drm_backend *b) * Creates one drm_plane structure for a hardware plane, and initialises its * properties and formats. * + * In the absence of universal plane support, where KMS does not explicitly + * expose the primary and cursor planes to userspace, this may also create + * an 'internal' plane for internal management. + * * This function does not add the plane to the list of usable planes in Weston * itself; the caller is responsible for this. * * Call drm_plane_destroy to clean up the plane. * + * @sa drm_output_find_special_plane * @param b DRM compositor backend - * @param kplane DRM plane to create + * @param kplane DRM plane to create, or NULL if creating internal plane + * @param output Output to create internal plane for, or NULL + * @param type Type to use when creating internal plane, or invalid + * @param format Format to use for internal planes, or 0 */ static struct drm_plane * -drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) +drm_plane_create(struct drm_backend *b, const drmModePlane *kplane, + struct drm_output *output, enum wdrm_plane_type type, + uint32_t format) { struct drm_plane *plane; drmModeObjectProperties *props; + int num_formats = (kplane) ? kplane->count_formats : 1; static struct drm_property_enum_info plane_type_enums[] = { [WDRM_PLANE_TYPE_PRIMARY] = { @@ -2718,41 +2814,152 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) }, }; - plane = zalloc(sizeof(*plane) + ((sizeof(uint32_t)) * - kplane->count_formats)); + plane = zalloc(sizeof(*plane) + + (sizeof(uint32_t) * num_formats)); if (!plane) { weston_log("%s: out of memory\n", __func__); return NULL; } plane->backend = b; - plane->possible_crtcs = kplane->possible_crtcs; - plane->plane_id = kplane->plane_id; - plane->count_formats = kplane->count_formats; plane->state_cur = drm_plane_state_alloc(NULL, plane); plane->state_cur->complete = true; - memcpy(plane->formats, kplane->formats, - kplane->count_formats * sizeof(kplane->formats[0])); - props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id, - DRM_MODE_OBJECT_PLANE); - if (!props) { - weston_log("couldn't get plane properties\n"); - free(plane); - return NULL; + if (kplane) { + plane->possible_crtcs = kplane->possible_crtcs; + plane->plane_id = kplane->plane_id; + plane->count_formats = kplane->count_formats; + memcpy(plane->formats, kplane->formats, + kplane->count_formats * sizeof(kplane->formats[0])); + + props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id, + DRM_MODE_OBJECT_PLANE); + if (!props) { + weston_log("couldn't get plane properties\n"); + goto err; + } + drm_property_info_populate(b, plane_props, plane->props, + WDRM_PLANE__COUNT, props); + plane->type = + drm_property_get_value(&plane->props[WDRM_PLANE_TYPE], + props, + WDRM_PLANE_TYPE__COUNT); + drmModeFreeObjectProperties(props); + } + else { + plane->possible_crtcs = (1 << output->pipe); + plane->plane_id = 0; + plane->count_formats = 1; + plane->formats[0] = format; + plane->type = type; + } + + if (plane->type == WDRM_PLANE_TYPE__COUNT) + goto err_props; + + /* With universal planes, everything is a DRM plane; without + * universal planes, the only DRM planes are overlay planes. + * Everything else is a fake plane. */ + if (b->universal_planes) { + assert(kplane); + } else { + if (kplane) + assert(plane->type == WDRM_PLANE_TYPE_OVERLAY); + else + assert(plane->type != WDRM_PLANE_TYPE_OVERLAY && + output); } - drm_property_info_populate(b, plane_props, plane->props, - WDRM_PLANE__COUNT, props); - plane->type = - drm_property_get_value(&plane->props[WDRM_PLANE_TYPE], - props, - WDRM_PLANE_TYPE_OVERLAY); - drmModeFreeObjectProperties(props); weston_plane_init(&plane->base, b->compositor, 0, 0); wl_list_insert(&b->plane_list, &plane->link); return plane; + +err_props: + drm_property_info_free(plane->props, WDRM_PLANE__COUNT); +err: + drm_plane_state_free(plane->state_cur, true); + free(plane); + return NULL; +} + +/** + * Find, or create, a special-purpose plane + * + * Primary and cursor planes are a special case, in that before universal + * planes, they are driven by non-plane API calls. Without universal plane + * support, the only way to configure a primary plane is via drmModeSetCrtc, + * and the only way to configure a cursor plane is drmModeSetCursor2. + * + * Although they may actually be regular planes in the hardware, without + * universal plane support, these planes are not actually exposed to + * userspace in the regular plane list. + * + * However, for ease of internal tracking, we want to manage all planes + * through the same drm_plane structures. Therefore, when we are running + * without universal plane support, we create fake drm_plane structures + * to track these planes. + * + * @param b DRM backend + * @param output Output to use for plane + * @param type Type of plane + */ +static struct drm_plane * +drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, + enum wdrm_plane_type type) +{ + struct drm_plane *plane; + + if (!b->universal_planes) { + uint32_t format; + + switch (type) { + case WDRM_PLANE_TYPE_CURSOR: + format = GBM_FORMAT_ARGB8888; + break; + default: + assert(!"invalid type in drm_output_find_special_plane"); + break; + } + + return drm_plane_create(b, NULL, output, type, format); + } + + wl_list_for_each(plane, &b->plane_list, link) { + struct drm_output *tmp; + bool found_elsewhere = false; + + if (plane->type != type) + continue; + if (!drm_plane_is_available(plane, output)) + continue; + + /* On some platforms, primary/cursor planes can roam + * between different CRTCs, so make sure we don't claim the + * same plane for two outputs. */ + wl_list_for_each(tmp, &b->compositor->pending_output_list, + base.link) { + if (tmp->cursor_plane == plane) { + found_elsewhere = true; + break; + } + } + wl_list_for_each(tmp, &b->compositor->output_list, + base.link) { + if (tmp->cursor_plane == plane) { + found_elsewhere = true; + break; + } + } + + if (found_elsewhere) + continue; + + plane->possible_crtcs = (1 << output->pipe); + return plane; + } + + return NULL; } /** @@ -2766,8 +2973,9 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) static void drm_plane_destroy(struct drm_plane *plane) { - drmModeSetPlane(plane->backend->drm.fd, plane->plane_id, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0); + if (plane->type == WDRM_PLANE_TYPE_OVERLAY) + drmModeSetPlane(plane->backend->drm.fd, plane->plane_id, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); drm_plane_state_free(plane->state_cur, true); drm_property_info_free(plane->props, WDRM_PLANE__COUNT); weston_plane_release(&plane->base); @@ -2804,7 +3012,8 @@ create_sprites(struct drm_backend *b) if (!kplane) continue; - drm_plane = drm_plane_create(b, kplane); + drm_plane = drm_plane_create(b, kplane, NULL, + WDRM_PLANE_TYPE__COUNT, 0); drmModeFreePlane(kplane); if (!drm_plane) continue; @@ -3070,6 +3279,10 @@ drm_output_init_cursor_egl(struct drm_output *output, struct drm_backend *b) { unsigned int i; + /* No point creating cursors if we don't have a plane for them. */ + if (!output->cursor_plane) + return 0; + for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) { struct gbm_bo *bo; @@ -3659,11 +3872,15 @@ drm_output_enable(struct weston_output *base) output->base.gamma_size = output->original_crtc->gamma_size; output->base.set_gamma = drm_output_set_gamma; - weston_plane_init(&output->cursor_plane, b->compositor, - INT32_MIN, INT32_MIN); weston_plane_init(&output->scanout_plane, b->compositor, 0, 0); - weston_compositor_stack_plane(b->compositor, &output->cursor_plane, NULL); + if (output->cursor_plane) + weston_compositor_stack_plane(b->compositor, + &output->cursor_plane->base, + NULL); + else + b->cursors_are_broken = 1; + weston_compositor_stack_plane(b->compositor, &output->scanout_plane, &b->compositor->primary_plane); @@ -3706,10 +3923,17 @@ drm_output_deinit(struct weston_output *base) drm_output_fini_egl(output); weston_plane_release(&output->scanout_plane); - weston_plane_release(&output->cursor_plane); - /* Turn off hardware cursor */ - drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); + /* Since our planes are no longer in use anywhere, remove their base + * weston_plane's link from the plane stacking list, unless we're + * shutting down, in which case the plane has already been + * destroyed. */ + if (output->cursor_plane && !b->shutting_down) { + wl_list_remove(&output->cursor_plane->base.link); + wl_list_init(&output->cursor_plane->base.link); + /* Turn off hardware cursor */ + drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); + } } static void @@ -3729,6 +3953,23 @@ drm_output_destroy(struct weston_output *base) if (output->base.enabled) drm_output_deinit(&output->base); + if (!b->universal_planes && !b->shutting_down) { + /* With universal planes, the 'special' planes are allocated at + * startup, freed at shutdown, and live on the plane list in + * between. We want the planes to continue to exist and be freed + * up for other outputs. + * + * Without universal planes, our special planes are + * pseudo-planes allocated at output creation, freed at output + * destruction, and not usable by other outputs. + * + * On the other hand, if the compositor is already shutting down, + * the plane has already been destroyed. + */ + if (output->cursor_plane) + drm_plane_destroy(output->cursor_plane); + } + wl_list_for_each_safe(drm_mode, next, &output->base.mode_list, base.link) { wl_list_remove(&drm_mode->base.link); @@ -3889,6 +4130,12 @@ create_output_for_connector(struct drm_backend *b, } } + /* Failing to find a cursor plane is not fatal, as we'll fall back + * to software cursor. */ + output->cursor_plane = + drm_output_find_special_plane(b, output, + WDRM_PLANE_TYPE_CURSOR); + weston_compositor_add_pending_output(&output->base, b->compositor); return 0; @@ -4129,7 +4376,9 @@ session_notify(struct wl_listener *listener, void *data) wl_list_for_each(output, &compositor->output_list, base.link) { output->base.repaint_needed = false; - drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); + if (output->cursor_plane) + drmModeSetCursor(b->drm.fd, output->crtc_id, + 0, 0, 0); } output = container_of(compositor->output_list.next, From e2e80136334fe64b12225183d05a0d32c12723de Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 16 Jan 2018 15:37:33 +0000 Subject: [PATCH 0266/1642] compositor-drm: Use drm_plane for scanout plane Use a real drm_plane to back the scanout plane, displacing output->fb_{last,cur,pending} to their plane-tracked equivalents. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 222 +++++++++++++++++++++++++------------ 1 file changed, 151 insertions(+), 71 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 31377bd7b..3ca437bac 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -362,17 +362,8 @@ struct drm_output { struct gbm_surface *gbm_surface; uint32_t gbm_format; - /* Plane for a fullscreen direct scanout view */ - struct weston_plane scanout_plane; - - /* The last framebuffer submitted to the kernel for this CRTC. */ - struct drm_fb *fb_current; - /* The previously-submitted framebuffer, where the hardware has not - * yet acknowledged display of fb_current. */ - struct drm_fb *fb_last; - /* Framebuffer we are going to submit to the kernel when the current - * repaint is flushed. */ - struct drm_fb *fb_pending; + /* Plane being displayed directly on the CRTC */ + struct drm_plane *scanout_plane; /* The last state submitted to the kernel for this CRTC. */ struct drm_output_state *state_cur; @@ -1369,6 +1360,8 @@ drm_output_assign_state(struct drm_output_state *state, if (plane->type == WDRM_PLANE_TYPE_OVERLAY) output->vblank_pending++; + else if (plane->type == WDRM_PLANE_TYPE_PRIMARY) + output->page_flip_pending = 1; } } @@ -1416,6 +1409,8 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, { struct drm_output *output = output_state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_plane *scanout_plane = output->scanout_plane; + struct drm_plane_state *state; struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; struct gbm_bo *bo; @@ -1456,6 +1451,15 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, if (ev->alpha != 1.0f) return NULL; + state = drm_output_state_get_plane(output_state, scanout_plane); + if (state->fb) { + /* If there is already a framebuffer on the scanout plane, + * a client view has already been placed on the scanout + * view. In that case, do not free or put back the state, + * but just leave it in place and quietly exit. */ + return NULL; + } + bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, buffer->resource, GBM_BO_USE_SCANOUT); @@ -1465,19 +1469,33 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, format = drm_output_check_scanout_format(output, ev->surface, bo); if (format == 0) { + drm_plane_state_put_back(state); gbm_bo_destroy(bo); return NULL; } - output->fb_pending = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); - if (!output->fb_pending) { + state->fb = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); + if (!state->fb) { + drm_plane_state_put_back(state); gbm_bo_destroy(bo); return NULL; } - drm_fb_set_buffer(output->fb_pending, buffer); + drm_fb_set_buffer(state->fb, buffer); - return &output->scanout_plane; + state->output = output; + + state->src_x = 0; + state->src_y = 0; + state->src_w = state->fb->width << 16; + state->src_h = state->fb->height << 16; + + state->dest_x = 0; + state->dest_y = 0; + state->dest_w = output->base.current_mode->width; + state->dest_h = output->base.current_mode->height; + + return &scanout_plane->base; } static struct drm_fb * @@ -1542,12 +1560,15 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) { struct drm_output *output = state->output; struct weston_compositor *c = output->base.compositor; + struct drm_plane_state *scanout_state; struct drm_backend *b = to_drm_backend(c); struct drm_fb *fb; /* If we already have a client buffer promoted to scanout, then we don't * want to render. */ - if (output->fb_pending) + scanout_state = drm_output_state_get_plane(state, + output->scanout_plane); + if (scanout_state->fb) return; if (b->use_pixman) @@ -1555,9 +1576,24 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) else fb = drm_output_render_gl(state, damage); - if (!fb) + if (!fb) { + drm_plane_state_put_back(scanout_state); return; - output->fb_pending = fb; + } + + scanout_state->fb = fb; + scanout_state->output = output; + + scanout_state->src_x = 0; + scanout_state->src_y = 0; + scanout_state->src_w = output->base.current_mode->width << 16; + scanout_state->src_h = output->base.current_mode->height << 16; + + scanout_state->dest_x = 0; + scanout_state->dest_y = 0; + scanout_state->dest_w = scanout_state->src_w >> 16; + scanout_state->dest_h = scanout_state->src_h >> 16; + pixman_region32_subtract(&c->primary_plane.damage, &c->primary_plane.damage, damage); @@ -1620,6 +1656,8 @@ drm_output_repaint(struct weston_output *output_base, struct drm_output *output = to_drm_output(output_base); struct drm_backend *backend = to_drm_backend(output->base.compositor); + struct drm_plane *scanout_plane = output->scanout_plane; + struct drm_plane_state *scanout_state; struct drm_plane_state *ps; struct drm_plane *p; struct drm_mode *mode; @@ -1639,7 +1677,6 @@ drm_output_repaint(struct weston_output *output_base, pending_state, DRM_OUTPUT_STATE_CLEAR_PLANES); - assert(!output->fb_last); /* If disable_planes is set then assign_planes() wasn't * called for this render, so we could still have a stale @@ -1652,14 +1689,29 @@ drm_output_repaint(struct weston_output *output_base, } drm_output_render(state, damage); - if (!output->fb_pending) + scanout_state = drm_output_state_get_plane(state, scanout_plane); + if (!scanout_state || !scanout_state->fb) goto err; + /* The legacy SetCrtc API doesn't allow us to do scaling, and the + * legacy PageFlip API doesn't allow us to do clipping either. */ + assert(scanout_state->src_x == 0); + assert(scanout_state->src_y == 0); + assert(scanout_state->src_w == + (unsigned) (output->base.current_mode->width << 16)); + assert(scanout_state->src_h == + (unsigned) (output->base.current_mode->height << 16)); + assert(scanout_state->dest_x == 0); + assert(scanout_state->dest_y == 0); + assert(scanout_state->dest_w == scanout_state->src_w >> 16); + assert(scanout_state->dest_h == scanout_state->src_h >> 16); + mode = container_of(output->base.current_mode, struct drm_mode, base); - if (output->state_invalid || !output->fb_current || - output->fb_current->stride != output->fb_pending->stride) { + if (output->state_invalid || !scanout_plane->state_cur->fb || + scanout_plane->state_cur->fb->stride != scanout_state->fb->stride) { ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, - output->fb_pending->fb_id, 0, 0, + scanout_state->fb->fb_id, + 0, 0, &output->connector_id, 1, &mode->mode_info); if (ret) { @@ -1672,18 +1724,13 @@ drm_output_repaint(struct weston_output *output_base, } if (drmModePageFlip(backend->drm.fd, output->crtc_id, - output->fb_pending->fb_id, + scanout_state->fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { weston_log("queueing pageflip failed: %m\n"); goto err; } - output->fb_last = output->fb_current; - output->fb_current = output->fb_pending; - output->fb_pending = NULL; - assert(!output->page_flip_pending); - output->page_flip_pending = 1; if (output->pageflip_timer) wl_event_source_timer_update(output->pageflip_timer, @@ -1743,10 +1790,7 @@ drm_output_repaint(struct weston_output *output_base, err: output->cursor_view = NULL; - if (output->fb_pending) { - drm_fb_unref(output->fb_pending); - output->fb_pending = NULL; - } + drm_output_state_free(state); return -1; @@ -1759,6 +1803,7 @@ drm_output_start_repaint_loop(struct weston_output *output_base) struct drm_pending_state *pending_state = NULL; struct drm_output_state *state; struct drm_plane_state *plane_state; + struct drm_plane *scanout_plane = output->scanout_plane; struct drm_backend *backend = to_drm_backend(output_base->compositor); uint32_t fb_id; @@ -1775,7 +1820,7 @@ drm_output_start_repaint_loop(struct weston_output *output_base) if (output->disable_pending || output->destroy_pending) return; - if (!output->fb_current) { + if (!output->scanout_plane->state_cur->fb) { /* We can't page flip if there's no mode set */ goto finish_frame; } @@ -1786,6 +1831,8 @@ drm_output_start_repaint_loop(struct weston_output *output_base) if (output->state_invalid) goto finish_frame; + assert(scanout_plane->state_cur->output == output); + /* Try to get current msc and timestamp via instant query */ vbl.request.type |= drm_waitvblank_pipe(output); ret = drmWaitVBlank(backend->drm.fd, &vbl); @@ -1815,10 +1862,9 @@ drm_output_start_repaint_loop(struct weston_output *output_base) /* Immediate query didn't provide valid timestamp. * Use pageflip fallback. */ - fb_id = output->fb_current->fb_id; + fb_id = scanout_plane->state_cur->fb->fb_id; assert(!output->page_flip_pending); - assert(!output->fb_last); assert(!output->state_last); pending_state = drm_pending_state_alloc(backend); @@ -1835,9 +1881,6 @@ drm_output_start_repaint_loop(struct weston_output *output_base) wl_event_source_timer_update(output->pageflip_timer, backend->pageflip_timeout); - output->fb_last = drm_fb_ref(output->fb_current); - output->page_flip_pending = 1; - wl_list_for_each(plane_state, &state->plane_list, link) { if (plane_state->plane->type != WDRM_PLANE_TYPE_OVERLAY) continue; @@ -1909,9 +1952,6 @@ page_flip_handler(int fd, unsigned int frame, assert(output->page_flip_pending); output->page_flip_pending = 0; - drm_fb_unref(output->fb_last); - output->fb_last = NULL; - if (output->vblank_pending) return; @@ -2591,10 +2631,7 @@ drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mo * sledgehammer modeswitch first, and only later showing new * content. */ - drm_fb_unref(output->fb_current); - assert(!output->fb_last); - assert(!output->fb_pending); - output->fb_last = output->fb_current = NULL; + output->state_invalid = true; if (b->use_pixman) { drm_output_fini_pixman(output); @@ -2917,6 +2954,13 @@ drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, case WDRM_PLANE_TYPE_CURSOR: format = GBM_FORMAT_ARGB8888; break; + case WDRM_PLANE_TYPE_PRIMARY: + /* We don't know what formats the primary plane supports + * before universal planes, so we just assume that the + * GBM format works; however, this isn't set until after + * the output is created. */ + format = 0; + break; default: assert(!"invalid type in drm_output_find_special_plane"); break; @@ -2939,14 +2983,16 @@ drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, * same plane for two outputs. */ wl_list_for_each(tmp, &b->compositor->pending_output_list, base.link) { - if (tmp->cursor_plane == plane) { + if (tmp->cursor_plane == plane || + tmp->scanout_plane == plane) { found_elsewhere = true; break; } } wl_list_for_each(tmp, &b->compositor->output_list, base.link) { - if (tmp->cursor_plane == plane) { + if (tmp->cursor_plane == plane || + tmp->scanout_plane == plane) { found_elsewhere = true; break; } @@ -3352,6 +3398,19 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) static void drm_output_fini_egl(struct drm_output *output) { + struct drm_backend *b = to_drm_backend(output->base.compositor); + + /* Destroying the GBM surface will destroy all our GBM buffers, + * regardless of refcount. Ensure we destroy them here. */ + if (!b->shutting_down && + output->scanout_plane->state_cur->fb && + output->scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE) { + drm_plane_state_free(output->scanout_plane->state_cur, true); + output->scanout_plane->state_cur = + drm_plane_state_alloc(NULL, output->scanout_plane); + output->scanout_plane->state_cur->complete = true; + } + gl_renderer->output_destroy(&output->base); gbm_surface_destroy(output->gbm_surface); drm_output_fini_cursor_egl(output); @@ -3417,8 +3476,20 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) static void drm_output_fini_pixman(struct drm_output *output) { + struct drm_backend *b = to_drm_backend(output->base.compositor); unsigned int i; + /* Destroying the Pixman surface will destroy all our buffers, + * regardless of refcount. Ensure we destroy them here. */ + if (!b->shutting_down && + output->scanout_plane->state_cur->fb && + output->scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB) { + drm_plane_state_free(output->scanout_plane->state_cur, true); + output->scanout_plane->state_cur = + drm_plane_state_alloc(NULL, output->scanout_plane); + output->scanout_plane->state_cur->complete = true; + } + pixman_renderer_output_destroy(&output->base); pixman_region32_fini(&output->previous_damage); @@ -3821,6 +3892,12 @@ drm_output_set_gbm_format(struct weston_output *base, if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1) output->gbm_format = b->gbm_format; + + /* Without universal planes, we can't discover which formats are + * supported by the primary plane; we just hope that the GBM format + * works. */ + if (!b->universal_planes) + output->scanout_plane->formats[0] = output->gbm_format; } static void @@ -3872,8 +3949,6 @@ drm_output_enable(struct weston_output *base) output->base.gamma_size = output->original_crtc->gamma_size; output->base.set_gamma = drm_output_set_gamma; - weston_plane_init(&output->scanout_plane, b->compositor, 0, 0); - if (output->cursor_plane) weston_compositor_stack_plane(b->compositor, &output->cursor_plane->base, @@ -3881,7 +3956,8 @@ drm_output_enable(struct weston_output *base) else b->cursors_are_broken = 1; - weston_compositor_stack_plane(b->compositor, &output->scanout_plane, + weston_compositor_stack_plane(b->compositor, + &output->scanout_plane->base, &b->compositor->primary_plane); weston_log("Output %s, (connector %d, crtc %d)\n", @@ -3910,29 +3986,25 @@ drm_output_deinit(struct weston_output *base) struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); - /* output->fb_last and output->fb_pending must not be set here; - * destroy_pending/disable_pending exist to guarantee exactly this. */ - assert(!output->fb_last); - assert(!output->fb_pending); - drm_fb_unref(output->fb_current); - output->fb_current = NULL; - if (b->use_pixman) drm_output_fini_pixman(output); else drm_output_fini_egl(output); - weston_plane_release(&output->scanout_plane); - /* Since our planes are no longer in use anywhere, remove their base * weston_plane's link from the plane stacking list, unless we're * shutting down, in which case the plane has already been * destroyed. */ - if (output->cursor_plane && !b->shutting_down) { - wl_list_remove(&output->cursor_plane->base.link); - wl_list_init(&output->cursor_plane->base.link); - /* Turn off hardware cursor */ - drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); + if (!b->shutting_down) { + wl_list_remove(&output->scanout_plane->base.link); + wl_list_init(&output->scanout_plane->base.link); + + if (output->cursor_plane) { + wl_list_remove(&output->cursor_plane->base.link); + wl_list_init(&output->cursor_plane->base.link); + /* Turn off hardware cursor */ + drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); + } } } @@ -3968,6 +4040,7 @@ drm_output_destroy(struct weston_output *base) */ if (output->cursor_plane) drm_plane_destroy(output->cursor_plane); + drm_plane_destroy(output->scanout_plane); } wl_list_for_each_safe(drm_mode, next, &output->base.mode_list, @@ -4016,10 +4089,6 @@ drm_output_disable(struct weston_output *base) if (output->base.enabled) drm_output_deinit(&output->base); - assert(!output->fb_last); - assert(!output->fb_current); - assert(!output->fb_pending); - output->disable_pending = 0; weston_log("Disabling output %s\n", output->base.name); @@ -4130,6 +4199,16 @@ create_output_for_connector(struct drm_backend *b, } } + output->scanout_plane = + drm_output_find_special_plane(b, output, + WDRM_PLANE_TYPE_PRIMARY); + if (!output->scanout_plane) { + weston_log("Failed to find primary plane for output %s\n", + output->base.name); + drm_output_destroy(&output->base); + return -1; + } + /* Failing to find a cursor plane is not fatal, as we'll fall back * to software cursor. */ output->cursor_plane = @@ -4610,7 +4689,8 @@ recorder_frame_notify(struct wl_listener *listener, void *data) if (!output->recorder) return; - ret = drmPrimeHandleToFD(b->drm.fd, output->fb_current->handle, + ret = drmPrimeHandleToFD(b->drm.fd, + output->scanout_plane->state_cur->fb->handle, DRM_CLOEXEC, &fd); if (ret) { weston_log("[libva recorder] " @@ -4619,7 +4699,7 @@ recorder_frame_notify(struct wl_listener *listener, void *data) } ret = vaapi_recorder_frame(output->recorder, fd, - output->fb_current->stride); + output->scanout_plane->state_cur->fb->stride); if (ret < 0) { weston_log("[libva recorder] aborted: %m\n"); recorder_destroy(output); From 02d487a59073c4bc95aef7476b145f7f16cd1713 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Sat, 7 Oct 2017 14:01:45 +0100 Subject: [PATCH 0267/1642] compositor-drm: Remove NULL checks in switch_mode Calling switch_mode with no output or mode never makes any sense. Drop the NULL checks. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 3ca437bac..18954e776 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2593,26 +2593,13 @@ drm_output_fini_pixman(struct drm_output *output); static int drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mode) { - struct drm_output *output; - struct drm_mode *drm_mode; - struct drm_backend *b; - - if (output_base == NULL) { - weston_log("output is NULL.\n"); - return -1; - } - - if (mode == NULL) { - weston_log("mode is NULL.\n"); - return -1; - } - - b = to_drm_backend(output_base->compositor); - output = to_drm_output(output_base); - drm_mode = choose_mode (output, mode); + struct drm_output *output = to_drm_output(output_base); + struct drm_backend *b = to_drm_backend(output_base->compositor); + struct drm_mode *drm_mode = choose_mode(output, mode); if (!drm_mode) { - weston_log("%s, invalid resolution:%dx%d\n", __func__, mode->width, mode->height); + weston_log("%s: invalid resolution %dx%d\n", + output_base->name, mode->width, mode->height); return -1; } From e95169b6af72aa81d7e12e0defeb83277b16abb4 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 14 Nov 2016 17:46:59 +0000 Subject: [PATCH 0268/1642] compositor-drm: Don't repaint if no damage If we don't have any damage for the primary plane, then don't force a repaint; simply reuse the old buffer we already have. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 18954e776..ff43940de 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1561,6 +1561,7 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) struct drm_output *output = state->output; struct weston_compositor *c = output->base.compositor; struct drm_plane_state *scanout_state; + struct drm_plane *scanout_plane = output->scanout_plane; struct drm_backend *b = to_drm_backend(c); struct drm_fb *fb; @@ -1571,10 +1572,20 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) if (scanout_state->fb) return; - if (b->use_pixman) + if (!pixman_region32_not_empty(damage) && + scanout_plane->state_cur->fb && + (scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE || + scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB) && + scanout_plane->state_cur->fb->width == + output->base.current_mode->width && + scanout_plane->state_cur->fb->height == + output->base.current_mode->height) { + fb = drm_fb_ref(scanout_plane->state_cur->fb); + } else if (b->use_pixman) { fb = drm_output_render_pixman(state, damage); - else + } else { fb = drm_output_render_gl(state, damage); + } if (!fb) { drm_plane_state_put_back(scanout_state); From 88abc6ab74b691ae4a5758d0ecce4107e1bb0fab Mon Sep 17 00:00:00 2001 From: Arnaud Vrac Date: Wed, 17 Jan 2018 19:36:32 +0100 Subject: [PATCH 0269/1642] gl-renderer: save OpenGL version in renderer context This will allow to make some assumptions in further patches when GLES3 is available. Signed-off-by: Arnaud Vrac Reviewed-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index abf556f03..c4b6af18b 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -60,6 +60,12 @@ #include "shared/timespec-util.h" #include "weston-egl-ext.h" +#define GR_GL_VERSION(major, minor) \ + (((uint32_t)(major) << 16) | (uint32_t)(minor)) + +#define GR_GL_VERSION_INVALID \ + GR_GL_VERSION(0, 0) + struct gl_shader { GLuint program; GLuint vertex_shader, fragment_shader; @@ -199,6 +205,8 @@ struct gl_renderer { EGLSurface dummy_surface; + uint32_t gl_version; + struct wl_array vertices; struct wl_array vtxcnt; @@ -3586,6 +3594,22 @@ fan_debug_repaint_binding(struct weston_keyboard *keyboard, weston_compositor_damage_all(compositor); } +static uint32_t +get_gl_version(void) +{ + const char *version; + int major, minor; + + version = (const char *) glGetString(GL_VERSION); + if (version && + (sscanf(version, "%d.%d", &major, &minor) == 2 || + sscanf(version, "OpenGL ES %d.%d", &major, &minor) == 2)) { + return GR_GL_VERSION(major, minor); + } + + return GR_GL_VERSION_INVALID; +} + static int gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) { @@ -3626,6 +3650,13 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) return -1; } + gr->gl_version = get_gl_version(); + if (gr->gl_version == GR_GL_VERSION_INVALID) { + weston_log("warning: failed to detect GLES version, " + "defaulting to 2.0.\n"); + gr->gl_version = GR_GL_VERSION(2, 0); + } + log_egl_gl_info(gr->egl_display); gr->image_target_texture_2d = From 439e5fdd5f6bf3dc51eec73eb1dde5fc53943152 Mon Sep 17 00:00:00 2001 From: Arnaud Vrac Date: Wed, 17 Jan 2018 19:36:33 +0100 Subject: [PATCH 0270/1642] gl-renderer: try to create a GLES3 context GL drivers might allow using GLES3 features even in GLES2 contexts, but that's not always the case. To make sure we can use GLES3, first try to create a GLES3 context and then fallback to GLES2 on failure. The reported GL version is used to determine which GLES version is actually available. Signed-off-by: Arnaud Vrac Reviewed-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index c4b6af18b..ff0a2bb98 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -3618,8 +3618,8 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) EGLConfig context_config; EGLBoolean ret; - static const EGLint context_attribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, + EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 0, EGL_NONE }; @@ -3634,12 +3634,22 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) if (gr->has_configless_context) context_config = EGL_NO_CONFIG_KHR; + /* try to create an OpenGLES 3 context first */ + context_attribs[1] = 3; gr->egl_context = eglCreateContext(gr->egl_display, context_config, EGL_NO_CONTEXT, context_attribs); if (gr->egl_context == NULL) { - weston_log("failed to create context\n"); - gl_renderer_print_egl_error_state(); - return -1; + /* and then fallback to OpenGLES 2 */ + context_attribs[1] = 2; + gr->egl_context = eglCreateContext(gr->egl_display, + context_config, + EGL_NO_CONTEXT, + context_attribs); + if (gr->egl_context == NULL) { + weston_log("failed to create context\n"); + gl_renderer_print_egl_error_state(); + return -1; + } } ret = eglMakeCurrent(gr->egl_display, egl_surface, From cc1a22bca9401b40c9660a1bba95b1cadb00e86a Mon Sep 17 00:00:00 2001 From: Arnaud Vrac Date: Wed, 17 Jan 2018 19:36:34 +0100 Subject: [PATCH 0271/1642] gl-renderer: move GL_EXT_texture_rg extension check This is a GL extension and not EGL, so it should be checked after the EGL context has been created. Signed-off-by: Arnaud Vrac Reviewed-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index ff0a2bb98..c2767cfc7 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -3221,9 +3221,6 @@ gl_renderer_setup_egl_extensions(struct weston_compositor *ec) gr->has_dmabuf_import_modifiers = 1; } - if (weston_check_egl_extension(extensions, "GL_EXT_texture_rg")) - gr->has_gl_texture_rg = 1; - if (weston_check_egl_extension(extensions, "EGL_KHR_fence_sync") && weston_check_egl_extension(extensions, "EGL_ANDROID_native_fence_sync")) { gr->create_sync = @@ -3691,6 +3688,9 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) if (weston_check_egl_extension(extensions, "GL_EXT_unpack_subimage")) gr->has_unpack_subimage = 1; + if (weston_check_egl_extension(extensions, "GL_EXT_texture_rg")) + gr->has_gl_texture_rg = 1; + if (weston_check_egl_extension(extensions, "GL_OES_EGL_image_external")) gr->has_egl_image_external = 1; From e79694ceec3caa8a686d5926d917e8874abae4a0 Mon Sep 17 00:00:00 2001 From: Arnaud Vrac Date: Wed, 17 Jan 2018 19:36:35 +0100 Subject: [PATCH 0272/1642] gl-renderer: always enable unpack subimage and RG textures in ES3 contexts The GL_EXT_unpack_subimage and GL_EXT_texture_rg are part of the core ES 3.0 specification, so also check the GL driver version in addition to the extension string to determine if those features are supported. This allows using those extensions on some GL drivers that do not expose them in the extensions string, but still support OpenGLES3. Signed-off-by: Arnaud Vrac Reviewed-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index c2767cfc7..8a9e4c575 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -3685,10 +3685,12 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) else ec->read_format = PIXMAN_a8b8g8r8; - if (weston_check_egl_extension(extensions, "GL_EXT_unpack_subimage")) + if (gr->gl_version >= GR_GL_VERSION(3, 0) || + weston_check_egl_extension(extensions, "GL_EXT_unpack_subimage")) gr->has_unpack_subimage = 1; - if (weston_check_egl_extension(extensions, "GL_EXT_texture_rg")) + if (gr->gl_version >= GR_GL_VERSION(3, 0) || + weston_check_egl_extension(extensions, "GL_EXT_texture_rg")) gr->has_gl_texture_rg = 1; if (weston_check_egl_extension(extensions, "GL_OES_EGL_image_external")) From 99ef816fd10ae9503220b8671d855f7965102c66 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Tue, 9 Jan 2018 14:00:30 +0100 Subject: [PATCH 0273/1642] dmabuf: set modifier to invalid for not supporting clients modifier_hi and modifier_lo are set to 0 by clients, which are not supporting modifiers. Modifier attributes of buffers of these clients set to 0 too in linux-dmabuf.c import_simple_dmabuf function in gl-renderer.c compares modifier attribute of the buffer with DRM_FORMAT_MOD_INVALID. DRM_FORMAT_MOD_INVALID is equal to ((1ULL<<56) - 1). Therefore, modifer 0 is accepted as valid. Then, the function checks support for eglQueryDmaBufModifiersEXT. If it is not supported import_simple_dmabuf function is returning NULL. This patch sets the modifier attribute to DRM_FORMAT_MOD_INVALID for clients which are not supporting modifiers. Without this patch linux-dmabuf protocol is not working for not supporting clients. Fixes: b138d7afb3a2a7d51dccb12f08d70c2d86766901 ("gl-renderer: Ignore INVALID modifier") Signed-off-by: Emre Ucan Reviewed-by: Daniel Stone Tested-by: Tomohito Esaki --- libweston/gl-renderer.c | 2 +- libweston/linux-dmabuf.c | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 8a9e4c575..56666ed34 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -2164,7 +2164,7 @@ gl_renderer_import_dmabuf(struct weston_compositor *ec, for (i = 0; i < dmabuf->attributes.n_planes; i++) { /* return if EGL doesn't support import modifiers */ - if (dmabuf->attributes.modifier[i] != 0) + if (dmabuf->attributes.modifier[i] != DRM_FORMAT_MOD_INVALID) if (!gr->has_dmabuf_import_modifiers) return false; diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index d81b63d54..59705845e 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -111,8 +111,13 @@ params_add(struct wl_client *client, buffer->attributes.fd[plane_idx] = name_fd; buffer->attributes.offset[plane_idx] = offset; buffer->attributes.stride[plane_idx] = stride; - buffer->attributes.modifier[plane_idx] = ((uint64_t)modifier_hi << 32) | - modifier_lo; + + if (wl_resource_get_version(params_resource) < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) + buffer->attributes.modifier[plane_idx] = DRM_FORMAT_MOD_INVALID; + else + buffer->attributes.modifier[plane_idx] = ((uint64_t)modifier_hi << 32) | + modifier_lo; + buffer->attributes.n_planes++; } From b0a749dcb312b3da124b5c19e090ca325e497b2c Mon Sep 17 00:00:00 2001 From: Michael Tretter Date: Wed, 17 Jan 2018 17:54:31 +0100 Subject: [PATCH 0274/1642] linux-dmabuf: send deprecated format events Although the format event is deprecated, some clients, especially the GStreamer waylandsink, only support zwp_linux_dmabuf_v1 version 1 and require the deprecated format event. Send format events instead of the modifier event, if the client binds on a protocol version before version 3, skipping formats that only support non-linear modifiers. Signed-off-by: Michael Tretter Signed-off-by: Philipp Zabel Reviewed-by: Daniel Stone --- libweston/linux-dmabuf.c | 19 ++++++++++++------- libweston/linux-dmabuf.h | 3 +++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index 59705845e..148c61fb6 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -488,8 +488,6 @@ bind_linux_dmabuf(struct wl_client *client, wl_resource_set_implementation(resource, &linux_dmabuf_implementation, compositor, NULL); - if (version < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) - return; /* * Use EGL_EXT_image_dma_buf_import_modifiers to query and advertise * format/modifier codes. @@ -510,11 +508,18 @@ bind_linux_dmabuf(struct wl_client *client, modifiers = &modifier_invalid; } for (j = 0; j < num_modifiers; j++) { - uint32_t modifier_lo = modifiers[j] & 0xFFFFFFFF; - uint32_t modifier_hi = modifiers[j] >> 32; - zwp_linux_dmabuf_v1_send_modifier(resource, formats[i], - modifier_hi, - modifier_lo); + if (version >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) { + uint32_t modifier_lo = modifiers[j] & 0xFFFFFFFF; + uint32_t modifier_hi = modifiers[j] >> 32; + zwp_linux_dmabuf_v1_send_modifier(resource, + formats[i], + modifier_hi, + modifier_lo); + } else if (modifiers[j] == DRM_FORMAT_MOD_LINEAR || + modifiers == &modifier_invalid) { + zwp_linux_dmabuf_v1_send_format(resource, + formats[i]); + } } if (modifiers != &modifier_invalid) free(modifiers); diff --git a/libweston/linux-dmabuf.h b/libweston/linux-dmabuf.h index f4ab52cbd..dbeda6602 100644 --- a/libweston/linux-dmabuf.h +++ b/libweston/linux-dmabuf.h @@ -32,6 +32,9 @@ #ifndef DRM_FORMAT_MOD_INVALID #define DRM_FORMAT_MOD_INVALID ((1ULL<<56) - 1) #endif +#ifndef DRM_FORMAT_MOD_LINEAR +#define DRM_FORMAT_MOD_LINEAR 0 +#endif struct linux_dmabuf_buffer; typedef void (*dmabuf_user_data_destroy_func)( From 543d0a0bb652894d9825f2a08b0c10118e65334e Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Wed, 17 Jan 2018 17:54:32 +0100 Subject: [PATCH 0275/1642] gl-renderer: return conservative format list if dmabuf import modifiers unsupported If the EGL_EXT_image_dma_buf_import_modifiers extension is not supported, let gl_renderer_query_dmabuf_formats return a hardcoded fallback list. That list contains ARGB8888, XRGB8888, and if the GL_EXT_texture_rg extension is supported, YUYV, NV12, YUV420, and YUV444. Signed-off-by: Philipp Zabel Reviewed-by: Emil Velikov Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 56666ed34..371e06b30 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -2096,14 +2096,23 @@ gl_renderer_query_dmabuf_formats(struct weston_compositor *wc, int **formats, int *num_formats) { struct gl_renderer *gr = get_renderer(wc); + static const int fallback_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_YUYV, + DRM_FORMAT_NV12, + DRM_FORMAT_YUV420, + DRM_FORMAT_YUV444, + }; + bool fallback = false; EGLint num; assert(gr->has_dmabuf_import); if (!gr->has_dmabuf_import_modifiers || !gr->query_dmabuf_formats(gr->egl_display, 0, NULL, &num)) { - *num_formats = 0; - return; + num = gr->has_gl_texture_rg ? ARRAY_LENGTH(fallback_formats) : 2; + fallback = true; } *formats = calloc(num, sizeof(int)); @@ -2111,6 +2120,13 @@ gl_renderer_query_dmabuf_formats(struct weston_compositor *wc, *num_formats = 0; return; } + + if (fallback) { + memcpy(formats, fallback_formats, num * sizeof(int)); + *num_formats = num; + return; + } + if (!gr->query_dmabuf_formats(gr->egl_display, num, *formats, &num)) { *num_formats = 0; free(*formats); From 8564a0d109c9ebe41ea3664edf42265c2258ed2f Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Sun, 25 Jun 2017 12:21:39 +0000 Subject: [PATCH 0276/1642] compositor: Implement runtime output transform changes Up to now we could set the transform only on output initialization. However, on certain situations(like tablets and convertible laptops), screen orientation can change while the compositor is running and thus the need for change of the output transform arises. When the transform changes, we must update the output geometry, output->region and output->previous_damage, as well as send this change to clients. We also have to check whether any of the pointers are inside the output which is being rotated. If this is the case, they are moved to the new center, because otherwise the pointer is stuck outside of the screen ans "lost" to the user. What is more, after calling this function compositors should check if any view is now outside of the screen and move it according to their wish. Signed-off-by: Ilia Bozhinov Reviewed-by: Derek Foreman Acked-by: Daniel Stone --- libweston/compositor.c | 62 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 083664fd8..aec937bb8 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4679,9 +4679,6 @@ weston_output_set_scale(struct weston_output *output, * \param output The weston_output object that the transform is set for. * \param transform Transform value for the given output. * - * It only supports setting transform for an output that is - * not enabled and it can only be ran once. - * * Refer to wl_output::transform section located at * https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_output * for list of values that can be passed to this function. @@ -4692,13 +4689,62 @@ WL_EXPORT void weston_output_set_transform(struct weston_output *output, uint32_t transform) { - /* We can only set transform on a disabled output */ - assert(!output->enabled); + struct weston_pointer_motion_event ev; + struct wl_resource *resource; + struct weston_seat *seat; + pixman_region32_t old_region; + int mid_x, mid_y; - /* We only want to set transform once */ - assert(output->transform == UINT32_MAX); + if (!output->enabled && output->transform == UINT32_MAX) { + output->transform = transform; + return; + } - output->transform = transform; + weston_output_transform_scale_init(output, transform, output->scale); + + pixman_region32_init(&old_region); + pixman_region32_copy(&old_region, &output->region); + + pixman_region32_fini(&output->region); + pixman_region32_fini(&output->previous_damage); + + weston_output_init_geometry(output, output->x, output->y); + + output->dirty = 1; + + /* Notify clients of the change for output transform. */ + wl_resource_for_each(resource, &output->resource_list) { + wl_output_send_geometry(resource, + output->x, + output->y, + output->mm_width, + output->mm_height, + output->subpixel, + output->make, + output->model, + output->transform); + + if (wl_resource_get_version(resource) >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output_send_done(resource); + } + + /* we must ensure that pointers are inside output, otherwise they disappear */ + mid_x = output->x + output->width / 2; + mid_y = output->y + output->height / 2; + + ev.mask = WESTON_POINTER_MOTION_ABS; + ev.x = wl_fixed_to_double(wl_fixed_from_int(mid_x)); + ev.y = wl_fixed_to_double(wl_fixed_from_int(mid_y)); + + wl_list_for_each(seat, &output->compositor->seat_list, link) { + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (pointer && pixman_region32_contains_point(&old_region, + wl_fixed_to_int(pointer->x), + wl_fixed_to_int(pointer->y), + NULL)) + weston_pointer_move(pointer, &ev); + } } /** Initializes a weston_output object with enough data so From 332d1892bbb380b32ff1c9f99d20184b447535dd Mon Sep 17 00:00:00 2001 From: Ian Ray Date: Thu, 18 Jan 2018 11:44:04 +0000 Subject: [PATCH 0277/1642] xwm: do not include shadow in input region The window frame was created with position and size which include an offset for margins and shadow. Set the input region to ignore shadow. [daniels: Fixed type mismatch, removed unused variable.] Signed-off-by: Ian Ray Reviewed-by: Daniel Stone Tested-by: Scott Moreau --- xwayland/window-manager.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index a6b6a3906..be895e7a8 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -641,6 +641,20 @@ weston_wm_window_get_child_position(struct weston_wm_window *window, } } +static void +weston_wm_window_get_input_rect(struct weston_wm_window *window, + int32_t *x, int32_t *y, + int32_t *width, int32_t *height) +{ + if (!window->decorate) { + weston_wm_window_get_child_position(window, x, y); + *width = window->width; + *height = window->height; + } else { + frame_input_rect(window->frame, x, y, width, height); + } +} + static void weston_wm_window_send_configure_notify(struct weston_wm_window *window) { @@ -966,6 +980,7 @@ weston_wm_window_create_frame(struct weston_wm_window *window) { struct weston_wm *wm = window->wm; uint32_t values[3]; + xcb_rectangle_t rect; int x, y, width, height; int buttons = FRAME_BUTTON_CLOSE; @@ -1020,6 +1035,25 @@ weston_wm_window_create_frame(struct weston_wm_window *window) &wm->format_rgba, width, height); + weston_wm_window_get_input_rect(window, &x, &y, &width, &height); + rect.x = x; + rect.y = y; + rect.width = width; + rect.height = height; + + /* The window frame was created with position and size which include + * an offset for margins and shadow. Set the input region to ignore + * shadow. */ + xcb_shape_rectangles(wm->conn, + XCB_SHAPE_SO_SET, + XCB_SHAPE_SK_INPUT, + 0, + window->frame_id, + 0, + 0, + 1, + &rect); + hash_table_insert(wm->window_hash, window->frame_id, window); } From cb04cc4f6881c7014acf13d9606e8d6a6cc189e7 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 16 Nov 2016 11:51:27 +0000 Subject: [PATCH 0278/1642] compositor-drm: Add to_drm_mode helper Much like we already have to_drm_output and to_drm_backend. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index ff43940de..fe59bf52f 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -437,6 +437,12 @@ drm_output_pageflip_timer_create(struct drm_output *output) return 0; } +static inline struct drm_mode * +to_drm_mode(struct weston_mode *base) +{ + return container_of(base, struct drm_mode, base); +} + /** * Get the current value of a KMS property * @@ -1717,7 +1723,7 @@ drm_output_repaint(struct weston_output *output_base, assert(scanout_state->dest_w == scanout_state->src_w >> 16); assert(scanout_state->dest_h == scanout_state->src_h >> 16); - mode = container_of(output->base.current_mode, struct drm_mode, base); + mode = to_drm_mode(output->base.current_mode); if (output->state_invalid || !scanout_plane->state_cur->fb || scanout_plane->state_cur->fb->stride != scanout_state->fb->stride) { ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, @@ -2576,7 +2582,7 @@ choose_mode (struct drm_output *output, struct weston_mode *target_mode) output->base.current_mode->height == target_mode->height && (output->base.current_mode->refresh == target_mode->refresh || target_mode->refresh == 0)) - return (struct drm_mode *)output->base.current_mode; + return to_drm_mode(output->base.current_mode); wl_list_for_each(mode, &output->base.mode_list, base.link) { if (mode->mode_info.hdisplay == target_mode->width && From 6b58ea8c43ac81e519bd418efbf24687a5d731b8 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 1 Dec 2017 19:20:40 +0100 Subject: [PATCH 0279/1642] xwm: Add icon support to the frame This fetches the _NET_WM_ICON property of the X11 window, and use the first image found as the frame icon. This has been tested with various X11 programs, and improves usability and user-friendliness a bit. Changes since v1: - Changed frame_button_create() to use frame_button_create_from_surface() internally. - Removed a check that should never have been commited. Changes since v2: - Request UINT32_MAX items instead of 2048, to avoid cutting valid icons. - Strengthen checks against malformed input. - Handle XCB_PROPERTY_DELETE to remove the icon. - Schedule a repaint if the icon changed. Changes since v3: - Keep the previous Cairo surface until the new one has been successfully loaded. - Use uint32_t for cardinals. Unsigned is the same type except on 16-bit machines, but uint32_t is clearer. - Declare length as uint32_t too, like in xcb_get_property_reply_t. Signed-off-by: Emmanuel Gil Peyrot Reviewed-by: Quentin Glidic --- clients/window.c | 4 +-- libweston/compositor-wayland.c | 2 +- shared/cairo-util.h | 2 +- shared/frame.c | 54 ++++++++++++++++++++-------- xwayland/window-manager.c | 65 ++++++++++++++++++++++++++++++++-- 5 files changed, 107 insertions(+), 20 deletions(-) diff --git a/clients/window.c b/clients/window.c index 95796d465..15a86e153 100644 --- a/clients/window.c +++ b/clients/window.c @@ -2546,7 +2546,7 @@ window_frame_create(struct window *window, void *data) frame = xzalloc(sizeof *frame); frame->frame = frame_create(window->display->theme, 0, 0, - buttons, window->title); + buttons, window->title, NULL); frame->widget = window_add_widget(window, frame); frame->child = widget_add_widget(frame->widget, data); @@ -5449,7 +5449,7 @@ create_menu(struct display *display, menu->user_data = user_data; menu->widget = window_add_widget(menu->window, menu); menu->frame = frame_create(window->display->theme, 0, 0, - FRAME_BUTTON_NONE, NULL); + FRAME_BUTTON_NONE, NULL, NULL); fail_on_null(menu->frame, 0, __FILE__, __LINE__); menu->entries = entries; menu->count = count; diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 3bdfb03ee..c5290d855 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -869,7 +869,7 @@ wayland_output_set_windowed(struct wayland_output *output) return -1; } output->frame = frame_create(b->theme, 100, 100, - FRAME_BUTTON_CLOSE, output->title); + FRAME_BUTTON_CLOSE, output->title, NULL); if (!output->frame) return -1; diff --git a/shared/cairo-util.h b/shared/cairo-util.h index 9481e58ca..bab48083d 100644 --- a/shared/cairo-util.h +++ b/shared/cairo-util.h @@ -126,7 +126,7 @@ enum { struct frame * frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons, - const char *title); + const char *title, cairo_surface_t *icon); void frame_destroy(struct frame *frame); diff --git a/shared/frame.c b/shared/frame.c index 5ca7e08b8..dc7ff85c4 100644 --- a/shared/frame.c +++ b/shared/frame.c @@ -108,9 +108,9 @@ struct frame { }; static struct frame_button * -frame_button_create(struct frame *frame, const char *icon, - enum frame_status status_effect, - enum frame_button_flags flags) +frame_button_create_from_surface(struct frame *frame, cairo_surface_t *icon, + enum frame_status status_effect, + enum frame_button_flags flags) { struct frame_button *button; @@ -118,12 +118,7 @@ frame_button_create(struct frame *frame, const char *icon, if (!button) return NULL; - button->icon = cairo_image_surface_create_from_png(icon); - if (!button->icon) { - free(button); - return NULL; - } - + button->icon = icon; button->frame = frame; button->flags = flags; button->status_effect = status_effect; @@ -133,6 +128,30 @@ frame_button_create(struct frame *frame, const char *icon, return button; } +static struct frame_button * +frame_button_create(struct frame *frame, const char *icon_name, + enum frame_status status_effect, + enum frame_button_flags flags) +{ + struct frame_button *button; + cairo_surface_t *icon; + + icon = cairo_image_surface_create_from_png(icon_name); + if (cairo_surface_status(icon) != CAIRO_STATUS_SUCCESS) + goto error; + + button = frame_button_create_from_surface(frame, icon, status_effect, + flags); + if (!button) + goto error; + + return button; + +error: + cairo_surface_destroy(icon); + return NULL; +} + static void frame_button_destroy(struct frame_button *button) { @@ -305,7 +324,7 @@ frame_destroy(struct frame *frame) struct frame * frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons, - const char *title) + const char *title, cairo_surface_t *icon) { struct frame *frame; struct frame_button *button; @@ -332,10 +351,17 @@ frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons, } if (title) { - button = frame_button_create(frame, - DATADIR "/weston/icon_window.png", - FRAME_STATUS_MENU, - FRAME_BUTTON_CLICK_DOWN); + if (icon) { + button = frame_button_create_from_surface(frame, + icon, + FRAME_STATUS_MENU, + FRAME_BUTTON_CLICK_DOWN); + } else { + button = frame_button_create(frame, + DATADIR "/weston/icon_window.png", + FRAME_STATUS_MENU, + FRAME_BUTTON_CLICK_DOWN); + } if (!button) goto free_frame; } diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index be895e7a8..ac44a29af 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -138,6 +138,8 @@ struct weston_wm_window { xcb_window_t frame_id; struct frame *frame; cairo_surface_t *cairo_surface; + int icon; + cairo_surface_t *icon_surface; uint32_t surface_id; struct weston_surface *surface; struct weston_desktop_xwayland_surface *shsurf; @@ -473,6 +475,7 @@ weston_wm_window_read_properties(struct weston_wm_window *window) { wm->atom.net_wm_state, TYPE_NET_WM_STATE, NULL }, { wm->atom.net_wm_window_type, XCB_ATOM_ATOM, F(type) }, { wm->atom.net_wm_name, XCB_ATOM_STRING, F(name) }, + { wm->atom.net_wm_icon, XCB_ATOM_CARDINAL, F(icon) }, { wm->atom.net_wm_pid, XCB_ATOM_CARDINAL, F(pid) }, { wm->atom.motif_wm_hints, TYPE_MOTIF_WM_HINTS, NULL }, { wm->atom.wm_client_machine, XCB_ATOM_WM_CLIENT_MACHINE, F(machine) }, @@ -988,8 +991,9 @@ weston_wm_window_create_frame(struct weston_wm_window *window) buttons |= FRAME_BUTTON_MAXIMIZE; window->frame = frame_create(window->wm->theme, - window->width, window->height, - buttons, window->name); + window->width, window->height, + buttons, window->name, + window->icon_surface); frame_resize_inside(window->frame, window->width, window->height); weston_wm_window_get_frame_size(window, &width, &height); @@ -1347,6 +1351,53 @@ weston_wm_window_schedule_repaint(struct weston_wm_window *window) weston_wm_window_do_repaint, window); } +static void +weston_wm_handle_icon(struct weston_wm *wm, struct weston_wm_window *window) +{ + xcb_get_property_reply_t *reply; + xcb_get_property_cookie_t cookie; + uint32_t length; + uint32_t *data, width, height; + cairo_surface_t *new_surface; + + /* TODO: icons don’t have any specified order, we should pick the + * closest one to the target dimension instead of the first one. */ + + cookie = xcb_get_property(wm->conn, 0, window->id, + wm->atom.net_wm_icon, XCB_ATOM_ANY, 0, + UINT32_MAX); + reply = xcb_get_property_reply(wm->conn, cookie, NULL); + length = xcb_get_property_value_length(reply); + + /* This is in 32-bit words, not in bytes. */ + if (length < 2) + return; + + data = xcb_get_property_value(reply); + width = *data++; + height = *data++; + + /* Some checks against malformed input. */ + if (width == 0 || height == 0 || length < 2 + width * height) + return; + + new_surface = + cairo_image_surface_create_for_data((unsigned char *)data, + CAIRO_FORMAT_ARGB32, + width, height, width * 4); + + /* Bail out in case anything wrong happened during surface creation. */ + if (cairo_surface_status(new_surface) != CAIRO_STATUS_SUCCESS) { + cairo_surface_destroy(new_surface); + return; + } + + if (window->icon_surface) + cairo_surface_destroy(window->icon_surface); + + window->icon_surface = new_surface; +} + static void weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *event) { @@ -1367,6 +1418,16 @@ weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *even read_and_dump_property(wm, property_notify->window, property_notify->atom); + if (property_notify->atom == wm->atom.net_wm_icon) { + if (property_notify->state != XCB_PROPERTY_DELETE) { + weston_wm_handle_icon(wm, window); + } else { + cairo_surface_destroy(window->icon_surface); + window->icon_surface = NULL; + } + weston_wm_window_schedule_repaint(window); + } + if (property_notify->atom == wm->atom.net_wm_name || property_notify->atom == XCB_ATOM_WM_NAME) weston_wm_window_schedule_repaint(window); From bac2c549f009fda3cf5e2aa6e7665be7511f8cad Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 18 Jan 2018 12:32:08 +0000 Subject: [PATCH 0280/1642] xwayland: Explicitly link with xcb-shape XWM uses xcb-shape as of 332d1892bb, to exclude the shadow from the input region. However, it does not explicitly link xcb-shape for the new symbols; on one of my machines this is pulled in as a transient dependency (masking the issue), but apparently not the other. Solve it by explicitly linking xcb-shape and requiring it in configure. Signed-off-by: Daniel Stone Reviewed-by: Quentin Glidic Fixes: 332d1892bb ("xwm: do not include shadow in input region") Cc: Ian Ray --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 285c2efdf..129ed6371 100644 --- a/configure.ac +++ b/configure.ac @@ -168,7 +168,7 @@ AC_ARG_ENABLE(xwayland-test, [ --enable-xwayland-test],, AM_CONDITIONAL(ENABLE_XWAYLAND, test x$enable_xwayland = xyes) AM_CONDITIONAL(ENABLE_XWAYLAND_TEST, test x$enable_xwayland = xyes -a x$enable_xwayland_test = xyes) if test x$enable_xwayland = xyes; then - PKG_CHECK_MODULES([XWAYLAND], xcb xcb-xfixes xcb-composite xcursor cairo-xcb) + PKG_CHECK_MODULES([XWAYLAND], xcb xcb-xfixes xcb-composite xcb-shape xcursor cairo-xcb) AC_DEFINE([BUILD_XWAYLAND], [1], [Build the X server launcher]) AC_ARG_WITH(xserver-path, AS_HELP_STRING([--with-xserver-path=PATH], From 551b8054304159784cf4819737aae29d59bb1607 Mon Sep 17 00:00:00 2001 From: Greg V Date: Sun, 17 Dec 2017 22:04:36 +0300 Subject: [PATCH 0281/1642] Use $(SED) to make sure GNU sed is used FreeBSD's default sed is not compatible with this expression. Reviewed-by: Daniel Stone --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 64cceec47..e224d606b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1619,7 +1619,7 @@ $(if $(findstring unstable,$1),unstable,stable) endef define protoname -$(shell echo $1 | sed 's/\([a-z\-]\+\)-[a-z]\+-v[0-9]\+/\1/') +$(shell echo $1 | $(SED) 's/\([a-z\-]\+\)-[a-z]\+-v[0-9]\+/\1/') endef protocol/%-protocol.c : $(WAYLAND_PROTOCOLS_DATADIR)/$$(call protostability,$$*)/$$(call protoname,$$*)/$$*.xml From bf31f6cfb92a64650b286379c17535b836501dd0 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Wed, 31 Jan 2018 15:15:45 +0900 Subject: [PATCH 0282/1642] desktop-shell: remove surface listener when surface is destroyed There may be race condition between destroying surface and destroying output. If handle_output_destroy() is called after surface is destroyed, illegal memory access occurs when surface destroy signals is unregistered from the panel/background. This patch fixes this issue and removes unnecessary initialization for panel surface listener. Signed-off-by: Tomohito Esaki Reviewed-by: Pekka Paalanen --- desktop-shell/shell.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index a2a93e2f8..de76ebe57 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2939,6 +2939,7 @@ handle_background_surface_destroy(struct wl_listener *listener, void *data) container_of(listener, struct shell_output, background_surface_listener); weston_log("background surface gone\n"); + wl_list_remove(&output->background_surface_listener.link); output->background_surface = NULL; } @@ -3023,6 +3024,7 @@ handle_panel_surface_destroy(struct wl_listener *listener, void *data) container_of(listener, struct shell_output, panel_surface_listener); weston_log("panel surface gone\n"); + wl_list_remove(&output->panel_surface_listener.link); output->panel_surface = NULL; } @@ -4714,8 +4716,10 @@ handle_output_destroy(struct wl_listener *listener, void *data) shell_for_each_layer(shell, shell_output_destroy_move_layer, output); - wl_list_remove(&output_listener->panel_surface_listener.link); - wl_list_remove(&output_listener->background_surface_listener.link); + if (output_listener->panel_surface) + wl_list_remove(&output_listener->panel_surface_listener.link); + if (output_listener->background_surface) + wl_list_remove(&output_listener->background_surface_listener.link); wl_list_remove(&output_listener->destroy_listener.link); wl_list_remove(&output_listener->link); free(output_listener); @@ -4761,7 +4765,6 @@ create_shell_output(struct desktop_shell *shell, shell_output->output = output; shell_output->shell = shell; shell_output->destroy_listener.notify = handle_output_destroy; - wl_list_init(&shell_output->panel_surface_listener.link); wl_signal_add(&output->destroy_signal, &shell_output->destroy_listener); wl_list_insert(shell->output_list.prev, &shell_output->link); From 7a314d63283b9a8ffcc02d9a465d6f6467b861b2 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 26 Jan 2018 18:47:56 +0200 Subject: [PATCH 0283/1642] libweston: Make weston_keyboard destruction safe Ensure the server can safely handle client requests for wl_keyboard resources that have become inert due to a weston_keyboard object destruction. This change involves, among other things, setting the weston_keyboard object, instead of the weston_seat object, as the user data for wl_keyboard resources. Although this is not strictly required at the moment (since no code is using the wl_keyboard user data), it makes the code safer: * It makes more sense conceptually. * It is consistent with how wl_pointer resources are handled. * It allows us to clear the user data during weston_keyboard destruction, so other code can check whether the resource is inert. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- libweston/input.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/libweston/input.c b/libweston/input.c index 94a3423a9..6db476f73 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -1144,7 +1144,18 @@ weston_xkb_info_destroy(struct weston_xkb_info *xkb_info); WL_EXPORT void weston_keyboard_destroy(struct weston_keyboard *keyboard) { - /* XXX: What about keyboard->resource_list? */ + struct wl_resource *resource; + + wl_resource_for_each(resource, &keyboard->resource_list) { + wl_resource_set_user_data(resource, NULL); + } + + wl_resource_for_each(resource, &keyboard->focus_resource_list) { + wl_resource_set_user_data(resource, NULL); + } + + wl_list_remove(&keyboard->resource_list); + wl_list_remove(&keyboard->focus_resource_list); xkb_state_unref(keyboard->xkb_state.state); if (keyboard->xkb_info) @@ -2479,7 +2490,7 @@ seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, * focused */ wl_list_insert(&keyboard->resource_list, wl_resource_get_link(cr)); wl_resource_set_implementation(cr, &keyboard_interface, - seat, unbind_resource); + keyboard, unbind_resource); if (wl_resource_get_version(cr) >= WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION) { wl_keyboard_send_repeat_info(cr, From b0b598c73c621206fd6e306fc976bad40e94fd12 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 26 Jan 2018 18:47:57 +0200 Subject: [PATCH 0284/1642] libweston: Make weston_touch destruction safe Ensure the server can safely handle client requests for wl_touch resources that have become inert due to a weston_touch object destruction. This change involves, among other things, setting the weston_touch object, instead of the weston_seat object, as the user data for wl_touch resources. Although this is not strictly required at the moment (since no code is using the wl_touch user data), it makes the code safer: * It makes more sense conceptually. * It is consistent with how wl_pointer resources are handled. * It allows us to clear the user data during weston_touch destruction, so other code can check whether the resource is inert. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- libweston/input.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libweston/input.c b/libweston/input.c index 6db476f73..96cded47c 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -1199,8 +1199,18 @@ weston_touch_create(void) WL_EXPORT void weston_touch_destroy(struct weston_touch *touch) { - /* XXX: What about touch->resource_list? */ + struct wl_resource *resource; + + wl_resource_for_each(resource, &touch->resource_list) { + wl_resource_set_user_data(resource, NULL); + } + + wl_resource_for_each(resource, &touch->focus_resource_list) { + wl_resource_set_user_data(resource, NULL); + } + wl_list_remove(&touch->resource_list); + wl_list_remove(&touch->focus_resource_list); wl_list_remove(&touch->focus_view_listener.link); wl_list_remove(&touch->focus_resource_listener.link); free(touch); @@ -2574,7 +2584,7 @@ seat_get_touch(struct wl_client *client, struct wl_resource *resource, wl_resource_get_link(cr)); } wl_resource_set_implementation(cr, &touch_interface, - seat, unbind_resource); + touch, unbind_resource); } static void From 85f8432911cdea1350709ab9a9ca525a0eefadff Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 26 Jan 2018 18:48:00 +0200 Subject: [PATCH 0285/1642] tests: Support weston_test request for adding a test seat Support adding a test seat using the weston_test.device_add request. This will be used in tests in upcoming commits where we will need to re-add the seat after having it removed. We only support one test seat at the moment, so this commit also introduces checks to ensure the client doesn't try to create multiple test seats or try to remove an already removed test seat. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- tests/weston-test.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/tests/weston-test.c b/tests/weston-test.c index 80b3d65b0..9a2fd2869 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -50,6 +50,7 @@ struct weston_test { struct weston_layer layer; struct weston_process process; struct weston_seat seat; + bool is_seat_initialized; }; struct weston_test_surface { @@ -76,6 +77,22 @@ test_client_sigchld(struct weston_process *process, int status) wl_display_terminate(test->compositor->wl_display); } +static int +test_seat_init(struct weston_test *test) +{ + /* create our own seat */ + weston_seat_init(&test->seat, test->compositor, "test-seat"); + test->is_seat_initialized = true; + + /* add devices */ + weston_seat_init_pointer(&test->seat); + if (weston_seat_init_keyboard(&test->seat, NULL) < 0) + return -1; + weston_seat_init_touch(&test->seat); + + return 0; +} + static struct weston_seat * get_seat(struct weston_test *test) { @@ -253,7 +270,10 @@ device_release(struct wl_client *client, } else if (strcmp(device, "touch") == 0) { weston_seat_release_touch(seat); } else if (strcmp(device, "seat") == 0) { + assert(test->is_seat_initialized && + "Trying to release already released test seat"); weston_seat_release(seat); + test->is_seat_initialized = false; } else { assert(0 && "Unsupported device"); } @@ -272,6 +292,10 @@ device_add(struct wl_client *client, weston_seat_init_keyboard(seat, NULL); } else if (strcmp(device, "touch") == 0) { weston_seat_init_touch(seat); + } else if (strcmp(device, "seat") == 0) { + assert(!test->is_seat_initialized && + "Trying to add already added test seat"); + test_seat_init(test); } else { assert(0 && "Unsupported device"); } @@ -611,14 +635,8 @@ wet_module_init(struct weston_compositor *ec, test, bind_test) == NULL) return -1; - /* create our own seat */ - weston_seat_init(&test->seat, ec, "test-seat"); - - /* add devices */ - weston_seat_init_pointer(&test->seat); - if (weston_seat_init_keyboard(&test->seat, NULL) < 0) + if (test_seat_init(test) == -1) return -1; - weston_seat_init_touch(&test->seat); loop = wl_display_get_event_loop(ec->wl_display); wl_event_loop_add_idle(loop, idle_launch_client, test); From 087ddf04e2f9065f4c3cd17f6baac9e9d8047d20 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 14 Feb 2017 17:51:30 +0000 Subject: [PATCH 0286/1642] compositor-drm: Track unused connectors and CRTCs Rather than a more piecemeal approach at backend creation, explicitly track connectors and CRTCs we do not intend to use, so we can ensure they are disabled where appropriate. When we have an updated list of connector and CRTC IDs, we add any which are not owned by an enabled drm_output to the list. We remove them from the list when drm_output_repaint() is called for that output, and re-add them when the output is disabled or destroyed. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 89 +++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index fe59bf52f..f2d99b97f 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -188,6 +188,10 @@ struct drm_backend { void *repaint_data; + /* Connector and CRTC IDs not used by any enabled output. */ + struct wl_array unused_connectors; + struct wl_array unused_crtcs; + int cursors_are_broken; bool universal_planes; @@ -386,6 +390,26 @@ static struct gl_renderer_interface *gl_renderer; static const char default_seat[] = "seat0"; +static void +wl_array_remove_uint32(struct wl_array *array, uint32_t elm) +{ + uint32_t *pos, *end; + + end = (uint32_t *) ((char *) array->data + array->size); + + wl_array_for_each(pos, array) { + if (*pos != elm) + continue; + + array->size -= sizeof(*pos); + if (pos + 1 == end) + break; + + memmove(pos, pos + 1, (char *) end - (char *) (pos + 1)); + break; + } +} + static inline struct drm_output * to_drm_output(struct weston_output *base) { @@ -1694,7 +1718,6 @@ drm_output_repaint(struct weston_output *output_base, pending_state, DRM_OUTPUT_STATE_CLEAR_PLANES); - /* If disable_planes is set then assign_planes() wasn't * called for this render, so we could still have a stale * cursor plane set up. @@ -1710,6 +1733,10 @@ drm_output_repaint(struct weston_output *output_base, if (!scanout_state || !scanout_state->fb) goto err; + wl_array_remove_uint32(&backend->unused_connectors, + output->connector_id); + wl_array_remove_uint32(&backend->unused_crtcs, output->crtc_id); + /* The legacy SetCrtc API doesn't allow us to do scaling, and the * legacy PageFlip API doesn't allow us to do clipping either. */ assert(scanout_state->src_x == 0); @@ -3989,6 +4016,7 @@ drm_output_deinit(struct weston_output *base) { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); + uint32_t *unused; if (b->use_pixman) drm_output_fini_pixman(output); @@ -4010,6 +4038,11 @@ drm_output_deinit(struct weston_output *base) drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); } } + + unused = wl_array_add(&b->unused_connectors, sizeof(*unused)); + *unused = output->connector_id; + unused = wl_array_add(&b->unused_crtcs, sizeof(*unused)); + *unused = output->crtc_id; } static void @@ -4105,6 +4138,51 @@ drm_output_disable(struct weston_output *base) return 0; } +/** + * Update the list of unused connectors and CRTCs + * + * This keeps the unused_connectors and unused_crtcs arrays up to date. + * + * @param b Weston backend structure + * @param resources DRM resources for this device + */ +static void +drm_backend_update_unused_outputs(struct drm_backend *b, drmModeRes *resources) +{ + int i; + + wl_array_release(&b->unused_connectors); + wl_array_init(&b->unused_connectors); + + for (i = 0; i < resources->count_connectors; i++) { + struct drm_output *output; + uint32_t *connector_id; + + output = drm_output_find_by_connector(b, resources->connectors[i]); + if (output && output->base.enabled) + continue; + + connector_id = wl_array_add(&b->unused_connectors, + sizeof(*connector_id)); + *connector_id = resources->connectors[i]; + } + + wl_array_release(&b->unused_crtcs); + wl_array_init(&b->unused_crtcs); + + for (i = 0; i < resources->count_crtcs; i++) { + struct drm_output *output; + uint32_t *crtc_id; + + output = drm_output_find_by_crtc(b, resources->crtcs[i]); + if (output && output->base.enabled) + continue; + + crtc_id = wl_array_add(&b->unused_crtcs, sizeof(*crtc_id)); + *crtc_id = resources->crtcs[i]; + } +} + /** * Create a Weston output structure * @@ -4265,6 +4343,8 @@ create_outputs(struct drm_backend *b, struct udev_device *drm_device) } } + drm_backend_update_unused_outputs(b, resources); + if (wl_list_empty(&b->compositor->output_list) && wl_list_empty(&b->compositor->pending_output_list)) weston_log("No currently active connector found.\n"); @@ -4356,6 +4436,8 @@ update_outputs(struct drm_backend *b, struct udev_device *drm_device) drm_output_destroy(&output->base); } + drm_backend_update_unused_outputs(b, resources); + free(connected); drmModeFreeResources(resources); } @@ -4422,6 +4504,9 @@ drm_destroy(struct weston_compositor *ec) weston_launcher_destroy(ec->launcher); + wl_array_release(&b->unused_crtcs); + wl_array_release(&b->unused_connectors); + close(b->drm.fd); free(b); } @@ -4854,6 +4939,8 @@ drm_backend_create(struct weston_compositor *compositor, return NULL; b->drm.fd = -1; + wl_array_init(&b->unused_crtcs); + wl_array_init(&b->unused_connectors); /* * KMS support for hardware planes cannot properly synchronize From 6020f478c7738548c815ab0fd1e673c47a537321 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 5 Feb 2018 15:46:20 +0000 Subject: [PATCH 0287/1642] compositor-drm: Disable unused CRTCs/connectors If we have an unused CRTC or connector, explicitly disable it during the end of the repaint cycle, or when we get VT-switched back in. This commit moves state_invalid from an output property to a backend property, as the unused CRTCs or connectors are likely not tracked by drm_outputs. This matches the mechanics of later commits, where we move to a global repaint-flush hook, applying the state for all outputs in one go. The output state_invalid flag originally provoked full changes on output creation (via setting the flag at output enable time) and session enter. For the new-output case, we will not have any FB in output->state_cur, so we still take the same path in repaint as if state_invalid were set. At session enter, we preserve the existing behaviour: as start_repaint_loop will fail when state_invalid is set, all outputs will be scheduled for repaint together, and state_invalid will not be cleared until after all outputs have been repainted, inside repaint_flush. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index f2d99b97f..22bf75aec 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -188,6 +188,8 @@ struct drm_backend { void *repaint_data; + bool state_invalid; + /* Connector and CRTC IDs not used by any enabled output. */ struct wl_array unused_connectors; struct wl_array unused_crtcs; @@ -351,8 +353,6 @@ struct drm_output { enum dpms_enum dpms; struct backlight *backlight; - bool state_invalid; - int vblank_pending; int page_flip_pending; int destroy_pending; @@ -1751,7 +1751,7 @@ drm_output_repaint(struct weston_output *output_base, assert(scanout_state->dest_h == scanout_state->src_h >> 16); mode = to_drm_mode(output->base.current_mode); - if (output->state_invalid || !scanout_plane->state_cur->fb || + if (backend->state_invalid || !scanout_plane->state_cur->fb || scanout_plane->state_cur->fb->stride != scanout_state->fb->stride) { ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, scanout_state->fb->fb_id, @@ -1763,8 +1763,6 @@ drm_output_repaint(struct weston_output *output_base, goto err; } output_base->set_dpms(output_base, WESTON_DPMS_ON); - - output->state_invalid = false; } if (drmModePageFlip(backend->drm.fd, output->crtc_id, @@ -1872,7 +1870,7 @@ drm_output_start_repaint_loop(struct weston_output *output_base) /* Need to smash all state in from scratch; current timings might not * be what we want, page flip might not work, etc. */ - if (output->state_invalid) + if (backend->state_invalid) goto finish_frame; assert(scanout_plane->state_cur->output == output); @@ -2036,12 +2034,26 @@ drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) struct drm_backend *b = to_drm_backend(compositor); struct drm_pending_state *pending_state = repaint_data; struct drm_output_state *output_state, *tmp; + uint32_t *unused; + + if (b->state_invalid) { + /* If we need to reset all our state (e.g. because we've + * just started, or just been VT-switched in), explicitly + * disable all the CRTCs we aren't using. This also disables + * all connectors on these CRTCs, so we don't need to do that + * separately with the pre-atomic API. */ + wl_array_for_each(unused, &b->unused_crtcs) + drmModeSetCrtc(b->drm.fd, *unused, 0, 0, 0, NULL, 0, + NULL); + } wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, link) { drm_output_assign_state(output_state, DRM_STATE_APPLY_ASYNC); } + b->state_invalid = false; + drm_pending_state_free(pending_state); b->repaint_data = NULL; } @@ -2662,7 +2674,7 @@ drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mo * sledgehammer modeswitch first, and only later showing new * content. */ - output->state_invalid = true; + b->state_invalid = true; if (b->use_pixman) { drm_output_fini_pixman(output); @@ -4003,8 +4015,6 @@ drm_output_enable(struct weston_output *base) output->connector->count_modes == 0 ? ", built-in" : ""); - output->state_invalid = true; - return 0; err: @@ -4523,10 +4533,7 @@ session_notify(struct wl_listener *listener, void *data) weston_log("activating session\n"); weston_compositor_wake(compositor); weston_compositor_damage_all(compositor); - - wl_list_for_each(output, &compositor->output_list, base.link) - output->state_invalid = true; - + b->state_invalid = true; udev_input_enable(&b->input); } else { weston_log("deactivating session\n"); @@ -4938,6 +4945,7 @@ drm_backend_create(struct weston_compositor *compositor, if (b == NULL) return NULL; + b->state_invalid = true; b->drm.fd = -1; wl_array_init(&b->unused_crtcs); wl_array_init(&b->unused_connectors); From a08512f464cd86cb45753488c96c9cda051d2d19 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 8 Nov 2016 17:46:10 +0000 Subject: [PATCH 0288/1642] compositor-drm: Move repaint state application to flush Split repaint into two stages, as implied by the grouped-repaint interface: drm_output_repaint generates the repaint state only, and drm_repaint_flush applies it. This also moves DPMS into output state. Previously, the usual way to DPMS off was that repaint would be called and apply its state, followed by set_dpms being called afterwards to push the DPMS state separately. As this happens before the repaint_flush hook, with no change to DPMS we would set DPMS off, then immediately re-enable the output by posting the repaint. Not ideal. Moving DPMS application at the same time complicates this patch, but I couldn't find a way to split it; if we keep set_dpms before begin_flush then we break DPMS off, or if we try to move DPMS to output state before using the repaint flush, we get stuck as the repaint hook generates an asynchronous state update, followed immediately by set_dpms generating a synchronous state update. In drm_output_update_complete, the *_pending flags are cleared before any of the pending actions are taken; this ensures that the actions cannot recurse. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 377 ++++++++++++++++++++++++++++++------- 1 file changed, 304 insertions(+), 73 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 22bf75aec..21c5c166b 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -274,6 +274,7 @@ struct drm_output_state { struct drm_pending_state *pending_state; struct drm_output *output; struct wl_list link; + enum dpms_enum dpms; struct wl_list plane_list; }; @@ -350,13 +351,13 @@ struct drm_output { /* Holds the properties for the connector */ struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT]; - enum dpms_enum dpms; struct backlight *backlight; int vblank_pending; int page_flip_pending; int destroy_pending; int disable_pending; + int dpms_off_pending; struct drm_fb *gbm_cursor_fb[2]; struct drm_plane *cursor_plane; @@ -1157,6 +1158,7 @@ drm_output_state_alloc(struct drm_output *output, assert(state); state->output = output; + state->dpms = WESTON_DPMS_OFF; state->pending_state = pending_state; if (pending_state) wl_list_insert(&pending_state->output_list, &state->link); @@ -1233,6 +1235,30 @@ drm_output_state_free(struct drm_output_state *state) free(state); } +/** + * Get output state to disable output + * + * Returns a pointer to an output_state object which can be used to disable + * an output (e.g. DPMS off). + * + * @param pending_state The pending state object owning this update + * @param output The output to disable + * @returns A drm_output_state to disable the output + */ +static struct drm_output_state * +drm_output_get_disable_state(struct drm_pending_state *pending_state, + struct drm_output *output) +{ + struct drm_output_state *output_state; + + output_state = drm_output_state_duplicate(output->state_cur, + pending_state, + DRM_OUTPUT_STATE_CLEAR_PLANES); + output_state->dpms = WESTON_DPMS_OFF; + + return output_state; +} + /** * Allocate a new drm_pending_state * @@ -1305,6 +1331,8 @@ drm_pending_state_get_output(struct drm_pending_state *pending_state, return NULL; } +static int drm_pending_state_apply_sync(struct drm_pending_state *state); + /** * Mark a drm_output_state (the output's last state) as complete. This handles * any post-completion actions such as updating the repaint timer, disabling the @@ -1314,6 +1342,7 @@ static void drm_output_update_complete(struct drm_output *output, uint32_t flags, unsigned int sec, unsigned int usec) { + struct drm_backend *b = to_drm_backend(output->base.compositor); struct drm_plane_state *ps; struct timespec ts; @@ -1328,11 +1357,29 @@ drm_output_update_complete(struct drm_output *output, uint32_t flags, output->state_last = NULL; if (output->destroy_pending) { + output->destroy_pending = 0; + output->disable_pending = 0; + output->dpms_off_pending = 0; drm_output_destroy(&output->base); return; } else if (output->disable_pending) { - weston_output_disable(&output->base); output->disable_pending = 0; + output->dpms_off_pending = 0; + weston_output_disable(&output->base); + return; + } else if (output->dpms_off_pending) { + struct drm_pending_state *pending = drm_pending_state_alloc(b); + output->dpms_off_pending = 0; + drm_output_get_disable_state(pending, output); + drm_pending_state_apply_sync(pending); + return; + } else if (output->state_cur->dpms == WESTON_DPMS_OFF && + output->base.repaint_status != REPAINT_AWAITING_COMPLETION) { + /* DPMS can happen to us either in the middle of a repaint + * cycle (when we have painted fresh content, only to throw it + * away for DPMS off), or at any other random point. If the + * latter is true, then we cannot go through finish_frame, + * because the repaint machinery does not expect this. */ return; } @@ -1395,7 +1442,6 @@ drm_output_assign_state(struct drm_output_state *state, } } - static int drm_view_transform_supported(struct weston_view *ev) { @@ -1688,36 +1734,20 @@ drm_waitvblank_pipe(struct drm_output *output) } static int -drm_output_repaint(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) +drm_output_apply_state(struct drm_output_state *state) { - struct drm_pending_state *pending_state = repaint_data; - struct drm_output_state *state = NULL; - struct drm_output *output = to_drm_output(output_base); - struct drm_backend *backend = - to_drm_backend(output->base.compositor); + struct drm_output *output = state->output; + struct drm_backend *backend = to_drm_backend(output->base.compositor); struct drm_plane *scanout_plane = output->scanout_plane; + struct drm_property_info *dpms_prop = + &output->props_conn[WDRM_CONNECTOR_DPMS]; struct drm_plane_state *scanout_state; struct drm_plane_state *ps; struct drm_plane *p; struct drm_mode *mode; + struct timespec now; int ret = 0; - if (output->disable_pending || output->destroy_pending) - goto err; - - assert(!output->state_last); - - /* If planes have been disabled in the core, we might not have - * hit assign_planes at all, so might not have valid output state - * here. */ - state = drm_pending_state_get_output(pending_state, output); - if (!state) - state = drm_output_state_duplicate(output->state_cur, - pending_state, - DRM_OUTPUT_STATE_CLEAR_PLANES); - /* If disable_planes is set then assign_planes() wasn't * called for this render, so we could still have a stale * cursor plane set up. @@ -1728,14 +1758,44 @@ drm_output_repaint(struct weston_output *output_base, output->cursor_plane->base.y = INT32_MIN; } - drm_output_render(state, damage); - scanout_state = drm_output_state_get_plane(state, scanout_plane); - if (!scanout_state || !scanout_state->fb) - goto err; + if (state->dpms != WESTON_DPMS_ON) { + wl_list_for_each(ps, &state->plane_list, link) { + p = ps->plane; + assert(ps->fb == NULL); + assert(ps->output == NULL); - wl_array_remove_uint32(&backend->unused_connectors, - output->connector_id); - wl_array_remove_uint32(&backend->unused_crtcs, output->crtc_id); + if (p->type != WDRM_PLANE_TYPE_OVERLAY) + continue; + + ret = drmModeSetPlane(backend->drm.fd, p->plane_id, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + if (ret) + weston_log("drmModeSetPlane failed disable: %m\n"); + } + + if (output->cursor_plane) { + ret = drmModeSetCursor(backend->drm.fd, output->crtc_id, + 0, 0, 0); + if (ret) + weston_log("drmModeSetCursor failed disable: %m\n"); + } + + ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, 0, 0, 0, + &output->connector_id, 0, NULL); + if (ret) + weston_log("drmModeSetCrtc failed disabling: %m\n"); + + drm_output_assign_state(state, DRM_STATE_APPLY_SYNC); + weston_compositor_read_presentation_clock(output->base.compositor, &now); + drm_output_update_complete(output, + WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION, + now.tv_sec, now.tv_nsec / 1000); + + return 0; + } + + scanout_state = + drm_output_state_get_existing_plane(state, scanout_plane); /* The legacy SetCrtc API doesn't allow us to do scaling, and the * legacy PageFlip API doesn't allow us to do clipping either. */ @@ -1762,7 +1822,6 @@ drm_output_repaint(struct weston_output *output_base, weston_log("set mode failed: %m\n"); goto err; } - output_base->set_dpms(output_base, WESTON_DPMS_ON); } if (drmModePageFlip(backend->drm.fd, output->crtc_id, @@ -1828,13 +1887,159 @@ drm_output_repaint(struct weston_output *output_base, } } + if (dpms_prop->prop_id && state->dpms != output->state_cur->dpms) { + ret = drmModeConnectorSetProperty(backend->drm.fd, + output->connector_id, + dpms_prop->prop_id, + state->dpms); + if (ret) { + weston_log("DRM: DPMS: failed property set for %s\n", + output->base.name); + } + } + + drm_output_assign_state(state, DRM_STATE_APPLY_ASYNC); + return 0; err: output->cursor_view = NULL; - drm_output_state_free(state); + return -1; +} + +/** + * Applies all of a pending_state asynchronously: the primary entry point for + * applying KMS state to a device. Updates the state for all outputs in the + * pending_state, as well as disabling any unclaimed outputs. + * + * Unconditionally takes ownership of pending_state, and clears state_invalid. + */ +static int +drm_pending_state_apply(struct drm_pending_state *pending_state) +{ + struct drm_backend *b = pending_state->backend; + struct drm_output_state *output_state, *tmp; + uint32_t *unused; + if (b->state_invalid) { + /* If we need to reset all our state (e.g. because we've + * just started, or just been VT-switched in), explicitly + * disable all the CRTCs we aren't using. This also disables + * all connectors on these CRTCs, so we don't need to do that + * separately with the pre-atomic API. */ + wl_array_for_each(unused, &b->unused_crtcs) + drmModeSetCrtc(b->drm.fd, *unused, 0, 0, 0, NULL, 0, + NULL); + } + + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, + link) { + struct drm_output *output = output_state->output; + int ret; + + ret = drm_output_apply_state(output_state); + if (ret != 0) { + weston_log("Couldn't apply state for output %s\n", + output->base.name); + } + } + + b->state_invalid = false; + + assert(wl_list_empty(&pending_state->output_list)); + + drm_pending_state_free(pending_state); + + return 0; +} + +/** + * The synchronous version of drm_pending_state_apply. May only be used to + * disable outputs. Does so synchronously: the request is guaranteed to have + * completed on return, and the output will not be touched afterwards. + * + * Unconditionally takes ownership of pending_state, and clears state_invalid. + */ +static int +drm_pending_state_apply_sync(struct drm_pending_state *pending_state) +{ + struct drm_backend *b = pending_state->backend; + struct drm_output_state *output_state, *tmp; + uint32_t *unused; + + if (b->state_invalid) { + /* If we need to reset all our state (e.g. because we've + * just started, or just been VT-switched in), explicitly + * disable all the CRTCs we aren't using. This also disables + * all connectors on these CRTCs, so we don't need to do that + * separately with the pre-atomic API. */ + wl_array_for_each(unused, &b->unused_crtcs) + drmModeSetCrtc(b->drm.fd, *unused, 0, 0, 0, NULL, 0, + NULL); + } + + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, + link) { + int ret; + + assert(output_state->dpms == WESTON_DPMS_OFF); + ret = drm_output_apply_state(output_state); + if (ret != 0) { + weston_log("Couldn't apply state for output %s\n", + output_state->output->base.name); + } + } + + b->state_invalid = false; + + assert(wl_list_empty(&pending_state->output_list)); + + drm_pending_state_free(pending_state); + + return 0; +} + +static int +drm_output_repaint(struct weston_output *output_base, + pixman_region32_t *damage, + void *repaint_data) +{ + struct drm_pending_state *pending_state = repaint_data; + struct drm_output *output = to_drm_output(output_base); + struct drm_backend *backend = to_drm_backend(output_base->compositor); + struct drm_output_state *state = NULL; + struct drm_plane_state *scanout_state; + + if (output->disable_pending || output->destroy_pending) + goto err; + + assert(!output->state_last); + + /* If planes have been disabled in the core, we might not have + * hit assign_planes at all, so might not have valid output state + * here. */ + state = drm_pending_state_get_output(pending_state, output); + if (!state) + state = drm_output_state_duplicate(output->state_cur, + pending_state, + DRM_OUTPUT_STATE_CLEAR_PLANES); + state->dpms = WESTON_DPMS_ON; + + drm_output_render(state, damage); + scanout_state = drm_output_state_get_plane(state, + output->scanout_plane); + if (!scanout_state || !scanout_state->fb) + goto err; + + wl_array_remove_uint32(&backend->unused_connectors, + output->connector_id); + wl_array_remove_uint32(&backend->unused_crtcs, output->crtc_id); + + return 0; + +err: + drm_output_state_free(state); return -1; } @@ -2033,28 +2238,8 @@ drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) { struct drm_backend *b = to_drm_backend(compositor); struct drm_pending_state *pending_state = repaint_data; - struct drm_output_state *output_state, *tmp; - uint32_t *unused; - if (b->state_invalid) { - /* If we need to reset all our state (e.g. because we've - * just started, or just been VT-switched in), explicitly - * disable all the CRTCs we aren't using. This also disables - * all connectors on these CRTCs, so we don't need to do that - * separately with the pre-atomic API. */ - wl_array_for_each(unused, &b->unused_crtcs) - drmModeSetCrtc(b->drm.fd, *unused, 0, 0, 0, NULL, 0, - NULL); - } - - wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, - link) { - drm_output_assign_state(output_state, DRM_STATE_APPLY_ASYNC); - } - - b->state_invalid = false; - - drm_pending_state_free(pending_state); + drm_pending_state_apply(pending_state); b->repaint_data = NULL; } @@ -3234,28 +3419,72 @@ drm_set_backlight(struct weston_output *output_base, uint32_t value) backlight_set_brightness(output->backlight, new_brightness); } +/** + * Power output on or off + * + * The DPMS/power level of an output is used to switch it on or off. This + * is DRM's hook for doing so, which can called either as part of repaint, + * or independently of the repaint loop. + * + * If we are called as part of repaint, we simply set the relevant bit in + * state and return. + */ static void drm_set_dpms(struct weston_output *output_base, enum dpms_enum level) { struct drm_output *output = to_drm_output(output_base); - struct weston_compositor *ec = output_base->compositor; - struct drm_backend *b = to_drm_backend(ec); - struct drm_property_info *prop = - &output->props_conn[WDRM_CONNECTOR_DPMS]; + struct drm_backend *b = to_drm_backend(output_base->compositor); + struct drm_pending_state *pending_state = b->repaint_data; + struct drm_output_state *state; int ret; - if (!prop->prop_id) + if (output->state_cur->dpms == level) return; - ret = drmModeConnectorSetProperty(b->drm.fd, output->connector_id, - prop->prop_id, level); - if (ret) { - weston_log("DRM: DPMS: failed property set for %s\n", - output->base.name); + /* If we're being called during the repaint loop, then this is + * simple: discard any previously-generated state, and create a new + * state where we disable everything. When we come to flush, this + * will be applied. + * + * However, we need to be careful: we can be called whilst another + * output is in its repaint cycle (pending_state exists), but our + * output still has an incomplete state application outstanding. + * In that case, we need to wait until that completes. */ + if (pending_state && !output->state_last) { + /* The repaint loop already sets DPMS on; we don't need to + * explicitly set it on here, as it will already happen + * whilst applying the repaint state. */ + if (level == WESTON_DPMS_ON) + return; + + state = drm_pending_state_get_output(pending_state, output); + if (state) + drm_output_state_free(state); + state = drm_output_get_disable_state(pending_state, output); + return; + } + + /* As we throw everything away when disabling, just send us back through + * a repaint cycle. */ + if (level == WESTON_DPMS_ON) { + if (output->dpms_off_pending) + output->dpms_off_pending = 0; + weston_output_schedule_repaint(output_base); + return; + } + + /* If we've already got a request in the pipeline, then we need to + * park our DPMS request until that request has quiesced. */ + if (output->state_last) { + output->dpms_off_pending = 1; return; } - output->dpms = level; + pending_state = drm_pending_state_alloc(b); + drm_output_get_disable_state(pending_state, output); + ret = drm_pending_state_apply_sync(pending_state); + if (ret != 0) + weston_log("drm_set_dpms: couldn't disable output?\n"); } static const char * const connector_type_names[] = { @@ -4127,24 +4356,26 @@ drm_output_disable(struct weston_output *base) { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); + struct drm_pending_state *pending_state; + int ret; if (output->page_flip_pending || output->vblank_pending) { output->disable_pending = 1; return -1; } + weston_log("Disabling output %s\n", output->base.name); + pending_state = drm_pending_state_alloc(b); + drm_output_get_disable_state(pending_state, output); + ret = drm_pending_state_apply_sync(pending_state); + if (ret) + weston_log("Couldn't disable output %s\n", output->base.name); + if (output->base.enabled) drm_output_deinit(&output->base); output->disable_pending = 0; - weston_log("Disabling output %s\n", output->base.name); - drmModeSetCrtc(b->drm.fd, output->crtc_id, - 0, 0, 0, 0, 0, NULL); - - drm_output_state_free(output->state_cur); - output->state_cur = drm_output_state_alloc(output, NULL); - return 0; } From 8747f95682b3a52664353aa8c5e047d075e14149 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 29 Nov 2016 20:17:32 +0000 Subject: [PATCH 0289/1642] compositor-drm: Use apply_state for starting repaint Rather than open-coding it ourselves, use the new apply_state helper in drm_output_start_repaint. Signed-off-by: Daniel Stone Reported-by: Fabien DESSENNE Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 21c5c166b..85fef2af8 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2047,13 +2047,10 @@ static void drm_output_start_repaint_loop(struct weston_output *output_base) { struct drm_output *output = to_drm_output(output_base); - struct drm_pending_state *pending_state = NULL; - struct drm_output_state *state; - struct drm_plane_state *plane_state; + struct drm_pending_state *pending_state; struct drm_plane *scanout_plane = output->scanout_plane; struct drm_backend *backend = to_drm_backend(output_base->compositor); - uint32_t fb_id; struct timespec ts, tnow; struct timespec vbl2now; int64_t refresh_nsec; @@ -2109,44 +2106,23 @@ drm_output_start_repaint_loop(struct weston_output *output_base) /* Immediate query didn't provide valid timestamp. * Use pageflip fallback. */ - fb_id = scanout_plane->state_cur->fb->fb_id; assert(!output->page_flip_pending); assert(!output->state_last); pending_state = drm_pending_state_alloc(backend); - state = drm_output_state_duplicate(output->state_cur, pending_state, - DRM_OUTPUT_STATE_PRESERVE_PLANES); + drm_output_state_duplicate(output->state_cur, pending_state, + DRM_OUTPUT_STATE_PRESERVE_PLANES); - if (drmModePageFlip(backend->drm.fd, output->crtc_id, fb_id, - DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { - weston_log("queueing pageflip failed: %m\n"); + ret = drm_pending_state_apply(pending_state); + if (ret != 0) { + weston_log("applying repaint-start state failed: %m\n"); goto finish_frame; } - if (output->pageflip_timer) - wl_event_source_timer_update(output->pageflip_timer, - backend->pageflip_timeout); - - wl_list_for_each(plane_state, &state->plane_list, link) { - if (plane_state->plane->type != WDRM_PLANE_TYPE_OVERLAY) - continue; - - vbl.request.type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT; - vbl.request.type |= drm_waitvblank_pipe(output); - vbl.request.sequence = 1; - vbl.request.signal = (unsigned long) plane_state; - drmWaitVBlank(backend->drm.fd, &vbl); - } - - drm_output_assign_state(state, DRM_STATE_APPLY_ASYNC); - drm_pending_state_free(pending_state); - return; finish_frame: - drm_pending_state_free(pending_state); - /* if we cannot page-flip, immediately finish frame */ weston_output_finish_frame(output_base, NULL, WP_PRESENTATION_FEEDBACK_INVALID); From 62c0d63a82b6d08f75343c51d6edf5abce9efac2 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 5 Feb 2018 13:01:02 +0000 Subject: [PATCH 0290/1642] compositor-drm: Consistent failure paths for output creation Rather than a smattering of error handlers, use consistent jump labels for error paths in create_output_for_connector(). Signed-off-by: Daniel Stone Reported-by: Pekka Paalanen Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 85fef2af8..002a21f4e 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4292,7 +4292,8 @@ drm_output_destroy(struct weston_output *base) */ if (output->cursor_plane) drm_plane_destroy(output->cursor_plane); - drm_plane_destroy(output->scanout_plane); + if (output->scanout_plane) + drm_plane_destroy(output->scanout_plane); } wl_list_for_each_safe(drm_mode, next, &output->base.mode_list, @@ -4436,12 +4437,12 @@ create_output_for_connector(struct drm_backend *b, i = find_crtc_for_connector(b, resources, connector); if (i < 0) { weston_log("No usable crtc/encoder pair for connector.\n"); - goto err; + goto err_init; } output = zalloc(sizeof *output); if (output == NULL) - goto err; + goto err_init; output->connector = connector; output->crtc_id = resources->crtcs[i]; @@ -4468,7 +4469,7 @@ create_output_for_connector(struct drm_backend *b, DRM_MODE_OBJECT_CONNECTOR); if (!props) { weston_log("failed to get connector properties\n"); - goto err; + goto err_output; } drm_property_info_populate(b, connector_props, output->props_conn, WDRM_CONNECTOR__COUNT, props); @@ -4493,8 +4494,8 @@ create_output_for_connector(struct drm_backend *b, for (i = 0; i < output->connector->count_modes; i++) { drm_mode = drm_output_add_mode(output, &output->connector->modes[i]); if (!drm_mode) { - drm_output_destroy(&output->base); - return -1; + weston_log("failed to add mode\n"); + goto err_output; } } @@ -4504,8 +4505,7 @@ create_output_for_connector(struct drm_backend *b, if (!output->scanout_plane) { weston_log("Failed to find primary plane for output %s\n", output->base.name); - drm_output_destroy(&output->base); - return -1; + goto err_output; } /* Failing to find a cursor plane is not fatal, as we'll fall back @@ -4518,9 +4518,13 @@ create_output_for_connector(struct drm_backend *b, return 0; -err: - drmModeFreeConnector(connector); +err_output: + drm_output_destroy(&output->base); + return -1; + /* no fallthrough! */ +err_init: + drmModeFreeConnector(connector); return -1; } From c1d0f477bd18deb7a605ed88d0ffa46cdd806157 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 27 Jan 2017 15:11:33 +0000 Subject: [PATCH 0291/1642] compositor-drm: Don't restore original CRTC mode When leaving Weston, don't attempt to restore the previous CRTC settings. The framebuffer may well have disappeared, and in every likelihood, whoever gets the KMS device afterwards will be repainting anyway. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 002a21f4e..ad79aac4a 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -345,7 +345,6 @@ struct drm_output { uint32_t crtc_id; /* object ID to pass to DRM functions */ int pipe; /* index of CRTC in resource array / bitmasks */ uint32_t connector_id; - drmModeCrtcPtr original_crtc; struct drm_edid edid; /* Holds the properties for the connector */ @@ -1698,8 +1697,6 @@ drm_output_set_gamma(struct weston_output *output_base, /* check */ if (output_base->gamma_size != size) return; - if (!output->original_crtc) - return; rc = drmModeCrtcSetGamma(backend->drm.fd, output->crtc_id, @@ -4193,8 +4190,6 @@ drm_output_enable(struct weston_output *base) output->base.assign_planes = drm_assign_planes; output->base.set_dpms = drm_set_dpms; output->base.switch_mode = drm_output_switch_mode; - - output->base.gamma_size = output->original_crtc->gamma_size; output->base.set_gamma = drm_output_set_gamma; if (output->cursor_plane) @@ -4266,7 +4261,6 @@ drm_output_destroy(struct weston_output *base) struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); struct drm_mode *drm_mode, *next; - drmModeCrtcPtr origcrtc = output->original_crtc; if (output->page_flip_pending || output->vblank_pending) { output->destroy_pending = 1; @@ -4302,14 +4296,6 @@ drm_output_destroy(struct weston_output *base) free(drm_mode); } - if (origcrtc) { - /* Restore original CRTC state */ - drmModeSetCrtc(b->drm.fd, origcrtc->crtc_id, origcrtc->buffer_id, - origcrtc->x, origcrtc->y, - &output->connector_id, 1, &origcrtc->mode); - drmModeFreeCrtc(origcrtc); - } - if (output->pageflip_timer) wl_event_source_remove(output->pageflip_timer); @@ -4427,6 +4413,7 @@ create_output_for_connector(struct drm_backend *b, const char *make = "unknown"; const char *model = "unknown"; const char *serial_number = "unknown"; + drmModeCrtcPtr origcrtc; int i; static const struct drm_property_info connector_props[] = { @@ -4452,8 +4439,6 @@ create_output_for_connector(struct drm_backend *b, output->backlight = backlight_init(drm_device, connector->connector_type); - output->original_crtc = drmModeGetCrtc(b->drm.fd, output->crtc_id); - name = make_connector_name(connector); weston_output_init(&output->base, b->compositor, name); free(name); @@ -4462,6 +4447,13 @@ create_output_for_connector(struct drm_backend *b, output->base.destroy = drm_output_destroy; output->base.disable = drm_output_disable; + origcrtc = drmModeGetCrtc(b->drm.fd, output->crtc_id); + if (origcrtc == NULL) + goto err_output; + + output->base.gamma_size = origcrtc->gamma_size; + drmModeFreeCrtc(origcrtc); + output->destroy_pending = 0; output->disable_pending = 0; @@ -4480,6 +4472,8 @@ create_output_for_connector(struct drm_backend *b, output->base.serial_number = (char *)serial_number; output->base.subpixel = drm_subpixel_to_wayland(output->connector->subpixel); + drmModeFreeObjectProperties(props); + if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) output->base.connection_internal = true; @@ -4489,8 +4483,6 @@ create_output_for_connector(struct drm_backend *b, output->base.mm_width = output->connector->mmWidth; output->base.mm_height = output->connector->mmHeight; - drmModeFreeObjectProperties(props); - for (i = 0; i < output->connector->count_modes; i++) { drm_mode = drm_output_add_mode(output, &output->connector->modes[i]); if (!drm_mode) { From cd011a6e96f9527f98228cf021e3555736c184fe Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 15 Nov 2016 22:07:49 +0000 Subject: [PATCH 0292/1642] compositor-drm: Discover atomic properties Set the atomic client cap, where it exists, and use this to discover the plane/CRTC/connector properties we require for atomic modesetting. Signed-off-by: Daniel Stone Co-authored-by: Pekka Paalanen Reviewed-by: Pekka Paalanen --- configure.ac | 3 ++ libweston/compositor-drm.c | 59 +++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 129ed6371..bb5dd18bb 100644 --- a/configure.ac +++ b/configure.ac @@ -206,6 +206,9 @@ AM_CONDITIONAL(ENABLE_DRM_COMPOSITOR, test x$enable_drm_compositor = xyes) if test x$enable_drm_compositor = xyes; then AC_DEFINE([BUILD_DRM_COMPOSITOR], [1], [Build the DRM compositor]) PKG_CHECK_MODULES(DRM_COMPOSITOR, [libudev >= 136 libdrm >= 2.4.30 gbm]) + PKG_CHECK_MODULES(DRM_COMPOSITOR_ATOMIC, [libdrm >= 2.4.62], + [AC_DEFINE([HAVE_DRM_ATOMIC], 1, [libdrm supports atomic API])], + [AC_MSG_WARN([libdrm does not support atomic modesetting, will omit that capability])]) PKG_CHECK_MODULES(DRM_COMPOSITOR_GBM, [gbm >= 10.2], [AC_DEFINE([HAVE_GBM_FD_IMPORT], 1, [gbm supports dmabuf import])], [AC_MSG_WARN([gbm does not support dmabuf import, will omit that capability])]) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index ad79aac4a..7545bad76 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -88,6 +88,16 @@ */ enum wdrm_plane_property { WDRM_PLANE_TYPE = 0, + WDRM_PLANE_SRC_X, + WDRM_PLANE_SRC_Y, + WDRM_PLANE_SRC_W, + WDRM_PLANE_SRC_H, + WDRM_PLANE_CRTC_X, + WDRM_PLANE_CRTC_Y, + WDRM_PLANE_CRTC_W, + WDRM_PLANE_CRTC_H, + WDRM_PLANE_FB_ID, + WDRM_PLANE_CRTC_ID, WDRM_PLANE__COUNT }; @@ -107,6 +117,7 @@ enum wdrm_plane_type { enum wdrm_connector_property { WDRM_CONNECTOR_EDID = 0, WDRM_CONNECTOR_DPMS, + WDRM_CONNECTOR_CRTC_ID, WDRM_CONNECTOR__COUNT }; @@ -139,6 +150,15 @@ struct drm_property_info { struct drm_property_enum_info *enum_values; /**< array of enum values */ }; +/** + * List of properties attached to DRM CRTCs + */ +enum wdrm_crtc_property { + WDRM_CRTC_MODE_ID = 0, + WDRM_CRTC_ACTIVE, + WDRM_CRTC__COUNT +}; + /** * Mode for drm_output_state_duplicate. */ @@ -197,6 +217,7 @@ struct drm_backend { int cursors_are_broken; bool universal_planes; + bool atomic_modeset; int use_pixman; @@ -349,6 +370,8 @@ struct drm_output { /* Holds the properties for the connector */ struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT]; + /* Holds the properties for the CRTC */ + struct drm_property_info props_crtc[WDRM_CRTC__COUNT]; struct backlight *backlight; @@ -2907,6 +2930,15 @@ init_kms_caps(struct drm_backend *b) weston_log("DRM: %s universal planes\n", b->universal_planes ? "supports" : "does not support"); +#ifdef HAVE_DRM_ATOMIC + if (b->universal_planes && !getenv("WESTON_DISABLE_ATOMIC")) { + ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ATOMIC, 1); + b->atomic_modeset = (ret == 0); + } +#endif + weston_log("DRM: %s atomic modesetting\n", + b->atomic_modeset ? "supports" : "does not support"); + return 0; } @@ -3050,6 +3082,16 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane, .enum_values = plane_type_enums, .num_enum_values = WDRM_PLANE_TYPE__COUNT, }, + [WDRM_PLANE_SRC_X] = { .name = "SRC_X", }, + [WDRM_PLANE_SRC_Y] = { .name = "SRC_Y", }, + [WDRM_PLANE_SRC_W] = { .name = "SRC_W", }, + [WDRM_PLANE_SRC_H] = { .name = "SRC_H", }, + [WDRM_PLANE_CRTC_X] = { .name = "CRTC_X", }, + [WDRM_PLANE_CRTC_Y] = { .name = "CRTC_Y", }, + [WDRM_PLANE_CRTC_W] = { .name = "CRTC_W", }, + [WDRM_PLANE_CRTC_H] = { .name = "CRTC_H", }, + [WDRM_PLANE_FB_ID] = { .name = "FB_ID", }, + [WDRM_PLANE_CRTC_ID] = { .name = "CRTC_ID", }, }; plane = zalloc(sizeof(*plane) + @@ -3246,7 +3288,6 @@ create_sprites(struct drm_backend *b) drmModePlane *kplane; struct drm_plane *drm_plane; uint32_t i; - kplane_res = drmModeGetPlaneResources(b->drm.fd); if (!kplane_res) { weston_log("failed to get plane resources: %s\n", @@ -4302,6 +4343,7 @@ drm_output_destroy(struct weston_output *base) weston_output_release(&output->base); drm_property_info_free(output->props_conn, WDRM_CONNECTOR__COUNT); + drm_property_info_free(output->props_crtc, WDRM_CRTC__COUNT); drmModeFreeConnector(output->connector); @@ -4419,6 +4461,11 @@ create_output_for_connector(struct drm_backend *b, static const struct drm_property_info connector_props[] = { [WDRM_CONNECTOR_EDID] = { .name = "EDID" }, [WDRM_CONNECTOR_DPMS] = { .name = "DPMS" }, + [WDRM_CONNECTOR_CRTC_ID] = { .name = "CRTC_ID", }, + }; + static const struct drm_property_info crtc_props[] = { + [WDRM_CRTC_MODE_ID] = { .name = "MODE_ID", }, + [WDRM_CRTC_ACTIVE] = { .name = "ACTIVE", }, }; i = find_crtc_for_connector(b, resources, connector); @@ -4457,6 +4504,16 @@ create_output_for_connector(struct drm_backend *b, output->destroy_pending = 0; output->disable_pending = 0; + props = drmModeObjectGetProperties(b->drm.fd, output->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!props) { + weston_log("failed to get CRTC properties\n"); + goto err_output; + } + drm_property_info_populate(b, crtc_props, output->props_crtc, + WDRM_CRTC__COUNT, props); + drmModeFreeObjectProperties(props); + props = drmModeObjectGetProperties(b->drm.fd, connector->connector_id, DRM_MODE_OBJECT_CONNECTOR); if (!props) { From d5526cb974af4b2b56116d9026fa96b2394997f6 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 16 Nov 2016 10:54:10 +0000 Subject: [PATCH 0293/1642] compositor-drm: Add blob_id member to drm_mode For atomic modesetting support, the mode is identified by a blob property ID, rather than being passed inline. Add a blob_id member to drm_mode to handle this, including refactoring mode destruction into a helper function. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 7545bad76..282ab684a 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -234,6 +234,7 @@ struct drm_backend { struct drm_mode { struct weston_mode base; drmModeModeInfo mode_info; + uint32_t blob_id; }; enum drm_fb_type { @@ -3368,6 +3369,7 @@ drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) mode->base.refresh = refresh; mode->mode_info = *info; + mode->blob_id = 0; if (info->type & DRM_MODE_TYPE_PREFERRED) mode->base.flags |= WL_OUTPUT_MODE_PREFERRED; @@ -3377,6 +3379,18 @@ drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) return mode; } +/** + * Destroys a mode, and removes it from the list. + */ +static void +drm_output_destroy_mode(struct drm_backend *backend, struct drm_mode *mode) +{ + if (mode->blob_id) + drmModeDestroyPropertyBlob(backend->drm.fd, mode->blob_id); + wl_list_remove(&mode->base.link); + free(mode); +} + static int drm_subpixel_to_wayland(int drm_value) { @@ -4332,10 +4346,8 @@ drm_output_destroy(struct weston_output *base) } wl_list_for_each_safe(drm_mode, next, &output->base.mode_list, - base.link) { - wl_list_remove(&drm_mode->base.link); - free(drm_mode); - } + base.link) + drm_output_destroy_mode(b, drm_mode); if (output->pageflip_timer) wl_event_source_remove(output->pageflip_timer); From 598ee9ddf53f17494e7e1e83d8d576712f0dad19 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 16 Nov 2016 11:55:20 +0000 Subject: [PATCH 0294/1642] compositor-drm: Atomic modesetting support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for using the atomic-modesetting API to apply output state. Unlike previous series, this commit does not unflip sprites_are_broken, until further work has been done with assign_planes to make it reliable. Signed-off-by: Daniel Stone Co-authored-by: Pekka Paalanen Co-authored-by: Louis-Francis Ratté-Boulianne Co-authored-by: Derek Foreman Reviewed-by: Pekka Paalanen --- configure.ac | 2 +- libweston/compositor-drm.c | 506 +++++++++++++++++++++++++++++++------ 2 files changed, 434 insertions(+), 74 deletions(-) diff --git a/configure.ac b/configure.ac index bb5dd18bb..61ba14fb5 100644 --- a/configure.ac +++ b/configure.ac @@ -206,7 +206,7 @@ AM_CONDITIONAL(ENABLE_DRM_COMPOSITOR, test x$enable_drm_compositor = xyes) if test x$enable_drm_compositor = xyes; then AC_DEFINE([BUILD_DRM_COMPOSITOR], [1], [Build the DRM compositor]) PKG_CHECK_MODULES(DRM_COMPOSITOR, [libudev >= 136 libdrm >= 2.4.30 gbm]) - PKG_CHECK_MODULES(DRM_COMPOSITOR_ATOMIC, [libdrm >= 2.4.62], + PKG_CHECK_MODULES(DRM_COMPOSITOR_ATOMIC, [libdrm >= 2.4.78], [AC_DEFINE([HAVE_DRM_ATOMIC], 1, [libdrm supports atomic API])], [AC_MSG_WARN([libdrm does not support atomic modesetting, will omit that capability])]) PKG_CHECK_MODULES(DRM_COMPOSITOR_GBM, [gbm >= 10.2], diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 282ab684a..61c9bf5ec 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -83,6 +83,35 @@ #define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64 #endif +/** + * Represents the values of an enum-type KMS property + */ +struct drm_property_enum_info { + const char *name; /**< name as string (static, not freed) */ + bool valid; /**< true if value is supported; ignore if false */ + uint64_t value; /**< raw value */ +}; + +/** + * Holds information on a DRM property, including its ID and the enum + * values it holds. + * + * DRM properties are allocated dynamically, and maintained as DRM objects + * within the normal object ID space; they thus do not have a stable ID + * to refer to. This includes enum values, which must be referred to by + * integer values, but these are not stable. + * + * drm_property_info allows a cache to be maintained where Weston can use + * enum values internally to refer to properties, with the mapping to DRM + * ID values being maintained internally. + */ +struct drm_property_info { + const char *name; /**< name as string (static, not freed) */ + uint32_t prop_id; /**< KMS property object ID */ + unsigned int num_enum_values; /**< number of enum values */ + struct drm_property_enum_info *enum_values; /**< array of enum values */ +}; + /** * List of properties attached to DRM planes */ @@ -111,6 +140,36 @@ enum wdrm_plane_type { WDRM_PLANE_TYPE__COUNT }; +static struct drm_property_enum_info plane_type_enums[] = { + [WDRM_PLANE_TYPE_PRIMARY] = { + .name = "Primary", + }, + [WDRM_PLANE_TYPE_OVERLAY] = { + .name = "Overlay", + }, + [WDRM_PLANE_TYPE_CURSOR] = { + .name = "Cursor", + }, +}; + +static const struct drm_property_info plane_props[] = { + [WDRM_PLANE_TYPE] = { + .name = "type", + .enum_values = plane_type_enums, + .num_enum_values = WDRM_PLANE_TYPE__COUNT, + }, + [WDRM_PLANE_SRC_X] = { .name = "SRC_X", }, + [WDRM_PLANE_SRC_Y] = { .name = "SRC_Y", }, + [WDRM_PLANE_SRC_W] = { .name = "SRC_W", }, + [WDRM_PLANE_SRC_H] = { .name = "SRC_H", }, + [WDRM_PLANE_CRTC_X] = { .name = "CRTC_X", }, + [WDRM_PLANE_CRTC_Y] = { .name = "CRTC_Y", }, + [WDRM_PLANE_CRTC_W] = { .name = "CRTC_W", }, + [WDRM_PLANE_CRTC_H] = { .name = "CRTC_H", }, + [WDRM_PLANE_FB_ID] = { .name = "FB_ID", }, + [WDRM_PLANE_CRTC_ID] = { .name = "CRTC_ID", }, +}; + /** * List of properties attached to a DRM connector */ @@ -121,33 +180,10 @@ enum wdrm_connector_property { WDRM_CONNECTOR__COUNT }; -/** - * Represents the values of an enum-type KMS property - */ -struct drm_property_enum_info { - const char *name; /**< name as string (static, not freed) */ - bool valid; /**< true if value is supported; ignore if false */ - uint64_t value; /**< raw value */ -}; - -/** - * Holds information on a DRM property, including its ID and the enum - * values it holds. - * - * DRM properties are allocated dynamically, and maintained as DRM objects - * within the normal object ID space; they thus do not have a stable ID - * to refer to. This includes enum values, which must be referred to by - * integer values, but these are not stable. - * - * drm_property_info allows a cache to be maintained where Weston can use - * enum values internally to refer to properties, with the mapping to DRM - * ID values being maintained internally. - */ -struct drm_property_info { - const char *name; /**< name as string (static, not freed) */ - uint32_t prop_id; /**< KMS property object ID */ - unsigned int num_enum_values; /**< number of enum values */ - struct drm_property_enum_info *enum_values; /**< array of enum values */ +static const struct drm_property_info connector_props[] = { + [WDRM_CONNECTOR_EDID] = { .name = "EDID" }, + [WDRM_CONNECTOR_DPMS] = { .name = "DPMS" }, + [WDRM_CONNECTOR_CRTC_ID] = { .name = "CRTC_ID", }, }; /** @@ -159,6 +195,11 @@ enum wdrm_crtc_property { WDRM_CRTC__COUNT }; +static const struct drm_property_info crtc_props[] = { + [WDRM_CRTC_MODE_ID] = { .name = "MODE_ID", }, + [WDRM_CRTC_ACTIVE] = { .name = "ACTIVE", }, +}; + /** * Mode for drm_output_state_duplicate. */ @@ -378,6 +419,7 @@ struct drm_output { int vblank_pending; int page_flip_pending; + int atomic_complete_pending; int destroy_pending; int disable_pending; int dpms_off_pending; @@ -1427,6 +1469,7 @@ drm_output_assign_state(struct drm_output_state *state, enum drm_state_apply_mode mode) { struct drm_output *output = state->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); struct drm_plane_state *plane_state; assert(!output->state_last); @@ -1442,6 +1485,9 @@ drm_output_assign_state(struct drm_output_state *state, output->state_cur = state; + if (b->atomic_modeset && mode == DRM_STATE_APPLY_ASYNC) + output->atomic_complete_pending = 1; + /* Replace state_cur on each affected plane with the new state, being * careful to dispose of orphaned (but only orphaned) previous state. * If the previous state is not orphaned (still has an output_state @@ -1458,6 +1504,9 @@ drm_output_assign_state(struct drm_output_state *state, continue; } + if (b->atomic_modeset) + continue; + if (plane->type == WDRM_PLANE_TYPE_OVERLAY) output->vblank_pending++; else if (plane->type == WDRM_PLANE_TYPE_PRIMARY) @@ -1755,7 +1804,7 @@ drm_waitvblank_pipe(struct drm_output *output) } static int -drm_output_apply_state(struct drm_output_state *state) +drm_output_apply_state_legacy(struct drm_output_state *state) { struct drm_output *output = state->output; struct drm_backend *backend = to_drm_backend(output->base.compositor); @@ -1929,6 +1978,297 @@ drm_output_apply_state(struct drm_output_state *state) return -1; } +#ifdef HAVE_DRM_ATOMIC +static int +crtc_add_prop(drmModeAtomicReq *req, struct drm_output *output, + enum wdrm_crtc_property prop, uint64_t val) +{ + struct drm_property_info *info = &output->props_crtc[prop]; + int ret; + + if (info->prop_id == 0) + return -1; + + ret = drmModeAtomicAddProperty(req, output->crtc_id, info->prop_id, + val); + return (ret <= 0) ? -1 : 0; +} + +static int +connector_add_prop(drmModeAtomicReq *req, struct drm_output *output, + enum wdrm_connector_property prop, uint64_t val) +{ + struct drm_property_info *info = &output->props_conn[prop]; + int ret; + + if (info->prop_id == 0) + return -1; + + ret = drmModeAtomicAddProperty(req, output->connector_id, + info->prop_id, val); + return (ret <= 0) ? -1 : 0; +} + +static int +plane_add_prop(drmModeAtomicReq *req, struct drm_plane *plane, + enum wdrm_plane_property prop, uint64_t val) +{ + struct drm_property_info *info = &plane->props[prop]; + int ret; + + if (info->prop_id == 0) + return -1; + + ret = drmModeAtomicAddProperty(req, plane->plane_id, info->prop_id, + val); + return (ret <= 0) ? -1 : 0; +} + +static int +drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode) +{ + int ret; + + if (mode->blob_id) + return 0; + + ret = drmModeCreatePropertyBlob(backend->drm.fd, + &mode->mode_info, + sizeof(mode->mode_info), + &mode->blob_id); + if (ret != 0) + weston_log("failed to create mode property blob: %m\n"); + + return ret; +} + +static int +drm_output_apply_state_atomic(struct drm_output_state *state, + drmModeAtomicReq *req, + uint32_t *flags) +{ + struct drm_output *output = state->output; + struct drm_backend *backend = to_drm_backend(output->base.compositor); + struct drm_plane_state *plane_state; + struct drm_mode *current_mode = to_drm_mode(output->base.current_mode); + int ret = 0; + + if (state->dpms != output->state_cur->dpms) + *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + + if (state->dpms == WESTON_DPMS_ON) { + ret = drm_mode_ensure_blob(backend, current_mode); + if (ret != 0) + return ret; + + ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, + current_mode->blob_id); + ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 1); + ret |= connector_add_prop(req, output, WDRM_CONNECTOR_CRTC_ID, + output->crtc_id); + } else { + ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, 0); + ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 0); + ret |= connector_add_prop(req, output, WDRM_CONNECTOR_CRTC_ID, + 0); + } + + if (ret != 0) { + weston_log("couldn't set atomic CRTC/connector state\n"); + return ret; + } + + wl_list_for_each(plane_state, &state->plane_list, link) { + struct drm_plane *plane = plane_state->plane; + + ret |= plane_add_prop(req, plane, WDRM_PLANE_FB_ID, + plane_state->fb ? plane_state->fb->fb_id : 0); + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_ID, + plane_state->fb ? output->crtc_id : 0); + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_X, + plane_state->src_x); + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_Y, + plane_state->src_y); + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_W, + plane_state->src_w); + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_H, + plane_state->src_h); + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_X, + plane_state->dest_x); + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_Y, + plane_state->dest_y); + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_W, + plane_state->dest_w); + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_H, + plane_state->dest_h); + + if (ret != 0) { + weston_log("couldn't set plane state\n"); + return ret; + } + } + + return 0; +} + +/** + * Helper function used only by drm_pending_state_apply, with the same + * guarantees and constraints as that function. + */ +static int +drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, + enum drm_state_apply_mode mode) +{ + struct drm_backend *b = pending_state->backend; + struct drm_output_state *output_state, *tmp; + struct drm_plane *plane; + drmModeAtomicReq *req = drmModeAtomicAlloc(); + uint32_t flags = 0; + int ret = 0; + + if (!req) + return -1; + + if (b->state_invalid) { + uint32_t *unused; + int err; + + /* If we need to reset all our state (e.g. because we've + * just started, or just been VT-switched in), explicitly + * disable all the CRTCs and connectors we aren't using. */ + wl_array_for_each(unused, &b->unused_connectors) { + struct drm_property_info infos[WDRM_CONNECTOR__COUNT]; + struct drm_property_info *info; + drmModeObjectProperties *props; + + memset(infos, 0, sizeof(infos)); + + props = drmModeObjectGetProperties(b->drm.fd, + *unused, + DRM_MODE_OBJECT_CONNECTOR); + if (!props) { + ret = -1; + continue; + } + + drm_property_info_populate(b, connector_props, infos, + WDRM_CONNECTOR__COUNT, + props); + drmModeFreeObjectProperties(props); + + info = &infos[WDRM_CONNECTOR_CRTC_ID]; + err = drmModeAtomicAddProperty(req, *unused, + info->prop_id, 0); + if (err <= 0) + ret = -1; + + info = &infos[WDRM_CONNECTOR_DPMS]; + if (info->prop_id > 0) + err = drmModeAtomicAddProperty(req, *unused, + info->prop_id, + DRM_MODE_DPMS_OFF); + if (err <= 0) + ret = -1; + + drm_property_info_free(infos, WDRM_CONNECTOR__COUNT); + } + + wl_array_for_each(unused, &b->unused_crtcs) { + struct drm_property_info infos[WDRM_CRTC__COUNT]; + struct drm_property_info *info; + drmModeObjectProperties *props; + uint64_t active; + + memset(infos, 0, sizeof(infos)); + + /* We can't emit a disable on a CRTC that's already + * off, as the kernel will refuse to generate an event + * for an off->off state and fail the commit. + */ + props = drmModeObjectGetProperties(b->drm.fd, + *unused, + DRM_MODE_OBJECT_CRTC); + if (!props) { + ret = -1; + continue; + } + + drm_property_info_populate(b, crtc_props, infos, + WDRM_CRTC__COUNT, + props); + + info = &infos[WDRM_CRTC_ACTIVE]; + active = drm_property_get_value(info, props, 0); + drmModeFreeObjectProperties(props); + if (active == 0) { + drm_property_info_free(infos, WDRM_CRTC__COUNT); + continue; + } + + err = drmModeAtomicAddProperty(req, *unused, + info->prop_id, 0); + if (err <= 0) + ret = -1; + + info = &infos[WDRM_CRTC_MODE_ID]; + err = drmModeAtomicAddProperty(req, *unused, + info->prop_id, 0); + if (err <= 0) + ret = -1; + + drm_property_info_free(infos, WDRM_CRTC__COUNT); + } + + /* Disable all the planes; planes which are being used will + * override this state in the output-state application. */ + wl_list_for_each(plane, &b->plane_list, link) { + plane_add_prop(req, plane, WDRM_PLANE_CRTC_ID, 0); + plane_add_prop(req, plane, WDRM_PLANE_FB_ID, 0); + } + + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + + wl_list_for_each(output_state, &pending_state->output_list, link) { + if (mode == DRM_STATE_APPLY_SYNC) + assert(output_state->dpms == WESTON_DPMS_OFF); + ret |= drm_output_apply_state_atomic(output_state, req, &flags); + } + + if (ret != 0) { + weston_log("atomic: couldn't compile atomic state\n"); + goto out; + } + + switch (mode) { + case DRM_STATE_APPLY_SYNC: + break; + case DRM_STATE_APPLY_ASYNC: + flags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; + break; + } + + ret = drmModeAtomicCommit(b->drm.fd, req, flags, b); + if (ret != 0) { + weston_log("atomic: couldn't commit new state: %m\n"); + goto out; + } + + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, + link) + drm_output_assign_state(output_state, mode); + + b->state_invalid = false; + + assert(wl_list_empty(&pending_state->output_list)); + +out: + drmModeAtomicFree(req); + drm_pending_state_free(pending_state); + return ret; +} +#endif + /** * Applies all of a pending_state asynchronously: the primary entry point for * applying KMS state to a device. Updates the state for all outputs in the @@ -1943,6 +2283,12 @@ drm_pending_state_apply(struct drm_pending_state *pending_state) struct drm_output_state *output_state, *tmp; uint32_t *unused; +#ifdef HAVE_DRM_ATOMIC + if (b->atomic_modeset) + return drm_pending_state_apply_atomic(pending_state, + DRM_STATE_APPLY_ASYNC); +#endif + if (b->state_invalid) { /* If we need to reset all our state (e.g. because we've * just started, or just been VT-switched in), explicitly @@ -1959,7 +2305,7 @@ drm_pending_state_apply(struct drm_pending_state *pending_state) struct drm_output *output = output_state->output; int ret; - ret = drm_output_apply_state(output_state); + ret = drm_output_apply_state_legacy(output_state); if (ret != 0) { weston_log("Couldn't apply state for output %s\n", output->base.name); @@ -1989,6 +2335,12 @@ drm_pending_state_apply_sync(struct drm_pending_state *pending_state) struct drm_output_state *output_state, *tmp; uint32_t *unused; +#ifdef HAVE_DRM_ATOMIC + if (b->atomic_modeset) + return drm_pending_state_apply_atomic(pending_state, + DRM_STATE_APPLY_SYNC); +#endif + if (b->state_invalid) { /* If we need to reset all our state (e.g. because we've * just started, or just been VT-switched in), explicitly @@ -2005,7 +2357,7 @@ drm_pending_state_apply_sync(struct drm_pending_state *pending_state) int ret; assert(output_state->dpms == WESTON_DPMS_OFF); - ret = drm_output_apply_state(output_state); + ret = drm_output_apply_state_legacy(output_state); if (ret != 0) { weston_log("Couldn't apply state for output %s\n", output_state->output->base.name); @@ -2167,9 +2519,12 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, struct drm_plane_state *ps = (struct drm_plane_state *) data; struct drm_output_state *os = ps->output_state; struct drm_output *output = os->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; + assert(!b->atomic_modeset); + drm_output_update_msc(output, frame); output->vblank_pending--; assert(output->vblank_pending >= 0); @@ -2187,12 +2542,14 @@ page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { struct drm_output *output = data; + struct drm_backend *b = to_drm_backend(output->base.compositor); uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; drm_output_update_msc(output, frame); + assert(!b->atomic_modeset); assert(output->page_flip_pending); output->page_flip_pending = 0; @@ -2256,6 +2613,33 @@ drm_repaint_cancel(struct weston_compositor *compositor, void *repaint_data) b->repaint_data = NULL; } +#ifdef HAVE_DRM_ATOMIC +static void +atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, + unsigned int usec, unsigned int crtc_id, void *data) +{ + struct drm_backend *b = data; + struct drm_output *output = drm_output_find_by_crtc(b, crtc_id); + uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | + WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | + WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; + + /* During the initial modeset, we can disable CRTCs which we don't + * actually handle during normal operation; this will give us events + * for unknown outputs. Ignore them. */ + if (!output || !output->base.enabled) + return; + + drm_output_update_msc(output, frame); + + assert(b->atomic_modeset); + assert(output->atomic_complete_pending); + output->atomic_complete_pending = 0; + + drm_output_update_complete(output, flags, sec, usec); +} +#endif + static uint32_t drm_output_check_plane_format(struct drm_plane *p, struct weston_view *ev, struct gbm_bo *bo) @@ -2880,11 +3264,21 @@ drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mo static int on_drm_input(int fd, uint32_t mask, void *data) { +#ifdef HAVE_DRM_ATOMIC + struct drm_backend *b = data; +#endif drmEventContext evctx; memset(&evctx, 0, sizeof evctx); +#ifndef HAVE_DRM_ATOMIC evctx.version = 2; - evctx.page_flip_handler = page_flip_handler; +#else + evctx.version = 3; + if (b->atomic_modeset) + evctx.page_flip_handler2 = atomic_flip_handler; + else +#endif + evctx.page_flip_handler = page_flip_handler; evctx.vblank_handler = vblank_handler; drmHandleEvent(fd, &evctx); @@ -2933,8 +3327,11 @@ init_kms_caps(struct drm_backend *b) #ifdef HAVE_DRM_ATOMIC if (b->universal_planes && !getenv("WESTON_DISABLE_ATOMIC")) { + ret = drmGetCap(b->drm.fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap); + if (ret != 0) + cap = 0; ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ATOMIC, 1); - b->atomic_modeset = (ret == 0); + b->atomic_modeset = ((ret == 0) && (cap == 1)); } #endif weston_log("DRM: %s atomic modesetting\n", @@ -3066,35 +3463,6 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane, drmModeObjectProperties *props; int num_formats = (kplane) ? kplane->count_formats : 1; - static struct drm_property_enum_info plane_type_enums[] = { - [WDRM_PLANE_TYPE_PRIMARY] = { - .name = "Primary", - }, - [WDRM_PLANE_TYPE_OVERLAY] = { - .name = "Overlay", - }, - [WDRM_PLANE_TYPE_CURSOR] = { - .name = "Cursor", - }, - }; - static const struct drm_property_info plane_props[] = { - [WDRM_PLANE_TYPE] = { - .name = "type", - .enum_values = plane_type_enums, - .num_enum_values = WDRM_PLANE_TYPE__COUNT, - }, - [WDRM_PLANE_SRC_X] = { .name = "SRC_X", }, - [WDRM_PLANE_SRC_Y] = { .name = "SRC_Y", }, - [WDRM_PLANE_SRC_W] = { .name = "SRC_W", }, - [WDRM_PLANE_SRC_H] = { .name = "SRC_H", }, - [WDRM_PLANE_CRTC_X] = { .name = "CRTC_X", }, - [WDRM_PLANE_CRTC_Y] = { .name = "CRTC_Y", }, - [WDRM_PLANE_CRTC_W] = { .name = "CRTC_W", }, - [WDRM_PLANE_CRTC_H] = { .name = "CRTC_H", }, - [WDRM_PLANE_FB_ID] = { .name = "FB_ID", }, - [WDRM_PLANE_CRTC_ID] = { .name = "CRTC_ID", }, - }; - plane = zalloc(sizeof(*plane) + (sizeof(uint32_t) * num_formats)); if (!plane) { @@ -4317,7 +4685,8 @@ drm_output_destroy(struct weston_output *base) struct drm_backend *b = to_drm_backend(base->compositor); struct drm_mode *drm_mode, *next; - if (output->page_flip_pending || output->vblank_pending) { + if (output->page_flip_pending || output->vblank_pending || + output->atomic_complete_pending) { output->destroy_pending = 1; weston_log("destroy output while page flip pending\n"); return; @@ -4376,7 +4745,8 @@ drm_output_disable(struct weston_output *base) struct drm_pending_state *pending_state; int ret; - if (output->page_flip_pending || output->vblank_pending) { + if (output->page_flip_pending || output->vblank_pending || + output->atomic_complete_pending) { output->disable_pending = 1; return -1; } @@ -4470,16 +4840,6 @@ create_output_for_connector(struct drm_backend *b, drmModeCrtcPtr origcrtc; int i; - static const struct drm_property_info connector_props[] = { - [WDRM_CONNECTOR_EDID] = { .name = "EDID" }, - [WDRM_CONNECTOR_DPMS] = { .name = "DPMS" }, - [WDRM_CONNECTOR_CRTC_ID] = { .name = "CRTC_ID", }, - }; - static const struct drm_property_info crtc_props[] = { - [WDRM_CRTC_MODE_ID] = { .name = "MODE_ID", }, - [WDRM_CRTC_ACTIVE] = { .name = "ACTIVE", }, - }; - i = find_crtc_for_connector(b, resources, connector); if (i < 0) { weston_log("No usable crtc/encoder pair for connector.\n"); From 83b900fb4649346162e8f8165bcff1927177c31f Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 6 Feb 2018 15:18:36 -0600 Subject: [PATCH 0295/1642] shared: Add a function to prefix filenames with datadir Currently we look for png files in their install directory, and we manufacture filenames with string pasting at compile time. This new function will allow overriding the compile time setting with the env var WESTON_DATA_DIR so we can do neat tricks like allow our test suite to pass when we haven't yet installed icons system-wide. Signed-off-by: Derek Foreman Acked-by: Daniel Stone Reviewed-by: Quentin Glidic [Pekka: split if-branch into two lines.] Signed-off-by: Pekka Paalanen --- shared/file-util.c | 19 +++++++++++++++++++ shared/file-util.h | 3 +++ 2 files changed, 22 insertions(+) diff --git a/shared/file-util.c b/shared/file-util.c index 979a3811f..e254217cb 100644 --- a/shared/file-util.c +++ b/shared/file-util.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "file-util.h" @@ -119,3 +120,21 @@ file_create_dated(const char *path_prefix, const char *suffix, return fdopen(fd, "w"); } + +char * +file_name_with_datadir(const char *filename) +{ + const char *base = getenv("WESTON_DATA_DIR"); + char *out; + int len; + + if (base) + len = asprintf(&out, "%s/%s", base, filename); + else + len = asprintf(&out, "%s/weston/%s", DATADIR, filename); + + if (len == -1) + return NULL; + + return out; +} diff --git a/shared/file-util.h b/shared/file-util.h index f639c446e..dbda07a8c 100644 --- a/shared/file-util.h +++ b/shared/file-util.h @@ -36,6 +36,9 @@ FILE * file_create_dated(const char *path_prefix, const char *suffix, char *name_out, size_t name_len); +char * +file_name_with_datadir(const char *); + #ifdef __cplusplus } #endif From b7e3db6a76ca74c878a79717300d0a1f9b3be06e Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 6 Feb 2018 15:18:37 -0600 Subject: [PATCH 0296/1642] tests: Set WESTON_DATA_DIR for tests Set the env var to override the system data directory so we can run tests with uninstalled icons. We don't yet use the code that checks this env var, so make distcheck will still fail. Signed-off-by: Derek Foreman Acked-by: Daniel Stone Reviewed-by: Quentin Glidic Reviewed-by: Pekka Paalanen --- tests/weston-tests-env | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/weston-tests-env b/tests/weston-tests-env index 6da0696e7..f62b70ec2 100755 --- a/tests/weston-tests-env +++ b/tests/weston-tests-env @@ -40,6 +40,7 @@ case $TEST_FILE in SHELL_PLUGIN=$MODDIR/ivi-shell.so set -x + WESTON_DATA_DIR=$abs_top_srcdir/data \ WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ $WESTON --backend=$MODDIR/$BACKEND \ @@ -53,6 +54,7 @@ case $TEST_FILE in ;; *.la|*.so) set -x + WESTON_DATA_DIR=$abs_top_srcdir/data \ WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ $WESTON --backend=$MODDIR/$BACKEND \ @@ -68,6 +70,7 @@ case $TEST_FILE in SHELL_PLUGIN=$MODDIR/ivi-shell.so set -x + WESTON_DATA_DIR=$abs_top_srcdir/data \ WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ WESTON_TEST_CLIENT_PATH=$abs_builddir/$TEST_FILE \ @@ -82,6 +85,7 @@ case $TEST_FILE in ;; *) set -x + WESTON_DATA_DIR=$abs_top_srcdir/data \ WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ WESTON_TEST_CLIENT_PATH=$abs_builddir/$TEST_FILE \ From e277276b850bad39ed6995be5a82f24aa6b17bf1 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 6 Feb 2018 15:18:38 -0600 Subject: [PATCH 0297/1642] shared: Update all users of DATADIR Replace every use of DATADIR to create a filename with a call to the new function that allows overriding DATADIR with an env var at runtime. No attention is paid to asprintf failure. This restores make distcheck to a passing state after commit 6b58ea began checking cairo surfaces for validity and exchanged undefined behaviour we shouldn't have been dependent on for consistent test failure. Signed-off-by: Derek Foreman Acked-by: Daniel Stone Reviewed-by: Quentin Glidic [Pekka: split if-branches into two lines] Signed-off-by: Pekka Paalanen --- clients/desktop-shell.c | 14 ++++++++++--- clients/ivi-shell-user-interface.c | 30 ++++++++++++++++++++------- libweston/compositor-x11.c | 6 +++++- shared/frame.c | 33 ++++++++++++++++++++++++++---- 4 files changed, 68 insertions(+), 15 deletions(-) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index 599295ee3..12bc971e1 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -49,6 +49,7 @@ #include "shared/helpers.h" #include "shared/xalloc.h" #include "shared/zalloc.h" +#include "shared/file-util.h" #include "weston-desktop-shell-client-protocol.h" @@ -760,8 +761,12 @@ background_draw(struct widget *widget, void *data) image = NULL; if (background->image) image = load_cairo_surface(background->image); - else if (background->color == 0) - image = load_cairo_surface(DATADIR "/weston/pattern.png"); + else if (background->color == 0) { + char *name = file_name_with_datadir("pattern.png"); + + image = load_cairo_surface(name); + free(name); + } if (image && background->type != -1) { im_w = cairo_image_surface_get_width(image); @@ -1351,10 +1356,13 @@ panel_add_launchers(struct panel *panel, struct desktop *desktop) } if (count == 0) { + char *name = file_name_with_datadir("terminal.png"); + /* add default launcher */ panel_add_launcher(panel, - DATADIR "/weston/terminal.png", + name, BINDIR "/weston-terminal"); + free(name); } } diff --git a/clients/ivi-shell-user-interface.c b/clients/ivi-shell-user-interface.c index f4e061d08..a38d6af86 100644 --- a/clients/ivi-shell-user-interface.c +++ b/clients/ivi-shell-user-interface.c @@ -43,6 +43,7 @@ #include "shared/os-compatibility.h" #include "shared/xalloc.h" #include "shared/zalloc.h" +#include "shared/file-util.h" #include "ivi-application-client-protocol.h" #include "ivi-hmi-controller-client-protocol.h" @@ -1076,6 +1077,7 @@ hmi_homescreen_setting_create(void) const char *name = NULL; uint32_t workspace_layer_id; uint32_t icon_surface_id = 0; + char *filename; wl_list_init(&setting->workspace_list); wl_list_init(&setting->launcher_list); @@ -1095,51 +1097,65 @@ hmi_homescreen_setting_create(void) weston_config_section_get_uint( shellSection, "workspace-layer-id", &workspace_layer_id, 3000); + filename = file_name_with_datadir("background.png"); weston_config_section_get_string( shellSection, "background-image", &setting->background.filePath, - DATADIR "/weston/background.png"); + filename); + free(filename); weston_config_section_get_uint( shellSection, "background-id", &setting->background.id, 1001); + filename = file_name_with_datadir("panel.png"); weston_config_section_get_string( shellSection, "panel-image", &setting->panel.filePath, - DATADIR "/weston/panel.png"); + filename); + free(filename); weston_config_section_get_uint( shellSection, "panel-id", &setting->panel.id, 1002); + filename = file_name_with_datadir("tiling.png"); weston_config_section_get_string( shellSection, "tiling-image", &setting->tiling.filePath, - DATADIR "/weston/tiling.png"); + filename); + free(filename); weston_config_section_get_uint( shellSection, "tiling-id", &setting->tiling.id, 1003); + filename = file_name_with_datadir("sidebyside.png"); weston_config_section_get_string( shellSection, "sidebyside-image", &setting->sidebyside.filePath, - DATADIR "/weston/sidebyside.png"); + filename); + free(filename); weston_config_section_get_uint( shellSection, "sidebyside-id", &setting->sidebyside.id, 1004); + filename = file_name_with_datadir("fullscreen.png"); weston_config_section_get_string( shellSection, "fullscreen-image", &setting->fullscreen.filePath, - DATADIR "/weston/fullscreen.png"); + filename); + free(filename); weston_config_section_get_uint( shellSection, "fullscreen-id", &setting->fullscreen.id, 1005); + filename = file_name_with_datadir("random.png"); weston_config_section_get_string( shellSection, "random-image", &setting->random.filePath, - DATADIR "/weston/random.png"); + filename); + free(filename); weston_config_section_get_uint( shellSection, "random-id", &setting->random.id, 1006); + filename = file_name_with_datadir("home.png"); weston_config_section_get_string( shellSection, "home-image", &setting->home.filePath, - DATADIR "/weston/home.png"); + filename); + free(filename); weston_config_section_get_uint( shellSection, "home-id", &setting->home.id, 1007); diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index fd9485406..a1d212705 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -56,6 +56,7 @@ #include "shared/helpers.h" #include "shared/image-loader.h" #include "shared/timespec-util.h" +#include "shared/file-util.h" #include "gl-renderer.h" #include "weston-egl-ext.h" #include "pixman-renderer.h" @@ -911,6 +912,7 @@ x11_output_enable(struct weston_output *base) xcb_screen_t *screen; struct wm_normal_hints normal_hints; struct wl_event_loop *loop; + char *icon_filename; int ret; uint32_t mask = XCB_CW_EVENT_MASK | XCB_CW_CURSOR; @@ -991,7 +993,9 @@ x11_output_enable(struct weston_output *base) b->atom.wm_class, b->atom.string, 8, sizeof class, class); - x11_output_set_icon(b, output, DATADIR "/weston/wayland.png"); + icon_filename = file_name_with_datadir("wayland.png"); + x11_output_set_icon(b, output, icon_filename); + free(icon_filename); x11_output_set_wm_protocols(b, output); diff --git a/shared/frame.c b/shared/frame.c index dc7ff85c4..acac2ca88 100644 --- a/shared/frame.c +++ b/shared/frame.c @@ -34,6 +34,7 @@ #include #include "cairo-util.h" +#include "shared/file-util.h" enum frame_button_flags { FRAME_BUTTON_ALIGN_RIGHT = 0x1, @@ -357,41 +358,65 @@ frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons, FRAME_STATUS_MENU, FRAME_BUTTON_CLICK_DOWN); } else { + char *name = file_name_with_datadir("icon_window.png"); + + if (!name) + goto free_frame; + button = frame_button_create(frame, - DATADIR "/weston/icon_window.png", + name, FRAME_STATUS_MENU, FRAME_BUTTON_CLICK_DOWN); + free(name); } if (!button) goto free_frame; } if (buttons & FRAME_BUTTON_CLOSE) { + char *name = file_name_with_datadir("sign_close.png"); + + if (!name) + goto free_frame; + button = frame_button_create(frame, - DATADIR "/weston/sign_close.png", + name, FRAME_STATUS_CLOSE, FRAME_BUTTON_ALIGN_RIGHT | FRAME_BUTTON_DECORATED); + free(name); if (!button) goto free_frame; } if (buttons & FRAME_BUTTON_MAXIMIZE) { + char *name = file_name_with_datadir("sign_maximize.png"); + + if (!name) + goto free_frame; + button = frame_button_create(frame, - DATADIR "/weston/sign_maximize.png", + name, FRAME_STATUS_MAXIMIZE, FRAME_BUTTON_ALIGN_RIGHT | FRAME_BUTTON_DECORATED); + free(name); if (!button) goto free_frame; } if (buttons & FRAME_BUTTON_MINIMIZE) { + char *name = file_name_with_datadir("sign_minimize.png"); + + if (!name) + goto free_frame; + button = frame_button_create(frame, - DATADIR "/weston/sign_minimize.png", + name, FRAME_STATUS_MINIMIZE, FRAME_BUTTON_ALIGN_RIGHT | FRAME_BUTTON_DECORATED); + free(name); if (!button) goto free_frame; } From b809d79d9988e4785e9df6f3d82c8cc6a1ef9962 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 6 Feb 2018 15:18:39 -0600 Subject: [PATCH 0298/1642] build: Clean up -DDATADIR in makefiles Now only libshared (and libshared_cairo) requires this. Signed-off-by: Derek Foreman Acked-by: Daniel Stone Reviewed-by: Quentin Glidic Reviewed-by: Pekka Paalanen --- Makefile.am | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index e224d606b..5132e35a4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -50,7 +50,6 @@ AM_CPPFLAGS = \ -I$(top_builddir)/tests \ -I$(top_srcdir)/shared \ -I$(top_builddir)/protocol \ - -DDATADIR='"$(datadir)"' \ -DLIBWESTON_MODULEDIR='"$(libweston_moduledir)"' \ -DLIBEXECDIR='"$(libexecdir)"' \ -DBINDIR='"$(bindir)"' @@ -951,7 +950,6 @@ desktop_shell_la_CPPFLAGS = \ -I$(top_builddir)/libweston \ -I$(top_srcdir)/libweston \ -I$(top_builddir)/desktop-shell \ - -DDATADIR='"$(datadir)"' \ -DMODULEDIR='"$(moduledir)"' \ -DLIBEXECDIR='"$(libexecdir)"' \ -DIN_WESTON @@ -1081,7 +1079,6 @@ xwayland_la_CPPFLAGS = \ -I$(top_builddir)/libweston \ -I$(top_srcdir)/libweston \ -I$(top_builddir)/xwayland \ - -DDATADIR='"$(datadir)"' \ -DMODULEDIR='"$(moduledir)"' \ -DLIBEXECDIR='"$(libexecdir)"' @@ -1118,6 +1115,10 @@ endif noinst_LTLIBRARIES += libshared.la libshared-cairo.la \ libzunitc.la libzunitcmain.la +libshared_la_CPPFLAGS = \ + -DDATADIR='"$(datadir)"' \ + $(AM_CPPFLAGS) + libshared_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) libshared_la_SOURCES = \ @@ -1132,8 +1133,9 @@ libshared_la_SOURCES = \ shared/xalloc.c \ shared/xalloc.h +libshared_cairo_la_CPPFLAGS = $(libshared_la_CPPFLAGS) + libshared_cairo_la_CFLAGS = \ - -DDATADIR='"$(datadir)"' \ $(AM_CFLAGS) \ $(COMPOSITOR_CFLAGS) \ $(PIXMAN_CFLAGS) \ From ce9bc3518517468b6726d9ea38820bb72fee976d Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Thu, 25 Jan 2018 14:36:10 +0100 Subject: [PATCH 0299/1642] ivi-shell: register ivi_layout_interface Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout-export.h | 13 +++++++++++++ ivi-shell/ivi-layout.c | 6 ++++++ ivi-shell/ivi-shell.c | 2 -- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index 277ac59e8..c15c7f83f 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -59,6 +59,7 @@ extern "C" { #include "stdbool.h" #include "compositor.h" +#include "plugin-registry.h" #define IVI_SUCCEEDED (0) #define IVI_FAILED (-1) @@ -140,6 +141,8 @@ enum ivi_layout_transition_type{ IVI_LAYOUT_TRANSITION_MAX, }; +#define IVI_LAYOUT_API_NAME "ivi_layout_api_v1" + struct ivi_layout_interface { /** @@ -572,6 +575,16 @@ struct ivi_layout_interface { struct ivi_layout_layer *removelayer); }; +static inline const struct ivi_layout_interface * +ivi_layout_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, IVI_LAYOUT_API_NAME, + sizeof(struct ivi_layout_interface)); + + return (const struct ivi_layout_interface *)api; +} + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 394179b8c..a11b65852 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -1906,6 +1906,8 @@ ivi_layout_surface_create(struct weston_surface *wl_surface, return ivisurf; } +static struct ivi_layout_interface ivi_layout_interface; + void ivi_layout_init_with_compositor(struct weston_compositor *ec) { @@ -1934,6 +1936,10 @@ ivi_layout_init_with_compositor(struct weston_compositor *ec) layout->transitions = ivi_layout_transition_set_create(ec); wl_list_init(&layout->pending_transition_list); + + weston_plugin_api_register(ec, IVI_LAYOUT_API_NAME, + &ivi_layout_interface, + sizeof(struct ivi_layout_interface)); } static struct ivi_layout_interface ivi_layout_interface = { diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 766a1fd1c..c09695468 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -425,8 +425,6 @@ ivi_shell_setting_create(struct ivi_shell_setting *dest, if (!dest->ivi_module && weston_config_section_get_string(section, "ivi-module", &dest->ivi_module, NULL) < 0) { - weston_log("Error: ivi-shell: No ivi-module set\n"); - result = -1; } weston_config_section_get_bool(section, "developermode", From a9db8d7d64f8fae1f2a0ea3767634024855102d5 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Thu, 25 Jan 2018 14:36:11 +0100 Subject: [PATCH 0300/1642] hmi-controller: remove ivi_layout_interface global Put the interface into hmi_controller struct. It is better to have it in an object. Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/hmi-controller.c | 194 +++++++++++++++++++------------------ 1 file changed, 100 insertions(+), 94 deletions(-) diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index 5a2ff78c9..34abc11b6 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -138,6 +138,8 @@ struct hmi_controller { struct weston_output * workspace_background_output; int32_t screen_num; + + const struct ivi_layout_interface *interface; }; struct launcher_info { @@ -146,8 +148,6 @@ struct launcher_info { int32_t index; }; -const struct ivi_layout_interface *ivi_layout_interface; - int controller_module_init(struct weston_compositor *ec, int *argc, char *argv[], @@ -169,7 +169,7 @@ static int32_t is_surf_in_ui_widget(struct hmi_controller *hmi_ctrl, struct ivi_layout_surface *ivisurf) { - uint32_t id = ivi_layout_interface->get_id_of_surface(ivisurf); + uint32_t id = hmi_ctrl->interface->get_id_of_surface(ivisurf); uint32_t *ui_widget_id = NULL; wl_array_for_each(ui_widget_id, &hmi_ctrl->ui_widgets) { @@ -259,24 +259,24 @@ mode_divided_into_tiling(struct hmi_controller *hmi_ctrl, surface_y = (int32_t)surface_height; } - ivi_layout_interface->surface_set_transition(ivisurf, + hmi_ctrl->interface->surface_set_transition(ivisurf, IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, duration); - ivi_layout_interface->surface_set_visibility(ivisurf, true); - ivi_layout_interface->surface_set_destination_rectangle(ivisurf, + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); + hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, surface_x, surface_y, (int32_t)surface_width, (int32_t)surface_height); } - ivi_layout_interface->layer_set_render_order(ivilayer, new_order, i); + hmi_ctrl->interface->layer_set_render_order(ivilayer, new_order, i); - ivi_layout_interface->layer_set_transition(ivilayer, + hmi_ctrl->interface->layer_set_transition(ivilayer, IVI_LAYOUT_TRANSITION_LAYER_VIEW_ORDER, duration); } for (i = idx; i < surf_num; i++) - ivi_layout_interface->surface_set_visibility(surfaces[i], false); + hmi_ctrl->interface->surface_set_visibility(surfaces[i], false); free(surfaces); free(new_order); @@ -327,24 +327,24 @@ mode_divided_into_sidebyside(struct hmi_controller *hmi_ctrl, ivisurf = surfaces[idx]; new_order[i] = ivisurf; - ivi_layout_interface->surface_set_transition(ivisurf, + hmi_ctrl->interface->surface_set_transition(ivisurf, IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, duration); - ivi_layout_interface->surface_set_visibility(ivisurf, true); + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - ivi_layout_interface->surface_set_destination_rectangle(ivisurf, + hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, i * surface_width, 0, surface_width, surface_height); } - ivi_layout_interface->layer_set_render_order(ivilayer, new_order, i); + hmi_ctrl->interface->layer_set_render_order(ivilayer, new_order, i); } for (i = idx; i < surf_num; i++) { - ivi_layout_interface->surface_set_transition(surfaces[i], + hmi_ctrl->interface->surface_set_transition(surfaces[i], IVI_LAYOUT_TRANSITION_VIEW_FADE_ONLY, duration); - ivi_layout_interface->surface_set_visibility(surfaces[i], false); + hmi_ctrl->interface->surface_set_visibility(surfaces[i], false); } free(surfaces); @@ -377,21 +377,21 @@ mode_fullscreen_someone(struct hmi_controller *hmi_ctrl, surfaces[surf_num++] = ivisurf; } - ivi_layout_interface->layer_set_render_order(layer->ivilayer, surfaces, surf_num); + hmi_ctrl->interface->layer_set_render_order(layer->ivilayer, surfaces, surf_num); for (i = 0; i < surf_num; i++) { ivisurf = surfaces[i]; if ((i > 0) && (i < hmi_ctrl->screen_num)) { layer = wl_container_of(layer->link.prev, layer, link); - ivi_layout_interface->layer_set_render_order(layer->ivilayer, &ivisurf, 1); + hmi_ctrl->interface->layer_set_render_order(layer->ivilayer, &ivisurf, 1); } - ivi_layout_interface->surface_set_transition(ivisurf, + hmi_ctrl->interface->surface_set_transition(ivisurf, IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, duration); - ivi_layout_interface->surface_set_visibility(ivisurf, true); - ivi_layout_interface->surface_set_destination_rectangle(ivisurf, 0, 0, + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); + hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, 0, 0, surface_width, surface_height); } @@ -433,24 +433,24 @@ mode_random_replace(struct hmi_controller *hmi_ctrl, /* surface determined at random a layer that belongs */ layer_idx = rand() % hmi_ctrl->screen_num; - ivi_layout_interface->surface_set_transition(ivisurf, + hmi_ctrl->interface->surface_set_transition(ivisurf, IVI_LAYOUT_TRANSITION_VIEW_DEFAULT, duration); - ivi_layout_interface->surface_set_visibility(ivisurf, true); + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); surface_width = (int32_t)(layers[layer_idx]->width * 0.25f); surface_height = (int32_t)(layers[layer_idx]->height * 0.25f); surface_x = rand() % (layers[layer_idx]->width - surface_width); surface_y = rand() % (layers[layer_idx]->height - surface_height); - ivi_layout_interface->surface_set_destination_rectangle(ivisurf, + hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, surface_x, surface_y, surface_width, surface_height); - ivi_layout_interface->layer_add_surface(layers[layer_idx]->ivilayer, ivisurf); + hmi_ctrl->interface->layer_add_surface(layers[layer_idx]->ivilayer, ivisurf); } free(layers); @@ -495,7 +495,7 @@ switch_mode(struct hmi_controller *hmi_ctrl, hmi_ctrl->layout_mode = layout_mode; - ret = ivi_layout_interface->get_surfaces(&surface_length, &pp_surface); + ret = hmi_ctrl->interface->get_surfaces(&surface_length, &pp_surface); assert(!ret); if (!has_application_surface(hmi_ctrl, pp_surface, surface_length)) { @@ -523,7 +523,7 @@ switch_mode(struct hmi_controller *hmi_ctrl, break; } - ivi_layout_interface->commit_changes(); + hmi_ctrl->interface->commit_changes(); free(pp_surface); } @@ -541,10 +541,10 @@ hmi_controller_fade_run(struct hmi_controller *hmi_ctrl, uint32_t is_fade_in, fade->is_fade_in = is_fade_in; wl_list_for_each(linklayer, &fade->layer_list, link) { - ivi_layout_interface->layer_set_transition(linklayer->layout_layer, + hmi_ctrl->interface->layer_set_transition(linklayer->layout_layer, IVI_LAYOUT_TRANSITION_LAYER_FADE, duration); - ivi_layout_interface->layer_set_fade_info(linklayer->layout_layer, + hmi_ctrl->interface->layer_set_fade_info(linklayer->layout_layer, is_fade_in, 1.0 - tint, tint); } } @@ -555,26 +555,27 @@ hmi_controller_fade_run(struct hmi_controller *hmi_ctrl, uint32_t is_fade_in, */ static void create_layer(struct weston_output *output, - struct hmi_controller_layer *layer) + struct hmi_controller_layer *layer, + struct hmi_controller *hmi_ctrl) { int32_t ret = 0; layer->ivilayer = - ivi_layout_interface->layer_create_with_dimension(layer->id_layer, + hmi_ctrl->interface->layer_create_with_dimension(layer->id_layer, layer->width, layer->height); assert(layer->ivilayer != NULL); - ret = ivi_layout_interface->screen_add_layer(output, layer->ivilayer); + ret = hmi_ctrl->interface->screen_add_layer(output, layer->ivilayer); assert(!ret); - ret = ivi_layout_interface->layer_set_destination_rectangle(layer->ivilayer, + ret = hmi_ctrl->interface->layer_set_destination_rectangle(layer->ivilayer, layer->x, layer->y, layer->width, layer->height); assert(!ret); - ret = ivi_layout_interface->layer_set_visibility(layer->ivilayer, true); + ret = hmi_ctrl->interface->layer_set_visibility(layer->ivilayer, true); assert(!ret); } @@ -599,7 +600,7 @@ set_notification_create_surface(struct wl_listener *listener, void *data) if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) return; - ret = ivi_layout_interface->layer_add_surface(application_layer, ivisurf); + ret = hmi_ctrl->interface->layer_add_surface(application_layer, ivisurf); assert(!ret); } @@ -637,9 +638,9 @@ set_notification_configure_surface(struct wl_listener *listener, void *data) * if application changes size of wl_buffer. The source rectangle shall be * fit to the size. */ - surface = ivi_layout_interface->surface_get_weston_surface(ivisurf); + surface = hmi_ctrl->interface->surface_get_weston_surface(ivisurf); if (surface) { - ivi_layout_interface->surface_set_source_rectangle( + hmi_ctrl->interface->surface_set_source_rectangle( ivisurf, 0, 0, surface->width, surface->height); } @@ -650,7 +651,7 @@ set_notification_configure_surface(struct wl_listener *listener, void *data) */ wl_list_for_each_reverse(layer_link, &hmi_ctrl->application_layer_list, link) { application_layer = layer_link->ivilayer; - ivi_layout_interface->get_surfaces_on_layer(application_layer, + hmi_ctrl->interface->get_surfaces_on_layer(application_layer, &length, &ivisurfs); for (i = 0; i < length; i++) { if (ivisurf == ivisurfs[i]) { @@ -658,7 +659,7 @@ set_notification_configure_surface(struct wl_listener *listener, void *data) * if it is non new invoked application, just call * commit_changes to apply source_rectangle. */ - ivi_layout_interface->commit_changes(); + hmi_ctrl->interface->commit_changes(); free(ivisurfs); return; } @@ -765,7 +766,8 @@ hmi_controller_destroy(struct wl_listener *listener, void *data) * ivi_hmi_controller_home is requested. */ static struct hmi_controller * -hmi_controller_create(struct weston_compositor *ec) +hmi_controller_create(struct weston_compositor *ec, + const struct ivi_layout_interface *interface) { struct link_layer *tmp_link_layer = NULL; int32_t panel_height = 0; @@ -781,6 +783,7 @@ hmi_controller_create(struct weston_compositor *ec) hmi_ctrl->hmi_setting = hmi_server_setting_create(ec); hmi_ctrl->compositor = ec; hmi_ctrl->screen_num = wl_list_length(&ec->output_list); + hmi_ctrl->interface = interface; /* init base ivi_layer*/ wl_list_init(&hmi_ctrl->base_layer_list); @@ -795,7 +798,7 @@ hmi_controller_create(struct weston_compositor *ec) (i * hmi_ctrl->hmi_setting->base_layer_id_offset); wl_list_insert(&hmi_ctrl->base_layer_list, &base_layer->link); - create_layer(output, base_layer); + create_layer(output, base_layer, hmi_ctrl); i++; } @@ -815,7 +818,7 @@ hmi_controller_create(struct weston_compositor *ec) (i * hmi_ctrl->hmi_setting->base_layer_id_offset); wl_list_insert(&hmi_ctrl->application_layer_list, &application_layer->link); - create_layer(output, application_layer); + create_layer(output, application_layer, hmi_ctrl); i++; } @@ -832,10 +835,10 @@ hmi_controller_create(struct weston_compositor *ec) hmi_ctrl->workspace_background_layer.id_layer = hmi_ctrl->hmi_setting->workspace_background_layer_id; - create_layer(output, &hmi_ctrl->workspace_background_layer); - ivi_layout_interface->layer_set_opacity( + create_layer(output, &hmi_ctrl->workspace_background_layer, hmi_ctrl); + hmi_ctrl->interface->layer_set_opacity( hmi_ctrl->workspace_background_layer.ivilayer, 0); - ivi_layout_interface->layer_set_visibility( + hmi_ctrl->interface->layer_set_visibility( hmi_ctrl->workspace_background_layer.ivilayer, false); @@ -847,10 +850,10 @@ hmi_controller_create(struct weston_compositor *ec) &tmp_link_layer->link); hmi_ctrl->surface_removed.notify = set_notification_remove_surface; - ivi_layout_interface->add_listener_remove_surface(&hmi_ctrl->surface_removed); + hmi_ctrl->interface->add_listener_remove_surface(&hmi_ctrl->surface_removed); hmi_ctrl->surface_configured.notify = set_notification_configure_surface; - ivi_layout_interface->add_listener_configure_surface(&hmi_ctrl->surface_configured); + hmi_ctrl->interface->add_listener_configure_surface(&hmi_ctrl->surface_configured); hmi_ctrl->destroy_listener.notify = hmi_controller_destroy; wl_signal_add(&hmi_ctrl->compositor->destroy_signal, @@ -894,17 +897,17 @@ ivi_hmi_controller_set_background(struct hmi_controller *hmi_ctrl, height = base_layer->height; ivilayer = base_layer->ivilayer; - ivisurf = ivi_layout_interface->get_surface_from_id(*add_surface_id); + ivisurf = hmi_ctrl->interface->get_surface_from_id(*add_surface_id); assert(ivisurf != NULL); - ret = ivi_layout_interface->layer_add_surface(ivilayer, ivisurf); + ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); assert(!ret); - ret = ivi_layout_interface->surface_set_destination_rectangle(ivisurf, + ret = hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, dstx, dsty, width, height); assert(!ret); - ret = ivi_layout_interface->surface_set_visibility(ivisurf, true); + ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); assert(!ret); i++; @@ -938,20 +941,20 @@ ivi_hmi_controller_set_panel(struct hmi_controller *hmi_ctrl, *add_surface_id = id_surface + (i * hmi_ctrl->ui_setting.surface_id_offset); ivilayer = base_layer->ivilayer; - ivisurf = ivi_layout_interface->get_surface_from_id(*add_surface_id); + ivisurf = hmi_ctrl->interface->get_surface_from_id(*add_surface_id); assert(ivisurf != NULL); - ret = ivi_layout_interface->layer_add_surface(ivilayer, ivisurf); + ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); assert(!ret); dsty = base_layer->height - panel_height; width = base_layer->width; - ret = ivi_layout_interface->surface_set_destination_rectangle( + ret = hmi_ctrl->interface->surface_set_destination_rectangle( ivisurf, dstx, dsty, width, panel_height); assert(!ret); - ret = ivi_layout_interface->surface_set_visibility(ivisurf, true); + ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); assert(!ret); i++; @@ -987,10 +990,10 @@ ivi_hmi_controller_set_button(struct hmi_controller *hmi_ctrl, sizeof(*add_surface_id)); *add_surface_id = id_surface; - ivisurf = ivi_layout_interface->get_surface_from_id(id_surface); + ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); assert(ivisurf != NULL); - ret = ivi_layout_interface->layer_add_surface(ivilayer, ivisurf); + ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); assert(!ret); panel_height = hmi_ctrl->hmi_setting->panel_height; @@ -998,11 +1001,11 @@ ivi_hmi_controller_set_button(struct hmi_controller *hmi_ctrl, dstx = (60 * number) + 15; dsty = (base_layer->height - panel_height) + 5; - ret = ivi_layout_interface->surface_set_destination_rectangle( + ret = hmi_ctrl->interface->surface_set_destination_rectangle( ivisurf,dstx, dsty, width, height); assert(!ret); - ret = ivi_layout_interface->surface_set_visibility(ivisurf, true); + ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); assert(!ret); } @@ -1033,17 +1036,17 @@ ivi_hmi_controller_set_home_button(struct hmi_controller *hmi_ctrl, sizeof(*add_surface_id)); *add_surface_id = id_surface; - ivisurf = ivi_layout_interface->get_surface_from_id(id_surface); + ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); assert(ivisurf != NULL); - ret = ivi_layout_interface->layer_add_surface(ivilayer, ivisurf); + ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); assert(!ret); - ret = ivi_layout_interface->surface_set_destination_rectangle( + ret = hmi_ctrl->interface->surface_set_destination_rectangle( ivisurf, dstx, dsty, size, size); assert(!ret); - ret = ivi_layout_interface->surface_set_visibility(ivisurf, true); + ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); assert(!ret); } @@ -1069,17 +1072,17 @@ ivi_hmi_controller_set_workspacebackground(struct hmi_controller *hmi_ctrl, *add_surface_id = id_surface; ivilayer = hmi_ctrl->workspace_background_layer.ivilayer; - ivisurf = ivi_layout_interface->get_surface_from_id(id_surface); + ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); assert(ivisurf != NULL); - ret = ivi_layout_interface->layer_add_surface(ivilayer, ivisurf); + ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); assert(!ret); - ret = ivi_layout_interface->surface_set_destination_rectangle(ivisurf, + ret = hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, 0, 0, width, height); assert(!ret); - ret = ivi_layout_interface->surface_set_visibility(ivisurf, true); + ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); assert(!ret); } @@ -1205,10 +1208,10 @@ ivi_hmi_controller_add_launchers(struct hmi_controller *hmi_ctrl, y = ny * fcell_size_y + space_y; layout_surface = - ivi_layout_interface->get_surface_from_id(data->surface_id); + hmi_ctrl->interface->get_surface_from_id(data->surface_id); assert(layout_surface); - ret = ivi_layout_interface->surface_set_destination_rectangle( + ret = hmi_ctrl->interface->surface_set_destination_rectangle( layout_surface, x, y, icon_size, icon_size); assert(!ret); @@ -1230,9 +1233,10 @@ ivi_hmi_controller_add_launchers(struct hmi_controller *hmi_ctrl, hmi_ctrl->workspace_layer.id_layer = hmi_ctrl->hmi_setting->workspace_layer_id; - create_layer(hmi_ctrl->workspace_background_output, &hmi_ctrl->workspace_layer); - ivi_layout_interface->layer_set_opacity(hmi_ctrl->workspace_layer.ivilayer, 0); - ivi_layout_interface->layer_set_visibility(hmi_ctrl->workspace_layer.ivilayer, + create_layer(hmi_ctrl->workspace_background_output, + &hmi_ctrl->workspace_layer, hmi_ctrl); + hmi_ctrl->interface->layer_set_opacity(hmi_ctrl->workspace_layer.ivilayer, 0); + hmi_ctrl->interface->layer_set_visibility(hmi_ctrl->workspace_layer.ivilayer, false); tmp_link_layer = MEM_ALLOC(sizeof(*tmp_link_layer)); @@ -1243,19 +1247,19 @@ ivi_hmi_controller_add_launchers(struct hmi_controller *hmi_ctrl, /* Add surface to layer */ wl_array_for_each(data, &launchers) { layout_surface = - ivi_layout_interface->get_surface_from_id(data->surface_id); + hmi_ctrl->interface->get_surface_from_id(data->surface_id); assert(layout_surface); - ret = ivi_layout_interface->layer_add_surface(hmi_ctrl->workspace_layer.ivilayer, + ret = hmi_ctrl->interface->layer_add_surface(hmi_ctrl->workspace_layer.ivilayer, layout_surface); assert(!ret); - ret = ivi_layout_interface->surface_set_visibility(layout_surface, true); + ret = hmi_ctrl->interface->surface_set_visibility(layout_surface, true); assert(!ret); } wl_array_release(&launchers); - ivi_layout_interface->commit_changes(); + hmi_ctrl->interface->commit_changes(); } static void @@ -1272,7 +1276,7 @@ ivi_hmi_controller_UI_ready(struct wl_client *client, ivi_hmi_controller_set_button(hmi_ctrl, hmi_ctrl->ui_setting.random_id, 3); ivi_hmi_controller_set_home_button(hmi_ctrl, hmi_ctrl->ui_setting.home_id); ivi_hmi_controller_set_workspacebackground(hmi_ctrl, hmi_ctrl->ui_setting.workspace_background_id); - ivi_layout_interface->commit_changes(); + hmi_ctrl->interface->commit_changes(); ivi_hmi_controller_add_launchers(hmi_ctrl, 256); @@ -1280,7 +1284,7 @@ ivi_hmi_controller_UI_ready(struct wl_client *client, * Otherwise, surfaces of the launchers will be added to application * layer too.*/ hmi_ctrl->surface_created.notify = set_notification_create_surface; - ivi_layout_interface->add_listener_create_surface(&hmi_ctrl->surface_created); + hmi_ctrl->interface->add_listener_create_surface(&hmi_ctrl->surface_created); hmi_ctrl->is_initialized = 1; } @@ -1398,7 +1402,7 @@ move_workspace_grab_end(struct move_grab *move, struct wl_resource* resource, if (200 < from_motion_time) pointer_v = 0.0; - prop = ivi_layout_interface->get_properties_of_layer(layer); + prop = hmi_ctrl->interface->get_properties_of_layer(layer); pos_x = prop->dest_x; pos_y = prop->dest_y; @@ -1419,14 +1423,14 @@ move_workspace_grab_end(struct move_grab *move, struct wl_resource* resource, duration = hmi_ctrl->hmi_setting->transition_duration; ivi_hmi_controller_send_workspace_end_control(resource, move->is_moved); - ivi_layout_interface->layer_set_transition(layer, + hmi_ctrl->interface->layer_set_transition(layer, IVI_LAYOUT_TRANSITION_LAYER_MOVE, duration); - ivi_layout_interface->layer_set_destination_rectangle(layer, + hmi_ctrl->interface->layer_set_destination_rectangle(layer, end_pos, pos_y, hmi_ctrl->workspace_layer.width, hmi_ctrl->workspace_layer.height); - ivi_layout_interface->commit_changes(); + hmi_ctrl->interface->commit_changes(); } static void @@ -1517,20 +1521,20 @@ move_grab_update(struct move_grab *move, wl_fixed_t pointer[2]) } static void -layer_set_pos(struct ivi_layout_layer *layer, wl_fixed_t pos_x, - wl_fixed_t pos_y) +layer_set_pos(struct hmi_controller *hmi_ctrl, struct ivi_layout_layer *layer, + wl_fixed_t pos_x, wl_fixed_t pos_y) { const struct ivi_layout_layer_properties *prop; int32_t layout_pos_x = 0; int32_t layout_pos_y = 0; - prop = ivi_layout_interface->get_properties_of_layer(layer); + prop = hmi_ctrl->interface->get_properties_of_layer(layer); layout_pos_x = wl_fixed_to_int(pos_x); layout_pos_y = wl_fixed_to_int(pos_y); - ivi_layout_interface->layer_set_destination_rectangle(layer, + hmi_ctrl->interface->layer_set_destination_rectangle(layer, layout_pos_x, layout_pos_y, prop->dest_width, prop->dest_height); - ivi_layout_interface->commit_changes(); + hmi_ctrl->interface->commit_changes(); } static void @@ -1540,12 +1544,14 @@ pointer_move_grab_motion(struct weston_pointer_grab *grab, { struct pointer_move_grab *pnt_move_grab = (struct pointer_move_grab *)grab; + struct hmi_controller *hmi_ctrl = + wl_resource_get_user_data(pnt_move_grab->base.resource); wl_fixed_t pointer_pos[2]; weston_pointer_motion_to_abs(grab->pointer, event, &pointer_pos[0], &pointer_pos[1]); move_grab_update(&pnt_move_grab->move, pointer_pos); - layer_set_pos(pnt_move_grab->base.layer, + layer_set_pos(hmi_ctrl, pnt_move_grab->base.layer, pnt_move_grab->move.pos[0], pnt_move_grab->move.pos[1]); weston_pointer_move(pnt_move_grab->base.grab.pointer, event); } @@ -1556,6 +1562,8 @@ touch_move_grab_motion(struct weston_touch_grab *grab, wl_fixed_t x, wl_fixed_t y) { struct touch_move_grab *tch_move_grab = (struct touch_move_grab *)grab; + struct hmi_controller *hmi_ctrl = + wl_resource_get_user_data(tch_move_grab->base.resource); if (!tch_move_grab->is_active) return; @@ -1566,7 +1574,7 @@ touch_move_grab_motion(struct weston_touch_grab *grab, }; move_grab_update(&tch_move_grab->move, pointer_pos); - layer_set_pos(tch_move_grab->base.layer, + layer_set_pos(hmi_ctrl, tch_move_grab->base.layer, tch_move_grab->move.pos[0], tch_move_grab->move.pos[1]); } @@ -1706,7 +1714,7 @@ move_grab_init_workspace(struct move_grab* move, wl_fixed_t rgn[2][2] = {{0}}; wl_fixed_t grab_pos[2] = { grab_x, grab_y }; - prop = ivi_layout_interface->get_properties_of_layer(layer); + prop = hmi_ctrl->interface->get_properties_of_layer(layer); layer_pos_x = prop->dest_x; layer_pos_y = prop->dest_y; @@ -1779,7 +1787,7 @@ ivi_hmi_controller_workspace_control(struct wl_client *client, layer = hmi_ctrl->workspace_layer.ivilayer; - ivi_layout_interface->transition_move_layer_cancel(layer); + hmi_ctrl->interface->transition_move_layer_cancel(layer); switch (device) { case HMI_GRAB_DEVICE_POINTER: @@ -1841,7 +1849,7 @@ ivi_hmi_controller_home(struct wl_client *client, &hmi_ctrl->workspace_fade); } - ivi_layout_interface->commit_changes(); + hmi_ctrl->interface->commit_changes(); } /** @@ -1959,9 +1967,7 @@ controller_module_init(struct weston_compositor *ec, return -1; } - ivi_layout_interface = interface; - - hmi_ctrl = hmi_controller_create(ec); + hmi_ctrl = hmi_controller_create(ec, interface); if (hmi_ctrl == NULL) return -1; From ffaf09eb2f4bdcaf0ea57e8e49b1cd8ba4fb18d5 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Thu, 25 Jan 2018 14:36:12 +0100 Subject: [PATCH 0301/1642] hmi-controller: load as weston module weston loads hmi-controller as a weston module. IVI-shell does not need to load it explicitly. Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/hmi-controller.c | 36 ++++++++++++++++-------------------- ivi-shell/weston.ini.in | 2 +- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index 34abc11b6..b13936f59 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -148,12 +148,6 @@ struct launcher_info { int32_t index; }; -int -controller_module_init(struct weston_compositor *ec, - int *argc, char *argv[], - const struct ivi_layout_interface *interface, - size_t interface_version); - /***************************************************************************** * local functions ****************************************************************************/ @@ -766,17 +760,26 @@ hmi_controller_destroy(struct wl_listener *listener, void *data) * ivi_hmi_controller_home is requested. */ static struct hmi_controller * -hmi_controller_create(struct weston_compositor *ec, - const struct ivi_layout_interface *interface) +hmi_controller_create(struct weston_compositor *ec) { struct link_layer *tmp_link_layer = NULL; int32_t panel_height = 0; - struct hmi_controller *hmi_ctrl = MEM_ALLOC(sizeof(*hmi_ctrl)); + struct hmi_controller *hmi_ctrl; + const struct ivi_layout_interface *interface; struct hmi_controller_layer *base_layer = NULL; struct hmi_controller_layer *application_layer = NULL; struct weston_output *output; + int32_t i; - int32_t i = 0; + interface = ivi_layout_get_api(ec); + + if (!interface) { + weston_log("Cannot use ivi_layout_interface.\n"); + return NULL; + } + + hmi_ctrl = MEM_ALLOC(sizeof(*hmi_ctrl)); + i = 0; wl_array_init(&hmi_ctrl->ui_widgets); hmi_ctrl->layout_mode = IVI_HMI_CONTROLLER_LAYOUT_MODE_TILING; @@ -1954,20 +1957,13 @@ launch_hmi_client_process(void *data) * exported functions ****************************************************************************/ WL_EXPORT int -controller_module_init(struct weston_compositor *ec, - int *argc, char *argv[], - const struct ivi_layout_interface *interface, - size_t interface_version) +wet_module_init(struct weston_compositor *ec, + int *argc, char *argv[]) { struct hmi_controller *hmi_ctrl = NULL; struct wl_event_loop *loop = NULL; - if (interface_version < sizeof(struct ivi_layout_interface)) { - weston_log("ivi-shell: version mismatch of controller interface\n"); - return -1; - } - - hmi_ctrl = hmi_controller_create(ec, interface); + hmi_ctrl = hmi_controller_create(ec); if (hmi_ctrl == NULL) return -1; diff --git a/ivi-shell/weston.ini.in b/ivi-shell/weston.ini.in index 9b53691a6..3f11e1c02 100644 --- a/ivi-shell/weston.ini.in +++ b/ivi-shell/weston.ini.in @@ -1,8 +1,8 @@ [core] shell=@plugin_prefix@ivi-shell.so +modules=@plugin_prefix@hmi-controller.so [ivi-shell] -ivi-module=@plugin_prefix@hmi-controller.so ivi-shell-user-interface=@abs_top_builddir@/weston-ivi-shell-user-interface #developermode=true From 0707b0e5d48fa6f1ac41ea60c2adc2e6430c7425 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Thu, 25 Jan 2018 14:36:13 +0100 Subject: [PATCH 0302/1642] tests: load ivi-shell test plugins as weston module It is better to load ivi controller modules as a generic weston module. Then, we do not need to have a specific ivi way of loading modules. Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- tests/ivi_layout-internal-test.c | 21 ++++++++------------- tests/ivi_layout-test-plugin.c | 20 +++++++------------- tests/weston-tests-env | 3 +-- 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/tests/ivi_layout-internal-test.c b/tests/ivi_layout-internal-test.c index 4d73eff1a..1054d9700 100644 --- a/tests/ivi_layout-internal-test.c +++ b/tests/ivi_layout-internal-test.c @@ -33,6 +33,7 @@ #include #include "compositor.h" +#include "compositor/weston.h" #include "ivi-shell/ivi-layout-export.h" #include "ivi-shell/ivi-layout-private.h" #include "ivi-test.h" @@ -991,24 +992,18 @@ run_internal_tests(void *data) free(ctx); } -int -controller_module_init(struct weston_compositor *compositor, - int *argc, char *argv[], - const struct ivi_layout_interface *iface, - size_t iface_version); - WL_EXPORT int -controller_module_init(struct weston_compositor *compositor, - int *argc, char *argv[], - const struct ivi_layout_interface *iface, - size_t iface_version) +wet_module_init(struct weston_compositor *compositor, + int *argc, char *argv[]) { struct wl_event_loop *loop; struct test_context *ctx; + const struct ivi_layout_interface *iface; + + iface = ivi_layout_get_api(compositor); - /* strict check, since this is an internal test module */ - if (iface_version != sizeof(*iface)) { - weston_log("fatal: controller interface mismatch\n"); + if (!iface) { + weston_log("fatal: cannot use ivi_layout_interface.\n"); return -1; } diff --git a/tests/ivi_layout-test-plugin.c b/tests/ivi_layout-test-plugin.c index 19eab81a0..1f19c55f0 100644 --- a/tests/ivi_layout-test-plugin.c +++ b/tests/ivi_layout-test-plugin.c @@ -217,25 +217,19 @@ idle_launch_client(void *data) weston_watch_process(&launcher->process); } -int -controller_module_init(struct weston_compositor *compositor, - int *argc, char *argv[], - const struct ivi_layout_interface *iface, - size_t iface_version); - WL_EXPORT int -controller_module_init(struct weston_compositor *compositor, - int *argc, char *argv[], - const struct ivi_layout_interface *iface, - size_t iface_version) +wet_module_init(struct weston_compositor *compositor, + int *argc, char *argv[]) { struct wl_event_loop *loop; struct test_launcher *launcher; const char *path; + const struct ivi_layout_interface *iface; + + iface = ivi_layout_get_api(compositor); - /* strict check, since this is an internal test module */ - if (iface_version != sizeof(*iface)) { - weston_log("fatal: controller interface mismatch\n"); + if (!iface) { + weston_log("fatal: cannot use ivi_layout_interface.\n"); return -1; } diff --git a/tests/weston-tests-env b/tests/weston-tests-env index f62b70ec2..f08270e2a 100755 --- a/tests/weston-tests-env +++ b/tests/weston-tests-env @@ -47,8 +47,7 @@ case $TEST_FILE in --config=$abs_builddir/tests/weston-ivi.ini \ --shell=$SHELL_PLUGIN \ --socket=test-${TEST_NAME} \ - --modules=$TEST_PLUGIN \ - --ivi-module=$MODDIR/${TEST_FILE/.la/.so} \ + --modules=$TEST_PLUGIN,$MODDIR/${TEST_FILE/.la/.so}\ --log="$SERVERLOG" \ &> "$OUTLOG" ;; From 0c1bbb9e52eeec78b76d54a7816eaf7d9fc3294b Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Thu, 25 Jan 2018 14:36:14 +0100 Subject: [PATCH 0303/1642] ivi-shell: don't load controller modules controller modules can be loaded as weston modules from the main function of weston. Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout.c | 40 ---------------------------------------- ivi-shell/ivi-shell.c | 28 ++++------------------------ 2 files changed, 4 insertions(+), 64 deletions(-) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index a11b65852..f9a83abf1 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -2012,43 +2012,3 @@ static struct ivi_layout_interface ivi_layout_interface = { .surface_get_size = ivi_layout_surface_get_size, .surface_dump = ivi_layout_surface_dump, }; - -int -load_controller_modules(struct weston_compositor *compositor, const char *modules, - int *argc, char *argv[]) -{ - const char *p, *end; - char buffer[256]; - int (*controller_module_init)(struct weston_compositor *compositor, - int *argc, char *argv[], - const struct ivi_layout_interface *interface, - size_t interface_version); - - if (modules == NULL) - return 0; - - p = modules; - while (*p) { - end = strchrnul(p, ','); - snprintf(buffer, sizeof buffer, "%.*s", (int)(end - p), p); - - controller_module_init = - wet_load_module_entrypoint(buffer, - "controller_module_init"); - if (!controller_module_init) - return -1; - - if (controller_module_init(compositor, argc, argv, - &ivi_layout_interface, - sizeof(struct ivi_layout_interface)) != 0) { - weston_log("ivi-shell: Initialization of controller module fails"); - return -1; - } - - p = end; - while (*p == ',') - p++; - } - - return 0; -} diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index c09695468..ebca2fc25 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -69,7 +69,6 @@ struct ivi_shell_surface struct ivi_shell_setting { - char *ivi_module; int developermode; }; @@ -413,20 +412,8 @@ ivi_shell_setting_create(struct ivi_shell_setting *dest, struct weston_config *config = wet_get_config(compositor); struct weston_config_section *section; - const struct weston_option ivi_shell_options[] = { - { WESTON_OPTION_STRING, "ivi-module", 0, &dest->ivi_module }, - }; - - parse_options(ivi_shell_options, ARRAY_LENGTH(ivi_shell_options), - argc, argv); - section = weston_config_get_section(config, "ivi-shell", NULL, NULL); - if (!dest->ivi_module && - weston_config_section_get_string(section, "ivi-module", - &dest->ivi_module, NULL) < 0) { - } - weston_config_section_get_bool(section, "developermode", &dest->developermode, 0); @@ -512,29 +499,22 @@ wet_shell_init(struct weston_compositor *compositor, wl_signal_add(&compositor->destroy_signal, &shell->destroy_listener); if (input_panel_setup(shell) < 0) - goto out_settings; + goto out; shell->text_backend = text_backend_init(compositor); if (!shell->text_backend) - goto out_settings; + goto out; if (wl_global_create(compositor->wl_display, &ivi_application_interface, 1, shell, bind_ivi_application) == NULL) - goto out_settings; + goto out; ivi_layout_init_with_compositor(compositor); shell_add_bindings(compositor, shell); - /* Call module_init of ivi-modules which are defined in weston.ini */ - if (load_controller_modules(compositor, setting.ivi_module, - argc, argv) < 0) - goto out_settings; - retval = 0; -out_settings: - free(setting.ivi_module); - +out: return retval; } From f85bf152c197d4e378f5aa727cef0bde6afc4e80 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Thu, 25 Jan 2018 14:37:38 +0100 Subject: [PATCH 0304/1642] ivi-shell: remove ivi_shell_setting it has only developermode option parameter. The parameter is only used in init_ivi_shell. Therefore, we can basically remove the struct, and check the option locally in the function. Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-shell.c | 42 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index ebca2fc25..51e13a0f5 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -67,11 +67,6 @@ struct ivi_shell_surface struct wl_list link; }; -struct ivi_shell_setting -{ - int developermode; -}; - /* * Implementation of ivi_surface */ @@ -384,16 +379,24 @@ terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, } static void -init_ivi_shell(struct weston_compositor *compositor, struct ivi_shell *shell, - const struct ivi_shell_setting *setting) +init_ivi_shell(struct weston_compositor *compositor, struct ivi_shell *shell) { + struct weston_config *config = wet_get_config(compositor); + struct weston_config_section *section; + int developermode; + shell->compositor = compositor; wl_list_init(&shell->ivi_surface_list); weston_layer_init(&shell->input_panel_layer, compositor); - if (setting->developermode) { + section = weston_config_get_section(config, "ivi-shell", NULL, NULL); + + weston_config_section_get_bool(section, "developermode", + &developermode, 0); + + if (developermode) { weston_install_debug_key_binding(compositor, MODIFIER_SUPER); weston_compositor_add_key_binding(compositor, KEY_BACKSPACE, @@ -403,23 +406,6 @@ init_ivi_shell(struct weston_compositor *compositor, struct ivi_shell *shell, } } -static int -ivi_shell_setting_create(struct ivi_shell_setting *dest, - struct weston_compositor *compositor, - int *argc, char *argv[]) -{ - int result = 0; - struct weston_config *config = wet_get_config(compositor); - struct weston_config_section *section; - - section = weston_config_get_section(config, "ivi-shell", NULL, NULL); - - weston_config_section_get_bool(section, "developermode", - &dest->developermode, 0); - - return result; -} - static void activate_binding(struct weston_seat *seat, struct weston_view *focus_view) @@ -483,17 +469,13 @@ wet_shell_init(struct weston_compositor *compositor, int *argc, char *argv[]) { struct ivi_shell *shell; - struct ivi_shell_setting setting = { }; int retval = -1; shell = zalloc(sizeof *shell); if (shell == NULL) return -1; - if (ivi_shell_setting_create(&setting, compositor, argc, argv) != 0) - return -1; - - init_ivi_shell(compositor, shell, &setting); + init_ivi_shell(compositor, shell); shell->destroy_listener.notify = shell_destroy; wl_signal_add(&compositor->destroy_signal, &shell->destroy_listener); From 4b72ff0e89c380d2dc3183bdc5be9f11fce16817 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 5 Feb 2018 15:59:29 -0600 Subject: [PATCH 0305/1642] xwayland: Fix crash on weston shutdown commit e7fff215ada3fd3d1b2af664888f960c082f9065 made initializing the selection_listener conditional, but didn't make its clean-up conditional at shutdown. Simply initializing the listener's list link at init time makes this harmless. To see this, run weston -Bheadless-backend.so and then connect to it with an X client. When killing weston it will attempt shutdown but die with a segfault. Signed-off-by: Derek Foreman Reviewed-by: Daniel Stone --- xwayland/selection.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xwayland/selection.c b/xwayland/selection.c index 19a24d26d..a75557044 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -681,6 +681,8 @@ weston_wm_selection_init(struct weston_wm *wm) struct weston_seat *seat; uint32_t values[1], mask; + wl_list_init(&wm->selection_listener.link); + wm->selection_request.requestor = XCB_NONE; values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; From a4608461b2abb763d0358fc5fb92777568dab39f Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Fri, 26 Jan 2018 15:04:56 +0100 Subject: [PATCH 0306/1642] ivi-shell: change layer visibility to bool ivi_layout_layer_set_visibility has bool as argument. Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout-export.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index c15c7f83f..016d8b5c7 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -102,7 +102,7 @@ struct ivi_layout_layer_properties int32_t dest_width; int32_t dest_height; enum wl_output_transform orientation; - uint32_t visibility; + bool visibility; int32_t transition_type; uint32_t transition_duration; double start_alpha; From 7b690559a51da83ca1177d7d486672de8722a6ae Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Fri, 26 Jan 2018 15:04:57 +0100 Subject: [PATCH 0307/1642] ivi-shell: don't schedule compositor repaint it is not necessary to repaint all outputs after each commit_changes. Only outputs with modified views has to be repainted. We need to call weston_view_update_transform for assigning views to outputs first. Then, We can call weston_view_schedule_repaint to trigger repaint for outputs. Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index f9a83abf1..569083e50 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -27,7 +27,7 @@ * Implementation of ivi-layout library. The actual view on ivi_screen is * not updated until ivi_layout_commit_changes is called. An overview from * calling API for updating properties of ivi_surface/ivi_layer to asking - * compositor to compose them by using weston_compositor_schedule_repaint, + * compositor to compose them by using weston_view_schedule_repaint, * 0/ initialize this library by ivi_layout_init_with_compositor * with (struct weston_compositor *ec) from ivi-shell. * 1/ When an API for updating properties of ivi_surface/ivi_layer, it updates @@ -51,8 +51,8 @@ * 4/ According properties, set transformation by using weston_matrix and * weston_view per ivi_surfaces and ivi_layers in while loop. * 5/ Set damage and trigger transform by using weston_view_geometry_dirty. - * 6/ Notify update of properties. - * 7/ Trigger composition by weston_compositor_schedule_repaint. + * 6/ Schedule repaint for each view by using weston_view_schedule_repaint. + * 7/ Notify update of properties. * */ #include "config.h" @@ -577,13 +577,13 @@ update_prop(struct ivi_layout_view *ivi_view) &ivi_view->transform.link); weston_view_set_transform_parent(ivi_view->view, NULL); + weston_view_geometry_dirty(ivi_view->view); + weston_view_update_transform(ivi_view->view); } ivisurf->update_count++; - weston_view_geometry_dirty(ivi_view->view); - - weston_surface_damage(ivisurf->surface); + weston_view_schedule_repaint(ivi_view->view); } static void @@ -1752,7 +1752,6 @@ ivi_layout_commit_changes(void) commit_changes(layout); send_prop(layout); - weston_compositor_schedule_repaint(layout->compositor); return IVI_SUCCEEDED; } From 40d67c28623c94cd20ffc92531db7455060f49f9 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Wed, 7 Feb 2018 16:54:30 +0100 Subject: [PATCH 0308/1642] ivi-shell: don't expilicitly assign outputs to views it is assigned in weston_view_assign_outputs Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 569083e50..5346d7ea0 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -822,7 +822,6 @@ commit_screen_list(struct ivi_layout *layout) weston_layer_entry_insert(&layout->layout_layer.view_list, &ivi_view->view->layer_link); - ivi_view->view->output = iviscrn->output; ivi_view->ivisurf->surface->is_mapped = true; ivi_view->view->is_mapped = true; } From e8ff7df863a10eb4be5273017fb544b5f823fc6a Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Fri, 26 Jan 2018 15:04:59 +0100 Subject: [PATCH 0309/1642] ivi-shell: fix the layer assignment change from one screen to another if the layer is in order of some screen we need to remove it from there and mark the screen order as dirty so it will be removed in commit_screen_list call later layer should only be assigned to one screen at a time Signed-off-by: Eugen Friedrich Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 5346d7ea0..d9a0c2dee 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -1538,6 +1538,11 @@ ivi_layout_screen_add_layer(struct weston_output *output, iviscrn = get_screen_from_output(output); + /*if layer is already assigned to screen make order of it dirty + * we are going to remove it (in commit_screen_list)*/ + if (addlayer->on_screen) + addlayer->on_screen->order.dirty = 1; + wl_list_remove(&addlayer->pending.link); wl_list_insert(&iviscrn->pending.layer_list, &addlayer->pending.link); From e3c2a76d8f6ec09da2dc7fb3e9e9a56f8f48ca44 Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Tue, 23 Jan 2018 01:05:20 +0100 Subject: [PATCH 0310/1642] screenshot: save each new screenshot in a different file Instead of overwriting the 'wayland-screenshot.png' file over and over, store each requested screenshot in a filename based on timestamp and sequence number. Signed-off-by: Aleksander Morgado Reviewed-by: Daniel Stone --- clients/screenshot.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/clients/screenshot.c b/clients/screenshot.c index 6e43d5ce4..be8d9fe6d 100644 --- a/clients/screenshot.c +++ b/clients/screenshot.c @@ -39,6 +39,7 @@ #include "weston-screenshooter-client-protocol.h" #include "shared/os-compatibility.h" #include "shared/xalloc.h" +#include "shared/file-util.h" /* The screenshooter is a good example of a custom object exposed by * the compositor and serves as a test bed for implementing client @@ -188,6 +189,8 @@ write_png(int width, int height) cairo_surface_t *surface; void *data, *d, *s; struct screenshooter_output *output, *next; + FILE *fp; + char filepath[PATH_MAX]; buffer_stride = width * 4; @@ -213,7 +216,12 @@ write_png(int width, int height) surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, width, height, buffer_stride); - cairo_surface_write_to_png(surface, "wayland-screenshot.png"); + + fp = file_create_dated("wayland-screenshot-", ".png", filepath, sizeof(filepath)); + if (fp) { + fclose (fp); + cairo_surface_write_to_png(surface, filepath); + } cairo_surface_destroy(surface); free(data); } From 72032accbf6edf5a1f445f5d3ba67458e29f503e Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Tue, 23 Jan 2018 01:05:21 +0100 Subject: [PATCH 0311/1642] file-util: allow specifying path separately in file_create_dated() Instead of assuming the file prefix contains the path and filename prefix, give these two items separately. A NULL or empty string path may still be given to refer to the current directory. Signed-off-by: Aleksander Morgado Reviewed-by: Daniel Stone --- libweston/timeline.c | 2 +- shared/file-util.c | 20 +++++++++++++------- shared/file-util.h | 2 +- tests/surface-screenshot.c | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/libweston/timeline.c b/libweston/timeline.c index 8234c27cd..f5a39cbad 100644 --- a/libweston/timeline.c +++ b/libweston/timeline.c @@ -53,7 +53,7 @@ weston_timeline_do_open(void) const char *suffix = ".log"; char fname[1000]; - timeline_.file = file_create_dated(prefix, suffix, + timeline_.file = file_create_dated(NULL, prefix, suffix, fname, sizeof(fname)); if (!timeline_.file) { const char *msg; diff --git a/shared/file-util.c b/shared/file-util.c index e254217cb..fcc4c3f4f 100644 --- a/shared/file-util.c +++ b/shared/file-util.c @@ -66,14 +66,15 @@ create_file_excl(const char *fname) /** Create a unique file with date and time in the name * - * \param path_prefix Path and file name prefix. + * \param path File path + * \param prefix File name prefix. * \param suffix File name suffix. * \param name_out[out] Buffer for the resulting file name. * \param name_len Number of bytes usable in name_out. * \return stdio FILE pointer, or NULL on failure. * * Create and open a new file with the name concatenated from - * path_prefix, date and time, and suffix. If a file with this name + * path/prefix, date and time, and suffix. If a file with this name * already exists, an counter number is added to the end of the * date and time sub-string. The counter is increased until a free file * name is found. @@ -82,19 +83,23 @@ create_file_excl(const char *fname) * On failure, the contents of name_out are undefined and errno is set. */ FILE * -file_create_dated(const char *path_prefix, const char *suffix, +file_create_dated(const char *path, const char *prefix, const char *suffix, char *name_out, size_t name_len) { char timestr[128]; int ret; int fd; int cnt = 0; + int with_path; + + with_path = path && path[0]; if (current_time_str(timestr, sizeof(timestr), "%F_%H-%M-%S") < 0) return NULL; - ret = snprintf(name_out, name_len, "%s%s%s", - path_prefix, timestr, suffix); + ret = snprintf(name_out, name_len, "%s%s%s%s%s", + with_path ? path : "", with_path ? "/" : "", + prefix, timestr, suffix); if (ret < 0 || (size_t)ret >= name_len) { errno = ENOBUFS; return NULL; @@ -105,8 +110,9 @@ file_create_dated(const char *path_prefix, const char *suffix, while (fd == -1 && errno == EEXIST) { cnt++; - ret = snprintf(name_out, name_len, "%s%s-%d%s", - path_prefix, timestr, cnt, suffix); + ret = snprintf(name_out, name_len, "%s%s%s%s-%d%s", + with_path ? path : "", with_path ? "/" : "", + prefix, timestr, cnt, suffix); if (ret < 0 || (size_t)ret >= name_len) { errno = ENOBUFS; return NULL; diff --git a/shared/file-util.h b/shared/file-util.h index dbda07a8c..b20bbdacd 100644 --- a/shared/file-util.h +++ b/shared/file-util.h @@ -33,7 +33,7 @@ extern "C" { #include FILE * -file_create_dated(const char *path_prefix, const char *suffix, +file_create_dated(const char *path, const char *prefix, const char *suffix, char *name_out, size_t name_len); char * diff --git a/tests/surface-screenshot.c b/tests/surface-screenshot.c index f5199371a..908022dec 100644 --- a/tests/surface-screenshot.c +++ b/tests/surface-screenshot.c @@ -185,7 +185,7 @@ trigger_binding(struct weston_keyboard *keyboard, const struct timespec *time, unpremultiply_and_swap_a8b8g8r8_to_PAMrgba(pixels, sz); - fp = file_create_dated(prefix, suffix, fname, sizeof(fname)); + fp = file_create_dated(NULL, prefix, suffix, fname, sizeof(fname)); if (!fp) { const char *msg; From c34a9f5ca6611bfc64ded66ed464981285c78eeb Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Tue, 23 Jan 2018 01:05:22 +0100 Subject: [PATCH 0312/1642] screenshot: save screenshot files in XDG_PICTURES_DIR If XDG_PICTURES_DIR not given, it will use the current directory, as it was before. Signed-off-by: Aleksander Morgado Reviewed-by: Daniel Stone --- clients/screenshot.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clients/screenshot.c b/clients/screenshot.c index be8d9fe6d..78a5d4242 100644 --- a/clients/screenshot.c +++ b/clients/screenshot.c @@ -217,7 +217,8 @@ write_png(int width, int height) CAIRO_FORMAT_ARGB32, width, height, buffer_stride); - fp = file_create_dated("wayland-screenshot-", ".png", filepath, sizeof(filepath)); + fp = file_create_dated(getenv("XDG_PICTURES_DIR"), "wayland-screenshot-", + ".png", filepath, sizeof(filepath)); if (fp) { fclose (fp); cairo_surface_write_to_png(surface, filepath); From bb707dc0fe331c9af112a0552b7aa6fde755dd83 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 7 Feb 2018 12:51:14 +0200 Subject: [PATCH 0313/1642] weston: remove SEGV and ABRT handlers Catching an ABRT is kind of ok, catching a SEGV is russian roulette. We have been quite lucky with it, but I've started hitting crashes inside malloc() which causes a deadlock when our SEGV handler needs to malloc() as well (weston_log_timestamp()). One reason to catch SEGV and ABRT was to attempt to restore the VT on the DRM-backend. Nowadays that job is done by logind or weston-launch. The signal handler also printed a backtrace, which for me personally has been extremely helpful. Arguably it's not necessary though, when we have core files and services that catch cores. For instance, if using systemd, 'coredumpctl gdb' is delightfully easy for getting into the saved core. Therefore, this code does more harm than it is useful, so remove it. We also drop an optional dependency to libunwind. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- Makefile.am | 8 ++-- compositor/main.c | 119 ---------------------------------------------- configure.ac | 20 -------- man/weston.man | 14 ------ 4 files changed, 4 insertions(+), 157 deletions(-) diff --git a/Makefile.am b/Makefile.am index 5132e35a4..32c9a0f25 100644 --- a/Makefile.am +++ b/Makefile.am @@ -71,8 +71,8 @@ install-libweston_moduleLTLIBRARIES install-moduleLTLIBRARIES: install-libLTLIBR lib_LTLIBRARIES = libweston-@LIBWESTON_MAJOR@.la libweston_@LIBWESTON_MAJOR@_la_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON libweston_@LIBWESTON_MAJOR@_la_CFLAGS = $(AM_CFLAGS) \ - $(COMPOSITOR_CFLAGS) $(EGL_CFLAGS) $(LIBUNWIND_CFLAGS) $(LIBDRM_CFLAGS) -libweston_@LIBWESTON_MAJOR@_la_LIBADD = $(COMPOSITOR_LIBS) $(LIBUNWIND_LIBS) \ + $(COMPOSITOR_CFLAGS) $(EGL_CFLAGS) $(LIBDRM_CFLAGS) +libweston_@LIBWESTON_MAJOR@_la_LIBADD = $(COMPOSITOR_LIBS) \ $(DL_LIBS) -lm $(CLOCK_GETTIME_LIBS) \ $(LIBINPUT_BACKEND_LIBS) libshared.la libweston_@LIBWESTON_MAJOR@_la_LDFLAGS = -version-info $(LT_VERSION_INFO) @@ -190,9 +190,9 @@ weston_LDFLAGS = -export-dynamic weston_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON \ -DMODULEDIR='"$(moduledir)"' \ -DXSERVER_PATH='"@XSERVER_PATH@"' -weston_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBUNWIND_CFLAGS) +weston_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) weston_LDADD = libshared.la libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) $(LIBUNWIND_LIBS) \ + $(COMPOSITOR_LIBS) \ $(DL_LIBS) $(LIBINPUT_BACKEND_LIBS) \ $(CLOCK_GETRES_LIBS) \ -lm diff --git a/compositor/main.c b/compositor/main.c index 7feb4cb0e..18810f288 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -43,11 +43,6 @@ #include #include -#ifdef HAVE_LIBUNWIND -#define UNW_LOCAL_ONLY -#include -#endif - #include "weston.h" #include "compositor.h" #include "../shared/os-compatibility.h" @@ -190,85 +185,6 @@ sigchld_handler(int signal_number, void *data) return 1; } -#ifdef HAVE_LIBUNWIND - -static void -print_backtrace(void) -{ - unw_cursor_t cursor; - unw_context_t context; - unw_word_t off; - unw_proc_info_t pip; - int ret, i = 0; - char procname[256]; - const char *filename; - Dl_info dlinfo; - - pip.unwind_info = NULL; - ret = unw_getcontext(&context); - if (ret) { - weston_log("unw_getcontext: %d\n", ret); - return; - } - - ret = unw_init_local(&cursor, &context); - if (ret) { - weston_log("unw_init_local: %d\n", ret); - return; - } - - ret = unw_step(&cursor); - while (ret > 0) { - ret = unw_get_proc_info(&cursor, &pip); - if (ret) { - weston_log("unw_get_proc_info: %d\n", ret); - break; - } - - ret = unw_get_proc_name(&cursor, procname, 256, &off); - if (ret && ret != -UNW_ENOMEM) { - if (ret != -UNW_EUNSPEC) - weston_log("unw_get_proc_name: %d\n", ret); - procname[0] = '?'; - procname[1] = 0; - } - - if (dladdr((void *)(pip.start_ip + off), &dlinfo) && dlinfo.dli_fname && - *dlinfo.dli_fname) - filename = dlinfo.dli_fname; - else - filename = "?"; - - weston_log("%u: %s (%s%s+0x%x) [%p]\n", i++, filename, procname, - ret == -UNW_ENOMEM ? "..." : "", (int)off, (void *)(pip.start_ip + off)); - - ret = unw_step(&cursor); - if (ret < 0) - weston_log("unw_step: %d\n", ret); - } -} - -#else - -static void -print_backtrace(void) -{ - void *buffer[32]; - int i, count; - Dl_info info; - - count = backtrace(buffer, ARRAY_LENGTH(buffer)); - for (i = 0; i < count; i++) { - dladdr(buffer[i], &info); - weston_log(" [%016lx] %s (%s)\n", - (long) buffer[i], - info.dli_sname ? info.dli_sname : "--", - info.dli_fname); - } -} - -#endif - static void child_client_exec(int sockfd, const char *path) { @@ -643,39 +559,6 @@ static int on_term_signal(int signal_number, void *data) return 1; } -static void -on_caught_signal(int s, siginfo_t *siginfo, void *context) -{ - /* This signal handler will do a best-effort backtrace, and - * then call the backend restore function, which will switch - * back to the vt we launched from or ungrab X etc and then - * raise SIGTRAP. If we run weston under gdb from X or a - * different vt, and tell gdb "handle *s* nostop", this - * will allow weston to switch back to gdb on crash and then - * gdb will catch the crash with SIGTRAP.*/ - - weston_log("caught signal: %d\n", s); - - print_backtrace(); - - if (segv_compositor && segv_compositor->backend) - segv_compositor->backend->restore(segv_compositor); - - raise(SIGTRAP); -} - -static void -catch_signals(void) -{ - struct sigaction action; - - action.sa_flags = SA_SIGINFO | SA_RESETHAND; - action.sa_sigaction = on_caught_signal; - sigemptyset(&action.sa_mask); - sigaction(SIGSEGV, &action, NULL); - sigaction(SIGABRT, &action, NULL); -} - static const char * clock_name(clockid_t clk_id) { @@ -1830,8 +1713,6 @@ int main(int argc, char *argv[]) weston_log_set_handler(vlog, vlog_continue); weston_log_file_open(log); - catch_signals(); - weston_log("%s\n" STAMP_SPACE "%s\n" STAMP_SPACE "Bug reports to: %s\n" diff --git a/configure.ac b/configure.ac index 61ba14fb5..dd344d6af 100644 --- a/configure.ac +++ b/configure.ac @@ -603,26 +603,6 @@ if test "x$GCC" = "xyes"; then fi AC_SUBST(GCC_CFLAGS) -AC_ARG_ENABLE(libunwind, - AS_HELP_STRING([--disable-libunwind], - [Disable libunwind usage for backtraces]),, - enable_libunwind=auto) -AM_CONDITIONAL(HAVE_LIBUNWIND, [test "x$enable_libunwind" = xyes]) -have_libunwind=no -if test "x$enable_libunwind" != "xno"; then - PKG_CHECK_MODULES(LIBUNWIND, - libunwind, - have_libunwind=yes, - have_libunwind=no) - if test "x$have_libunwind" = "xno" -a "x$enable_libunwind" = "xyes"; then - AC_MSG_ERROR([libunwind support explicitly requested, but libunwind couldn't be found]) - fi - if test "x$have_libunwind" = "xyes"; then - enable_libunwind=yes - AC_DEFINE(HAVE_LIBUNWIND, 1, [Have libunwind support]) - fi -fi - if test "x$WESTON_NATIVE_BACKEND" = "x"; then WESTON_NATIVE_BACKEND="drm-backend.so" diff --git a/man/weston.man b/man/weston.man index 7c3416c2c..596041dff 100644 --- a/man/weston.man +++ b/man/weston.man @@ -324,20 +324,6 @@ The directory for Weston's socket and lock files. Wayland clients will automatically use this. . .\" *************************************************************** -.SH DIAGNOSTICS -Weston has a segmentation fault handler, that attempts to restore -the virtual console or ungrab X before raising -.BR SIGTRAP . -If you run -.BR weston " under " gdb (1) -from an X11 terminal or a different virtual terminal, and tell gdb -.IP -handle SIGSEGV nostop -.PP -This will allow weston to switch back to gdb on crash and then -gdb will catch the crash with SIGTRAP. -. -.\" *************************************************************** .SH BUGS Bugs should be reported to the freedesktop.org bugzilla at https://bugs.freedesktop.org with product "Wayland" and From f4614145011e6e016690fdc706458ca854a484bb Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 7 Feb 2018 12:51:15 +0200 Subject: [PATCH 0314/1642] libweston: remove restore functionality This was used from the crash handlers, which do not exist anymore. Nothing calls restore, so delete the dead code. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 7 ------- libweston/compositor-fbdev.c | 7 ------- libweston/compositor-headless.c | 6 ------ libweston/compositor-rdp.c | 6 ------ libweston/compositor-wayland.c | 6 ------ libweston/compositor-x11.c | 6 ------ libweston/compositor.h | 1 - libweston/launcher-direct.c | 1 - libweston/launcher-impl.h | 1 - libweston/launcher-logind.c | 8 -------- libweston/launcher-util.c | 7 ------- libweston/launcher-util.h | 3 --- libweston/launcher-weston-launch.c | 1 - 13 files changed, 60 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 61c9bf5ec..eb8b8ff67 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -5117,12 +5117,6 @@ udev_drm_event(int fd, uint32_t mask, void *data) return 1; } -static void -drm_restore(struct weston_compositor *ec) -{ - weston_launcher_restore(ec->launcher); -} - static void drm_destroy(struct weston_compositor *ec) { @@ -5651,7 +5645,6 @@ drm_backend_create(struct weston_compositor *compositor, } b->base.destroy = drm_destroy; - b->base.restore = drm_restore; b->base.repaint_begin = drm_repaint_begin; b->base.repaint_flush = drm_repaint_flush; b->base.repaint_cancel = drm_repaint_cancel; diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index fbab634b3..c63b1fc1f 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -698,12 +698,6 @@ session_notify(struct wl_listener *listener, void *data) } } -static void -fbdev_restore(struct weston_compositor *compositor) -{ - weston_launcher_restore(compositor->launcher); -} - static struct fbdev_backend * fbdev_backend_create(struct weston_compositor *compositor, struct weston_fbdev_backend_config *param) @@ -743,7 +737,6 @@ fbdev_backend_create(struct weston_compositor *compositor, } backend->base.destroy = fbdev_backend_destroy; - backend->base.restore = fbdev_restore; backend->prev_state = WESTON_COMPOSITOR_ACTIVE; diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index 2f01b64a4..9307a36a4 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -245,11 +245,6 @@ headless_output_create(struct weston_compositor *compositor, return 0; } -static void -headless_restore(struct weston_compositor *ec) -{ -} - static void headless_destroy(struct weston_compositor *ec) { @@ -283,7 +278,6 @@ headless_backend_create(struct weston_compositor *compositor, goto err_free; b->base.destroy = headless_destroy; - b->base.restore = headless_restore; b->use_pixman = config->use_pixman; if (b->use_pixman) { diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 982867372..4a4dc696c 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -596,11 +596,6 @@ rdp_backend_create_output(struct weston_compositor *compositor) return 0; } -static void -rdp_restore(struct weston_compositor *ec) -{ -} - static void rdp_destroy(struct weston_compositor *ec) { @@ -1303,7 +1298,6 @@ rdp_backend_create(struct weston_compositor *compositor, b->compositor = compositor; b->base.destroy = rdp_destroy; - b->base.restore = rdp_restore; b->rdp_key = config->rdp_key ? strdup(config->rdp_key) : NULL; b->no_clients_resize = config->no_clients_resize; diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index c5290d855..c6b926117 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -2450,11 +2450,6 @@ wayland_backend_handle_event(int fd, uint32_t mask, void *data) return count; } -static void -wayland_restore(struct weston_compositor *ec) -{ -} - static void wayland_destroy(struct weston_compositor *ec) { @@ -2618,7 +2613,6 @@ wayland_backend_create(struct weston_compositor *compositor, } b->base.destroy = wayland_destroy; - b->base.restore = wayland_restore; loop = wl_display_get_event_loop(compositor->wl_display); diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index a1d212705..14faeda03 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -1742,11 +1742,6 @@ x11_backend_get_wm_info(struct x11_backend *c) free(reply); } -static void -x11_restore(struct weston_compositor *ec) -{ -} - static void x11_destroy(struct weston_compositor *ec) { @@ -1839,7 +1834,6 @@ x11_backend_create(struct weston_compositor *compositor, weston_log("Using %s renderer\n", config->use_pixman ? "pixman" : "gl"); b->base.destroy = x11_destroy; - b->base.restore = x11_restore; if (x11_input_create(b, config->no_input) < 0) { weston_log("Failed to create X11 input\n"); diff --git a/libweston/compositor.h b/libweston/compositor.h index dffcba899..ca1acc605 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -820,7 +820,6 @@ struct weston_backend_config { struct weston_backend { void (*destroy)(struct weston_compositor *compositor); - void (*restore)(struct weston_compositor *compositor); /** Begin a repaint sequence * diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c index b4ca609a9..e0ce6d632 100644 --- a/libweston/launcher-direct.c +++ b/libweston/launcher-direct.c @@ -333,6 +333,5 @@ const struct launcher_interface launcher_direct_iface = { .open = launcher_direct_open, .close = launcher_direct_close, .activate_vt = launcher_direct_activate_vt, - .restore = launcher_direct_restore, .get_vt = launcher_direct_get_vt, }; diff --git a/libweston/launcher-impl.h b/libweston/launcher-impl.h index 404383ad2..b601baa9e 100644 --- a/libweston/launcher-impl.h +++ b/libweston/launcher-impl.h @@ -36,7 +36,6 @@ struct launcher_interface { int (* open) (struct weston_launcher *launcher, const char *path, int flags); void (* close) (struct weston_launcher *launcher, int fd); int (* activate_vt) (struct weston_launcher *launcher, int vt); - void (* restore) (struct weston_launcher *launcher); /* Get the number of the VT weston is running in */ int (* get_vt) (struct weston_launcher *launcher); }; diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c index 21d8c37b8..d0559c8fb 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -238,11 +238,6 @@ launcher_logind_close(struct weston_launcher *launcher, int fd) minor(st.st_rdev)); } -static void -launcher_logind_restore(struct weston_launcher *launcher) -{ -} - static int launcher_logind_activate_vt(struct weston_launcher *launcher, int vt) { @@ -382,7 +377,6 @@ static void disconnected_dbus(struct launcher_logind *wl) { weston_log("logind: dbus connection lost, exiting..\n"); - launcher_logind_restore(&wl->base); exit(-1); } @@ -403,7 +397,6 @@ session_removed(struct launcher_logind *wl, DBusMessage *m) if (!strcmp(name, wl->sid)) { weston_log("logind: our session got closed, exiting..\n"); - launcher_logind_restore(&wl->base); exit(-1); } } @@ -852,6 +845,5 @@ const struct launcher_interface launcher_logind_iface = { .open = launcher_logind_open, .close = launcher_logind_close, .activate_vt = launcher_logind_activate_vt, - .restore = launcher_logind_restore, .get_vt = launcher_logind_get_vt, }; diff --git a/libweston/launcher-util.c b/libweston/launcher-util.c index 96a0ba6f8..03f3219b4 100644 --- a/libweston/launcher-util.c +++ b/libweston/launcher-util.c @@ -86,13 +86,6 @@ weston_launcher_activate_vt(struct weston_launcher *launcher, int vt) return launcher->iface->activate_vt(launcher, vt); } -WL_EXPORT void -weston_launcher_restore(struct weston_launcher *launcher) -{ - launcher->iface->restore(launcher); -} - - static void switch_vt_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) diff --git a/libweston/launcher-util.h b/libweston/launcher-util.h index 93321ab78..242e1cc83 100644 --- a/libweston/launcher-util.h +++ b/libweston/launcher-util.h @@ -49,9 +49,6 @@ weston_launcher_close(struct weston_launcher *launcher, int fd); int weston_launcher_activate_vt(struct weston_launcher *launcher, int vt); -void -weston_launcher_restore(struct weston_launcher *launcher); - void weston_setup_vt_switch_bindings(struct weston_compositor *compositor); diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c index 3d3b4d2f6..65beb3256 100644 --- a/libweston/launcher-weston-launch.c +++ b/libweston/launcher-weston-launch.c @@ -303,6 +303,5 @@ const struct launcher_interface launcher_weston_launch_iface = { .open = launcher_weston_launch_open, .close = launcher_weston_launch_close, .activate_vt = launcher_weston_launch_activate_vt, - .restore = launcher_weston_launch_restore, .get_vt = launcher_weston_launch_get_vt, }; From ea40d6dbfc96c48046adb793e73ded80613ef944 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Thu, 8 Feb 2018 18:46:42 +0200 Subject: [PATCH 0315/1642] desktop-shell: Correctly migrate views to other outputs when output is disabled/disconnected Our case is when the view is the same as output being disabled/disconnected. There's not need to check the views' output with the output being disabled because weston_view_assign_output() already changes the output of the view when the output has been disabled/disconnected hence the check is not needed at all. The views' output will always be different than the output being disabled. By the time shell_output_destroy_move_layer() gets called the views' output has already changed to a "free" output. Tested this by unplugging/disabling the output on purpose. Signed-off-by: Marius Vlad Reviewed-by: Daniel Stone --- desktop-shell/shell.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index de76ebe57..d9dffd2cb 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -4698,12 +4698,8 @@ shell_output_destroy_move_layer(struct desktop_shell *shell, struct weston_output *output = data; struct weston_view *view; - wl_list_for_each(view, &layer->view_list.link, layer_link.link) { - if (view->output != output) - continue; - + wl_list_for_each(view, &layer->view_list.link, layer_link.link) shell_reposition_view_on_output_destroy(view); - } } static void From cb2d8836c099e456eedc1f4ef0310d562e3b6fae Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 9 Feb 2018 12:29:09 +0200 Subject: [PATCH 0316/1642] compositor-drm: let repaint cycle disable crtcs Rather than smashing the state to disable a CRTC immediately, just delegate that to the normal repaint cycle by setting state_invalid = true. drm_pending_state_apply() will pick up the unused_crtcs. A caveat here is that we have no enabled outputs at all, we will never enter repaint, and so CRTCs do not actually get turned off until we get at least one output to drive. However, this should help the problem reported here: https://lists.freedesktop.org/archives/wayland-devel/2018-January/036713.html Arguably it is better to leave an output spuriously on in rare cases rather than fail modeset completely in somewhat more common cases. My personal motivation for this change is that it helps if we later move CRTC allocation to output enable/deinit instead of create/destroy, because then the CRTC will not be available here for initial turn-off as the output has not been enabled to begin with. Cc: Philipp Zabel Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index eb8b8ff67..c21b117f6 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4742,8 +4742,6 @@ drm_output_disable(struct weston_output *base) { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); - struct drm_pending_state *pending_state; - int ret; if (output->page_flip_pending || output->vblank_pending || output->atomic_complete_pending) { @@ -4752,17 +4750,15 @@ drm_output_disable(struct weston_output *base) } weston_log("Disabling output %s\n", output->base.name); - pending_state = drm_pending_state_alloc(b); - drm_output_get_disable_state(pending_state, output); - ret = drm_pending_state_apply_sync(pending_state); - if (ret) - weston_log("Couldn't disable output %s\n", output->base.name); if (output->base.enabled) drm_output_deinit(&output->base); output->disable_pending = 0; + /* Force resetting unused connectors and crtcs. */ + b->state_invalid = true; + return 0; } From 5e12b553b13dce50510370ed3eaffb4d27d20f5a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 9 Feb 2018 12:29:10 +0200 Subject: [PATCH 0317/1642] compositor-drm: move state_invalid setting to deinit Setting state_invalid to true is moved together with the code adding new unused CRTCs and connectors in drm_output_deinit(). Logically these two operations belong together: state_invalid is required for the new unused item to be turned off. This does not hinder initial turning off of outputs, because on compositor start-up, state_invalid is initialized to true, making calls to drm_output_disable() for non-enabled outputs a no-op. Previous changes already ensure that if a compositor does not explicitly enable an output, the CRTC and connector will be turned off even without an explicit disable (provided there is a at least one enabled output). Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c21b117f6..50c0ebacb 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4676,6 +4676,9 @@ drm_output_deinit(struct weston_output *base) *unused = output->connector_id; unused = wl_array_add(&b->unused_crtcs, sizeof(*unused)); *unused = output->crtc_id; + + /* Force programming unused connectors and crtcs. */ + b->state_invalid = true; } static void @@ -4741,7 +4744,6 @@ static int drm_output_disable(struct weston_output *base) { struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); if (output->page_flip_pending || output->vblank_pending || output->atomic_complete_pending) { @@ -4756,9 +4758,6 @@ drm_output_disable(struct weston_output *base) output->disable_pending = 0; - /* Force resetting unused connectors and crtcs. */ - b->state_invalid = true; - return 0; } From 44fc1be913ab2faad0414f50e51d58310302d065 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Wed, 24 Jan 2018 23:29:53 +0100 Subject: [PATCH 0318/1642] xwm: Fix icon surface ownership The cairo surface used for the icon must be completely given to the frame as soon as said frame has been created. To prevent both the window and the frame from sharing ownership of the icon, we set window->icon_surface back to NULL right after creating or changing the frame, only keeping it there when no frame has been created yet. Fixes https://lists.freedesktop.org/archives/wayland-devel/2018-January/036655.html Reported-by: Derek Foreman Tested-by: Derek Foreman Signed-off-by: Emmanuel Gil Peyrot --- shared/cairo-util.h | 4 ++++ shared/frame.c | 14 ++++++++++++++ xwayland/window-manager.c | 16 ++++++++++------ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/shared/cairo-util.h b/shared/cairo-util.h index bab48083d..6fd11f6bf 100644 --- a/shared/cairo-util.h +++ b/shared/cairo-util.h @@ -135,6 +135,10 @@ frame_destroy(struct frame *frame); int frame_set_title(struct frame *frame, const char *title); +/* May set FRAME_STATUS_REPAINT */ +void +frame_set_icon(struct frame *frame, cairo_surface_t *icon); + /* May set FRAME_STATUS_REPAINT */ void frame_set_flag(struct frame *frame, enum frame_flag flag); diff --git a/shared/frame.c b/shared/frame.c index acac2ca88..e8a5cad62 100644 --- a/shared/frame.c +++ b/shared/frame.c @@ -448,6 +448,20 @@ frame_set_title(struct frame *frame, const char *title) return 0; } +void +frame_set_icon(struct frame *frame, cairo_surface_t *icon) +{ + struct frame_button *button; + wl_list_for_each(button, &frame->buttons, link) { + if (button->status_effect != FRAME_STATUS_MENU) + continue; + if (button->icon) + cairo_surface_destroy(button->icon); + button->icon = icon; + frame->status |= FRAME_STATUS_REPAINT; + } +} + void frame_set_flag(struct frame *frame, enum frame_flag flag) { diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index ac44a29af..c307e1992 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -139,7 +139,7 @@ struct weston_wm_window { struct frame *frame; cairo_surface_t *cairo_surface; int icon; - cairo_surface_t *icon_surface; + cairo_surface_t *icon_surface; /* A temporary slot, to be passed to frame on creation */ uint32_t surface_id; struct weston_surface *surface; struct weston_desktop_xwayland_surface *shsurf; @@ -994,6 +994,7 @@ weston_wm_window_create_frame(struct weston_wm_window *window) window->width, window->height, buttons, window->name, window->icon_surface); + window->icon_surface = NULL; frame_resize_inside(window->frame, window->width, window->height); weston_wm_window_get_frame_size(window, &width, &height); @@ -1392,10 +1393,10 @@ weston_wm_handle_icon(struct weston_wm *wm, struct weston_wm_window *window) return; } - if (window->icon_surface) - cairo_surface_destroy(window->icon_surface); - - window->icon_surface = new_surface; + if (window->frame) + frame_set_icon(window->frame, new_surface); + else /* We don’t have a frame yet */ + window->icon_surface = new_surface; } static void @@ -1422,7 +1423,10 @@ weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *even if (property_notify->state != XCB_PROPERTY_DELETE) { weston_wm_handle_icon(wm, window); } else { - cairo_surface_destroy(window->icon_surface); + if (window->frame) + frame_set_icon(window->frame, NULL); + if (window->icon_surface) + cairo_surface_destroy(window->icon_surface); window->icon_surface = NULL; } weston_wm_window_schedule_repaint(window); From 3995ffaf3d18acef6296861645ac2b22f72f9b88 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Fri, 9 Feb 2018 21:59:17 +0100 Subject: [PATCH 0319/1642] gl-renderer: Fix crash in dmabuf format query for fallback formats Since formats is an out parameter, we need to copy to the alloc'ed memory and not over the pointer address. Signed-off-by: Philipp Kerling Reviewed-by: Daniel Stone --- libweston/gl-renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 371e06b30..d091d1652 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -2122,7 +2122,7 @@ gl_renderer_query_dmabuf_formats(struct weston_compositor *wc, } if (fallback) { - memcpy(formats, fallback_formats, num * sizeof(int)); + memcpy(*formats, fallback_formats, num * sizeof(int)); *num_formats = num; return; } From cb1153804948d184ddcd4d0b42710639e3369971 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 7 Dec 2017 12:15:01 +0200 Subject: [PATCH 0320/1642] clients/desktop-shell: avoid invalid sized background If for some reason the desktop-shell plugin would configure a background with an invalid size, just destroy the whole background and forget about it for this wl_output. A following patch will cause desktop-shell to configure 0x0 background when it deems the background redundant. Fortify weston-desktop-shell against not every output having a background. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- clients/desktop-shell.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index 12bc971e1..872341165 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -92,6 +92,8 @@ struct surface { int32_t width, int32_t height); }; +struct output; + struct panel { struct surface base; struct window *window; @@ -106,6 +108,9 @@ struct panel { struct background { struct surface base; + + struct output *owner; + struct window *window; struct widget *widget; int painted; @@ -812,15 +817,27 @@ background_draw(struct widget *widget, void *data) check_desktop_ready(background->window); } +static void +background_destroy(struct background *background); + static void background_configure(void *data, struct weston_desktop_shell *desktop_shell, uint32_t edges, struct window *window, int32_t width, int32_t height) { + struct output *owner; struct background *background = (struct background *) window_get_user_data(window); + if (width < 1 || height < 1) { + /* Shell plugin configures 0x0 for redundant background. */ + owner = background->owner; + background_destroy(background); + owner->background = NULL; + return; + } + widget_schedule_resize(background->widget, width, height); } @@ -1094,13 +1111,14 @@ background_destroy(struct background *background) } static struct background * -background_create(struct desktop *desktop) +background_create(struct desktop *desktop, struct output *output) { struct background *background; struct weston_config_section *s; char *type; background = xzalloc(sizeof *background); + background->owner = output; background->base.configure = background_configure; background->window = window_create_custom(desktop->display); background->widget = window_add_widget(background->window, background); @@ -1178,7 +1196,8 @@ grab_surface_create(struct desktop *desktop) static void output_destroy(struct output *output) { - background_destroy(output->background); + if (output->background) + background_destroy(output->background); if (output->panel) panel_destroy(output->panel); wl_output_destroy(output->output); @@ -1212,7 +1231,8 @@ output_handle_geometry(void *data, if (output->panel) window_set_buffer_transform(output->panel->window, transform); - window_set_buffer_transform(output->background->window, transform); + if (output->background) + window_set_buffer_transform(output->background->window, transform); } static void @@ -1240,7 +1260,8 @@ output_handle_scale(void *data, if (output->panel) window_set_buffer_scale(output->panel->window, scale); - window_set_buffer_scale(output->background->window, scale); + if (output->background) + window_set_buffer_scale(output->background->window, scale); } static const struct wl_output_listener output_listener = { @@ -1262,7 +1283,7 @@ output_init(struct output *output, struct desktop *desktop) output->output, surface); } - output->background = background_create(desktop); + output->background = background_create(desktop, output); surface = window_get_wl_surface(output->background->window); weston_desktop_shell_set_background(desktop->shell, output->output, surface); From 1cbfcf49a76ae1f2b67c0f8fb906923fb40a1b22 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 7 Dec 2017 12:39:15 +0200 Subject: [PATCH 0321/1642] clients/desktop-shell: avoid invalid sized panel If for some reason the desktop-shell plugin would configure a panel with an invalid size, just destroy the whole panel and forget about it for this wl_output. A following patch will cause desktop-shell to configure 0x0 panel when it deems the panel redundant. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- clients/desktop-shell.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index 872341165..d75c86312 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -96,6 +96,9 @@ struct output; struct panel { struct surface base; + + struct output *owner; + struct window *window; struct widget *widget; struct wl_list launcher_list; @@ -525,6 +528,9 @@ panel_resize_handler(struct widget *widget, x, y, w + 1, h + 1); } +static void +panel_destroy(struct panel *panel); + static void panel_configure(void *data, struct weston_desktop_shell *desktop_shell, @@ -534,6 +540,15 @@ panel_configure(void *data, struct desktop *desktop = data; struct surface *surface = window_get_user_data(window); struct panel *panel = container_of(surface, struct panel, base); + struct output *owner; + + if (width < 1 || height < 1) { + /* Shell plugin configures 0x0 for redundant panel. */ + owner = panel->owner; + panel_destroy(panel); + owner->panel = NULL; + return; + } switch (desktop->panel_position) { case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: @@ -593,13 +608,14 @@ panel_destroy(struct panel *panel) } static struct panel * -panel_create(struct desktop *desktop) +panel_create(struct desktop *desktop, struct output *output) { struct panel *panel; struct weston_config_section *s; panel = xzalloc(sizeof *panel); + panel->owner = output; panel->base.configure = panel_configure; panel->window = window_create_custom(desktop->display); panel->widget = window_add_widget(panel->window, panel); @@ -1277,7 +1293,7 @@ output_init(struct output *output, struct desktop *desktop) struct wl_surface *surface; if (desktop->want_panel) { - output->panel = panel_create(desktop); + output->panel = panel_create(desktop, output); surface = window_get_wl_surface(output->panel->window); weston_desktop_shell_set_panel(desktop->shell, output->output, surface); From ff5e88d2765a1888f1f06a5198e5204a379ce5e3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 7 Dec 2017 11:44:18 +0200 Subject: [PATCH 0322/1642] desktop-shell: handle redundant backgrounds If for some reason the helper client weston-desktop-shell would create more than one background surface for the same weston_output, this code would corrupt the surface destroy listener list by adding a link already in one list into another list. Instead, do not store the new, redundant background surface and do not subscribe to its destruction. Also, tell the helper that the surface is redundant by configuring it with a 0x0 size, so that we don't waste memory on a background that is never used. (Clone mode is a valid reason why weston-desktop-shell could do that.) Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- desktop-shell/shell.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index d9dffd2cb..18d885536 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2971,16 +2971,27 @@ desktop_shell_set_background(struct wl_client *client, weston_surface_set_label_func(surface, background_get_label); surface->output = weston_output_from_resource(output_resource); view->output = surface->output; - weston_desktop_shell_send_configure(resource, 0, - surface_resource, - surface->output->width, - surface->output->height); sh_output = find_shell_output_from_weston_output(shell, surface->output); - sh_output->background_surface = surface; + if (sh_output->background_surface) { + /* The output already has a background, tell our helper + * there is no need for another one. */ + weston_desktop_shell_send_configure(resource, 0, + surface_resource, + 0, 0); + } else { + weston_desktop_shell_send_configure(resource, 0, + surface_resource, + surface->output->width, + surface->output->height); + + sh_output->background_surface = surface; - sh_output->background_surface_listener.notify = handle_background_surface_destroy; - wl_signal_add(&surface->destroy_signal, &sh_output->background_surface_listener); + sh_output->background_surface_listener.notify = + handle_background_surface_destroy; + wl_signal_add(&surface->destroy_signal, + &sh_output->background_surface_listener); + } } static int From 1a0239e40ffc945b55a4f3218ae9a41ce0f143e3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 7 Dec 2017 11:54:11 +0200 Subject: [PATCH 0323/1642] desktop-shell: handle redundant panels If for some reason the helper client weston-desktop-shell would create more than one panel surface for the same weston_output, this code would corrupt the surface destroy listener list by adding a link already in one list into another list. Instead, do not store the new, redundant panel surface and do not subscribe to its destruction. Also, tell the helper that the surface is redundant by configuring it with a 0x0 size, so that we don't waste memory on a panel that is never used. (Clone mode is a valid reason why weston-desktop-shell could do that.) Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- desktop-shell/shell.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 18d885536..1c35d18a3 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -3068,16 +3068,25 @@ desktop_shell_set_panel(struct wl_client *client, weston_surface_set_label_func(surface, panel_get_label); surface->output = weston_output_from_resource(output_resource); view->output = surface->output; - weston_desktop_shell_send_configure(resource, 0, - surface_resource, - surface->output->width, - surface->output->height); sh_output = find_shell_output_from_weston_output(shell, surface->output); - sh_output->panel_surface = surface; + if (sh_output->panel_surface) { + /* The output already has a panel, tell our helper + * there is no need for another one. */ + weston_desktop_shell_send_configure(resource, 0, + surface_resource, + 0, 0); + } else { + weston_desktop_shell_send_configure(resource, 0, + surface_resource, + surface->output->width, + surface->output->height); - sh_output->panel_surface_listener.notify = handle_panel_surface_destroy; - wl_signal_add(&surface->destroy_signal, &sh_output->panel_surface_listener); + sh_output->panel_surface = surface; + + sh_output->panel_surface_listener.notify = handle_panel_surface_destroy; + wl_signal_add(&surface->destroy_signal, &sh_output->panel_surface_listener); + } } static int From c1bcce6a259fa658129e6356098c3985dc50b0a0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 7 Dec 2017 15:30:18 +0200 Subject: [PATCH 0324/1642] clients/desktop-shell: preserve background/panel in clone mode In shared-CRTC clone mode there are several wl_output globals for one weston_output. Only one panel and background is needed per weston_output, so the extra wl_outputs do not get their own panel and background. When a head is unplugged, the corresponding wl_output is removed. If that was the wl_output associated with the background and panel surfaces, we must transfer the ownership to a remaining wl_output that was a clone to avoid losing the background and panel completely. The transfer relies on desktop-shell.so implementation to register background and panel surfaces with the weston_output, not the weston_head, so it does not actually matter the wl_output used to bind the surfaces is going away. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- clients/desktop-shell.c | 50 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index d75c86312..cabe851f4 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -128,6 +128,8 @@ struct output { uint32_t server_output_id; struct wl_list link; + int x; + int y; struct panel *panel; struct background *background; }; @@ -1245,6 +1247,9 @@ output_handle_geometry(void *data, { struct output *output = data; + output->x = x; + output->y = y; + if (output->panel) window_set_buffer_transform(output->panel->window, transform); if (output->background) @@ -1328,6 +1333,49 @@ create_output(struct desktop *desktop, uint32_t id) output_init(output, desktop); } +static void +output_remove(struct desktop *desktop, struct output *output) +{ + struct output *cur; + struct output *rep = NULL; + + if (!output->background) { + output_destroy(output); + return; + } + + /* Find a wl_output that is a clone of the removed wl_output. + * We don't want to leave the clone without a background or panel. */ + wl_list_for_each(cur, &desktop->outputs, link) { + if (cur == output) + continue; + + /* XXX: Assumes size matches. */ + if (cur->x == output->x && cur->y == output->y) { + rep = cur; + break; + } + } + + if (rep) { + /* If found, hand over the background and panel so they don't + * get destroyed. */ + assert(!rep->background); + assert(!rep->panel); + + rep->background = output->background; + output->background = NULL; + rep->background->owner = rep; + + rep->panel = output->panel; + output->panel = NULL; + if (rep->panel) + rep->panel->owner = rep; + } + + output_destroy(output); +} + static void global_handler(struct display *display, uint32_t id, const char *interface, uint32_t version, void *data) @@ -1357,7 +1405,7 @@ global_handler_remove(struct display *display, uint32_t id, if (!strcmp(interface, "wl_output")) { wl_list_for_each(output, &desktop->outputs, link) { if (output->server_output_id == id) { - output_destroy(output); + output_remove(desktop, output); break; } } From 9068e011cd8b45798d53d92b3adef9be9167d252 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 9 Feb 2018 13:31:50 +0200 Subject: [PATCH 0325/1642] compositor-drm: drm_output_enable updates unused_crtcs/connectors Let drm_output_enable() remove the CRTC and the connector from the unused id arrays. In the future when a list of drm_heads supersedes unused_connectors array, the usedness of a connector will be determined by the enabled state of the output the connector (head) is attached to. The enabled state is turned on by drm_output_enable(). If unused_crtcs array was still updated in drm_output_repaint(), the CRTC and connector usedness would be tracked in different places. Logically the two belong together. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 50c0ebacb..e3043b2ef 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2380,7 +2380,6 @@ drm_output_repaint(struct weston_output *output_base, { struct drm_pending_state *pending_state = repaint_data; struct drm_output *output = to_drm_output(output_base); - struct drm_backend *backend = to_drm_backend(output_base->compositor); struct drm_output_state *state = NULL; struct drm_plane_state *scanout_state; @@ -2405,10 +2404,6 @@ drm_output_repaint(struct weston_output *output_base, if (!scanout_state || !scanout_state->fb) goto err; - wl_array_remove_uint32(&backend->unused_connectors, - output->connector_id); - wl_array_remove_uint32(&backend->unused_crtcs, output->crtc_id); - return 0; err: @@ -4626,6 +4621,9 @@ drm_output_enable(struct weston_output *base) &output->scanout_plane->base, &b->compositor->primary_plane); + wl_array_remove_uint32(&b->unused_connectors, output->connector_id); + wl_array_remove_uint32(&b->unused_crtcs, output->crtc_id); + weston_log("Output %s, (connector %d, crtc %d)\n", output->base.name, output->connector_id, output->crtc_id); wl_list_for_each(m, &output->base.mode_list, link) From c4db6f762922d76ca200fa4f392c736a3ad9c104 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 5 Sep 2017 16:37:03 +0300 Subject: [PATCH 0326/1642] compositor-drm: factor out drm_output_init_gamma_size() Move this bit of code into its own function. The caller of this already cluttered and origcrtc is not used for anything else. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index e3043b2ef..ab473e4fe 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4574,6 +4574,25 @@ drm_output_set_seat(struct weston_output *base, seat ? seat : ""); } +static int +drm_output_init_gamma_size(struct drm_output *output) +{ + struct drm_backend *backend = to_drm_backend(output->base.compositor); + drmModeCrtc *crtc; + + assert(output->base.compositor); + assert(output->crtc_id != 0); + crtc = drmModeGetCrtc(backend->drm.fd, output->crtc_id); + if (!crtc) + return -1; + + output->base.gamma_size = crtc->gamma_size; + + drmModeFreeCrtc(crtc); + + return 0; +} + static int drm_output_enable(struct weston_output *base) { @@ -4830,7 +4849,6 @@ create_output_for_connector(struct drm_backend *b, const char *make = "unknown"; const char *model = "unknown"; const char *serial_number = "unknown"; - drmModeCrtcPtr origcrtc; int i; i = find_crtc_for_connector(b, resources, connector); @@ -4859,13 +4877,6 @@ create_output_for_connector(struct drm_backend *b, output->base.destroy = drm_output_destroy; output->base.disable = drm_output_disable; - origcrtc = drmModeGetCrtc(b->drm.fd, output->crtc_id); - if (origcrtc == NULL) - goto err_output; - - output->base.gamma_size = origcrtc->gamma_size; - drmModeFreeCrtc(origcrtc); - output->destroy_pending = 0; output->disable_pending = 0; @@ -4900,6 +4911,9 @@ create_output_for_connector(struct drm_backend *b, output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) output->base.connection_internal = true; + if (drm_output_init_gamma_size(output) < 0) + goto err_output; + output->state_cur = drm_output_state_alloc(output, NULL); output->base.mm_width = output->connector->mmWidth; From fc5f5d7126e8da9ff5416f15325b6ef30743ad88 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 5 Sep 2017 16:11:15 +0300 Subject: [PATCH 0327/1642] compositor-drm: factor out drm_output_init_crtc() Factor out drm_output_init_crtc() and drm_output_fini_crtc(), so that the call sites can later be moved easily. On fini, reset scanout_plane and cursor_plane to NULL, so that in the future when the drm_output is not longer destroyed immediately after, we free the planes for other use. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray [Pekka: set crtc_id/pipe at top, reset both on error] Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 158 +++++++++++++++++++++++++------------ 1 file changed, 106 insertions(+), 52 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index ab473e4fe..d192cf3be 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4593,6 +4593,109 @@ drm_output_init_gamma_size(struct drm_output *output) return 0; } +/** Allocate a CRTC for the output + * + * @param output The output with no allocated CRTC. + * @param resources DRM KMS resources. + * @param connector The DRM KMS connector data. + * @return 0 on success, -1 on failure. + * + * Finds a free CRTC that can drive the given connector, reserves the CRTC + * for the output, and loads the CRTC properties. + * + * Populates the cursor and scanout planes. + * + * On failure, the output remains without a CRTC. + */ +static int +drm_output_init_crtc(struct drm_output *output, + drmModeRes *resources, drmModeConnector *connector) +{ + struct drm_backend *b = to_drm_backend(output->base.compositor); + drmModeObjectPropertiesPtr props; + int i; + + assert(output->crtc_id == 0); + + i = find_crtc_for_connector(b, resources, connector); + if (i < 0) { + weston_log("No usable crtc/encoder pair for connector.\n"); + return -1; + } + + output->crtc_id = resources->crtcs[i]; + output->pipe = i; + + props = drmModeObjectGetProperties(b->drm.fd, output->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!props) { + weston_log("failed to get CRTC properties\n"); + goto err_crtc; + } + drm_property_info_populate(b, crtc_props, output->props_crtc, + WDRM_CRTC__COUNT, props); + drmModeFreeObjectProperties(props); + + output->scanout_plane = + drm_output_find_special_plane(b, output, + WDRM_PLANE_TYPE_PRIMARY); + if (!output->scanout_plane) { + weston_log("Failed to find primary plane for output %s\n", + output->base.name); + goto err_crtc; + } + + /* Failing to find a cursor plane is not fatal, as we'll fall back + * to software cursor. */ + output->cursor_plane = + drm_output_find_special_plane(b, output, + WDRM_PLANE_TYPE_CURSOR); + + return 0; + +err_crtc: + output->crtc_id = 0; + output->pipe = 0; + + return -1; +} + +/** Free the CRTC from the output + * + * @param output The output whose CRTC to deallocate. + * + * The CRTC reserved for the given output becomes free to use again. + */ +static void +drm_output_fini_crtc(struct drm_output *output) +{ + struct drm_backend *b = to_drm_backend(output->base.compositor); + + if (!b->universal_planes && !b->shutting_down) { + /* With universal planes, the 'special' planes are allocated at + * startup, freed at shutdown, and live on the plane list in + * between. We want the planes to continue to exist and be freed + * up for other outputs. + * + * Without universal planes, our special planes are + * pseudo-planes allocated at output creation, freed at output + * destruction, and not usable by other outputs. + * + * On the other hand, if the compositor is already shutting down, + * the plane has already been destroyed. + */ + if (output->cursor_plane) + drm_plane_destroy(output->cursor_plane); + if (output->scanout_plane) + drm_plane_destroy(output->scanout_plane); + } + + drm_property_info_free(output->props_crtc, WDRM_CRTC__COUNT); + output->crtc_id = 0; + output->cursor_plane = NULL; + output->scanout_plane = NULL; +} + static int drm_output_enable(struct weston_output *base) { @@ -4715,25 +4818,6 @@ drm_output_destroy(struct weston_output *base) if (output->base.enabled) drm_output_deinit(&output->base); - if (!b->universal_planes && !b->shutting_down) { - /* With universal planes, the 'special' planes are allocated at - * startup, freed at shutdown, and live on the plane list in - * between. We want the planes to continue to exist and be freed - * up for other outputs. - * - * Without universal planes, our special planes are - * pseudo-planes allocated at output creation, freed at output - * destruction, and not usable by other outputs. - * - * On the other hand, if the compositor is already shutting down, - * the plane has already been destroyed. - */ - if (output->cursor_plane) - drm_plane_destroy(output->cursor_plane); - if (output->scanout_plane) - drm_plane_destroy(output->scanout_plane); - } - wl_list_for_each_safe(drm_mode, next, &output->base.mode_list, base.link) drm_output_destroy_mode(b, drm_mode); @@ -4743,9 +4827,9 @@ drm_output_destroy(struct weston_output *base) weston_output_release(&output->base); - drm_property_info_free(output->props_conn, WDRM_CONNECTOR__COUNT); - drm_property_info_free(output->props_crtc, WDRM_CRTC__COUNT); + drm_output_fini_crtc(output); + drm_property_info_free(output->props_conn, WDRM_CONNECTOR__COUNT); drmModeFreeConnector(output->connector); if (output->backlight) @@ -4851,19 +4935,11 @@ create_output_for_connector(struct drm_backend *b, const char *serial_number = "unknown"; int i; - i = find_crtc_for_connector(b, resources, connector); - if (i < 0) { - weston_log("No usable crtc/encoder pair for connector.\n"); - goto err_init; - } - output = zalloc(sizeof *output); if (output == NULL) goto err_init; output->connector = connector; - output->crtc_id = resources->crtcs[i]; - output->pipe = i; output->connector_id = connector->connector_id; output->backlight = backlight_init(drm_device, @@ -4880,15 +4956,8 @@ create_output_for_connector(struct drm_backend *b, output->destroy_pending = 0; output->disable_pending = 0; - props = drmModeObjectGetProperties(b->drm.fd, output->crtc_id, - DRM_MODE_OBJECT_CRTC); - if (!props) { - weston_log("failed to get CRTC properties\n"); + if (drm_output_init_crtc(output, resources, connector) < 0) goto err_output; - } - drm_property_info_populate(b, crtc_props, output->props_crtc, - WDRM_CRTC__COUNT, props); - drmModeFreeObjectProperties(props); props = drmModeObjectGetProperties(b->drm.fd, connector->connector_id, DRM_MODE_OBJECT_CONNECTOR); @@ -4927,21 +4996,6 @@ create_output_for_connector(struct drm_backend *b, } } - output->scanout_plane = - drm_output_find_special_plane(b, output, - WDRM_PLANE_TYPE_PRIMARY); - if (!output->scanout_plane) { - weston_log("Failed to find primary plane for output %s\n", - output->base.name); - goto err_output; - } - - /* Failing to find a cursor plane is not fatal, as we'll fall back - * to software cursor. */ - output->cursor_plane = - drm_output_find_special_plane(b, output, - WDRM_PLANE_TYPE_CURSOR); - weston_compositor_add_pending_output(&output->base, b->compositor); return 0; From 383b3af5e1dc354c2594390a10bcd2ec494b5185 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 11 Sep 2017 14:40:48 +0300 Subject: [PATCH 0328/1642] compositor-drm: refactor into drm_mode_list_destroy() I need to destroy the list from more places, so factor out the common bits. No functional changes. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index d192cf3be..2f7247cf3 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3754,6 +3754,20 @@ drm_output_destroy_mode(struct drm_backend *backend, struct drm_mode *mode) free(mode); } +/** Destroy a list of drm_modes + * + * @param backend The backend for releasing mode property blobs. + * @param mode_list The list linked by drm_mode::base.link. + */ +static void +drm_mode_list_destroy(struct drm_backend *backend, struct wl_list *mode_list) +{ + struct drm_mode *mode, *next; + + wl_list_for_each_safe(mode, next, mode_list, base.link) + drm_output_destroy_mode(backend, mode); +} + static int drm_subpixel_to_wayland(int drm_value) { @@ -4806,7 +4820,6 @@ drm_output_destroy(struct weston_output *base) { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); - struct drm_mode *drm_mode, *next; if (output->page_flip_pending || output->vblank_pending || output->atomic_complete_pending) { @@ -4818,9 +4831,7 @@ drm_output_destroy(struct weston_output *base) if (output->base.enabled) drm_output_deinit(&output->base); - wl_list_for_each_safe(drm_mode, next, &output->base.mode_list, - base.link) - drm_output_destroy_mode(b, drm_mode); + drm_mode_list_destroy(b, &output->base.mode_list); if (output->pageflip_timer) wl_event_source_remove(output->pageflip_timer); From 46e4f97ab62a026968ca2d919a79bd2dd0a3c73a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 7 Sep 2017 15:32:01 +0300 Subject: [PATCH 0329/1642] compositor-drm: drm_property_info_free() must reset This function needs to reset the structures to NULL, otherwise it is not possible to re-use a once "freed" property info array. Being able to re-use an array is useful when the memory allocation and array lifetimes do not match. A specific example is drm_output that is changed to allocate the CRTC on enable() and deallocate it on disable(). A drm_output might be enabled and disabled multiple times. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 2f7247cf3..268394bf0 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -721,7 +721,9 @@ drm_property_info_populate(struct drm_backend *b, /** * Free DRM property information * - * Frees all memory associated with a DRM property info array. + * Frees all memory associated with a DRM property info array and zeroes + * it out, leaving it usable for a further drm_property_info_update() or + * drm_property_info_free(). * * @param info DRM property info array * @param num_props Number of entries in array to free @@ -733,6 +735,8 @@ drm_property_info_free(struct drm_property_info *info, int num_props) for (i = 0; i < num_props; i++) free(info[i].enum_values); + + memset(info, 0, sizeof(*info) * num_props); } static void From dc14fd4cd7345897f8e8fad0e6c1f844746caf37 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 10 Nov 2017 15:31:39 +0200 Subject: [PATCH 0330/1642] compositor-drm: move refresh rate computation Move it into to a new function. Following patches want to compute it elsewhere as well. No functional changes. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 268394bf0..02e7877ce 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3699,6 +3699,25 @@ destroy_sprites(struct drm_backend *b) drm_plane_destroy(plane); } +static uint32_t +drm_refresh_rate_mHz(const drmModeModeInfo *info) +{ + uint64_t refresh; + + /* Calculate higher precision (mHz) refresh rate */ + refresh = (info->clock * 1000000LL / info->htotal + + info->vtotal / 2) / info->vtotal; + + if (info->flags & DRM_MODE_FLAG_INTERLACE) + refresh *= 2; + if (info->flags & DRM_MODE_FLAG_DBLSCAN) + refresh /= 2; + if (info->vscan > 1) + refresh /= info->vscan; + + return refresh; +} + /** * Add a mode to output's mode list * @@ -3713,7 +3732,6 @@ static struct drm_mode * drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) { struct drm_mode *mode; - uint64_t refresh; mode = malloc(sizeof *mode); if (mode == NULL) @@ -3723,18 +3741,7 @@ drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) mode->base.width = info->hdisplay; mode->base.height = info->vdisplay; - /* Calculate higher precision (mHz) refresh rate */ - refresh = (info->clock * 1000000LL / info->htotal + - info->vtotal / 2) / info->vtotal; - - if (info->flags & DRM_MODE_FLAG_INTERLACE) - refresh *= 2; - if (info->flags & DRM_MODE_FLAG_DBLSCAN) - refresh /= 2; - if (info->vscan > 1) - refresh /= info->vscan; - - mode->base.refresh = refresh; + mode->base.refresh = drm_refresh_rate_mHz(info); mode->mode_info = *info; mode->blob_id = 0; From dc4e3c6118bfc1d7a30c88fc8a21d2975659fff3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 5 Dec 2017 15:37:41 +0200 Subject: [PATCH 0331/1642] compositor-drm: fix uninitialized bytes on modeinfo Fixes the following Valgrind error: ==21607== Syscall param ioctl(generic) points to uninitialised byte(s) ==21607== at 0x5E8C787: ioctl (in /lib64/libc-2.25.so) ==21607== by 0x8220C17: drmIoctl (in /usr/lib64/libdrm.so.2.4.0) ==21607== by 0x82263CD: drmModeSetCrtc (in /usr/lib64/libdrm.so.2.4.0) ==21607== by 0x7B22095: drm_output_apply_state_legacy (compositor-drm.c:2107) ==21607== by 0x7B2335D: drm_pending_state_apply (compositor-drm.c:2539) ==21607== by 0x7B23AEB: drm_repaint_flush (compositor-drm.c:2773) ==21607== by 0x4E4A3E4: output_repaint_timer_handler (compositor.c:2500) ==21607== by 0x5081496: wl_event_source_timer_dispatch (event-loop.c:235) ==21607== by 0x5081B61: wl_event_loop_dispatch (event-loop.c:633) ==21607== by 0x50803A4: wl_display_run (wayland-server.c:1245) ==21607== by 0x409DD8: main (main.c:2644) ==21607== Address 0xffefff59a is on thread 1's stack ==21607== in frame #2, created by drmModeSetCrtc (???:) ==21607== Uninitialised value was created by a stack allocation ==21607== at 0x7B2782F: drm_output_choose_initial_mode (compositor-drm.c:4842) Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray [Pekka: switch to memset] Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 02e7877ce..e1953d7d3 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4347,6 +4347,8 @@ parse_modeline(const char *s, drmModeModeInfo *mode) char vsync[16]; float fclock; + memset(mode, 0, sizeof *mode); + mode->type = DRM_MODE_TYPE_USERDEF; mode->hskew = 0; mode->vscan = 0; From 9bf4f371632567a6abc8d4303fb5021ac7547c86 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 7 Dec 2017 16:05:29 +0200 Subject: [PATCH 0332/1642] compositor-drm: free filename in exit Spotted by Valgrind. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index e1953d7d3..d2773f40f 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -5229,6 +5229,7 @@ drm_destroy(struct weston_compositor *ec) wl_array_release(&b->unused_connectors); close(b->drm.fd); + free(b->drm.filename); free(b); } From 5b0aa55d9e20d44f974e016e90a309260e8fd1f3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 7 Dec 2017 16:06:05 +0200 Subject: [PATCH 0333/1642] compositor-drm: unref udev monitor on exit Leaks spotted by Valgrind. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index d2773f40f..321ee1915 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -5221,6 +5221,7 @@ drm_destroy(struct weston_compositor *ec) if (b->gbm) gbm_device_destroy(b->gbm); + udev_monitor_unref(b->udev_monitor); udev_unref(b->udev); weston_launcher_destroy(ec->launcher); From 9350bfd9165086e95c7b15e347f1581dc05bf22d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 13 Feb 2018 16:18:05 +0200 Subject: [PATCH 0334/1642] desktop-shell: fix shell_output_destroy_move_layer unused variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /home/pq/git/weston/desktop-shell/shell.c: In function ‘shell_output_destroy_move_layer’: /home/pq/git/weston/desktop-shell/shell.c:4718:24: warning: unused variable ‘output’ [-Wunused-variable] struct weston_output *output = data; Since the data pointer is not used for anything, decided to also set it to NULL in the caller. This caused another variable to become unused. Signed-off-by: Pekka Paalanen Reviewed-by: Marius-Vlad Reviewed-by: Daniel Stone --- desktop-shell/shell.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 1c35d18a3..ceb45c74d 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -4715,7 +4715,6 @@ shell_output_destroy_move_layer(struct desktop_shell *shell, struct weston_layer *layer, void *data) { - struct weston_output *output = data; struct weston_view *view; wl_list_for_each(view, &layer->view_list.link, layer_link.link) @@ -4727,10 +4726,9 @@ handle_output_destroy(struct wl_listener *listener, void *data) { struct shell_output *output_listener = container_of(listener, struct shell_output, destroy_listener); - struct weston_output *output = output_listener->output; struct desktop_shell *shell = output_listener->shell; - shell_for_each_layer(shell, shell_output_destroy_move_layer, output); + shell_for_each_layer(shell, shell_output_destroy_move_layer, NULL); if (output_listener->panel_surface) wl_list_remove(&output_listener->panel_surface_listener.link); From eba58edc6a17c5aa380bedc4eeada801e4ec099c Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Tue, 13 Feb 2018 16:20:56 +0200 Subject: [PATCH 0335/1642] libweston-desktop/xdg-shell-v5: Drop xdg-shell v5 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop support for the obsolete xdg-shell v5 protocol. This clears the path to properly support xdg-shell stable, since xdg-shell stable and xdg-shell v5 can't currently co-exist in the same compositor, as both define structures with the same name (such as struct xdg_surface_interface). Signed-off-by: Alexandros Frantzis Proposed-by: Quentin Glidic Reviewed-by: Derek Foreman Acked-by: Daniel Stone Acked-by: Pekka Paalanen Acked-by: Jonas Ådahl --- Makefile.am | 6 +- libweston-desktop/internal.h | 3 - libweston-desktop/libweston-desktop.c | 10 - libweston-desktop/xdg-shell-v5.c | 911 -------------------------- 4 files changed, 1 insertion(+), 929 deletions(-) delete mode 100644 libweston-desktop/xdg-shell-v5.c diff --git a/Makefile.am b/Makefile.am index 32c9a0f25..189e7d8a0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -132,19 +132,15 @@ libweston_desktop_@LIBWESTON_MAJOR@_la_SOURCES = \ libweston-desktop/surface.c \ libweston-desktop/wl-shell.c \ libweston-desktop/xdg-shell-v6.c \ - libweston-desktop/xdg-shell-v5.c \ libweston-desktop/xwayland.c nodist_libweston_desktop_@LIBWESTON_MAJOR@_la_SOURCES = \ protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-server-protocol.h \ - protocol/xdg-shell-unstable-v5-protocol.c \ - protocol/xdg-shell-unstable-v5-server-protocol.h + protocol/xdg-shell-unstable-v6-server-protocol.h BUILT_SOURCES += $(nodist_libweston_desktop_@LIBWESTON_MAJOR@_la_SOURCES) libweston-desktop-@LIBWESTON_MAJOR@.la libweston-desktop/libweston_desktop_@LIBWESTON_MAJOR@_la-xdg-shell-v6.lo: protocol/xdg-shell-unstable-v6-server-protocol.h -libweston-desktop-@LIBWESTON_MAJOR@.la libweston-desktop/libweston_desktop_@LIBWESTON_MAJOR@_la-xdg-shell-v5.lo: protocol/xdg-shell-unstable-v5-server-protocol.h if SYSTEMD_NOTIFY_SUPPORT module_LTLIBRARIES += systemd-notify.la diff --git a/libweston-desktop/internal.h b/libweston-desktop/internal.h index 763355bf0..564f7b3cb 100644 --- a/libweston-desktop/internal.h +++ b/libweston-desktop/internal.h @@ -230,9 +230,6 @@ struct wl_global * weston_desktop_xdg_shell_v6_create(struct weston_desktop *desktop, struct wl_display *display); struct wl_global * -weston_desktop_xdg_shell_v5_create(struct weston_desktop *desktop, - struct wl_display *display); -struct wl_global * weston_desktop_wl_shell_create(struct weston_desktop *desktop, struct wl_display *display); void diff --git a/libweston-desktop/libweston-desktop.c b/libweston-desktop/libweston-desktop.c index 48e90009e..c840a8a9d 100644 --- a/libweston-desktop/libweston-desktop.c +++ b/libweston-desktop/libweston-desktop.c @@ -41,7 +41,6 @@ struct weston_desktop { struct weston_desktop_api api; void *user_data; struct wl_global *xdg_shell_v6; - struct wl_global *xdg_shell_v5; struct wl_global *wl_shell; }; @@ -77,13 +76,6 @@ weston_desktop_create(struct weston_compositor *compositor, return NULL; } - desktop->xdg_shell_v5 = - weston_desktop_xdg_shell_v5_create(desktop, display); - if (desktop->xdg_shell_v5 == NULL) { - weston_desktop_destroy(desktop); - return NULL; - } - desktop->wl_shell = weston_desktop_wl_shell_create(desktop, display); if (desktop->wl_shell == NULL) { @@ -104,8 +96,6 @@ weston_desktop_destroy(struct weston_desktop *desktop) if (desktop->wl_shell != NULL) wl_global_destroy(desktop->wl_shell); - if (desktop->xdg_shell_v5 != NULL) - wl_global_destroy(desktop->xdg_shell_v5); if (desktop->xdg_shell_v6 != NULL) wl_global_destroy(desktop->xdg_shell_v6); diff --git a/libweston-desktop/xdg-shell-v5.c b/libweston-desktop/xdg-shell-v5.c deleted file mode 100644 index ebe7940ed..000000000 --- a/libweston-desktop/xdg-shell-v5.c +++ /dev/null @@ -1,911 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * 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 "compositor.h" -#include "zalloc.h" -#include "xdg-shell-unstable-v5-server-protocol.h" - -#include "libweston-desktop.h" -#include "internal.h" - -#define WD_XDG_SHELL_PROTOCOL_VERSION 1 - -struct weston_desktop_xdg_surface_state { - bool maximized; - bool fullscreen; - bool resizing; - bool activated; -}; - -struct weston_desktop_xdg_surface_configure { - struct wl_list link; /* weston_desktop_xdg_surface::configure_list */ - uint32_t serial; - struct weston_desktop_xdg_surface_state state; - struct weston_size size; -}; - -struct weston_desktop_xdg_surface { - struct wl_resource *resource; - struct weston_desktop_surface *surface; - struct weston_desktop *desktop; - bool added; - struct wl_event_source *add_idle; - struct wl_event_source *configure_idle; - struct wl_list configure_list; /* weston_desktop_xdg_surface_configure::link */ - struct { - struct weston_desktop_xdg_surface_state state; - struct weston_size size; - } pending; - struct { - struct weston_desktop_xdg_surface_state state; - struct weston_size size; - } next; - struct { - struct weston_desktop_xdg_surface_state state; - } current; - bool has_next_geometry; - struct weston_geometry next_geometry; -}; - -struct weston_desktop_xdg_popup { - struct wl_resource *resource; - struct weston_desktop_surface *popup; - struct weston_desktop *desktop; - struct weston_desktop_seat *seat; - struct wl_display *display; -}; - -static void -weston_desktop_xdg_surface_ensure_added(struct weston_desktop_xdg_surface *surface) -{ - if (surface->added) - return; - - if (surface->add_idle != NULL) - wl_event_source_remove(surface->add_idle); - surface->add_idle = NULL; - weston_desktop_api_surface_added(surface->desktop, surface->surface); - surface->added = true; -} - -static void -weston_desktop_xdg_surface_send_configure(void *data) -{ - struct weston_desktop_xdg_surface *surface = data; - struct weston_desktop_xdg_surface_configure *configure; - uint32_t *s; - struct wl_array states; - - surface->configure_idle = NULL; - - configure = zalloc(sizeof(struct weston_desktop_xdg_surface_configure)); - if (configure == NULL) { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(surface->surface); - struct wl_client *wl_client = - weston_desktop_client_get_client(client); - wl_client_post_no_memory(wl_client); - return; - } - wl_list_insert(surface->configure_list.prev, &configure->link); - configure->serial = - wl_display_next_serial(weston_desktop_get_display(surface->desktop)); - configure->state = surface->pending.state; - configure->size = surface->pending.size; - - wl_array_init(&states); - if (surface->pending.state.maximized) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = XDG_SURFACE_STATE_MAXIMIZED; - } - if (surface->pending.state.fullscreen) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = XDG_SURFACE_STATE_FULLSCREEN; - } - if (surface->pending.state.resizing) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = XDG_SURFACE_STATE_RESIZING; - } - if (surface->pending.state.activated) { - s = wl_array_add(&states, sizeof(uint32_t)); - *s = XDG_SURFACE_STATE_ACTIVATED; - } - - xdg_surface_send_configure(surface->resource, - surface->pending.size.width, - surface->pending.size.height, - &states, - configure->serial); - - wl_array_release(&states); -}; - -static bool -weston_desktop_xdg_surface_state_compare(struct weston_desktop_xdg_surface *surface) -{ - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(surface->surface); - - if (surface->pending.state.activated != surface->current.state.activated) - return false; - if (surface->pending.state.fullscreen != surface->current.state.fullscreen) - return false; - if (surface->pending.state.maximized != surface->current.state.maximized) - return false; - if (surface->pending.state.resizing != surface->current.state.resizing) - return false; - - if (wsurface->width == surface->pending.size.width && - wsurface->height == surface->pending.size.height) - return true; - - if (surface->pending.size.width == 0 && - surface->pending.size.height == 0) - return true; - - return false; -} - -static void -weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface, - bool force) -{ - struct wl_display *display = weston_desktop_get_display(surface->desktop); - struct wl_event_loop *loop = wl_display_get_event_loop(display); - bool pending_same = - !force && weston_desktop_xdg_surface_state_compare(surface); - - if (surface->configure_idle != NULL) { - if (!pending_same) - return; - - wl_event_source_remove(surface->configure_idle); - surface->configure_idle = NULL; - } else { - if (pending_same) - return; - - surface->configure_idle = - wl_event_loop_add_idle(loop, - weston_desktop_xdg_surface_send_configure, - surface); - } -} - -static void -weston_desktop_xdg_surface_set_maximized(struct weston_desktop_surface *dsurface, - void *user_data, bool maximized) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - surface->pending.state.maximized = maximized; - weston_desktop_xdg_surface_schedule_configure(surface, false); -} - -static void -weston_desktop_xdg_surface_set_fullscreen(struct weston_desktop_surface *dsurface, - void *user_data, bool fullscreen) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - surface->pending.state.fullscreen = fullscreen; - weston_desktop_xdg_surface_schedule_configure(surface, false); -} - -static void -weston_desktop_xdg_surface_set_resizing(struct weston_desktop_surface *dsurface, - void *user_data, bool resizing) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - surface->pending.state.resizing = resizing; - weston_desktop_xdg_surface_schedule_configure(surface, false); -} - -static void -weston_desktop_xdg_surface_set_activated(struct weston_desktop_surface *dsurface, - void *user_data, bool activated) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - surface->pending.state.activated = activated; - weston_desktop_xdg_surface_schedule_configure(surface, false); -} - -static void -weston_desktop_xdg_surface_set_size(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t width, int32_t height) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - surface->pending.size.width = width; - surface->pending.size.height = height; - - weston_desktop_xdg_surface_schedule_configure(surface, false); -} - -static void -weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t sx, int32_t sy) -{ - struct weston_desktop_xdg_surface *surface = user_data; - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(surface->surface); - bool reconfigure = false; - - /* TODO: use the window geometry and not the surface size here - * We need to check the next geometry if there is one, but not accept it - * until we checked it, maybe. - */ - if (surface->next.state.maximized || surface->next.state.fullscreen) - reconfigure = surface->next.size.width != wsurface->width || - surface->next.size.height != wsurface->height; - - if (reconfigure) { - weston_desktop_xdg_surface_schedule_configure(surface, true); - } else { - surface->current.state = surface->next.state; - if (surface->has_next_geometry) { - surface->has_next_geometry = false; - weston_desktop_surface_set_geometry(surface->surface, - surface->next_geometry); - } - - weston_desktop_xdg_surface_ensure_added(surface); - weston_desktop_api_committed(surface->desktop, surface->surface, - sx, sy); - } -} - -static void -weston_desktop_xdg_surface_ping(struct weston_desktop_surface *dsurface, - uint32_t serial, void *user_data) -{ - struct weston_desktop_client *client = - weston_desktop_surface_get_client(dsurface); - - xdg_shell_send_ping(weston_desktop_client_get_resource(client), - serial); -} - -static void -weston_desktop_xdg_surface_close(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - xdg_surface_send_close(surface->resource); -} - -static bool -weston_desktop_xdg_surface_get_maximized(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - return surface->current.state.maximized; -} - -static bool -weston_desktop_xdg_surface_get_fullscreen(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - return surface->current.state.fullscreen; -} - -static bool -weston_desktop_xdg_surface_get_resizing(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - return surface->current.state.resizing; -} - -static bool -weston_desktop_xdg_surface_get_activated(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - return surface->current.state.activated; -} - -static void -weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - struct weston_desktop_xdg_surface_configure *configure, *temp; - - if (surface->added) - weston_desktop_api_surface_removed(surface->desktop, - surface->surface); - - if (surface->add_idle != NULL) - wl_event_source_remove(surface->add_idle); - - if (surface->configure_idle != NULL) - wl_event_source_remove(surface->configure_idle); - - wl_list_for_each_safe(configure, temp, &surface->configure_list, link) - free(configure); - - free(surface); -} - -static void -weston_desktop_xdg_surface_protocol_set_parent(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *parent_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_desktop_surface *parent = NULL; - - if (parent_resource != NULL) - parent = wl_resource_get_user_data(parent_resource); - - weston_desktop_xdg_surface_ensure_added(surface); - weston_desktop_api_set_parent(surface->desktop, dsurface, parent); -} - -static void -weston_desktop_xdg_surface_protocol_set_title(struct wl_client *wl_client, - struct wl_resource *resource, - const char *title) -{ - struct weston_desktop_surface *surface = - wl_resource_get_user_data(resource); - - weston_desktop_surface_set_title(surface, title); -} - -static void -weston_desktop_xdg_surface_protocol_set_app_id(struct wl_client *wl_client, - struct wl_resource *resource, - const char *app_id) -{ - struct weston_desktop_surface *surface = - wl_resource_get_user_data(resource); - - weston_desktop_surface_set_app_id(surface, app_id); -} - -static void -weston_desktop_xdg_surface_protocol_show_window_menu(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial, - int32_t x, int32_t y) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = - wl_resource_get_user_data(seat_resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_surface_ensure_added(surface); - weston_desktop_api_show_window_menu(surface->desktop, dsurface, seat, x, y); -} - -static void -weston_desktop_xdg_surface_protocol_move(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = - wl_resource_get_user_data(seat_resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_surface_ensure_added(surface); - weston_desktop_api_move(surface->desktop, dsurface, seat, serial); -} - -static void -weston_desktop_xdg_surface_protocol_resize(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial, - enum xdg_surface_resize_edge edges) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = - wl_resource_get_user_data(seat_resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - enum weston_desktop_surface_edge surf_edges = - (enum weston_desktop_surface_edge) edges; - - weston_desktop_xdg_surface_ensure_added(surface); - weston_desktop_api_resize(surface->desktop, dsurface, seat, serial, surf_edges); -} - -static void -weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t serial) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_desktop_xdg_surface_configure *configure, *temp; - bool found = false; - - wl_list_for_each_safe(configure, temp, &surface->configure_list, link) { - if (configure->serial < serial) { - wl_list_remove(&configure->link); - free(configure); - } else if (configure->serial == serial) { - wl_list_remove(&configure->link); - found = true; - break; - } else { - break; - } - } - if (!found) { - struct weston_desktop_client *client = - weston_desktop_surface_get_client(dsurface); - struct wl_resource *client_resource = - weston_desktop_client_get_resource(client); - wl_resource_post_error(client_resource, - XDG_SHELL_ERROR_DEFUNCT_SURFACES, - "Wrong configure serial: %u", serial); - return; - } - - surface->next.state = configure->state; - surface->next.size = configure->size; - - free(configure); -} - -static void -weston_desktop_xdg_surface_protocol_set_window_geometry(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t x, int32_t y, - int32_t width, int32_t height) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - surface->has_next_geometry = true; - surface->next_geometry.x = x; - surface->next_geometry.y = y; - surface->next_geometry.width = width; - surface->next_geometry.height = height; -} - -static void -weston_desktop_xdg_surface_protocol_set_maximized(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_surface_ensure_added(surface); - weston_desktop_api_maximized_requested(surface->desktop, dsurface, true); -} - -static void -weston_desktop_xdg_surface_protocol_unset_maximized(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_surface_ensure_added(surface); - weston_desktop_api_maximized_requested(surface->desktop, dsurface, false); -} - -static void -weston_desktop_xdg_surface_protocol_set_fullscreen(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *output_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_output *output = NULL; - - if (output_resource != NULL) - output = weston_output_from_resource(output_resource); - - weston_desktop_xdg_surface_ensure_added(surface); - weston_desktop_api_fullscreen_requested(surface->desktop, dsurface, - true, output); -} - -static void -weston_desktop_xdg_surface_protocol_unset_fullscreen(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_surface_ensure_added(surface); - weston_desktop_api_fullscreen_requested(surface->desktop, dsurface, - false, NULL); -} - -static void -weston_desktop_xdg_surface_protocol_set_minimized(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_xdg_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_xdg_surface_ensure_added(surface); - weston_desktop_api_minimized_requested(surface->desktop, dsurface); -} - -static const struct xdg_surface_interface weston_desktop_xdg_surface_implementation = { - .destroy = weston_desktop_destroy_request, - .set_parent = weston_desktop_xdg_surface_protocol_set_parent, - .set_title = weston_desktop_xdg_surface_protocol_set_title, - .set_app_id = weston_desktop_xdg_surface_protocol_set_app_id, - .show_window_menu = weston_desktop_xdg_surface_protocol_show_window_menu, - .move = weston_desktop_xdg_surface_protocol_move, - .resize = weston_desktop_xdg_surface_protocol_resize, - .ack_configure = weston_desktop_xdg_surface_protocol_ack_configure, - .set_window_geometry = weston_desktop_xdg_surface_protocol_set_window_geometry, - .set_maximized = weston_desktop_xdg_surface_protocol_set_maximized, - .unset_maximized = weston_desktop_xdg_surface_protocol_unset_maximized, - .set_fullscreen = weston_desktop_xdg_surface_protocol_set_fullscreen, - .unset_fullscreen = weston_desktop_xdg_surface_protocol_unset_fullscreen, - .set_minimized = weston_desktop_xdg_surface_protocol_set_minimized, -}; - -static const struct weston_desktop_surface_implementation weston_desktop_xdg_surface_internal_implementation = { - .set_maximized = weston_desktop_xdg_surface_set_maximized, - .set_fullscreen = weston_desktop_xdg_surface_set_fullscreen, - .set_resizing = weston_desktop_xdg_surface_set_resizing, - .set_activated = weston_desktop_xdg_surface_set_activated, - .set_size = weston_desktop_xdg_surface_set_size, - .committed = weston_desktop_xdg_surface_committed, - .ping = weston_desktop_xdg_surface_ping, - .close = weston_desktop_xdg_surface_close, - - .get_maximized = weston_desktop_xdg_surface_get_maximized, - .get_fullscreen = weston_desktop_xdg_surface_get_fullscreen, - .get_resizing = weston_desktop_xdg_surface_get_resizing, - .get_activated = weston_desktop_xdg_surface_get_activated, - - .destroy = weston_desktop_xdg_surface_destroy, -}; - -static void -weston_desktop_xdg_popup_close(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_popup *popup = user_data; - - xdg_popup_send_popup_done(popup->resource); -} - -static void -weston_desktop_xdg_popup_destroy(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_xdg_popup *popup = user_data; - struct weston_desktop_surface *topmost; - struct weston_desktop_client *client = - weston_desktop_surface_get_client(popup->popup); - - if (!weston_desktop_surface_get_grab(popup->popup)) - goto end; - - topmost = weston_desktop_seat_popup_grab_get_topmost_surface(popup->seat); - if (topmost != popup->popup) { - struct wl_resource *client_resource = - weston_desktop_client_get_resource(client); - - wl_resource_post_error(client_resource, - XDG_SHELL_ERROR_NOT_THE_TOPMOST_POPUP, - "xdg_popup was destroyed while it was not the topmost popup."); - } - - weston_desktop_surface_popup_ungrab(popup->popup, popup->seat); - -end: - free(popup); -} - -static const struct xdg_popup_interface weston_desktop_xdg_popup_implementation = { - .destroy = weston_desktop_destroy_request, -}; - -static const struct weston_desktop_surface_implementation weston_desktop_xdg_popup_internal_implementation = { - .close = weston_desktop_xdg_popup_close, - - .destroy = weston_desktop_xdg_popup_destroy, -}; - -static void -weston_desktop_xdg_shell_protocol_use_unstable_version(struct wl_client *wl_client, - struct wl_resource *resource, - int32_t version) -{ - if (version > 1) { - wl_resource_post_error(resource, - 1, "xdg_shell version not supported"); - return; - } -} - -static void -weston_desktop_xdg_surface_add_idle_callback(void *user_data) -{ - struct weston_desktop_xdg_surface *surface = user_data; - - surface->add_idle = NULL; - weston_desktop_xdg_surface_ensure_added(surface); -} - -static void -weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *surface_resource) -{ - struct weston_desktop_client *client = - wl_resource_get_user_data(resource); - struct weston_desktop *desktop = - weston_desktop_client_get_desktop(client); - struct weston_surface *wsurface = - wl_resource_get_user_data(surface_resource); - struct weston_desktop_xdg_surface *surface; - struct wl_display *display = weston_desktop_get_display(desktop); - struct wl_event_loop *loop = wl_display_get_event_loop(display); - - if (weston_surface_set_role(wsurface, "xdg_surface", resource, XDG_SHELL_ERROR_ROLE) < 0) - return; - - surface = zalloc(sizeof(struct weston_desktop_xdg_surface)); - if (surface == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - surface->desktop = desktop; - - surface->surface = - weston_desktop_surface_create(surface->desktop, client, - wsurface, - &weston_desktop_xdg_surface_internal_implementation, - surface); - if (surface->surface == NULL) { - free(surface); - return; - } - - surface->resource = - weston_desktop_surface_add_resource(surface->surface, - &xdg_surface_interface, - &weston_desktop_xdg_surface_implementation, - id, NULL); - if (surface->resource == NULL) - return; - - surface->add_idle = - wl_event_loop_add_idle(loop, - weston_desktop_xdg_surface_add_idle_callback, - surface); - - wl_list_init(&surface->configure_list); -} - -static void -weston_desktop_xdg_shell_protocol_get_xdg_popup(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *surface_resource, - struct wl_resource *parent_resource, - struct wl_resource *seat_resource, - uint32_t serial, - int32_t x, int32_t y) -{ - struct weston_desktop_client *client = - wl_resource_get_user_data(resource); - struct weston_surface *wsurface = - wl_resource_get_user_data(surface_resource); - struct weston_surface *wparent = - wl_resource_get_user_data(parent_resource); - struct weston_seat *wseat = wl_resource_get_user_data(seat_resource); - struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); - struct weston_desktop_surface *parent, *topmost; - bool parent_is_popup, parent_is_xdg; - struct weston_desktop_xdg_popup *popup; - - if (weston_surface_set_role(wsurface, "xdg_popup", resource, XDG_SHELL_ERROR_ROLE) < 0) - return; - - if (!weston_surface_is_desktop_surface(wparent)) { - wl_resource_post_error(resource, - XDG_SHELL_ERROR_INVALID_POPUP_PARENT, - "xdg_popup parent was invalid"); - return; - } - - parent = weston_surface_get_desktop_surface(wparent); - parent_is_xdg = - weston_desktop_surface_has_implementation(parent, - &weston_desktop_xdg_surface_internal_implementation); - parent_is_popup = - weston_desktop_surface_has_implementation(parent, - &weston_desktop_xdg_popup_internal_implementation); - - if (!parent_is_xdg && !parent_is_popup) { - wl_resource_post_error(resource, - XDG_SHELL_ERROR_INVALID_POPUP_PARENT, - "xdg_popup parent was invalid"); - return; - } - - topmost = weston_desktop_seat_popup_grab_get_topmost_surface(seat); - if ((topmost == NULL && parent_is_popup) || - (topmost != NULL && topmost != parent)) { - wl_resource_post_error(resource, - XDG_SHELL_ERROR_NOT_THE_TOPMOST_POPUP, - "xdg_popup was not created on the topmost popup"); - return; - } - - popup = zalloc(sizeof(struct weston_desktop_xdg_popup)); - if (popup == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - popup->desktop = weston_desktop_client_get_desktop(client); - popup->display = weston_desktop_get_display(popup->desktop); - popup->seat = seat; - - popup->popup = - weston_desktop_surface_create(popup->desktop, client, wsurface, - &weston_desktop_xdg_popup_internal_implementation, - popup); - if (popup->popup == NULL) { - free(popup); - return; - } - - popup->resource = - weston_desktop_surface_add_resource(popup->popup, - &xdg_popup_interface, - &weston_desktop_xdg_popup_implementation, - id, NULL); - if (popup->resource == NULL) - return; - - weston_desktop_surface_set_relative_to(popup->popup, parent, x, y, false); - weston_desktop_surface_popup_grab(popup->popup, popup->seat, serial); -} - -static void -weston_desktop_xdg_shell_protocol_pong(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t serial) -{ - struct weston_desktop_client *client = - wl_resource_get_user_data(resource); - - weston_desktop_client_pong(client, serial); -} - -static const struct xdg_shell_interface weston_desktop_xdg_shell_implementation = { - .destroy = weston_desktop_destroy_request, - .use_unstable_version = weston_desktop_xdg_shell_protocol_use_unstable_version, - .get_xdg_surface = weston_desktop_xdg_shell_protocol_get_xdg_surface, - .get_xdg_popup = weston_desktop_xdg_shell_protocol_get_xdg_popup, - .pong = weston_desktop_xdg_shell_protocol_pong, -}; - -static int -xdg_shell_unversioned_dispatch(const void *implementation, - void *_target, uint32_t opcode, - const struct wl_message *message, - union wl_argument *args) -{ - struct wl_resource *resource = _target; - struct weston_desktop_client *client = - wl_resource_get_user_data(resource); - - if (opcode != 1 /* XDG_SHELL_USE_UNSTABLE_VERSION */) { - wl_resource_post_error(resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "must call use_unstable_version first"); - return 0; - } - -#define XDG_SERVER_VERSION 5 - - if (args[0].i != XDG_SERVER_VERSION) { - wl_resource_post_error(resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "incompatible version, server is %d " "client wants %d", - XDG_SERVER_VERSION, args[0].i); - return 0; - } - - wl_resource_set_implementation(resource, - &weston_desktop_xdg_shell_implementation, - client, implementation); - - return 1; -} - -static void -weston_desktop_xdg_shell_bind(struct wl_client *client, void *data, - uint32_t version, uint32_t id) -{ - struct weston_desktop *desktop = data; - - weston_desktop_client_create(desktop, client, - xdg_shell_unversioned_dispatch, - &xdg_shell_interface, NULL, version, id); -} - -struct wl_global * -weston_desktop_xdg_shell_v5_create(struct weston_desktop *desktop, - struct wl_display *display) -{ - return wl_global_create(display, - &xdg_shell_interface, - WD_XDG_SHELL_PROTOCOL_VERSION, - desktop, weston_desktop_xdg_shell_bind); -} From c6e2942fab4a0364108ef7073381205590e1a504 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Wed, 14 Feb 2018 11:06:54 +0100 Subject: [PATCH 0336/1642] tests: fix a race condition in ivi-shell tests ivi-shell tests load their own controller plugin for testing purposes. Tests also uses the generated weston-ivi.in config file, which causes weston to load hmi-controller and its helper client. Existence of hmi-controller and its helper client confuses test plugins. Because they are creating surfaces and layers which are not expected by test plugins. We can start ivi-shell tests without config file to solve this problem. Then, weston will not load hmi-controller plugin. Reported-by: Pekka Paalanen Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen Acked-by: Daniel Stone --- Makefile.am | 16 +--------------- tests/weston-tests-env | 4 ++-- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/Makefile.am b/Makefile.am index 189e7d8a0..b5c29c044 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,15 +30,6 @@ ivi-shell/weston.ini : $(srcdir)/ivi-shell/weston.ini.in -e 's|@plugin_prefix[@]||g' \ $< > $@ -tests/weston-ivi.ini : $(srcdir)/ivi-shell/weston.ini.in - $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(SED) \ - -e 's|@bindir[@]|$(bindir)|g' \ - -e 's|@abs_top_builddir[@]|$(abs_top_builddir)|g' \ - -e 's|@abs_top_srcdir[@]|$(abs_top_srcdir)|g' \ - -e 's|@libexecdir[@]|$(abs_builddir)|g' \ - -e 's|@plugin_prefix[@]|$(abs_top_builddir)/.libs/|g' \ - $< > $@ - all-local : weston.ini ivi-shell/weston.ini AM_CFLAGS = $(GCC_CFLAGS) @@ -56,7 +47,6 @@ AM_CPPFLAGS = \ CLEANFILES = weston.ini \ ivi-shell/weston.ini \ - tests/weston-ivi.ini \ internal-screenshot-00.png \ $(BUILT_SOURCES) @@ -1234,10 +1224,6 @@ weston_tests = \ devices.weston \ touch.weston -ivi_tests = - -$(ivi_tests) : $(builddir)/tests/weston-ivi.ini - AM_TESTS_ENVIRONMENT = \ abs_builddir='$(abs_builddir)'; export abs_builddir; \ abs_top_srcdir='$(abs_top_srcdir)'; export abs_top_srcdir; @@ -1468,7 +1454,7 @@ nodist_ivi_layout_test_la_SOURCES = \ protocol/weston-test-protocol.c \ protocol/weston-test-server-protocol.h -ivi_tests += \ +ivi_tests = \ ivi-shell-app.weston ivi_shell_app_weston_SOURCES = tests/ivi-shell-app-test.c diff --git a/tests/weston-tests-env b/tests/weston-tests-env index f08270e2a..ac2473f77 100755 --- a/tests/weston-tests-env +++ b/tests/weston-tests-env @@ -44,7 +44,7 @@ case $TEST_FILE in WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ $WESTON --backend=$MODDIR/$BACKEND \ - --config=$abs_builddir/tests/weston-ivi.ini \ + --no-config \ --shell=$SHELL_PLUGIN \ --socket=test-${TEST_NAME} \ --modules=$TEST_PLUGIN,$MODDIR/${TEST_FILE/.la/.so}\ @@ -74,7 +74,7 @@ case $TEST_FILE in WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ WESTON_TEST_CLIENT_PATH=$abs_builddir/$TEST_FILE \ $WESTON --backend=$MODDIR/$BACKEND \ - --config=$abs_builddir/tests/weston-ivi.ini \ + --no-config \ --shell=$SHELL_PLUGIN \ --socket=test-${TEST_NAME} \ --modules=$TEST_PLUGIN \ From 0f14ae95b020e8e43da9b5593d1cba8e48f56a55 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 8 Feb 2018 15:37:52 +0200 Subject: [PATCH 0337/1642] libweston: Support NULL weston_pointer in init_pointer_constraint Fix init_pointer_constraint so that it creates a valid, but inert, resource if a NULL weston_pointer value is passed in. In that case no constraint object is associated with the resource, but this is not an issue since affected code can already handle NULL constraint objects. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- libweston/input.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/libweston/input.c b/libweston/input.c index 96cded47c..390698c7d 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -3572,7 +3572,7 @@ init_pointer_constraint(struct wl_resource *pointer_constraints_resource, struct wl_resource *cr; struct weston_pointer_constraint *constraint; - if (get_pointer_constraint_for_pointer(surface, pointer)) { + if (pointer && get_pointer_constraint_for_pointer(surface, pointer)) { wl_resource_post_error(pointer_constraints_resource, ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED, "the pointer has a lock/confine request on this surface"); @@ -3587,18 +3587,23 @@ init_pointer_constraint(struct wl_resource *pointer_constraints_resource, return; } - constraint = weston_pointer_constraint_create(surface, pointer, - region, lifetime, - cr, grab_interface); - if (constraint == NULL) { - wl_client_post_no_memory(client); - return; + if (pointer) { + constraint = weston_pointer_constraint_create(surface, pointer, + region, lifetime, + cr, grab_interface); + if (constraint == NULL) { + wl_client_post_no_memory(client); + return; + } + } else { + constraint = NULL; } wl_resource_set_implementation(cr, implementation, constraint, pointer_constraint_constrain_resource_destroyed); - maybe_enable_pointer_constraint(constraint); + if (constraint) + maybe_enable_pointer_constraint(constraint); } static void From 1c3a40edcd8f109618d2af3ac7f1e67815faeb05 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 8 Feb 2018 15:37:53 +0200 Subject: [PATCH 0338/1642] libweston: Make weston_pointer destruction safe Properly clean up all sub-objects (e.g., weston_pointer_client objects) when a weston_pointer object is destroyed. The clean-up ensures that the server is able to safely handle client requests to any associated pointer resources, which, as a consenquence of a weston_pointer destruction, have now become inert. The clean-up involves, among other things, unsetting the destroyed weston_pointer object from the user data of pointer resources, and handling this NULL user data case where required. Note that in many sites affected by this change the existing code already properly handles NULL weston_pointer (e.g. in init_pointer_constraint), so there is no need for additional updates there. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- libweston/input.c | 35 ++++++++++++++++++++++++++++++----- libweston/zoom.c | 5 ++++- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/libweston/input.c b/libweston/input.c index 390698c7d..647268af6 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -105,6 +105,19 @@ weston_pointer_client_create(struct wl_client *client) static void weston_pointer_client_destroy(struct weston_pointer_client *pointer_client) { + struct wl_resource *resource; + + wl_resource_for_each(resource, &pointer_client->pointer_resources) { + wl_resource_set_user_data(resource, NULL); + } + + wl_resource_for_each(resource, + &pointer_client->relative_pointer_resources) { + wl_resource_set_user_data(resource, NULL); + } + + wl_list_remove(&pointer_client->pointer_resources); + wl_list_remove(&pointer_client->relative_pointer_resources); free(pointer_client); } @@ -170,11 +183,14 @@ unbind_pointer_client_resource(struct wl_resource *resource) struct wl_client *client = wl_resource_get_client(resource); struct weston_pointer_client *pointer_client; - pointer_client = weston_pointer_get_pointer_client(pointer, client); - assert(pointer_client); - wl_list_remove(wl_resource_get_link(resource)); - weston_pointer_cleanup_pointer_client(pointer, pointer_client); + + if (pointer) { + pointer_client = weston_pointer_get_pointer_client(pointer, + client); + assert(pointer_client); + weston_pointer_cleanup_pointer_client(pointer, pointer_client); + } } static void unbind_resource(struct wl_resource *resource) @@ -1092,12 +1108,18 @@ weston_pointer_create(struct weston_seat *seat) WL_EXPORT void weston_pointer_destroy(struct weston_pointer *pointer) { + struct weston_pointer_client *pointer_client, *tmp; + wl_signal_emit(&pointer->destroy_signal, pointer); if (pointer->sprite) pointer_unmap_sprite(pointer); - /* XXX: What about pointer->resource_list? */ + wl_list_for_each_safe(pointer_client, tmp, &pointer->pointer_clients, + link) { + wl_list_remove(&pointer_client->link); + weston_pointer_client_destroy(pointer_client); + } wl_list_remove(&pointer->focus_resource_listener.link); wl_list_remove(&pointer->focus_view_listener.link); @@ -2318,6 +2340,9 @@ pointer_set_cursor(struct wl_client *client, struct wl_resource *resource, struct weston_pointer *pointer = wl_resource_get_user_data(resource); struct weston_surface *surface = NULL; + if (!pointer) + return; + if (surface_resource) surface = wl_resource_get_user_data(surface_resource); diff --git a/libweston/zoom.c b/libweston/zoom.c index 84f1a3204..b89264f7b 100644 --- a/libweston/zoom.c +++ b/libweston/zoom.c @@ -125,6 +125,9 @@ weston_output_update_zoom(struct weston_output *output) struct weston_seat *seat = output->zoom.seat; struct weston_pointer *pointer = weston_seat_get_pointer(seat); + if (!pointer) + return; + assert(output->zoom.active); output->zoom.current.x = wl_fixed_to_double(pointer->x); @@ -151,7 +154,7 @@ weston_output_activate_zoom(struct weston_output *output, { struct weston_pointer *pointer = weston_seat_get_pointer(seat); - if (output->zoom.active) + if (!pointer || output->zoom.active) return; output->zoom.active = true; From 8480d13f6d9edd38483faa3538e2c96bf721c6b1 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 15 Feb 2018 13:07:09 +0200 Subject: [PATCH 0339/1642] libweston: Make weston_seat release safe Ensure the server can safely handle client requests for wl_seat resource that have become inert due to weston_seat object release and subsequent destruction. The clean-up involves, among other things, unsetting the destroyed weston_seat object from the user data of wl_seat resources, and handling this NULL user data case where required. The list of sites extracting and using weston_seat object from wl_seat resources which were audited for this patch are: Legend: N/A = Not Applicable (not implemented by weston) FIXED = Fixed in the commit OK = Already works correctly == keyboard_shortcuts_inhibit_unstable_v1 == [N/A] zwp_keyboard_shortcuts_inhibit_manager_v1.inhibit_shortcuts == tablet_input_unstable_v{1,2} == [N/A] zwp_tablet_manager_v{1,2}.get_tablet_seat == text_input_unstable_v1 == [FIXED] zwp_text_input_v1.activate [FIXED] zwp_text_input_v1.deactivate == wl_data_device == [FIXED] wl_data_device_manager.get_data_device [OK] wl_data_device.start_drag [FIXED] wl_data_device.set_selection [OK] wl_data_device.release == wl_shell == [FIXED] wl_shell_surface.move [FIXED] wl_shell_surface.resize [FIXED] wl_shell_surface.set_popup == xdg_shell and xdg_shell_unstable_v6 == [FIXED] xdg_toplevel.show_window_menu [FIXED] xdg_toplevel.move [FIXED] xdg_toplevel.resize [FIXED] xdg_popup.grab == xdg_shell_unstable_v5 == [FIXED] xdg_shell.get_xdg_popup [FIXED] xdg_surface.show_window_menu [FIXED] xdg_surface.move [FIXED] xdg_surface.resize Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen Reviewed-by: Quentin Glidic --- compositor/text-backend.c | 8 +++-- libweston-desktop/seat.c | 18 ++++++---- libweston-desktop/wl-shell.c | 9 ++++- libweston-desktop/xdg-shell-v6.c | 24 +++++++++++++ libweston/data-device.c | 15 +++++--- libweston/input.c | 61 ++++++++++++++++++++++---------- 6 files changed, 103 insertions(+), 32 deletions(-) diff --git a/compositor/text-backend.c b/compositor/text-backend.c index e6ee249cb..4d8c085bb 100644 --- a/compositor/text-backend.c +++ b/compositor/text-backend.c @@ -193,10 +193,14 @@ text_input_activate(struct wl_client *client, { struct text_input *text_input = wl_resource_get_user_data(resource); struct weston_seat *weston_seat = wl_resource_get_user_data(seat); - struct input_method *input_method = weston_seat->input_method; + struct input_method *input_method; struct weston_compositor *ec = text_input->ec; struct text_input *current; + if (!weston_seat) + return; + + input_method = weston_seat->input_method; if (input_method->input == text_input) return; @@ -237,7 +241,7 @@ text_input_deactivate(struct wl_client *client, { struct weston_seat *weston_seat = wl_resource_get_user_data(seat); - if (weston_seat->input_method->input) + if (weston_seat && weston_seat->input_method->input) deactivate_input_method(weston_seat->input_method); } diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c index 382b9e413..ae1c5e9f1 100644 --- a/libweston-desktop/seat.c +++ b/libweston-desktop/seat.c @@ -242,6 +242,9 @@ weston_desktop_seat_from_seat(struct weston_seat *wseat) struct wl_listener *listener; struct weston_desktop_seat *seat; + if (wseat == NULL) + return NULL; + listener = wl_signal_get(&wseat->destroy_signal, weston_desktop_seat_destroy); if (listener != NULL) @@ -270,7 +273,7 @@ weston_desktop_seat_from_seat(struct weston_seat *wseat) struct weston_desktop_surface * weston_desktop_seat_popup_grab_get_topmost_surface(struct weston_desktop_seat *seat) { - if (wl_list_empty(&seat->popup_grab.surfaces)) + if (seat == NULL || wl_list_empty(&seat->popup_grab.surfaces)) return NULL; struct wl_list *grab_link = seat->popup_grab.surfaces.next; @@ -282,11 +285,14 @@ bool weston_desktop_seat_popup_grab_start(struct weston_desktop_seat *seat, struct wl_client *client, uint32_t serial) { - assert(seat->popup_grab.client == NULL || seat->popup_grab.client == client); - - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat->seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat->seat); - struct weston_touch *touch = weston_seat_get_touch(seat->seat); + assert(seat == NULL || seat->popup_grab.client == NULL || + seat->popup_grab.client == client); + + struct weston_seat *wseat = seat != NULL ? seat->seat : NULL; + /* weston_seat_get_* functions can properly handle a NULL wseat */ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(wseat); + struct weston_pointer *pointer = weston_seat_get_pointer(wseat); + struct weston_touch *touch = weston_seat_get_touch(wseat); if ((keyboard == NULL || keyboard->grab_serial != serial) && (pointer == NULL || pointer->grab_serial != serial) && diff --git a/libweston-desktop/wl-shell.c b/libweston-desktop/wl-shell.c index 66553f456..8467dfb88 100644 --- a/libweston-desktop/wl-shell.c +++ b/libweston-desktop/wl-shell.c @@ -220,6 +220,9 @@ weston_desktop_wl_shell_surface_protocol_move(struct wl_client *wl_client, struct weston_desktop_wl_shell_surface *surface = weston_desktop_surface_get_implementation_data(dsurface); + if (seat == NULL) + return; + weston_desktop_api_move(surface->desktop, dsurface, seat, serial); } @@ -238,6 +241,9 @@ weston_desktop_wl_shell_surface_protocol_resize(struct wl_client *wl_client, enum weston_desktop_surface_edge surf_edges = (enum weston_desktop_surface_edge) edges; + if (seat == NULL) + return; + weston_desktop_api_resize(surface->desktop, dsurface, seat, serial, surf_edges); } @@ -328,7 +334,8 @@ weston_desktop_wl_shell_surface_protocol_set_popup(struct wl_client *wl_client, struct weston_desktop_wl_shell_surface *surface = weston_desktop_surface_get_implementation_data(dsurface); - if (seat == NULL) { + /* Check that if we have a valid wseat we also got a valid desktop seat */ + if (wseat != NULL && seat == NULL) { wl_client_post_no_memory(wl_client); return; } diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index 4db3748b7..f9902ff02 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -378,6 +378,9 @@ weston_desktop_xdg_toplevel_protocol_show_window_menu(struct wl_client *wl_clien return; } + if (seat == NULL) + return; + weston_desktop_api_show_window_menu(toplevel->base.desktop, dsurface, seat, x, y); } @@ -402,6 +405,9 @@ weston_desktop_xdg_toplevel_protocol_move(struct wl_client *wl_client, return; } + if (seat == NULL) + return; + weston_desktop_api_move(toplevel->base.desktop, dsurface, seat, serial); } @@ -428,6 +434,9 @@ weston_desktop_xdg_toplevel_protocol_resize(struct wl_client *wl_client, return; } + if (seat == NULL) + return; + weston_desktop_api_resize(toplevel->base.desktop, dsurface, seat, serial, surf_edges); } @@ -762,6 +771,12 @@ weston_desktop_xdg_popup_protocol_grab(struct wl_client *wl_client, bool parent_is_toplevel = popup->parent->role == WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; + /* Check that if we have a valid wseat we also got a valid desktop seat */ + if (wseat != NULL && seat == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + if (popup->committed) { wl_resource_post_error(popup->resource, ZXDG_POPUP_V6_ERROR_INVALID_GRAB, @@ -769,6 +784,15 @@ weston_desktop_xdg_popup_protocol_grab(struct wl_client *wl_client, return; } + /* If seat is NULL then get_topmost_surface will return NULL. In + * combination with setting parent_is_toplevel to TRUE here we will + * avoid posting an error, and we will instead gracefully fail the + * grab and dismiss the surface. + * FIXME: this is a hack because currently we cannot check the topmost + * parent with a destroyed weston_seat */ + if (seat == NULL) + parent_is_toplevel = true; + topmost = weston_desktop_seat_popup_grab_get_topmost_surface(seat); if ((topmost == NULL && !parent_is_toplevel) || (topmost != NULL && topmost != popup->parent->desktop_surface)) { diff --git a/libweston/data-device.c b/libweston/data-device.c index b4bb4b37f..e3dbee3ef 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -1167,9 +1167,10 @@ data_device_set_selection(struct wl_client *client, struct wl_resource *resource, struct wl_resource *source_resource, uint32_t serial) { + struct weston_seat *seat = wl_resource_get_user_data(resource); struct weston_data_source *source; - if (!source_resource) + if (!seat || !source_resource) return; source = wl_resource_get_user_data(source_resource); @@ -1182,8 +1183,7 @@ data_device_set_selection(struct wl_client *client, } /* FIXME: Store serial and check against incoming serial here. */ - weston_seat_set_selection(wl_resource_get_user_data(resource), - source, serial); + weston_seat_set_selection(seat, source, serial); } static void data_device_release(struct wl_client *client, struct wl_resource *resource) @@ -1296,8 +1296,13 @@ get_data_device(struct wl_client *client, return; } - wl_list_insert(&seat->drag_resource_list, - wl_resource_get_link(resource)); + if (seat) { + wl_list_insert(&seat->drag_resource_list, + wl_resource_get_link(resource)); + } else { + wl_list_init(wl_resource_get_link(resource)); + } + wl_resource_set_implementation(resource, &data_device_interface, seat, unbind_data_device); } diff --git a/libweston/input.c b/libweston/input.c index 647268af6..da0025481 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -2420,13 +2420,10 @@ seat_get_pointer(struct wl_client *client, struct wl_resource *resource, * This prevents a race between the compositor sending new * capabilities and the client trying to use the old ones. */ - struct weston_pointer *pointer = seat->pointer_state; + struct weston_pointer *pointer = seat ? seat->pointer_state : NULL; struct wl_resource *cr; struct weston_pointer_client *pointer_client; - if (!pointer) - return; - cr = wl_resource_create(client, &wl_pointer_interface, wl_resource_get_version(resource), id); if (cr == NULL) { @@ -2434,6 +2431,15 @@ seat_get_pointer(struct wl_client *client, struct wl_resource *resource, return; } + wl_list_init(wl_resource_get_link(cr)); + wl_resource_set_implementation(cr, &pointer_interface, pointer, + unbind_pointer_client_resource); + + /* If we don't have a pointer_state, the resource is inert, so there + * is nothing more to set up */ + if (!pointer) + return; + pointer_client = weston_pointer_ensure_pointer_client(pointer, client); if (!pointer_client) { wl_client_post_no_memory(client); @@ -2442,8 +2448,6 @@ seat_get_pointer(struct wl_client *client, struct wl_resource *resource, wl_list_insert(&pointer_client->pointer_resources, wl_resource_get_link(cr)); - wl_resource_set_implementation(cr, &pointer_interface, pointer, - unbind_pointer_client_resource); if (pointer->focus && pointer->focus->surface->resource && wl_resource_get_client(pointer->focus->surface->resource) == client) { @@ -2507,12 +2511,9 @@ seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, * This prevents a race between the compositor sending new * capabilities and the client trying to use the old ones. */ - struct weston_keyboard *keyboard = seat->keyboard_state; + struct weston_keyboard *keyboard = seat ? seat->keyboard_state : NULL; struct wl_resource *cr; - if (!keyboard) - return; - cr = wl_resource_create(client, &wl_keyboard_interface, wl_resource_get_version(resource), id); if (cr == NULL) { @@ -2520,12 +2521,19 @@ seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, return; } + wl_list_init(wl_resource_get_link(cr)); + wl_resource_set_implementation(cr, &keyboard_interface, + keyboard, unbind_resource); + + /* If we don't have a keyboard_state, the resource is inert, so there + * is nothing more to set up */ + if (!keyboard) + return; + /* May be moved to focused list later by either * weston_keyboard_set_focus or directly if this client is already * focused */ wl_list_insert(&keyboard->resource_list, wl_resource_get_link(cr)); - wl_resource_set_implementation(cr, &keyboard_interface, - keyboard, unbind_resource); if (wl_resource_get_version(cr) >= WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION) { wl_keyboard_send_repeat_info(cr, @@ -2587,12 +2595,9 @@ seat_get_touch(struct wl_client *client, struct wl_resource *resource, * This prevents a race between the compositor sending new * capabilities and the client trying to use the old ones. */ - struct weston_touch *touch = seat->touch_state; + struct weston_touch *touch = seat ? seat->touch_state : NULL; struct wl_resource *cr; - if (!touch) - return; - cr = wl_resource_create(client, &wl_touch_interface, wl_resource_get_version(resource), id); if (cr == NULL) { @@ -2600,6 +2605,15 @@ seat_get_touch(struct wl_client *client, struct wl_resource *resource, return; } + wl_list_init(wl_resource_get_link(cr)); + wl_resource_set_implementation(cr, &touch_interface, + touch, unbind_resource); + + /* If we don't have a touch_state, the resource is inert, so there + * is nothing more to set up */ + if (!touch) + return; + if (touch->focus && wl_resource_get_client(touch->focus->surface->resource) == client) { wl_list_insert(&touch->focus_resource_list, @@ -2608,8 +2622,6 @@ seat_get_touch(struct wl_client *client, struct wl_resource *resource, wl_list_insert(&touch->resource_list, wl_resource_get_link(cr)); } - wl_resource_set_implementation(cr, &touch_interface, - touch, unbind_resource); } static void @@ -3087,6 +3099,19 @@ weston_seat_init(struct weston_seat *seat, struct weston_compositor *ec, WL_EXPORT void weston_seat_release(struct weston_seat *seat) { + struct wl_resource *resource; + + wl_resource_for_each(resource, &seat->base_resource_list) { + wl_resource_set_user_data(resource, NULL); + } + + wl_resource_for_each(resource, &seat->drag_resource_list) { + wl_resource_set_user_data(resource, NULL); + } + + wl_list_remove(&seat->base_resource_list); + wl_list_remove(&seat->drag_resource_list); + wl_list_remove(&seat->link); if (seat->saved_kbd_focus) From c1937971fbfc144aaa60b1fa82ca17fa8831e2c0 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 8 Feb 2018 15:37:55 +0200 Subject: [PATCH 0340/1642] tests: Handle removal of seat global in test clients The current test client code completely ignores removal of globals. This commit updates the code to properly handle removal of globals in general, and of seat globals in particular. This ensures that the test client objects are in sync with the server and any relevant resources are released accordingly. This update will be used by upcoming tests to check that seat removal and re-addition is working properly. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- tests/weston-test-client-helper.c | 83 +++++++++++++++++++++++++++---- tests/weston-test-client-helper.h | 1 + 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 6e0a5246b..5ee032ca2 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -538,6 +538,27 @@ static const struct weston_test_listener test_listener = { test_handle_capture_screenshot_done, }; +static void +input_destroy(struct input *inp) +{ + if (inp->pointer) { + wl_pointer_release(inp->pointer->wl_pointer); + free(inp->pointer); + } + if (inp->keyboard) { + wl_keyboard_release(inp->keyboard->wl_keyboard); + free(inp->keyboard); + } + if (inp->touch) { + wl_touch_release(inp->touch->wl_touch); + free(inp->touch); + } + wl_list_remove(&inp->link); + wl_seat_release(inp->wl_seat); + free(inp->seat_name); + free(inp); +} + static void input_update_devices(struct input *input) { @@ -705,6 +726,7 @@ handle_global(void *data, struct wl_registry *registry, &wl_compositor_interface, version); } else if (strcmp(interface, "wl_seat") == 0) { input = xzalloc(sizeof *input); + input->global_name = global->name; input->wl_seat = wl_registry_bind(registry, id, &wl_seat_interface, version); @@ -735,8 +757,59 @@ handle_global(void *data, struct wl_registry *registry, } } +static struct global * +client_find_global_with_name(struct client *client, uint32_t name) +{ + struct global *global; + + wl_list_for_each(global, &client->global_list, link) { + if (global->name == name) + return global; + } + + return NULL; +} + +static struct input * +client_find_input_with_name(struct client *client, uint32_t name) +{ + struct input *input; + + wl_list_for_each(input, &client->inputs, link) { + if (input->global_name == name) + return input; + } + + return NULL; +} + +static void +handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{ + struct client *client = data; + struct global *global; + struct input *input; + + global = client_find_global_with_name(client, name); + assert(global && "Request to remove unknown global"); + + if (strcmp(global->interface, "wl_seat") == 0) { + input = client_find_input_with_name(client, name); + if (input) { + if (client->input == input) + client->input = NULL; + input_destroy(input); + } + } + + wl_list_remove(&global->link); + free(global->interface); + free(global); +} + static const struct wl_registry_listener registry_listener = { - handle_global + handle_global, + handle_global_remove, }; void @@ -809,14 +882,6 @@ log_handler(const char *fmt, va_list args) vfprintf(stderr, fmt, args); } -static void -input_destroy(struct input *inp) -{ - wl_list_remove(&inp->link); - wl_seat_destroy(inp->wl_seat); - free(inp); -} - /* find the test-seat and set it in client. * Destroy other inputs */ static void diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 09a5df4a0..fb31125ce 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -74,6 +74,7 @@ struct test { }; struct input { + uint32_t global_name; struct wl_seat *wl_seat; struct pointer *pointer; struct keyboard *keyboard; From 468bd0b9c87e3dc6bf152af3bf48a16dc1ed696c Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 8 Feb 2018 15:37:56 +0200 Subject: [PATCH 0341/1642] tests: Support setting the test client input dynamically The current test client code waits for all wl_seat globals to arrive before checking them and deciding which one is the test seat global to use for the input object. Test code that needs to add/remove test seats would have to call the client_set_input() function for any seat changes to take effect. Although we could allow this by making client_set_input() public, we would be exposing unecessary implementation details. This commit applies any seat changes immediately upon arrival of the seat name, freeing test code from needing to call extra functions like client_set_input(). To achieve this the call to input_data_devices() is moved from client_set_input() to the seat name event handler. This commit also moves the check that all seats have names to an explicit test. To support this test, inputs corresponding to non-test seats are not destroyed (unless their seat global is removed), as was previously the case. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- tests/devices-test.c | 10 ++++++++++ tests/weston-test-client-helper.c | 33 ++++++++++--------------------- tests/weston-test-client-helper.h | 1 + 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/tests/devices-test.c b/tests/devices-test.c index 450713e7d..ce1cea3be 100644 --- a/tests/devices-test.c +++ b/tests/devices-test.c @@ -310,3 +310,13 @@ TEST(get_device_after_destroy_multiple) get_device_after_destroy(); } } + +TEST(seats_have_names) +{ + struct client *cl = create_client_and_test_surface(100, 100, 100, 100); + struct input *input; + + wl_list_for_each(input, &cl->inputs, link) { + assert(input->seat_name); + } +} diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 5ee032ca2..dc69e1511 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -635,6 +635,15 @@ seat_handle_name(void *data, struct wl_seat *seat, const char *name) input->seat_name = strdup(name); assert(input->seat_name && "No memory"); + /* We only update the devices and set client input for the test seat */ + if (strcmp(name, "test-seat") == 0) { + assert(!input->client->input && + "Multiple test seats detected!"); + + input_update_devices(input); + input->client->input = input; + } + fprintf(stderr, "test-client: got seat %p name: \'%s\'\n", input, name); } @@ -726,6 +735,7 @@ handle_global(void *data, struct wl_registry *registry, &wl_compositor_interface, version); } else if (strcmp(interface, "wl_seat") == 0) { input = xzalloc(sizeof *input); + input->client = client; input->global_name = global->name; input->wl_seat = wl_registry_bind(registry, id, @@ -882,26 +892,6 @@ log_handler(const char *fmt, va_list args) vfprintf(stderr, fmt, args); } -/* find the test-seat and set it in client. - * Destroy other inputs */ -static void -client_set_input(struct client *cl) -{ - struct input *inp, *inptmp; - wl_list_for_each_safe(inp, inptmp, &cl->inputs, link) { - assert(inp->seat_name && "BUG: input with no name"); - if (strcmp(inp->seat_name, "test-seat") == 0) { - cl->input = inp; - input_update_devices(inp); - } else { - input_destroy(inp); - } - } - - /* we keep only one input */ - assert(wl_list_length(&cl->inputs) == 1); -} - struct client * create_client(void) { @@ -927,9 +917,6 @@ create_client(void) * events */ client_roundtrip(client); - /* find the right input for us */ - client_set_input(client); - /* must have WL_SHM_FORMAT_ARGB32 */ assert(client->has_argb); diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index fb31125ce..255bbf660 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -74,6 +74,7 @@ struct test { }; struct input { + struct client *client; uint32_t global_name; struct wl_seat *wl_seat; struct pointer *pointer; From 849b333133c6c7c2953d526cc0cd2111c354e302 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 8 Feb 2018 15:37:57 +0200 Subject: [PATCH 0342/1642] tests: Run devices tests using the test desktop shell Use the weston-test-desktop-shell to run the devices tests, instead of the currently used desktop-shell. The test desktop shell doesn't interact with temporary globals (e.g. wl_seat), thus avoiding an inherent race in the current wayland protocol when removing globals. This will allow us to safely add tests which add/remove such globals in upcoming commits. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- tests/devices-test.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/devices-test.c b/tests/devices-test.c index ce1cea3be..a6ec6eaf2 100644 --- a/tests/devices-test.c +++ b/tests/devices-test.c @@ -40,6 +40,8 @@ WL_SEAT_CAPABILITY_POINTER |\ WL_SEAT_CAPABILITY_TOUCH) +char *server_parameters = "--shell=weston-test-desktop-shell.so"; + /* simply test if weston sends the right capabilities when * some devices are removed */ TEST(seat_capabilities_test) From 8b964bca50d8740f6864d9e24b27c1afdca65650 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 8 Feb 2018 15:37:58 +0200 Subject: [PATCH 0343/1642] tests: Add test for seat destruction and creation Add a test to check that we can destroy and create the test seat. Since after test seat destruction the test client releases any associated input resources, this test also checks that libweston properly handles release requests for inert input resources. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- tests/devices-test.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/devices-test.c b/tests/devices-test.c index a6ec6eaf2..147a27913 100644 --- a/tests/devices-test.c +++ b/tests/devices-test.c @@ -322,3 +322,25 @@ TEST(seats_have_names) assert(input->seat_name); } } + +TEST(seat_destroy_and_recreate) +{ + struct client *cl = create_client_and_test_surface(100, 100, 100, 100); + + weston_test_device_release(cl->test->weston_test, "seat"); + /* Roundtrip to receive and handle the seat global removal event */ + client_roundtrip(cl); + + assert(!cl->input); + + weston_test_device_add(cl->test->weston_test, "seat"); + /* First roundtrip to send request and receive new seat global */ + client_roundtrip(cl); + /* Second roundtrip to handle seat events and set up input devices */ + client_roundtrip(cl); + + assert(cl->input); + assert(cl->input->pointer); + assert(cl->input->keyboard); + assert(cl->input->touch); +} From 2d8331c4b7f5ae865b0fefc14a4ada1a2b7b778c Mon Sep 17 00:00:00 2001 From: Emil Velikov Date: Thu, 15 Feb 2018 18:51:50 +0000 Subject: [PATCH 0344/1642] gl-renderer: make use of linux_dmabuf_buffer_get_user_data() ... to get the user_data. Like everywhere else through weston. Signed-off-by: Emil Velikov Reviewed-by: Pekka Paalanen --- libweston/gl-renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index d091d1652..d3ed4a108 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -1748,7 +1748,7 @@ gl_renderer_attach_egl(struct weston_surface *es, struct weston_buffer *buffer, static void gl_renderer_destroy_dmabuf(struct linux_dmabuf_buffer *dmabuf) { - struct dmabuf_image *image = dmabuf->user_data; + struct dmabuf_image *image = linux_dmabuf_buffer_get_user_data(dmabuf); dmabuf_image_destroy(image); } From 7a93bb2f17137d1f97e66904cea9ea5e5f408d2a Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 16 Feb 2018 18:44:14 +0200 Subject: [PATCH 0345/1642] shared: Add timespec_eq helper function Add a helper function to check if two struct timespec values are equal. This helper function will be used in upcoming commits that implement the input_timestamps_unstable_v1 protocol. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- shared/timespec-util.h | 13 +++++++++++++ tests/timespec-test.c | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/shared/timespec-util.h b/shared/timespec-util.h index 5f4b2b9e0..ca0156afc 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -231,6 +231,19 @@ timespec_is_zero(const struct timespec *a) return a->tv_sec == 0 && a->tv_nsec == 0; } +/* Check if two timespecs are equal + * + * \param a[in] timespec to check + * \param b[in] timespec to check + * \return whether timespecs a and b are equal + */ +static inline bool +timespec_eq(const struct timespec *a, const struct timespec *b) +{ + return a->tv_sec == b->tv_sec && + a->tv_nsec == b->tv_nsec; +} + /* Convert milli-Hertz to nanoseconds * * \param mhz frequency in mHz, not zero diff --git a/tests/timespec-test.c b/tests/timespec-test.c index 54230f89a..244001872 100644 --- a/tests/timespec-test.c +++ b/tests/timespec-test.c @@ -294,3 +294,15 @@ ZUC_TEST(timespec_test, timespec_is_zero) ZUC_ASSERT_FALSE(timespec_is_zero(&non_zero_nsec)); ZUC_ASSERT_FALSE(timespec_is_zero(&non_zero_sec)); } + +ZUC_TEST(timespec_test, timespec_eq) +{ + struct timespec a = { .tv_sec = 2, .tv_nsec = 1 }; + struct timespec b = { .tv_sec = -1, .tv_nsec = 2 }; + + ZUC_ASSERT_TRUE(timespec_eq(&a, &a)); + ZUC_ASSERT_TRUE(timespec_eq(&b, &b)); + + ZUC_ASSERT_FALSE(timespec_eq(&a, &b)); + ZUC_ASSERT_FALSE(timespec_eq(&b, &a)); +} From c3b5d78c1dcc500fca8ff6d6c0f30cdaef277ec5 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 16 Feb 2018 18:44:15 +0200 Subject: [PATCH 0346/1642] tests: Introduce input timestamps helper Introduce helper test code to implement the client side of the input_timestamps_unstable_v1 protocol. This helper will be used in upcoming commits to test the server side implementation of the protocol in libweston. The input_timestamps_unstable_v1 protocol was introduced in version 1.13 of wayland-protocols, so this commit updates the version dependency in configure.ac accordingly. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- Makefile.am | 16 ++- configure.ac | 2 +- tests/input-timestamps-helper.c | 177 ++++++++++++++++++++++++++++++ tests/input-timestamps-helper.h | 46 ++++++++ tests/weston-test-client-helper.c | 16 +++ tests/weston-test-client-helper.h | 12 ++ 6 files changed, 263 insertions(+), 6 deletions(-) create mode 100644 tests/input-timestamps-helper.c create mode 100644 tests/input-timestamps-helper.h diff --git a/Makefile.am b/Makefile.am index b5c29c044..679e6b782 100644 --- a/Makefile.am +++ b/Makefile.am @@ -879,7 +879,9 @@ BUILT_SOURCES += \ protocol/ivi-application-protocol.c \ protocol/ivi-application-client-protocol.h \ protocol/linux-dmabuf-unstable-v1-protocol.c \ - protocol/linux-dmabuf-unstable-v1-client-protocol.h + protocol/linux-dmabuf-unstable-v1-client-protocol.h \ + protocol/input-timestamps-unstable-v1-protocol.c \ + protocol/input-timestamps-unstable-v1-client-protocol.h westondatadir = $(datadir)/weston dist_westondata_DATA = \ @@ -1335,10 +1337,14 @@ vertex_clip_test_LDADD = libtest-runner.la -lm $(CLOCK_GETTIME_LIBS) libtest_client_la_SOURCES = \ tests/weston-test-client-helper.c \ - tests/weston-test-client-helper.h -nodist_libtest_client_la_SOURCES = \ - protocol/weston-test-protocol.c \ - protocol/weston-test-client-protocol.h + tests/weston-test-client-helper.h \ + tests/input-timestamps-helper.c \ + tests/input-timestamps-helper.h +nodist_libtest_client_la_SOURCES = \ + protocol/weston-test-protocol.c \ + protocol/weston-test-client-protocol.h \ + protocol/input-timestamps-unstable-v1-protocol.c \ + protocol/input-timestamps-unstable-v1-client-protocol.h libtest_client_la_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) $(CAIRO_CFLAGS) libtest_client_la_LIBADD = libshared.la libtest-runner.la $(TEST_CLIENT_LIBS) $(CAIRO_LIBS) diff --git a/configure.ac b/configure.ac index dd344d6af..033b94844 100644 --- a/configure.ac +++ b/configure.ac @@ -218,7 +218,7 @@ fi PKG_CHECK_MODULES(LIBINPUT_BACKEND, [libinput >= 0.8.0]) PKG_CHECK_MODULES(COMPOSITOR, [$COMPOSITOR_MODULES]) -PKG_CHECK_MODULES(WAYLAND_PROTOCOLS, [wayland-protocols >= 1.8], +PKG_CHECK_MODULES(WAYLAND_PROTOCOLS, [wayland-protocols >= 1.13], [ac_wayland_protocols_pkgdatadir=`$PKG_CONFIG --variable=pkgdatadir wayland-protocols`]) AC_SUBST(WAYLAND_PROTOCOLS_DATADIR, $ac_wayland_protocols_pkgdatadir) diff --git a/tests/input-timestamps-helper.c b/tests/input-timestamps-helper.c new file mode 100644 index 000000000..9e90fc07e --- /dev/null +++ b/tests/input-timestamps-helper.c @@ -0,0 +1,177 @@ +/* + * Copyright © 2017 Collabora, Ltd. + * + * 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 "input-timestamps-helper.h" +#include "protocol/input-timestamps-unstable-v1-client-protocol.h" +#include "shared/timespec-util.h" +#include "shared/zalloc.h" +#include "weston-test-client-helper.h" + +struct input_timestamps { + struct zwp_input_timestamps_v1 *proxy; +}; + +static struct zwp_input_timestamps_manager_v1 * +get_input_timestamps_manager(struct client *client) +{ + struct global *g; + struct global *global_ts = NULL; + struct zwp_input_timestamps_manager_v1 *ts = NULL; + + wl_list_for_each(g, &client->global_list, link) { + if (strcmp(g->interface, zwp_input_timestamps_manager_v1_interface.name)) + continue; + + if (global_ts) + assert(!"Multiple input timestamp managers"); + + global_ts = g; + } + + assert(global_ts); + assert(global_ts->version == 1); + + ts = wl_registry_bind(client->wl_registry, global_ts->name, + &zwp_input_timestamps_manager_v1_interface, 1); + assert(ts); + + return ts; +} + +static void +input_timestamp(void *data, + struct zwp_input_timestamps_v1 *zwp_input_timestamps_v1, + uint32_t tv_sec_hi, + uint32_t tv_sec_lo, + uint32_t tv_nsec) +{ + struct timespec *timestamp = data; + + timespec_from_proto(timestamp, tv_sec_hi, tv_sec_lo, + tv_nsec); + + fprintf(stderr, "test-client: got input timestamp %ld.%ld\n", + timestamp->tv_sec, timestamp->tv_nsec); +} + +static const struct zwp_input_timestamps_v1_listener +input_timestamps_listener = { + .timestamp = input_timestamp, +}; + +struct input_timestamps * +input_timestamps_create_for_keyboard(struct client *client) +{ + struct zwp_input_timestamps_manager_v1 *manager = + get_input_timestamps_manager(client); + struct timespec *timestamp= &client->input->keyboard->input_timestamp; + struct input_timestamps *input_ts; + + input_ts = zalloc(sizeof *input_ts); + assert(input_ts); + + input_ts->proxy = + zwp_input_timestamps_manager_v1_get_keyboard_timestamps( + manager, client->input->keyboard->wl_keyboard); + assert(input_ts->proxy); + + zwp_input_timestamps_v1_add_listener(input_ts->proxy, + &input_timestamps_listener, + timestamp); + + zwp_input_timestamps_manager_v1_destroy(manager); + + client_roundtrip(client); + + return input_ts; +} + +struct input_timestamps * +input_timestamps_create_for_pointer(struct client *client) +{ + struct zwp_input_timestamps_manager_v1 *manager = + get_input_timestamps_manager(client); + struct timespec *timestamp= &client->input->pointer->input_timestamp; + struct input_timestamps *input_ts; + + input_ts = zalloc(sizeof *input_ts); + assert(input_ts); + + input_ts->proxy = + zwp_input_timestamps_manager_v1_get_pointer_timestamps( + manager, client->input->pointer->wl_pointer); + assert(input_ts->proxy); + + zwp_input_timestamps_v1_add_listener(input_ts->proxy, + &input_timestamps_listener, + timestamp); + + zwp_input_timestamps_manager_v1_destroy(manager); + + client_roundtrip(client); + + return input_ts; +} + +struct input_timestamps * +input_timestamps_create_for_touch(struct client *client) +{ + struct zwp_input_timestamps_manager_v1 *manager = + get_input_timestamps_manager(client); + struct timespec *timestamp= &client->input->touch->input_timestamp; + struct input_timestamps *input_ts; + + input_ts = zalloc(sizeof *input_ts); + assert(input_ts); + + input_ts->proxy = + zwp_input_timestamps_manager_v1_get_touch_timestamps( + manager, client->input->touch->wl_touch); + assert(input_ts->proxy); + + zwp_input_timestamps_v1_add_listener(input_ts->proxy, + &input_timestamps_listener, + timestamp); + + zwp_input_timestamps_manager_v1_destroy(manager); + + client_roundtrip(client); + + return input_ts; +} + +void +input_timestamps_destroy(struct input_timestamps *input_ts) +{ + zwp_input_timestamps_v1_destroy(input_ts->proxy); + free(input_ts); +} diff --git a/tests/input-timestamps-helper.h b/tests/input-timestamps-helper.h new file mode 100644 index 000000000..5301df0ee --- /dev/null +++ b/tests/input-timestamps-helper.h @@ -0,0 +1,46 @@ +/* + * Copyright © 2017 Collabora, Ltd. + * + * 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. + */ + +#ifndef INPUT_TIMESTAMPS_HELPER_H +#define INPUT_TIMESTAMPS_HELPER_H + +#include "config.h" + +struct client; +struct input_timestamps; + +struct input_timestamps * +input_timestamps_create_for_keyboard(struct client *client); + +struct input_timestamps * +input_timestamps_create_for_pointer(struct client *client); + +struct input_timestamps * +input_timestamps_create_for_touch(struct client *client); + +void +input_timestamps_destroy(struct input_timestamps *input_ts); + +#endif /* INPUT_TIMESTAMPS_HELPER_H */ diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index dc69e1511..ea202c0c7 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -155,6 +155,8 @@ pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, pointer->x = wl_fixed_to_int(x); pointer->y = wl_fixed_to_int(y); pointer->motion_time_msec = time_msec; + pointer->motion_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got pointer motion %d %d\n", pointer->x, pointer->y); @@ -170,6 +172,8 @@ pointer_handle_button(void *data, struct wl_pointer *wl_pointer, pointer->button = button; pointer->state = state; pointer->button_time_msec = time_msec; + pointer->button_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got pointer button %u %u\n", button, state); @@ -184,6 +188,8 @@ pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, pointer->axis = axis; pointer->axis_value = wl_fixed_to_double(value); pointer->axis_time_msec = time_msec; + pointer->axis_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got pointer axis %u %f\n", axis, wl_fixed_to_double(value)); @@ -210,6 +216,8 @@ pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, pointer->axis = axis; pointer->axis_stop_time_msec = time_msec; + pointer->axis_stop_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got pointer axis stop %u\n", axis); } @@ -281,6 +289,8 @@ keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, keyboard->key = key; keyboard->state = state; keyboard->key_time_msec = time_msec; + keyboard->key_time_timespec = keyboard->input_timestamp; + keyboard->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got keyboard key %u %u\n", key, state); } @@ -336,6 +346,8 @@ touch_handle_down(void *data, struct wl_touch *wl_touch, touch->down_y = wl_fixed_to_int(y_w); touch->id = id; touch->down_time_msec = time_msec; + touch->down_time_timespec = touch->input_timestamp; + touch->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got touch down %d %d, surf: %p, id: %d\n", touch->down_x, touch->down_y, surface, id); @@ -348,6 +360,8 @@ touch_handle_up(void *data, struct wl_touch *wl_touch, struct touch *touch = data; touch->up_id = id; touch->up_time_msec = time_msec; + touch->up_time_timespec = touch->input_timestamp; + touch->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got touch up, id: %d\n", id); } @@ -361,6 +375,8 @@ touch_handle_motion(void *data, struct wl_touch *wl_touch, touch->x = wl_fixed_to_int(x_w); touch->y = wl_fixed_to_int(y_w); touch->motion_time_msec = time_msec; + touch->motion_time_timespec = touch->input_timestamp; + touch->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got touch motion, %d %d, id: %d\n", touch->x, touch->y, id); diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 255bbf660..52e8a5608 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -98,6 +99,11 @@ struct pointer { uint32_t button_time_msec; uint32_t axis_time_msec; uint32_t axis_stop_time_msec; + struct timespec input_timestamp; + struct timespec motion_time_timespec; + struct timespec button_time_timespec; + struct timespec axis_time_timespec; + struct timespec axis_stop_time_timespec; }; struct keyboard { @@ -114,6 +120,8 @@ struct keyboard { int delay; } repeat_info; uint32_t key_time_msec; + struct timespec input_timestamp; + struct timespec key_time_timespec; }; struct touch { @@ -129,6 +137,10 @@ struct touch { uint32_t down_time_msec; uint32_t up_time_msec; uint32_t motion_time_msec; + struct timespec input_timestamp; + struct timespec down_time_timespec; + struct timespec up_time_timespec; + struct timespec motion_time_timespec; }; struct output { From 538749de7b7a3229d798a99bc4a78d1d8bc17bdf Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 16 Feb 2018 18:44:16 +0200 Subject: [PATCH 0347/1642] libweston: Introduce input-timestamps support Introduce code to support the implementation of the input_timestamps_unstable_v1 protocol in libweston. This commit does not implement the actual timestamp subscriptions, but sets up the zwp_input_timestamps_manager_v1 object and introduces dummy request handling functions for it, laying the foundation for timestamp subscriptions for keyboard/pointer/touch to be added cleanly in upcoming commits. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- Makefile.am | 4 ++- libweston/input.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 679e6b782..e028a2a13 100644 --- a/Makefile.am +++ b/Makefile.am @@ -166,7 +166,9 @@ nodist_libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ protocol/relative-pointer-unstable-v1-protocol.c \ protocol/relative-pointer-unstable-v1-server-protocol.h \ protocol/pointer-constraints-unstable-v1-protocol.c \ - protocol/pointer-constraints-unstable-v1-server-protocol.h + protocol/pointer-constraints-unstable-v1-server-protocol.h \ + protocol/input-timestamps-unstable-v1-protocol.c \ + protocol/input-timestamps-unstable-v1-server-protocol.h BUILT_SOURCES += $(nodist_libweston_@LIBWESTON_MAJOR@_la_SOURCES) diff --git a/libweston/input.c b/libweston/input.c index da0025481..2e8bd088e 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -42,6 +42,7 @@ #include "compositor.h" #include "relative-pointer-unstable-v1-server-protocol.h" #include "pointer-constraints-unstable-v1-server-protocol.h" +#include "input-timestamps-unstable-v1-server-protocol.h" enum pointer_constraint_type { POINTER_CONSTRAINT_TYPE_LOCK, @@ -4569,6 +4570,67 @@ bind_pointer_constraints(struct wl_client *client, void *data, NULL, NULL); } +static void +input_timestamps_manager_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +input_timestamps_manager_get_keyboard_timestamps(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *keyboard_resource) +{ + wl_client_post_no_memory(client); +} + +static void +input_timestamps_manager_get_pointer_timestamps(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *pointer_resource) +{ + wl_client_post_no_memory(client); +} + +static void +input_timestamps_manager_get_touch_timestamps(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *touch_resource) +{ + wl_client_post_no_memory(client); +} + +static const struct zwp_input_timestamps_manager_v1_interface + input_timestamps_manager_interface = { + input_timestamps_manager_destroy, + input_timestamps_manager_get_keyboard_timestamps, + input_timestamps_manager_get_pointer_timestamps, + input_timestamps_manager_get_touch_timestamps, +}; + +static void +bind_input_timestamps_manager(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct wl_resource *resource = + wl_resource_create(client, + &zwp_input_timestamps_manager_v1_interface, + 1, id); + + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, + &input_timestamps_manager_interface, + NULL, NULL); +} + int weston_input_init(struct weston_compositor *compositor) { @@ -4582,5 +4644,10 @@ weston_input_init(struct weston_compositor *compositor) NULL, bind_pointer_constraints)) return -1; + if (!wl_global_create(compositor->wl_display, + &zwp_input_timestamps_manager_v1_interface, 1, + NULL, bind_input_timestamps_manager)) + return -1; + return 0; } From 2b44248f606727813995561eeeb3d1a69dfd0715 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Tue, 20 Feb 2018 14:05:50 +0200 Subject: [PATCH 0348/1642] libweston: Implement keyboard timestamps for input_timestamps_unstable_v1 Implement the zwp_input_timestamps_manager_v1.get_keyboard_timestamps request to subscribe to timestamp events for wl_keyboard resources. Ensure that the request handling code can gracefully handle inert keyboard resources. This commit introduces a few internal helper functions which will also be useful in the implementation of the remaining zwp_input_timestamps_manager_v1 requests. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- libweston/compositor.h | 2 + libweston/input.c | 95 ++++++++++++++++++++++++++++++++++++++++-- tests/keyboard-test.c | 51 +++++++++++++++++++++++ 3 files changed, 145 insertions(+), 3 deletions(-) diff --git a/libweston/compositor.h b/libweston/compositor.h index ca1acc605..1566677fb 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -605,6 +605,8 @@ struct weston_keyboard { enum weston_led leds; } xkb_state; struct xkb_keymap *pending_keymap; + + struct wl_list timestamps_list; }; struct weston_seat { diff --git a/libweston/input.c b/libweston/input.c index 2e8bd088e..8028ec20f 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -87,6 +87,42 @@ region_init_infinite(pixman_region32_t *region) UINT32_MAX, UINT32_MAX); } +static void +send_timestamp(struct wl_resource *resource, + const struct timespec *time) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + zwp_input_timestamps_v1_send_timestamp(resource, tv_sec_hi, tv_sec_lo, + tv_nsec); +} + +static void +send_timestamps_for_input_resource(struct wl_resource *input_resource, + struct wl_list *list, + const struct timespec *time) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, list) { + if (wl_resource_get_user_data(resource) == input_resource) + send_timestamp(resource, time); + } +} + +static void +remove_input_resource_from_timestamps(struct wl_resource *input_resource, + struct wl_list *list) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, list) { + if (wl_resource_get_user_data(resource) == input_resource) + wl_resource_set_user_data(resource, NULL); + } +} + static struct weston_pointer_client * weston_pointer_client_create(struct wl_client *client) { @@ -884,8 +920,12 @@ weston_keyboard_send_key(struct weston_keyboard *keyboard, resource_list = &keyboard->focus_resource_list; serial = wl_display_next_serial(display); msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) + wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &keyboard->timestamps_list, + time); wl_keyboard_send_key(resource, serial, msecs, key, state); + } }; static void @@ -1157,6 +1197,7 @@ weston_keyboard_create(void) keyboard->default_grab.keyboard = keyboard; keyboard->grab = &keyboard->default_grab; wl_signal_init(&keyboard->focus_signal); + wl_list_init(&keyboard->timestamps_list); return keyboard; } @@ -1187,6 +1228,7 @@ weston_keyboard_destroy(struct weston_keyboard *keyboard) wl_array_release(&keyboard->keys); wl_list_remove(&keyboard->focus_resource_listener.link); + wl_list_remove(&keyboard->timestamps_list); free(keyboard); } @@ -2467,6 +2509,19 @@ seat_get_pointer(struct wl_client *client, struct wl_resource *resource, } } +static void +destroy_keyboard_resource(struct wl_resource *resource) +{ + struct weston_keyboard *keyboard = wl_resource_get_user_data(resource); + + wl_list_remove(wl_resource_get_link(resource)); + + if (keyboard) { + remove_input_resource_from_timestamps(resource, + &keyboard->timestamps_list); + } +} + static void keyboard_release(struct wl_client *client, struct wl_resource *resource) { @@ -2524,7 +2579,7 @@ seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, wl_list_init(wl_resource_get_link(cr)); wl_resource_set_implementation(cr, &keyboard_interface, - keyboard, unbind_resource); + keyboard, destroy_keyboard_resource); /* If we don't have a keyboard_state, the resource is inert, so there * is nothing more to set up */ @@ -4570,6 +4625,18 @@ bind_pointer_constraints(struct wl_client *client, void *data, NULL, NULL); } +static void +input_timestamps_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct zwp_input_timestamps_v1_interface + input_timestamps_interface = { + input_timestamps_destroy, +}; + static void input_timestamps_manager_destroy(struct wl_client *client, struct wl_resource *resource) @@ -4583,7 +4650,29 @@ input_timestamps_manager_get_keyboard_timestamps(struct wl_client *client, uint32_t id, struct wl_resource *keyboard_resource) { - wl_client_post_no_memory(client); + struct weston_keyboard *keyboard = + wl_resource_get_user_data(keyboard_resource); + struct wl_resource *input_ts; + + input_ts = wl_resource_create(client, + &zwp_input_timestamps_v1_interface, + 1, id); + if (!input_ts) { + wl_client_post_no_memory(client); + return; + } + + if (keyboard) { + wl_list_insert(&keyboard->timestamps_list, + wl_resource_get_link(input_ts)); + } else { + wl_list_init(wl_resource_get_link(input_ts)); + } + + wl_resource_set_implementation(input_ts, + &input_timestamps_interface, + keyboard_resource, + unbind_resource); } static void diff --git a/tests/keyboard-test.c b/tests/keyboard-test.c index 722bfd327..37ef83d07 100644 --- a/tests/keyboard-test.c +++ b/tests/keyboard-test.c @@ -27,11 +27,13 @@ #include +#include "input-timestamps-helper.h" #include "shared/timespec-util.h" #include "weston-test-client-helper.h" static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; +static const struct timespec t_other = { .tv_sec = 123, .tv_nsec = 456 }; static struct client * create_client_with_keyboard_focus(void) @@ -97,10 +99,59 @@ TEST(keyboard_key_event_time) { struct client *client = create_client_with_keyboard_focus(); struct keyboard *keyboard = client->input->keyboard; + struct input_timestamps *input_ts = + input_timestamps_create_for_keyboard(client); send_key(client, &t1, 1, WL_KEYBOARD_KEY_STATE_PRESSED); assert(keyboard->key_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&keyboard->key_time_timespec, &t1)); send_key(client, &t2, 1, WL_KEYBOARD_KEY_STATE_RELEASED); assert(keyboard->key_time_msec == timespec_to_msec(&t2)); + assert(timespec_eq(&keyboard->key_time_timespec, &t2)); + + input_timestamps_destroy(input_ts); +} + +TEST(keyboard_timestamps_stop_after_input_timestamps_object_is_destroyed) +{ + struct client *client = create_client_with_keyboard_focus(); + struct keyboard *keyboard = client->input->keyboard; + struct input_timestamps *input_ts = + input_timestamps_create_for_keyboard(client); + + send_key(client, &t1, 1, WL_KEYBOARD_KEY_STATE_PRESSED); + assert(keyboard->key_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&keyboard->key_time_timespec, &t1)); + + input_timestamps_destroy(input_ts); + + send_key(client, &t2, 1, WL_KEYBOARD_KEY_STATE_RELEASED); + assert(keyboard->key_time_msec == timespec_to_msec(&t2)); + assert(timespec_is_zero(&keyboard->key_time_timespec)); +} + +TEST(keyboard_timestamps_stop_after_client_releases_wl_keyboard) +{ + struct client *client = create_client_with_keyboard_focus(); + struct keyboard *keyboard = client->input->keyboard; + struct input_timestamps *input_ts = + input_timestamps_create_for_keyboard(client); + + send_key(client, &t1, 1, WL_KEYBOARD_KEY_STATE_PRESSED); + assert(keyboard->key_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&keyboard->key_time_timespec, &t1)); + + wl_keyboard_release(client->input->keyboard->wl_keyboard); + + /* Set input_timestamp to an arbitrary value (different from t1, t2 + * and 0) and check that it is not changed by sending the event. + * This is preferred over just checking for 0, since 0 is used + * internally for resetting the timestamp after handling an input + * event and checking for it here may lead to false negatives. */ + keyboard->input_timestamp = t_other; + send_key(client, &t2, 1, WL_KEYBOARD_KEY_STATE_RELEASED); + assert(timespec_eq(&keyboard->input_timestamp, &t_other)); + + input_timestamps_destroy(input_ts); } From db907b7188198a759ee052334386aba712de4c1a Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Tue, 20 Feb 2018 14:06:26 +0200 Subject: [PATCH 0349/1642] libweston: Implement pointer timestamps for input_timestamps_unstable_v1 Implement the zwp_input_timestamps_manager_v1.get_pointer_timestamps request to subscribe to timestamp events for wl_pointer resources. Ensure that the request handling code can gracefully handle inert pointer resources. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- libweston/compositor.h | 2 ++ libweston/input.c | 53 ++++++++++++++++++++++++++++---- tests/pointer-test.c | 70 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 6 deletions(-) diff --git a/libweston/compositor.h b/libweston/compositor.h index 1566677fb..78d2668ee 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -391,6 +391,8 @@ struct weston_pointer { uint32_t button_count; struct wl_listener output_destroy_listener; + + struct wl_list timestamps_list; }; diff --git a/libweston/input.c b/libweston/input.c index 8028ec20f..632c9c3c1 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -226,6 +226,8 @@ unbind_pointer_client_resource(struct wl_resource *resource) pointer_client = weston_pointer_get_pointer_client(pointer, client); assert(pointer_client); + remove_input_resource_from_timestamps(resource, + &pointer->timestamps_list); weston_pointer_cleanup_pointer_client(pointer, pointer_client); } } @@ -446,8 +448,12 @@ pointer_send_motion(struct weston_pointer *pointer, resource_list = &pointer->focus_client->pointer_resources; msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) + wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &pointer->timestamps_list, + time); wl_pointer_send_motion(resource, msecs, sx, sy); + } } WL_EXPORT void @@ -528,8 +534,12 @@ weston_pointer_send_button(struct weston_pointer *pointer, resource_list = &pointer->focus_client->pointer_resources; serial = wl_display_next_serial(display); msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) + wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &pointer->timestamps_list, + time); wl_pointer_send_button(resource, serial, msecs, button, state); + } } static void @@ -586,14 +596,21 @@ weston_pointer_send_axis(struct weston_pointer *pointer, wl_pointer_send_axis_discrete(resource, event->axis, event->discrete); - if (event->value) + if (event->value) { + send_timestamps_for_input_resource(resource, + &pointer->timestamps_list, + time); wl_pointer_send_axis(resource, msecs, event->axis, wl_fixed_from_double(event->value)); - else if (wl_resource_get_version(resource) >= - WL_POINTER_AXIS_STOP_SINCE_VERSION) + } else if (wl_resource_get_version(resource) >= + WL_POINTER_AXIS_STOP_SINCE_VERSION) { + send_timestamps_for_input_resource(resource, + &pointer->timestamps_list, + time); wl_pointer_send_axis_stop(resource, msecs, event->axis); + } } } @@ -1128,6 +1145,7 @@ weston_pointer_create(struct weston_seat *seat) wl_signal_init(&pointer->focus_signal); wl_list_init(&pointer->focus_view_listener.link); wl_signal_init(&pointer->destroy_signal); + wl_list_init(&pointer->timestamps_list); pointer->sprite_destroy_listener.notify = pointer_handle_sprite_destroy; @@ -1165,6 +1183,7 @@ weston_pointer_destroy(struct weston_pointer *pointer) wl_list_remove(&pointer->focus_resource_listener.link); wl_list_remove(&pointer->focus_view_listener.link); wl_list_remove(&pointer->output_destroy_listener.link); + wl_list_remove(&pointer->timestamps_list); free(pointer); } @@ -4681,7 +4700,29 @@ input_timestamps_manager_get_pointer_timestamps(struct wl_client *client, uint32_t id, struct wl_resource *pointer_resource) { - wl_client_post_no_memory(client); + struct weston_pointer *pointer = + wl_resource_get_user_data(pointer_resource); + struct wl_resource *input_ts; + + input_ts = wl_resource_create(client, + &zwp_input_timestamps_v1_interface, + 1, id); + if (!input_ts) { + wl_client_post_no_memory(client); + return; + } + + if (pointer) { + wl_list_insert(&pointer->timestamps_list, + wl_resource_get_link(input_ts)); + } else { + wl_list_init(wl_resource_get_link(input_ts)); + } + + wl_resource_set_implementation(input_ts, + &input_timestamps_interface, + pointer_resource, + unbind_resource); } static void diff --git a/tests/pointer-test.c b/tests/pointer-test.c index 4c438a227..eef522288 100644 --- a/tests/pointer-test.c +++ b/tests/pointer-test.c @@ -28,12 +28,14 @@ #include +#include "input-timestamps-helper.h" #include "shared/timespec-util.h" #include "weston-test-client-helper.h" static const struct timespec t0 = { .tv_sec = 0, .tv_nsec = 100000000 }; static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; +static const struct timespec t_other = { .tv_sec = 123, .tv_nsec = 456 }; static void send_motion(struct client *client, const struct timespec *time, int x, int y) @@ -341,11 +343,16 @@ TEST(pointer_motion_events) struct client *client = create_client_with_pointer_focus(100, 100, 100, 100); struct pointer *pointer = client->input->pointer; + struct input_timestamps *input_ts = + input_timestamps_create_for_pointer(client); send_motion(client, &t1, 150, 150); assert(pointer->x == 50); assert(pointer->y == 50); assert(pointer->motion_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&pointer->motion_time_timespec, &t1)); + + input_timestamps_destroy(input_ts); } TEST(pointer_button_events) @@ -353,6 +360,8 @@ TEST(pointer_button_events) struct client *client = create_client_with_pointer_focus(100, 100, 100, 100); struct pointer *pointer = client->input->pointer; + struct input_timestamps *input_ts = + input_timestamps_create_for_pointer(client); assert(pointer->button == 0); assert(pointer->state == 0); @@ -361,11 +370,15 @@ TEST(pointer_button_events) assert(pointer->button == BTN_LEFT); assert(pointer->state == WL_POINTER_BUTTON_STATE_PRESSED); assert(pointer->button_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&pointer->button_time_timespec, &t1)); send_button(client, &t2, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); assert(pointer->button == BTN_LEFT); assert(pointer->state == WL_POINTER_BUTTON_STATE_RELEASED); assert(pointer->button_time_msec == timespec_to_msec(&t2)); + assert(timespec_eq(&pointer->button_time_timespec, &t2)); + + input_timestamps_destroy(input_ts); } TEST(pointer_axis_events) @@ -373,13 +386,70 @@ TEST(pointer_axis_events) struct client *client = create_client_with_pointer_focus(100, 100, 100, 100); struct pointer *pointer = client->input->pointer; + struct input_timestamps *input_ts = + input_timestamps_create_for_pointer(client); send_axis(client, &t1, 1, 1.0); assert(pointer->axis == 1); assert(pointer->axis_value == 1.0); assert(pointer->axis_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&pointer->axis_time_timespec, &t1)); send_axis(client, &t2, 2, 0.0); assert(pointer->axis == 2); assert(pointer->axis_stop_time_msec == timespec_to_msec(&t2)); + assert(timespec_eq(&pointer->axis_stop_time_timespec, &t2)); + + input_timestamps_destroy(input_ts); +} + +TEST(pointer_timestamps_stop_after_input_timestamps_object_is_destroyed) +{ + struct client *client = create_client_with_pointer_focus(100, 100, + 100, 100); + struct pointer *pointer = client->input->pointer; + struct input_timestamps *input_ts = + input_timestamps_create_for_pointer(client); + + send_button(client, &t1, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); + assert(pointer->button == BTN_LEFT); + assert(pointer->state == WL_POINTER_BUTTON_STATE_PRESSED); + assert(pointer->button_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&pointer->button_time_timespec, &t1)); + + input_timestamps_destroy(input_ts); + + send_button(client, &t2, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); + assert(pointer->button == BTN_LEFT); + assert(pointer->state == WL_POINTER_BUTTON_STATE_RELEASED); + assert(pointer->button_time_msec == timespec_to_msec(&t2)); + assert(timespec_is_zero(&pointer->button_time_timespec)); +} + +TEST(pointer_timestamps_stop_after_client_releases_wl_pointer) +{ + struct client *client = create_client_with_pointer_focus(100, 100, + 100, 100); + struct pointer *pointer = client->input->pointer; + struct input_timestamps *input_ts = + input_timestamps_create_for_pointer(client); + + send_motion(client, &t1, 150, 150); + assert(pointer->x == 50); + assert(pointer->y == 50); + assert(pointer->motion_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&pointer->motion_time_timespec, &t1)); + + wl_pointer_release(client->input->pointer->wl_pointer); + + /* Set input_timestamp to an arbitrary value (different from t1, t2 + * and 0) and check that it is not changed by sending the event. + * This is preferred over just checking for 0, since 0 is used + * internally for resetting the timestamp after handling an input + * event and checking for it here may lead to false negatives. */ + pointer->input_timestamp = t_other; + send_motion(client, &t2, 175, 175); + assert(timespec_eq(&pointer->input_timestamp, &t_other)); + + input_timestamps_destroy(input_ts); } From d7157847343a6a37b2cd77be2e8e3ed22c5aee92 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Tue, 20 Feb 2018 14:07:03 +0200 Subject: [PATCH 0350/1642] libweston: Implement touch timestamps for input_timestamps_unstable_v1 Implement the zwp_input_timestamps_manager_v1.get_touch_timestamps request to subscribe to timestamp events for wl_touch resources. Ensure that the request handling code can gracefully handle inert touch resources. Signed-off-by: Alexandros Frantzis Reviewed-by: Pekka Paalanen --- libweston/compositor.h | 2 ++ libweston/input.c | 61 +++++++++++++++++++++++++++++++++++++----- tests/touch-test.c | 52 +++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 7 deletions(-) diff --git a/libweston/compositor.h b/libweston/compositor.h index 78d2668ee..010f1fa8b 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -415,6 +415,8 @@ struct weston_touch { wl_fixed_t grab_x, grab_y; uint32_t grab_serial; struct timespec grab_time; + + struct wl_list timestamps_list; }; void diff --git a/libweston/input.c b/libweston/input.c index 632c9c3c1..3f616941e 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -758,10 +758,14 @@ weston_touch_send_down(struct weston_touch *touch, const struct timespec *time, resource_list = &touch->focus_resource_list; serial = wl_display_next_serial(display); msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) - wl_touch_send_down(resource, serial, msecs, - touch->focus->surface->resource, - touch_id, sx, sy); + wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &touch->timestamps_list, + time); + wl_touch_send_down(resource, serial, msecs, + touch->focus->surface->resource, + touch_id, sx, sy); + } } static void @@ -798,8 +802,12 @@ weston_touch_send_up(struct weston_touch *touch, const struct timespec *time, resource_list = &touch->focus_resource_list; serial = wl_display_next_serial(display); msecs = timespec_to_msec(time); - wl_resource_for_each(resource, resource_list) + wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &touch->timestamps_list, + time); wl_touch_send_up(resource, serial, msecs, touch_id); + } } static void @@ -839,6 +847,9 @@ weston_touch_send_motion(struct weston_touch *touch, resource_list = &touch->focus_resource_list; msecs = timespec_to_msec(time); wl_resource_for_each(resource, resource_list) { + send_timestamps_for_input_resource(resource, + &touch->timestamps_list, + time); wl_touch_send_motion(resource, msecs, touch_id, sx, sy); } @@ -1276,6 +1287,7 @@ weston_touch_create(void) touch->default_grab.touch = touch; touch->grab = &touch->default_grab; wl_signal_init(&touch->focus_signal); + wl_list_init(&touch->timestamps_list); return touch; } @@ -1297,6 +1309,7 @@ weston_touch_destroy(struct weston_touch *touch) wl_list_remove(&touch->focus_resource_list); wl_list_remove(&touch->focus_view_listener.link); wl_list_remove(&touch->focus_resource_listener.link); + wl_list_remove(&touch->timestamps_list); free(touch); } @@ -2647,6 +2660,19 @@ seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, } } +static void +destroy_touch_resource(struct wl_resource *resource) +{ + struct weston_touch *touch = wl_resource_get_user_data(resource); + + wl_list_remove(wl_resource_get_link(resource)); + + if (touch) { + remove_input_resource_from_timestamps(resource, + &touch->timestamps_list); + } +} + static void touch_release(struct wl_client *client, struct wl_resource *resource) { @@ -2682,7 +2708,7 @@ seat_get_touch(struct wl_client *client, struct wl_resource *resource, wl_list_init(wl_resource_get_link(cr)); wl_resource_set_implementation(cr, &touch_interface, - touch, unbind_resource); + touch, destroy_touch_resource); /* If we don't have a touch_state, the resource is inert, so there * is nothing more to set up */ @@ -4731,7 +4757,28 @@ input_timestamps_manager_get_touch_timestamps(struct wl_client *client, uint32_t id, struct wl_resource *touch_resource) { - wl_client_post_no_memory(client); + struct weston_touch *touch = wl_resource_get_user_data(touch_resource); + struct wl_resource *input_ts; + + input_ts = wl_resource_create(client, + &zwp_input_timestamps_v1_interface, + 1, id); + if (!input_ts) { + wl_client_post_no_memory(client); + return; + } + + if (touch) { + wl_list_insert(&touch->timestamps_list, + wl_resource_get_link(input_ts)); + } else { + wl_list_init(wl_resource_get_link(input_ts)); + } + + wl_resource_set_implementation(input_ts, + &input_timestamps_interface, + touch_resource, + unbind_resource); } static const struct zwp_input_timestamps_manager_v1_interface diff --git a/tests/touch-test.c b/tests/touch-test.c index 9635257f2..baf5bc58e 100644 --- a/tests/touch-test.c +++ b/tests/touch-test.c @@ -27,6 +27,7 @@ #include +#include "input-timestamps-helper.h" #include "shared/timespec-util.h" #include "weston-test-client-helper.h" #include "wayland-server-protocol.h" @@ -34,6 +35,7 @@ static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; static const struct timespec t3 = { .tv_sec = 3, .tv_nsec = 3000001 }; +static const struct timespec t_other = { .tv_sec = 123, .tv_nsec = 456 }; static struct client * create_touch_test_client(void) @@ -59,13 +61,63 @@ TEST(touch_events) { struct client *client = create_touch_test_client(); struct touch *touch = client->input->touch; + struct input_timestamps *input_ts = + input_timestamps_create_for_touch(client); send_touch(client, &t1, WL_TOUCH_DOWN); assert(touch->down_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&touch->down_time_timespec, &t1)); send_touch(client, &t2, WL_TOUCH_MOTION); assert(touch->motion_time_msec == timespec_to_msec(&t2)); + assert(timespec_eq(&touch->motion_time_timespec, &t2)); send_touch(client, &t3, WL_TOUCH_UP); assert(touch->up_time_msec == timespec_to_msec(&t3)); + assert(timespec_eq(&touch->up_time_timespec, &t3)); + + input_timestamps_destroy(input_ts); +} + +TEST(touch_timestamps_stop_after_input_timestamps_object_is_destroyed) +{ + struct client *client = create_touch_test_client(); + struct touch *touch = client->input->touch; + struct input_timestamps *input_ts = + input_timestamps_create_for_touch(client); + + send_touch(client, &t1, WL_TOUCH_DOWN); + assert(touch->down_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&touch->down_time_timespec, &t1)); + + input_timestamps_destroy(input_ts); + + send_touch(client, &t2, WL_TOUCH_UP); + assert(touch->up_time_msec == timespec_to_msec(&t2)); + assert(timespec_is_zero(&touch->up_time_timespec)); +} + +TEST(touch_timestamps_stop_after_client_releases_wl_touch) +{ + struct client *client = create_touch_test_client(); + struct touch *touch = client->input->touch; + struct input_timestamps *input_ts = + input_timestamps_create_for_touch(client); + + send_touch(client, &t1, WL_TOUCH_DOWN); + assert(touch->down_time_msec == timespec_to_msec(&t1)); + assert(timespec_eq(&touch->down_time_timespec, &t1)); + + wl_touch_release(client->input->touch->wl_touch); + + /* Set input_timestamp to an arbitrary value (different from t1, t2 + * and 0) and check that it is not changed by sending the event. + * This is preferred over just checking for 0, since 0 is used + * internally for resetting the timestamp after handling an input + * event and checking for it here may lead to false negatives. */ + touch->input_timestamp = t_other; + send_touch(client, &t2, WL_TOUCH_UP); + assert(timespec_eq(&touch->input_timestamp, &t_other)); + + input_timestamps_destroy(input_ts); } From 1f7817613ac632a0c078454460df5be89f1f6144 Mon Sep 17 00:00:00 2001 From: Greg V Date: Mon, 19 Feb 2018 17:59:42 +0300 Subject: [PATCH 0351/1642] compositor-drm: handle null cursor_plane Was crashing when I tried to take a screenshot. Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 321ee1915..9594425f3 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1828,8 +1828,10 @@ drm_output_apply_state_legacy(struct drm_output_state *state) */ if (output->base.disable_planes) { output->cursor_view = NULL; - output->cursor_plane->base.x = INT32_MIN; - output->cursor_plane->base.y = INT32_MIN; + if (output->cursor_plane) { + output->cursor_plane->base.x = INT32_MIN; + output->cursor_plane->base.y = INT32_MIN; + } } if (state->dpms != WESTON_DPMS_ON) { From 9fc2e461b37ddc534146adb6f754d8f68e354f10 Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Fri, 9 Feb 2018 08:14:41 -0800 Subject: [PATCH 0352/1642] compositor-rdp: Correct mouse scrolling direction The direction of scrolling in the RDP compositor appears to be inverted. When using Weston directly in X, sending X11 button 4 cuases window contents to scroll up and button 4 to be reported to xwayland clients. Conversely, when using Weston through RDP (xfreerdp client), sending X11 button 4 causes window contents to scroll down and button 5 to be reported to xwayland clients. The xfreerdp client does not seem to be the cause of this since scrolling works correctly when connecting to a Windows host. Signed-off-by: Jason Gerecke Reviewed-by: David Fort --- libweston/compositor-rdp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 4a4dc696c..ee68e969b 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -1062,7 +1062,7 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) * * https://blogs.msdn.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value */ - value = (flags & 0xff) / 120.0; + value = -(flags & 0xff) / 120.0; if (flags & PTR_FLAGS_WHEEL_NEGATIVE) value = -value; From e1c69be6720c1c27c020e0868370852376c72034 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 26 Feb 2018 12:56:12 -0600 Subject: [PATCH 0353/1642] configure.ac: bump to version 3.0.91 for the alpha release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 033b94844..a0a7a271d 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [3]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [90]) +m4_define([weston_micro_version], [91]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [4]) From c3fcb5bed521c36413570bf9e77d6d5e0a3d743d Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 1 Dec 2016 12:33:54 +0000 Subject: [PATCH 0354/1642] compositor-drm: Don't need safe view-list traversal Nothing in this loop reorders views within the compositor's view_list. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 9594425f3..7cd0e0ddf 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3061,7 +3061,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) struct drm_output *output = to_drm_output(output_base); struct drm_output_state *state; struct drm_plane_state *plane_state; - struct weston_view *ev, *next; + struct weston_view *ev; pixman_region32_t overlap, surface_overlap; struct weston_plane *primary, *next_plane; bool picked_scanout = false; @@ -3087,7 +3087,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) pixman_region32_init(&overlap); primary = &output_base->compositor->primary_plane; - wl_list_for_each_safe(ev, next, &output_base->compositor->view_list, link) { + wl_list_for_each(ev, &output_base->compositor->view_list, link) { struct weston_surface *es = ev->surface; /* Test whether this buffer can ever go into a plane: From 115ed2c011df2fe195124f0290ca773dab18314d Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 8 Nov 2016 21:19:22 +0000 Subject: [PATCH 0355/1642] compositor-drm: Rename region variable Make it a bit more clear what the purpose of the variable is. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 7cd0e0ddf..d40a9e30e 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3062,7 +3062,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) struct drm_output_state *state; struct drm_plane_state *plane_state; struct weston_view *ev; - pixman_region32_t overlap, surface_overlap; + pixman_region32_t surface_overlap, renderer_region; struct weston_plane *primary, *next_plane; bool picked_scanout = false; @@ -3084,7 +3084,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) * the client buffer can be used directly for the sprite surface * as we do for flipping full screen surfaces. */ - pixman_region32_init(&overlap); + pixman_region32_init(&renderer_region); primary = &output_base->compositor->primary_plane; wl_list_for_each(ev, &output_base->compositor->view_list, link) { @@ -3108,7 +3108,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) es->keep_buffer = false; pixman_region32_init(&surface_overlap); - pixman_region32_intersect(&surface_overlap, &overlap, + pixman_region32_intersect(&surface_overlap, &renderer_region, &ev->transform.boundingbox); next_plane = NULL; @@ -3135,7 +3135,8 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) weston_view_move_to_plane(ev, next_plane); if (next_plane == primary) - pixman_region32_union(&overlap, &overlap, + pixman_region32_union(&renderer_region, + &renderer_region, &ev->transform.boundingbox); if (next_plane == primary || @@ -3152,7 +3153,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) pixman_region32_fini(&surface_overlap); } - pixman_region32_fini(&overlap); + pixman_region32_fini(&renderer_region); /* We rely on output->cursor_view being both an accurate reflection of * the cursor plane's state, but also being maintained across repaints From 1de42525ca440ca1719a70d46c3611bd527cc64a Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 17 Nov 2016 18:17:16 +0000 Subject: [PATCH 0356/1642] compositor-drm: Remove no_addfb2 handling If AddFB2 ever fails for any reason, we fall back to legacy AddFB, which doesn't support the same swathe of formats, or multi-planar formats, or modifiers. This can happen with arbitrary client buffers, condemning us to the fallback forever more. Remove this, at the cost of an unnecessary ioctl for users on old kernels without AddFB2; unfortunately, we cannot detect the complete absence of the ioctl, as the return here is -EINVAL rather than -ENOTTY. A check for whether or not the format is valid has been replaced with an assert, as its callers either check that the format is non-zero, return a FourCC format code from GBM, or use a static FourCC format. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 47 ++++++++++++-------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index d40a9e30e..81ca67d60 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -241,7 +241,6 @@ struct drm_backend { */ int min_width, max_width; int min_height, max_height; - int no_addfb2; struct wl_list plane_list; int sprites_are_broken; @@ -854,6 +853,7 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, struct drm_mode_create_dumb create_arg; struct drm_mode_destroy_dumb destroy_arg; struct drm_mode_map_dumb map_arg; + uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 }; fb = zalloc(sizeof *fb); if (!fb) @@ -893,23 +893,12 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, ret = -1; - if (!b->no_addfb2) { - uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 }; - - handles[0] = fb->handle; - pitches[0] = fb->stride; - offsets[0] = 0; - - ret = drmModeAddFB2(b->drm.fd, width, height, - fb->format->format, - handles, pitches, offsets, - &fb->fb_id, 0); - if (ret) { - weston_log("addfb2 failed: %m\n"); - b->no_addfb2 = 1; - } - } + handles[0] = fb->handle; + pitches[0] = fb->stride; + offsets[0] = 0; + ret = drmModeAddFB2(b->drm.fd, width, height, fb->format->format, + handles, pitches, offsets, &fb->fb_id, 0); if (ret) { ret = drmModeAddFB(b->drm.fd, width, height, fb->format->depth, fb->format->bpp, @@ -963,6 +952,8 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, return drm_fb_ref(fb); } + assert(format != 0); + fb = zalloc(sizeof *fb); if (fb == NULL) return NULL; @@ -993,23 +984,13 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, goto err_free; } - ret = -1; - - if (format && !backend->no_addfb2) { - handles[0] = fb->handle; - pitches[0] = fb->stride; - offsets[0] = 0; - - ret = drmModeAddFB2(backend->drm.fd, fb->width, fb->height, - format, handles, pitches, offsets, - &fb->fb_id, 0); - if (ret) { - weston_log("addfb2 failed: %m\n"); - backend->no_addfb2 = 1; - backend->sprites_are_broken = 1; - } - } + handles[0] = fb->handle; + pitches[0] = fb->stride; + offsets[0] = 0; + ret = drmModeAddFB2(backend->drm.fd, fb->width, fb->height, + fb->format->format, handles, pitches, offsets, + &fb->fb_id, 0); if (ret && fb->format->depth && fb->format->bpp) ret = drmModeAddFB(backend->drm.fd, fb->width, fb->height, fb->format->depth, fb->format->bpp, From b678befb6ed055e6c66466505d9195a3cebf8073 Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Thu, 1 Mar 2018 08:28:30 +0000 Subject: [PATCH 0357/1642] gl-renderer: Create a high priority context EGL_IMG_context_priority allows the client to request that their rendering be considered high priority. For ourselves, this is important as we are interactive and any delay in our rendering causes input-output jitter; a less than smooth user interactive. So if the driver supports setting the context priority, try and create our EGLContext as high priority. The driver may reject our request due to system restrictions, in which case it will fallback to normal priority, but if successful it will reschedule our rendering and all of its dependencies to execute earlier, especially important when the GPU is being hogged by background clients. Signed-off-by: Chris Wilson Reviewed-by: Daniel Stone Acked-by: Pekka Paalanen --- libweston/gl-renderer.c | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index d3ed4a108..a6b29a924 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -223,6 +223,8 @@ struct gl_renderer { PFNEGLQUERYWAYLANDBUFFERWL query_buffer; int has_bind_display; + int has_context_priority; + int has_egl_image_external; int has_egl_buffer_age; @@ -3189,6 +3191,9 @@ gl_renderer_setup_egl_extensions(struct weston_compositor *ec) return -1; } + if (weston_check_egl_extension(extensions, "EGL_IMG_context_priority")) + gr->has_context_priority = 1; + if (weston_check_egl_extension(extensions, "EGL_WL_bind_wayland_display")) gr->has_bind_display = 1; if (gr->has_bind_display) { @@ -3631,10 +3636,10 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) EGLConfig context_config; EGLBoolean ret; - EGLint context_attribs[] = { + EGLint context_attribs[16] = { EGL_CONTEXT_CLIENT_VERSION, 0, - EGL_NONE }; + unsigned int nattr = 2; if (!eglBindAPI(EGL_OPENGL_ES_API)) { weston_log("failed to bind EGL_OPENGL_ES_API\n"); @@ -3642,6 +3647,21 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) return -1; } + /* + * Being the compositor we require minimum output latency, + * so request a high priority context for ourselves - that should + * reschedule all of our rendering and its dependencies to be completed + * first. If the driver doesn't permit us to create a high priority + * context, it will fallback to the default priority (MEDIUM). + */ + if (gr->has_context_priority) { + context_attribs[nattr++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG; + context_attribs[nattr++] = EGL_CONTEXT_PRIORITY_HIGH_IMG; + } + + assert(nattr < ARRAY_LENGTH(context_attribs)); + context_attribs[nattr] = EGL_NONE; + context_config = gr->egl_config; if (gr->has_configless_context) @@ -3665,6 +3685,18 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) } } + if (gr->has_context_priority) { + EGLint value = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; + + eglQueryContext(gr->egl_display, gr->egl_context, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value); + + if (value != EGL_CONTEXT_PRIORITY_HIGH_IMG) { + weston_log("Failed to obtain a high priority context.\n"); + /* Not an error, continue on as normal */ + } + } + ret = eglMakeCurrent(gr->egl_display, egl_surface, egl_surface, gr->egl_context); if (ret == EGL_FALSE) { From d29db19fba871b14e849e1549c92a5bfd3a22a75 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Mon, 5 Mar 2018 20:02:40 +0100 Subject: [PATCH 0358/1642] autoconf: Remove configure line forgotten in bb707dc0fe331c9af112a0552b7aa6fde755dd83 Signed-off-by: Emmanuel Gil Peyrot Reviewed-by: Pekka Paalanen --- configure.ac | 1 - 1 file changed, 1 deletion(-) diff --git a/configure.ac b/configure.ac index a0a7a271d..0b326ccc9 100644 --- a/configure.ac +++ b/configure.ac @@ -718,6 +718,5 @@ AC_MSG_RESULT([ LCMS2 Support ${have_lcms} libjpeg Support ${have_jpeglib} libwebp Support ${have_webp} - libunwind Support ${have_libunwind} VA H.264 encoding Support ${have_libva} ]) From 8d6e14c991346a70830cd9788e213d77191aba6e Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 22 Feb 2018 16:54:17 +0200 Subject: [PATCH 0359/1642] compositor-wayland: handle wl_keyboard.enter(NULL) Destroying an output (wl_surface) can race against the parent compositor sending wl_keyboard.enter. When this race is lost, wayland-backend receives wl_keyboard.enter with a NULL wl_surface for the surface it just destroyed. Handle this case by ignoring such enter events. Since it is theoretically possible to follow enter with key events, drop those too. The modifiers event is sent before enter, so we cannot drop that on the same condition. wl_keyboard.leave handler seems to already handle the NULL focus case, but there is a question if the notify_keyboard_focus_out() call should be avoided. This patch fixes a hard to reproduce crash. I was running weston/x11 with two outputs, and weston/wayland --sprawl inside that, then closing the parent compositor windows one by one. Sometimes it would trigger this crash. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- libweston/compositor-wayland.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index c6b926117..9c401d2e6 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1858,6 +1858,11 @@ input_handle_keyboard_enter(void *data, weston_output_schedule_repaint(&focus->base); } + if (!surface) { + input->keyboard_focus = NULL; + return; + } + input->keyboard_focus = wl_surface_get_user_data(surface); input->keyboard_focus->keyboard_count++; @@ -1907,6 +1912,9 @@ input_handle_key(void *data, struct wl_keyboard *keyboard, struct wayland_input *input = data; struct timespec ts; + if (!input->keyboard_focus) + return; + timespec_from_msec(&ts, time); input->key_serial = serial; From 72e183bd2b41b888a4d26e3c6b389948964df85c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 22 Feb 2018 16:55:15 +0200 Subject: [PATCH 0360/1642] input: never set keyboard focus without wl_resource Do not attempt to set keyboard focus to a surface that has no wl_resource. The destroy listener hangs off the wl_resource, so if that is not present, nothing will clean up the pointer when the weston_surface gets destroyed and it goes stale. As keyboard_focus_resource_destroyed() sets the focus to NULL, this patch should be enough to guarantee that the keyboard focus surface will always have a wl_resource. I have confirmed the added branch in weston_keyboard_set_focus() can be hit, but doing so is hard. My test case has weston/x11 with two outputs, and weston/wayland --sprawl running on top of that, then closing the parent compositor output windows one by one. Sometimes it hits, often it does not. Having the window closing animation enabled may help to hit it. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- libweston/input.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libweston/input.c b/libweston/input.c index 3f616941e..3e91c266a 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -1460,6 +1460,14 @@ weston_keyboard_set_focus(struct weston_keyboard *keyboard, uint32_t serial; struct wl_list *focus_resource_list; + /* Keyboard focus on a surface without a client is equivalent to NULL + * focus as nothing would react to the keyboard events anyway. + * Just set focus to NULL instead - the destroy listener hangs on the + * wl_resource anyway. + */ + if (surface && !surface->resource) + surface = NULL; + focus_resource_list = &keyboard->focus_resource_list; if (!wl_list_empty(focus_resource_list) && keyboard->focus != surface) { @@ -1495,7 +1503,7 @@ weston_keyboard_set_focus(struct weston_keyboard *keyboard, wl_list_remove(&keyboard->focus_resource_listener.link); wl_list_init(&keyboard->focus_resource_listener.link); - if (surface && surface->resource) + if (surface) wl_resource_add_destroy_listener(surface->resource, &keyboard->focus_resource_listener); From 3f83937414dd0d34bbbb43f55a3f4c08ccb7defb Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 9 Mar 2018 10:08:37 +0000 Subject: [PATCH 0361/1642] compositor-wayland: Ignore pointer enter on destroyed surface Due to race conditions, it is (vanishingly unlikely but) possible to receive a wl_pointer.enter event referring to a wl_surface we have just destroyed. If this happens, wl_surface will be NULL. Detect this, clear out our focus, and return. Other pointer and keyboard events are robust against destroyed surfaces. Signed-off-by: Daniel Stone Cc: Pekka Paalanen [Pekka: remove call to input_set_cursor()] Signed-off-by: Pekka Paalanen --- libweston/compositor-wayland.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 9c401d2e6..f51f78dde 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1525,6 +1525,13 @@ input_handle_pointer_enter(void *data, struct wl_pointer *pointer, enum theme_location location; double x, y; + if (!surface) { + input->output = NULL; + input->has_focus = false; + notify_pointer_focus(&input->base, NULL, 0, 0); + return; + } + x = wl_fixed_to_double(fixed_x); y = wl_fixed_to_double(fixed_y); From 3f5f3afa813fd2d2c06def4f6979deb45b9455d2 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 9 Mar 2018 11:54:40 +0200 Subject: [PATCH 0362/1642] clients: consolidate timer code part 1 There are multiple copies for the timerfd handling code, and I need a timer in one more app. Consolidate all the timerfd code into window.c to reduce the duplication. Many of the copies were also flawed against the race mentioned in toytimer_fire(). This patch handles clickdot and window.c's tooltip timer and cursor timer. Signed-off-by: Pekka Paalanen Reviewed-by: Derek Foreman Acked-by: Daniel Stone --- clients/clickdot.c | 36 +++-------- clients/window.c | 150 ++++++++++++++++++++++++++++++--------------- clients/window.h | 27 ++++++++ 3 files changed, 135 insertions(+), 78 deletions(-) diff --git a/clients/clickdot.c b/clients/clickdot.c index f52fbf048..f9e6e640e 100644 --- a/clients/clickdot.c +++ b/clients/clickdot.c @@ -32,9 +32,8 @@ #include #include #include -#include -#include #include +#include #include #include @@ -62,8 +61,7 @@ struct clickdot { int reset; struct input *cursor_timeout_input; - int cursor_timeout_fd; - struct task cursor_timeout_task; + struct toytimer cursor_timeout; }; static void @@ -224,14 +222,7 @@ button_handler(struct widget *widget, static void cursor_timeout_reset(struct clickdot *clickdot) { - const long cursor_timeout = 500; - struct itimerspec its; - - its.it_interval.tv_sec = 0; - its.it_interval.tv_nsec = 0; - its.it_value.tv_sec = cursor_timeout / 1000; - its.it_value.tv_nsec = (cursor_timeout % 1000) * 1000 * 1000; - timerfd_settime(clickdot->cursor_timeout_fd, 0, &its, NULL); + toytimer_arm_once_usec(&clickdot->cursor_timeout, 500 * 1000); } static int @@ -271,15 +262,10 @@ leave_handler(struct widget *widget, } static void -cursor_timeout_func(struct task *task, uint32_t events) +cursor_timeout_func(struct toytimer *tt) { struct clickdot *clickdot = - container_of(task, struct clickdot, cursor_timeout_task); - uint64_t exp; - - if (read(clickdot->cursor_timeout_fd, &exp, sizeof (uint64_t)) != - sizeof(uint64_t)) - abort(); + container_of(tt, struct clickdot, cursor_timeout); input_set_pointer_image(clickdot->cursor_timeout_input, CURSOR_LEFT_PTR); @@ -317,12 +303,8 @@ clickdot_create(struct display *display) clickdot->line.old_y = -1; clickdot->reset = 0; - clickdot->cursor_timeout_fd = - timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); - clickdot->cursor_timeout_task.run = cursor_timeout_func; - display_watch_fd(window_get_display(clickdot->window), - clickdot->cursor_timeout_fd, - EPOLLIN, &clickdot->cursor_timeout_task); + toytimer_init(&clickdot->cursor_timeout, CLOCK_MONOTONIC, + display, cursor_timeout_func); return clickdot; } @@ -330,9 +312,7 @@ clickdot_create(struct display *display) static void clickdot_destroy(struct clickdot *clickdot) { - display_unwatch_fd(window_get_display(clickdot->window), - clickdot->cursor_timeout_fd); - close(clickdot->cursor_timeout_fd); + toytimer_fini(&clickdot->cursor_timeout); if (clickdot->buffer) cairo_surface_destroy(clickdot->buffer); widget_destroy(clickdot->widget); diff --git a/clients/window.c b/clients/window.c index 15a86e153..7c3d3bdd8 100644 --- a/clients/window.c +++ b/clients/window.c @@ -349,9 +349,8 @@ struct input { struct wl_callback *cursor_frame_cb; uint32_t cursor_timer_start; uint32_t cursor_anim_current; - int cursor_delay_fd; + struct toytimer cursor_timer; bool cursor_timer_running; - struct task cursor_task; struct wl_surface *pointer_surface; uint32_t modifiers; uint32_t pointer_enter_serial; @@ -440,8 +439,7 @@ struct tooltip { struct widget *parent; struct widget *widget; char *entry; - struct task tooltip_task; - int tooltip_fd; + struct toytimer timer; float x, y; }; @@ -2122,21 +2120,17 @@ widget_destroy_tooltip(struct widget *parent) tooltip->widget = NULL; } - close(tooltip->tooltip_fd); + toytimer_fini(&tooltip->timer); free(tooltip->entry); free(tooltip); parent->tooltip = NULL; } static void -tooltip_func(struct task *task, uint32_t events) +tooltip_func(struct toytimer *tt) { - struct tooltip *tooltip = - container_of(task, struct tooltip, tooltip_task); - uint64_t exp; + struct tooltip *tooltip = container_of(tt, struct tooltip, timer); - if (read(tooltip->tooltip_fd, &exp, sizeof (uint64_t)) != sizeof (uint64_t)) - abort(); window_create_tooltip(tooltip); } @@ -2144,16 +2138,7 @@ tooltip_func(struct task *task, uint32_t events) static int tooltip_timer_reset(struct tooltip *tooltip) { - struct itimerspec its; - - its.it_interval.tv_sec = 0; - its.it_interval.tv_nsec = 0; - its.it_value.tv_sec = TOOLTIP_TIMEOUT / 1000; - its.it_value.tv_nsec = (TOOLTIP_TIMEOUT % 1000) * 1000 * 1000; - if (timerfd_settime(tooltip->tooltip_fd, 0, &its, NULL) < 0) { - fprintf(stderr, "could not set timerfd\n: %m"); - return -1; - } + toytimer_arm_once_usec(&tooltip->timer, TOOLTIP_TIMEOUT * 1000); return 0; } @@ -2186,15 +2171,8 @@ widget_set_tooltip(struct widget *parent, char *entry, float x, float y) tooltip->x = x; tooltip->y = y; tooltip->entry = strdup(entry); - tooltip->tooltip_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); - if (tooltip->tooltip_fd < 0) { - fprintf(stderr, "could not create timerfd\n: %m"); - return -1; - } - - tooltip->tooltip_task.run = tooltip_func; - display_watch_fd(parent->window->display, tooltip->tooltip_fd, - EPOLLIN, &tooltip->tooltip_task); + toytimer_init(&tooltip->timer, CLOCK_MONOTONIC, + parent->window->display, tooltip_func); tooltip_timer_reset(tooltip); return 0; @@ -2685,19 +2663,12 @@ input_ungrab(struct input *input) static void cursor_delay_timer_reset(struct input *input, uint32_t duration) { - struct itimerspec its; - if (!duration) input->cursor_timer_running = false; else input->cursor_timer_running = true; - its.it_interval.tv_sec = 0; - its.it_interval.tv_nsec = 0; - its.it_value.tv_sec = duration / 1000; - its.it_value.tv_nsec = (duration % 1000) * 1000 * 1000; - if (timerfd_settime(input->cursor_delay_fd, 0, &its, NULL) < 0) - fprintf(stderr, "could not set cursor timerfd\n: %m"); + toytimer_arm_once_usec(&input->cursor_timer, duration * 1000); } static void cancel_pointer_image_update(struct input *input) @@ -3888,20 +3859,16 @@ pointer_surface_frame_callback(void *data, struct wl_callback *callback, } static void -cursor_timer_func(struct task *task, uint32_t events) +cursor_timer_func(struct toytimer *tt) { - struct input *input = container_of(task, struct input, cursor_task); + struct input *input = container_of(tt, struct input, cursor_timer); struct timespec tp; struct wl_cursor *cursor; uint32_t time; - uint64_t exp; if (!input->cursor_timer_running) return; - if (read(input->cursor_delay_fd, &exp, sizeof (uint64_t)) != sizeof (uint64_t)) - return; - cursor = input->display->cursors[input->current_cursor]; if (!cursor) return; @@ -5876,12 +5843,9 @@ display_add_input(struct display *d, uint32_t id, int display_seat_version) } input->pointer_surface = wl_compositor_create_surface(d->compositor); - input->cursor_task.run = cursor_timer_func; - input->cursor_delay_fd = timerfd_create(CLOCK_MONOTONIC, - TFD_CLOEXEC | TFD_NONBLOCK); - display_watch_fd(d, input->cursor_delay_fd, EPOLLIN, - &input->cursor_task); + toytimer_init(&input->cursor_timer, CLOCK_MONOTONIC, d, + cursor_timer_func); set_repeat_info(input, 40, 400); input->repeat_timer_fd = timerfd_create(CLOCK_MONOTONIC, @@ -5932,7 +5896,7 @@ input_destroy(struct input *input) wl_list_remove(&input->link); wl_seat_destroy(input->seat); close(input->repeat_timer_fd); - close(input->cursor_delay_fd); + toytimer_fini(&input->cursor_timer); free(input); } @@ -6562,3 +6526,89 @@ keysym_modifiers_get_mask(struct wl_array *modifiers_map, return 1 << index; } + +static void +toytimer_fire(struct task *tsk, uint32_t events) +{ + uint64_t e; + struct toytimer *tt; + + tt = container_of(tsk, struct toytimer, tsk); + + if (events != EPOLLIN) + fprintf(stderr, "unexpected timerfd events %x\n", events); + + if (!(events & EPOLLIN)) + return; + + if (read(tt->fd, &e, sizeof e) != sizeof e) { + /* If we change the timer between the fd becoming + * readable and getting here, there'll be nothing to + * read and we get EAGAIN. */ + if (errno != EAGAIN) + fprintf(stderr, "timer read failed: %m\n"); + return; + } + + tt->callback(tt); +} + +void +toytimer_init(struct toytimer *tt, clockid_t clock, struct display *display, + toytimer_cb callback) +{ + memset(tt, 0, sizeof *tt); + + tt->fd = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK); + if (tt->fd == -1) { + fprintf(stderr, "creating timer failed: %m\n"); + abort(); + } + + tt->display = display; + tt->callback = callback; + tt->tsk.run = toytimer_fire; + display_watch_fd(display, tt->fd, EPOLLIN, &tt->tsk); +} + +void +toytimer_fini(struct toytimer *tt) +{ + display_unwatch_fd(tt->display, tt->fd); + close(tt->fd); + tt->fd = -1; +} + +void +toytimer_arm(struct toytimer *tt, const struct itimerspec *its) +{ + int ret; + + ret = timerfd_settime(tt->fd, 0, its, NULL); + if (ret < 0) { + fprintf(stderr, "timer setup failed: %m\n"); + abort(); + } +} + +#define USEC_PER_SEC 1000000 + +void +toytimer_arm_once_usec(struct toytimer *tt, uint32_t usec) +{ + struct itimerspec its; + + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + its.it_value.tv_sec = usec / USEC_PER_SEC; + its.it_value.tv_nsec = (usec % USEC_PER_SEC) * 1000; + toytimer_arm(tt, &its); +} + +void +toytimer_disarm(struct toytimer *tt) +{ + struct itimerspec its = {}; + + toytimer_arm(tt, &its); +} diff --git a/clients/window.h b/clients/window.h index 1ec9eac53..3366ab15f 100644 --- a/clients/window.h +++ b/clients/window.h @@ -27,6 +27,7 @@ #include "config.h" #include +#include #include #include #include @@ -713,4 +714,30 @@ xkb_mod_mask_t keysym_modifiers_get_mask(struct wl_array *modifiers_map, const char *name); +struct toytimer; +typedef void (*toytimer_cb)(struct toytimer *); + +struct toytimer { + struct display *display; + struct task tsk; + int fd; + toytimer_cb callback; +}; + +void +toytimer_init(struct toytimer *tt, clockid_t clock, struct display *display, + toytimer_cb callback); + +void +toytimer_fini(struct toytimer *tt); + +void +toytimer_arm(struct toytimer *tt, const struct itimerspec *its); + +void +toytimer_arm_once_usec(struct toytimer *tt, uint32_t usec); + +void +toytimer_disarm(struct toytimer *tt); + #endif From 64a26bc192b7125b23c95725804ced0e0180b4e3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 9 Mar 2018 13:17:26 +0200 Subject: [PATCH 0363/1642] clients: consolidate timer code part 2 Continue moving bits to use toytimer instead of carelessly open-coded equivalent. Many of the copies were flawed against the race mentioned in toytimer_fire(). This patch handles window.c's key repeat, confine demo, and desktop-shell panel clock. Signed-off-by: Pekka Paalanen Reviewed-by: Derek Foreman Acked-by: Daniel Stone --- clients/confine.c | 35 +++++++---------------------------- clients/desktop-shell.c | 35 +++++++---------------------------- clients/window.c | 41 ++++++++++------------------------------- 3 files changed, 24 insertions(+), 87 deletions(-) diff --git a/clients/confine.c b/clients/confine.c index c0d908fb2..255236a35 100644 --- a/clients/confine.c +++ b/clients/confine.c @@ -33,8 +33,6 @@ #include #include #include -#include -#include #include #include @@ -64,8 +62,7 @@ struct confine { int reset; struct input *cursor_timeout_input; - int cursor_timeout_fd; - struct task cursor_timeout_task; + struct toytimer cursor_timeout; bool pointer_confined; @@ -347,14 +344,7 @@ button_handler(struct widget *widget, static void cursor_timeout_reset(struct confine *confine) { - const long cursor_timeout = 500; - struct itimerspec its; - - its.it_interval.tv_sec = 0; - its.it_interval.tv_nsec = 0; - its.it_value.tv_sec = cursor_timeout / 1000; - its.it_value.tv_nsec = (cursor_timeout % 1000) * 1000 * 1000; - timerfd_settime(confine->cursor_timeout_fd, 0, &its, NULL); + toytimer_arm_once_usec(&confine->cursor_timeout, 500 * 1000); } static int @@ -406,15 +396,10 @@ leave_handler(struct widget *widget, } static void -cursor_timeout_func(struct task *task, uint32_t events) +cursor_timeout_func(struct toytimer *tt) { struct confine *confine = - container_of(task, struct confine, cursor_timeout_task); - uint64_t exp; - - if (read(confine->cursor_timeout_fd, &exp, sizeof (uint64_t)) != - sizeof(uint64_t)) - abort(); + container_of(tt, struct confine, cursor_timeout); input_set_pointer_image(confine->cursor_timeout_input, CURSOR_LEFT_PTR); @@ -461,12 +446,8 @@ confine_create(struct display *display) confine->line.old_y = -1; confine->reset = 0; - confine->cursor_timeout_fd = - timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); - confine->cursor_timeout_task.run = cursor_timeout_func; - display_watch_fd(window_get_display(confine->window), - confine->cursor_timeout_fd, - EPOLLIN, &confine->cursor_timeout_task); + toytimer_init(&confine->cursor_timeout, CLOCK_MONOTONIC, + display, cursor_timeout_func); return confine; } @@ -474,9 +455,7 @@ confine_create(struct display *display) static void confine_destroy(struct confine *confine) { - display_unwatch_fd(window_get_display(confine->window), - confine->cursor_timeout_fd); - close(confine->cursor_timeout_fd); + toytimer_fini(&confine->cursor_timeout); if (confine->buffer) cairo_surface_destroy(confine->buffer); widget_destroy(confine->widget); diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index cabe851f4..6d19d0292 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -34,8 +34,6 @@ #include #include #include -#include -#include #include #include #include @@ -148,8 +146,7 @@ struct panel_launcher { struct panel_clock { struct widget *widget; struct panel *panel; - struct task clock_task; - int clock_fd; + struct toytimer timer; char *format_string; time_t refresh_timer; }; @@ -365,14 +362,10 @@ panel_launcher_touch_up_handler(struct widget *widget, struct input *input, } static void -clock_func(struct task *task, uint32_t events) +clock_func(struct toytimer *tt) { - struct panel_clock *clock = - container_of(task, struct panel_clock, clock_task); - uint64_t exp; + struct panel_clock *clock = container_of(tt, struct panel_clock, timer); - if (read(clock->clock_fd, &exp, sizeof exp) != sizeof exp) - abort(); widget_schedule_redraw(clock->widget); } @@ -423,10 +416,7 @@ clock_timer_reset(struct panel_clock *clock) its.it_interval.tv_nsec = 0; its.it_value.tv_sec = clock->refresh_timer; its.it_value.tv_nsec = 0; - if (timerfd_settime(clock->clock_fd, 0, &its, NULL) < 0) { - fprintf(stderr, "could not set timerfd\n: %m"); - return -1; - } + toytimer_arm(&clock->timer, &its); return 0; } @@ -435,9 +425,7 @@ static void panel_destroy_clock(struct panel_clock *clock) { widget_destroy(clock->widget); - - close(clock->clock_fd); - + toytimer_fini(&clock->timer); free(clock); } @@ -445,18 +433,10 @@ static void panel_add_clock(struct panel *panel) { struct panel_clock *clock; - int timerfd; - - timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); - if (timerfd < 0) { - fprintf(stderr, "could not create timerfd\n: %m"); - return; - } clock = xzalloc(sizeof *clock); clock->panel = panel; panel->clock = clock; - clock->clock_fd = timerfd; switch (panel->clock_format) { case CLOCK_FORMAT_MINUTES: @@ -471,9 +451,8 @@ panel_add_clock(struct panel *panel) assert(!"not reached"); } - clock->clock_task.run = clock_func; - display_watch_fd(window_get_display(panel->window), clock->clock_fd, - EPOLLIN, &clock->clock_task); + toytimer_init(&clock->timer, CLOCK_MONOTONIC, + window_get_display(panel->window), clock_func); clock_timer_reset(clock); clock->widget = widget_add_widget(panel->widget, clock); diff --git a/clients/window.c b/clients/window.c index 7c3d3bdd8..bcf2b017c 100644 --- a/clients/window.c +++ b/clients/window.c @@ -388,8 +388,7 @@ struct input { int32_t repeat_delay_sec; int32_t repeat_delay_nsec; - struct task repeat_task; - int repeat_timer_fd; + struct toytimer repeat_timer; uint32_t repeat_sym; uint32_t repeat_key; uint32_t repeat_time; @@ -2910,13 +2909,8 @@ static void input_remove_keyboard_focus(struct input *input) { struct window *window = input->keyboard_focus; - struct itimerspec its; - its.it_interval.tv_sec = 0; - its.it_interval.tv_nsec = 0; - its.it_value.tv_sec = 0; - its.it_value.tv_nsec = 0; - timerfd_settime(input->repeat_timer_fd, 0, &its, NULL); + toytimer_disarm(&input->repeat_timer); if (!window) return; @@ -2929,18 +2923,10 @@ input_remove_keyboard_focus(struct input *input) } static void -keyboard_repeat_func(struct task *task, uint32_t events) +keyboard_repeat_func(struct toytimer *tt) { - struct input *input = - container_of(task, struct input, repeat_task); + struct input *input = container_of(tt, struct input, repeat_timer); struct window *window = input->keyboard_focus; - uint64_t exp; - - if (read(input->repeat_timer_fd, &exp, sizeof exp) != sizeof exp) - /* If we change the timer between the fd becoming - * readable and getting here, there'll be nothing to - * read and we get EAGAIN. */ - return; if (window && window->key_handler) { (*window->key_handler)(window, input, input->repeat_time, @@ -3163,11 +3149,7 @@ keyboard_handle_key(void *data, struct wl_keyboard *keyboard, if (state == WL_KEYBOARD_KEY_STATE_RELEASED && key == input->repeat_key) { - its.it_interval.tv_sec = 0; - its.it_interval.tv_nsec = 0; - its.it_value.tv_sec = 0; - its.it_value.tv_nsec = 0; - timerfd_settime(input->repeat_timer_fd, 0, &its, NULL); + toytimer_disarm(&input->repeat_timer); } else if (state == WL_KEYBOARD_KEY_STATE_PRESSED && xkb_keymap_key_repeats(input->xkb.keymap, code)) { input->repeat_sym = sym; @@ -3177,7 +3159,7 @@ keyboard_handle_key(void *data, struct wl_keyboard *keyboard, its.it_interval.tv_nsec = input->repeat_rate_nsec; its.it_value.tv_sec = input->repeat_delay_sec; its.it_value.tv_nsec = input->repeat_delay_nsec; - timerfd_settime(input->repeat_timer_fd, 0, &its, NULL); + toytimer_arm(&input->repeat_timer, &its); } } @@ -5846,13 +5828,10 @@ display_add_input(struct display *d, uint32_t id, int display_seat_version) toytimer_init(&input->cursor_timer, CLOCK_MONOTONIC, d, cursor_timer_func); - set_repeat_info(input, 40, 400); - input->repeat_timer_fd = timerfd_create(CLOCK_MONOTONIC, - TFD_CLOEXEC | TFD_NONBLOCK); - input->repeat_task.run = keyboard_repeat_func; - display_watch_fd(d, input->repeat_timer_fd, - EPOLLIN, &input->repeat_task); + set_repeat_info(input, 40, 400); + toytimer_init(&input->repeat_timer, CLOCK_MONOTONIC, d, + keyboard_repeat_func); } static void @@ -5895,7 +5874,7 @@ input_destroy(struct input *input) wl_list_remove(&input->link); wl_seat_destroy(input->seat); - close(input->repeat_timer_fd); + toytimer_fini(&input->repeat_timer); toytimer_fini(&input->cursor_timer); free(input); } From d1510b4f40aad09372e9721d91d7df41fdaece71 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 13 Mar 2018 11:34:47 -0500 Subject: [PATCH 0364/1642] libweston-desktop/xdg-shell-v6: Fix crash when surface has buffer at creation When a surface has a buffer at creation time we send an error, which results in a disconnection and all resources being destroyed. Since we send that error and return before performing the configure_list init weston_desktop_xdg_surface_destroy() will walk an uninitialized list and dereference a NULL pointer. Initializing the list earlier prevents this from happening. Signed-off-by: Derek Foreman Reviewed-by: Quentin Glidic --- libweston-desktop/xdg-shell-v6.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index f9902ff02..8fa01a324 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -1370,6 +1370,7 @@ weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, surface->desktop = weston_desktop_client_get_desktop(client); surface->surface = wsurface; + wl_list_init(&surface->configure_list); surface->desktop_surface = weston_desktop_surface_create(surface->desktop, client, @@ -1395,8 +1396,6 @@ weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, "xdg_surface must not have a buffer at creation"); return; } - - wl_list_init(&surface->configure_list); } static void From df9278aea75282c67c80c78c667a8af73ad21c4e Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Tue, 6 Mar 2018 18:56:23 +0200 Subject: [PATCH 0365/1642] libweston/compositor: Place timeline recording after checking if stamp is valid The timestamp could be either NULL if there's no mode set, or 0 when output gets awaken. It either crashes weston or we get vblanks at [0, 0] for that output. Signed-off-by: Marius Vlad CC: Pekka Paalanen [Pekka: note, most start_repaint_loop pass in current time, not 0] Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index aec937bb8..274a22d34 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2495,8 +2495,6 @@ weston_output_finish_frame(struct weston_output *output, struct timespec now; int64_t msec_rel; - TL_POINT("core_repaint_finished", TLP_OUTPUT(output), - TLP_VBLANK(stamp), TLP_END); assert(output->repaint_status == REPAINT_AWAITING_COMPLETION); assert(stamp || (presented_flags & WP_PRESENTATION_FEEDBACK_INVALID)); @@ -2511,6 +2509,9 @@ weston_output_finish_frame(struct weston_output *output, goto out; } + TL_POINT("core_repaint_finished", TLP_OUTPUT(output), + TLP_VBLANK(stamp), TLP_END); + refresh_nsec = millihz_to_nsec(output->current_mode->refresh); weston_presentation_feedback_present_list(&output->feedback_list, output, refresh_nsec, stamp, From 5fa193430cb492a965885f8537fde59420e1019c Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Thu, 15 Mar 2018 13:45:28 +0000 Subject: [PATCH 0366/1642] build: honour libinput header location Add the respective CFLAGS to the build, otherwise it will error out as seen below. src/libinput-seat.c:30:22: fatal error: libinput.h: No such file or directory v2: add the CFLAGS only as needed, suggested by Pekka Cc: Pekka Paalanen Cc: Jan Engelhardt [Emil Velikov: polish commit message, v2] Signed-off-by: Emil Velikov Reviewed-by: Daniel Stone Reviewed-by: Pekka Paalanen --- Makefile.am | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index e028a2a13..7e930d2b0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -178,7 +178,7 @@ weston_LDFLAGS = -export-dynamic weston_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON \ -DMODULEDIR='"$(moduledir)"' \ -DXSERVER_PATH='"@XSERVER_PATH@"' -weston_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) +weston_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBINPUT_BACKEND_CFLAGS) weston_LDADD = libshared.la libweston-@LIBWESTON_MAJOR@.la \ $(COMPOSITOR_LIBS) \ $(DL_LIBS) $(LIBINPUT_BACKEND_LIBS) \ @@ -346,6 +346,7 @@ x11_backend_la_SOURCES = \ shared/helpers.h endif +INPUT_BACKEND_CFLAGS = $(LIBINPUT_BACKEND_CFLAGS) INPUT_BACKEND_LIBS = $(LIBINPUT_BACKEND_LIBS) INPUT_BACKEND_SOURCES = \ libweston/libinput-seat.c \ @@ -369,6 +370,7 @@ drm_backend_la_CFLAGS = \ $(COMPOSITOR_CFLAGS) \ $(EGL_CFLAGS) \ $(DRM_COMPOSITOR_CFLAGS) \ + $(INPUT_BACKEND_CFLAGS) \ $(AM_CFLAGS) drm_backend_la_SOURCES = \ libweston/compositor-drm.c \ @@ -443,6 +445,7 @@ fbdev_backend_la_CFLAGS = \ $(EGL_CFLAGS) \ $(FBDEV_COMPOSITOR_CFLAGS) \ $(PIXMAN_CFLAGS) \ + $(INPUT_BACKEND_CFLAGS) \ $(AM_CFLAGS) fbdev_backend_la_SOURCES = \ libweston/compositor-fbdev.c \ From 6dba368acc7964d5cb1d14667ba36f3cc4d697af Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Wed, 14 Mar 2018 10:59:52 +0200 Subject: [PATCH 0367/1642] compositor: do not free output region twice in weston_output_set_transform() This is already done when weston_output_init_geometry() is called. Actually this is a fix for 8564a0d, because without this patch, the compositor sometimes crashes when setting output transform Signed-off-by: Ilia Bozhinov Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 274a22d34..67b5d2830 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4706,9 +4706,6 @@ weston_output_set_transform(struct weston_output *output, pixman_region32_init(&old_region); pixman_region32_copy(&old_region, &output->region); - pixman_region32_fini(&output->region); - pixman_region32_fini(&output->previous_damage); - weston_output_init_geometry(output, output->x, output->y); output->dirty = 1; From 824e4995349a84b34aa308db8c923513526a8548 Mon Sep 17 00:00:00 2001 From: Michael Tretter Date: Wed, 14 Mar 2018 10:07:47 +0100 Subject: [PATCH 0368/1642] configure.ac: fix have_dbus if dbus support is disabled If dbus support is explicitly disabled, $have_dbus should be no, but was empty. systemd-login support depends on dbus, but the check does not trigger correctly, if $have_dbus is empty. Signed-off-by: Michael Tretter Reviewed-by: Pekka Paalanen --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index 0b326ccc9..788730cb5 100644 --- a/configure.ac +++ b/configure.ac @@ -502,6 +502,7 @@ AC_ARG_ENABLE(dbus, AS_HELP_STRING([--disable-dbus], [do not build with dbus support]),, enable_dbus=auto) +have_dbus=no if test "x$enable_dbus" != "xno"; then PKG_CHECK_MODULES(DBUS, dbus-1 >= 1.6, From 6ed5700da5aa2bbaa7bbc0218f42a0e04e2a848c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Thu, 15 Mar 2018 17:33:18 +0100 Subject: [PATCH 0369/1642] simple-dmabuf-drm: allow multiple backends This allows to enable freedreno and intel backends at the same time building the prerequisites for adding further ones. [Pekka: fix configure.ac if statements] Signed-off-by: Pekka Paalanen --- Makefile.am | 5 +++- clients/simple-dmabuf-drm.c | 53 ++++++++++++++++++++----------------- configure.ac | 24 ++++++++++------- 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/Makefile.am b/Makefile.am index 7e930d2b0..69ca6cba7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -627,7 +627,10 @@ nodist_weston_simple_dmabuf_drm_SOURCES = \ protocol/linux-dmabuf-unstable-v1-protocol.c \ protocol/linux-dmabuf-unstable-v1-client-protocol.h weston_simple_dmabuf_drm_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_DRM_CLIENT_CFLAGS) -weston_simple_dmabuf_drm_LDADD = $(SIMPLE_DMABUF_DRM_CLIENT_LIBS) $(LIBDRM_PLATFORM_LIBS) libshared.la +weston_simple_dmabuf_drm_LDADD = $(SIMPLE_DMABUF_DRM_CLIENT_LIBS) \ + $(LIBDRM_PLATFORM_FREEDRENO_LIBS) \ + $(LIBDRM_PLATFORM_INTEL_LIBS) \ + libshared.la endif if BUILD_SIMPLE_DMABUF_V4L_CLIENT diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 14d716de1..51ad51f88 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -44,7 +44,8 @@ #ifdef HAVE_LIBDRM_INTEL #include #include -#elif HAVE_LIBDRM_FREEDRENO +#endif +#ifdef HAVE_LIBDRM_FREEDRENO #include #endif #include @@ -93,10 +94,11 @@ struct buffer { #ifdef HAVE_LIBDRM_INTEL drm_intel_bufmgr *bufmgr; - drm_intel_bo *bo; -#elif HAVE_LIBDRM_FREEDRENO + drm_intel_bo *intel_bo; +#endif /* HAVE_LIBDRM_INTEL */ +#if HAVE_LIBDRM_FREEDRENO struct fd_device *fd_dev; - struct fd_bo *bo; + struct fd_bo *fd_bo; #endif /* HAVE_LIBDRM_FREEDRENO */ uint32_t gem_handle; @@ -152,15 +154,15 @@ intel_alloc_bo(struct buffer *my_buf) assert(my_buf->bufmgr); - my_buf->bo = drm_intel_bo_alloc_tiled(my_buf->bufmgr, "test", - my_buf->width, my_buf->height, - (my_buf->bpp / 8), &tiling, - &my_buf->stride, 0); + my_buf->intel_bo = drm_intel_bo_alloc_tiled(my_buf->bufmgr, "test", + my_buf->width, my_buf->height, + (my_buf->bpp / 8), &tiling, + &my_buf->stride, 0); printf("buffer allocated w %d, h %d, stride %lu, size %lu\n", - my_buf->width, my_buf->height, my_buf->stride, my_buf->bo->size); + my_buf->width, my_buf->height, my_buf->stride, my_buf->intel_bo->size); - if (!my_buf->bo) + if (!my_buf->intel_bo) return 0; if (tiling != I915_TILING_NONE) @@ -172,16 +174,16 @@ intel_alloc_bo(struct buffer *my_buf) static void intel_free_bo(struct buffer *my_buf) { - drm_intel_bo_unreference(my_buf->bo); + drm_intel_bo_unreference(my_buf->intel_bo); } static int intel_map_bo(struct buffer *my_buf) { - if (drm_intel_gem_bo_map_gtt(my_buf->bo) != 0) + if (drm_intel_gem_bo_map_gtt(my_buf->intel_bo) != 0) return 0; - my_buf->mmap = my_buf->bo->virtual; + my_buf->mmap = my_buf->intel_bo->virtual; return 1; } @@ -189,15 +191,16 @@ intel_map_bo(struct buffer *my_buf) static int intel_bo_export_to_prime(struct buffer *buffer) { - return drm_intel_bo_gem_export_to_prime(buffer->bo, &buffer->dmabuf_fd); + return drm_intel_bo_gem_export_to_prime(buffer->intel_bo, &buffer->dmabuf_fd); } static void intel_unmap_bo(struct buffer *my_buf) { - drm_intel_gem_bo_unmap_gtt(my_buf->bo); + drm_intel_gem_bo_unmap_gtt(my_buf->intel_bo); } -#elif HAVE_LIBDRM_FREEDRENO +#endif /* HAVE_LIBDRM_INTEL */ +#ifdef HAVE_LIBDRM_FREEDRENO #define ALIGN(v, a) ((v + a - 1) & ~(a - 1)) static @@ -207,9 +210,9 @@ int fd_alloc_bo(struct buffer *buf) int size = buf->width * buf->height * buf->bpp / 8; buf->fd_dev = fd_device_new(buf->drm_fd); - buf->bo = fd_bo_new(buf->fd_dev, size, flags); + buf->fd_bo = fd_bo_new(buf->fd_dev, size, flags); - if (!buf->bo) + if (!buf->fd_bo) return 0; buf->stride = ALIGN(buf->width, 32) * buf->bpp / 8; return 1; @@ -218,13 +221,13 @@ int fd_alloc_bo(struct buffer *buf) static void fd_free_bo(struct buffer *buf) { - fd_bo_del(buf->bo); + fd_bo_del(buf->fd_bo); } static int fd_bo_export_to_prime(struct buffer *buf) { - buf->dmabuf_fd = fd_bo_dmabuf(buf->bo); + buf->dmabuf_fd = fd_bo_dmabuf(buf->fd_bo); if (buf->dmabuf_fd > 0) return 0; @@ -234,7 +237,7 @@ int fd_bo_export_to_prime(struct buffer *buf) static int fd_map_bo(struct buffer *buf) { - buf->mmap = fd_bo_map(buf->bo); + buf->mmap = fd_bo_map(buf->fd_bo); if (buf->mmap != NULL) return 1; @@ -246,7 +249,7 @@ static void fd_unmap_bo(struct buffer *buf) { } -#endif +#endif /* HAVE_LIBDRM_FREEDRENO */ static void fill_content(struct buffer *my_buf) @@ -278,7 +281,8 @@ drm_device_destroy(struct buffer *buf) { #ifdef HAVE_LIBDRM_INTEL drm_intel_bufmgr_destroy(buf->bufmgr); -#elif HAVE_LIBDRM_FREEDRENO +#endif +#ifdef HAVE_LIBDRM_FREEDRENO fd_device_del(buf->fd_dev); #endif @@ -308,7 +312,8 @@ drm_device_init(struct buffer *buf) dev->map_bo = intel_map_bo; dev->unmap_bo = intel_unmap_bo; } -#elif HAVE_LIBDRM_FREEDRENO +#endif +#ifdef HAVE_LIBDRM_FREEDRENO else if (!strcmp(dev->name, "msm")) { dev->alloc_bo = fd_alloc_bo; dev->free_bo = fd_free_bo; diff --git a/configure.ac b/configure.ac index 788730cb5..90ffc88d7 100644 --- a/configure.ac +++ b/configure.ac @@ -384,19 +384,23 @@ AC_ARG_ENABLE(simple-dmabuf-drm-client, [do not build the simple dmabuf drm client]),, enable_simple_dmabuf_drm_client="auto") if ! test "x$enable_simple_dmabuf_drm_client" = "xno"; then - PKG_CHECK_MODULES(SIMPLE_DMABUF_DRM_CLIENT, [wayland-client libdrm egl], - [PKG_CHECK_MODULES(LIBDRM_PLATFORM, [libdrm_freedreno], - AC_DEFINE([HAVE_LIBDRM_FREEDRENO], [1], [Build freedreno dmabuf client]) have_simple_dmabuf_drm_client=freedreno, - [PKG_CHECK_MODULES(LIBDRM_PLATFORM, [libdrm_intel], - AC_DEFINE([HAVE_LIBDRM_INTEL], [1], [Build intel dmabuf client]) have_simple_dmabuf_drm_client=intel, - have_simple_dmabuf_drm_client=unsupported)])], - have_simple_dmabuf_drm_client=unsupported) - - if test "x$have_simple_dmabuf_drm_client" = "xunsupported" -a "x$enable_simple_dmabuf_drm_client" = "xyes"; then + PKG_CHECK_MODULES(SIMPLE_DMABUF_DRM_CLIENT, [wayland-client libdrm egl], [have_simple_dmabuf_libs=yes], + [have_simple_dmabuf_libs=no]) + + PKG_CHECK_MODULES(LIBDRM_PLATFORM_FREEDRENO, [libdrm_freedreno], + AC_DEFINE([HAVE_LIBDRM_FREEDRENO], [1], [Build freedreno dmabuf client]) have_simple_dmabuf_drm_client=yes, + [true]) + PKG_CHECK_MODULES(LIBDRM_PLATFORM_INTEL, [libdrm_intel], + AC_DEFINE([HAVE_LIBDRM_INTEL], [1], [Build intel dmabuf client]) have_simple_dmabuf_drm_client=yes, + [true]) + + if test "x$have_simple_dmabuf_drm_client" != "xyes" -o \ + "x$have_simple_dmabuf_libs" = "xno" && \ + test "x$enable_simple_dmabuf_drm_client" = "xyes"; then AC_MSG_ERROR([DRM dmabuf client explicitly enabled, but libdrm_intel or libdrm_freedreno not found]) fi - if test "x$have_simple_dmabuf_drm_client" = "xfreedreno" -o "x$have_simple_dmabuf_drm_client" = "xintel"; then + if test "x$have_simple_dmabuf_drm_client" = "xyes" -a "x$have_simple_dmabuf_libs" = "xyes"; then enable_simple_dmabuf_drm_client="yes" fi fi From 512d29f82821dee69d2ed5f0a78b5f48ea462576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Thu, 15 Mar 2018 17:33:19 +0100 Subject: [PATCH 0370/1642] simpla-dmabuf-drm: Use more weston like coding style Reviewed-by: Pekka Paalanen --- clients/simple-dmabuf-drm.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 51ad51f88..b576ad3a8 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -203,8 +203,8 @@ intel_unmap_bo(struct buffer *my_buf) #ifdef HAVE_LIBDRM_FREEDRENO #define ALIGN(v, a) ((v + a - 1) & ~(a - 1)) -static -int fd_alloc_bo(struct buffer *buf) +static int +fd_alloc_bo(struct buffer *buf) { int flags = DRM_FREEDRENO_GEM_CACHE_WCOMBINE; int size = buf->width * buf->height * buf->bpp / 8; @@ -218,14 +218,14 @@ int fd_alloc_bo(struct buffer *buf) return 1; } -static -void fd_free_bo(struct buffer *buf) +static void +fd_free_bo(struct buffer *buf) { fd_bo_del(buf->fd_bo); } -static -int fd_bo_export_to_prime(struct buffer *buf) +static int +fd_bo_export_to_prime(struct buffer *buf) { buf->dmabuf_fd = fd_bo_dmabuf(buf->fd_bo); if (buf->dmabuf_fd > 0) @@ -234,8 +234,8 @@ int fd_bo_export_to_prime(struct buffer *buf) return 1; } -static -int fd_map_bo(struct buffer *buf) +static int +fd_map_bo(struct buffer *buf) { buf->mmap = fd_bo_map(buf->fd_bo); @@ -245,8 +245,8 @@ int fd_map_bo(struct buffer *buf) return 0; } -static -void fd_unmap_bo(struct buffer *buf) +static void +fd_unmap_bo(struct buffer *buf) { } #endif /* HAVE_LIBDRM_FREEDRENO */ From 4fc3a679eb0678dded230afac00adad0332bef26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Thu, 15 Mar 2018 17:33:20 +0100 Subject: [PATCH 0371/1642] simple-dmabuf-drm: use vfunc for drm_device_destroy Remove ifdef clutter and makes sure it's only called for the active backend. Reviewed-by: Pekka Paalanen --- clients/simple-dmabuf-drm.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index b576ad3a8..492c96675 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -83,6 +83,7 @@ struct drm_device { int (*export_bo_to_prime)(struct buffer *buf); int (*map_bo)(struct buffer *buf); void (*unmap_bo)(struct buffer *buf); + void (*device_destroy)(struct buffer *buf); }; struct buffer { @@ -199,6 +200,13 @@ intel_unmap_bo(struct buffer *my_buf) { drm_intel_gem_bo_unmap_gtt(my_buf->intel_bo); } + +static void +intel_device_destroy(struct buffer *my_buf) +{ + drm_intel_bufmgr_destroy(my_buf->bufmgr); +} + #endif /* HAVE_LIBDRM_INTEL */ #ifdef HAVE_LIBDRM_FREEDRENO #define ALIGN(v, a) ((v + a - 1) & ~(a - 1)) @@ -249,6 +257,12 @@ static void fd_unmap_bo(struct buffer *buf) { } + +static void +fd_device_destroy(struct buffer *buf) +{ + fd_device_del(buf->fd_dev); +} #endif /* HAVE_LIBDRM_FREEDRENO */ static void @@ -279,13 +293,7 @@ fill_content(struct buffer *my_buf) static void drm_device_destroy(struct buffer *buf) { -#ifdef HAVE_LIBDRM_INTEL - drm_intel_bufmgr_destroy(buf->bufmgr); -#endif -#ifdef HAVE_LIBDRM_FREEDRENO - fd_device_del(buf->fd_dev); -#endif - + buf->dev->device_destroy(buf); close(buf->drm_fd); } @@ -311,6 +319,7 @@ drm_device_init(struct buffer *buf) dev->export_bo_to_prime = intel_bo_export_to_prime; dev->map_bo = intel_map_bo; dev->unmap_bo = intel_unmap_bo; + dev->device_destroy = intel_device_destroy; } #endif #ifdef HAVE_LIBDRM_FREEDRENO @@ -320,6 +329,7 @@ drm_device_init(struct buffer *buf) dev->export_bo_to_prime = fd_bo_export_to_prime; dev->map_bo = fd_map_bo; dev->unmap_bo = fd_unmap_bo; + dev->device_destroy = fd_device_destroy; } #endif else { From 02c5697704ef535a5e8089417acba9a182a9e144 Mon Sep 17 00:00:00 2001 From: Dima Ryazanov Date: Fri, 16 Mar 2018 00:16:29 -0400 Subject: [PATCH 0372/1642] weston: Add a help string for --xwayland Signed-off-by: Dima Ryazanov Reviewed-by: Pekka Paalanen --- compositor/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/compositor/main.c b/compositor/main.c index 18810f288..1e827884e 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -469,6 +469,7 @@ usage(int error_code) " --shell=MODULE\tShell module, defaults to desktop-shell.so\n" " -S, --socket=NAME\tName of socket to listen on\n" " -i, --idle-time=SECS\tIdle time in seconds\n" + " --xwayland\t\tLoad the xwayland module\n" " --modules\t\tLoad the comma-separated list of modules\n" " --log=FILE\t\tLog to the given file\n" " -c, --config=FILE\tConfig file to load, defaults to weston.ini\n" From 63fcad4884e136781087c7cda59da0a64191b302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 16 Mar 2018 13:12:41 +0100 Subject: [PATCH 0373/1642] .gitignore weston-simple-dmabuf-drm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Got renamed in f9dec67990a54afe14d4d2db694bf696ae418bcd Signed-off-by: Guido Günther Reviewed-by: Pekka Paalanen --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ac76fd9f2..661f48824 100644 --- a/.gitignore +++ b/.gitignore @@ -67,7 +67,7 @@ weston-nested-client weston-presentation-shm weston-resizor weston-scaler -weston-simple-dmabuf-intel +weston-simple-dmabuf-drm weston-simple-dmabuf-v4l weston-simple-egl weston-simple-shm From a4e206e170ffbebfa8847d7978109cbbc536166d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 16 Mar 2018 18:56:48 +0100 Subject: [PATCH 0374/1642] Allow simple-dmabuf-drm to pass y_inverted flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows to check if ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT is interpreted correctly by the compositor. We introduce an OPT_* bitmask to hold this flag and possible future command line flags. Signed-off-by: Guido Günther Reviewed-by: Pekka Paalanen --- clients/simple-dmabuf-drm.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 492c96675..1789db737 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -60,6 +60,10 @@ extern const unsigned nv12_tiled[]; struct buffer; +/* Possible options that affect the displayed image */ +#define OPT_Y_INVERTED 1 /* contents has y axis inverted */ + + struct display { struct wl_display *display; struct wl_registry *registry; @@ -394,11 +398,11 @@ static const struct zwp_linux_buffer_params_v1_listener params_listener = { static int create_dmabuf_buffer(struct display *display, struct buffer *buffer, - int width, int height, int format) + int width, int height, int format, uint32_t opts) { struct zwp_linux_buffer_params_v1 *params; uint64_t modifier = 0; - uint32_t flags; + uint32_t flags = 0; struct drm_device *drm_dev; if (!drm_connect(buffer)) { @@ -449,7 +453,8 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, * correct height to the compositor. */ buffer->height = height; - flags = 0; + if (opts & OPT_Y_INVERTED) + flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); zwp_linux_buffer_params_v1_add(params, @@ -532,7 +537,8 @@ static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { }; static struct window * -create_window(struct display *display, int width, int height, int format) +create_window(struct display *display, int width, int height, int format, + uint32_t opts) { struct window *window; int i; @@ -581,7 +587,7 @@ create_window(struct display *display, int width, int height, int format) for (i = 0; i < NUM_BUFFERS; ++i) { ret = create_dmabuf_buffer(display, &window->buffers[i], - width, height, format); + width, height, format, opts); if (ret < 0) return NULL; @@ -829,13 +835,15 @@ print_usage_and_exit(void) printf("usage flags:\n" "\t'--import-immediate=<>'\n\t\t0 to import dmabuf via roundtrip," "\n\t\t1 to enable import without roundtrip\n" + "\t'--y-inverted=<>'\n\t\t0 to not pass Y_INVERTED flag," + "\n\t\t1 to pass Y_INVERTED flag\n" "\t'--import-format=<>'\n\t\tXRGB to import dmabuf as XRGB8888," "\n\t\tNV12 to import as multi plane NV12 with tiling modifier\n"); exit(0); } static int -is_import_mode_immediate(const char* c) +is_true(const char* c) { if (!strcmp(c, "1")) return 1; @@ -867,22 +875,29 @@ main(int argc, char **argv) struct display *display; struct window *window; int is_immediate = 0; + int opts = 0; int import_format = DRM_FORMAT_XRGB8888; int ret = 0, i = 0; if (argc > 1) { static const char import_mode[] = "--import-immediate="; static const char format[] = "--import-format="; + static const char y_inverted[] = "--y-inverted="; for (i = 1; i < argc; i++) { if (!strncmp(argv[i], import_mode, sizeof(import_mode) - 1)) { - is_immediate = is_import_mode_immediate(argv[i] + is_immediate = is_true(argv[i] + sizeof(import_mode) - 1); } else if (!strncmp(argv[i], format, sizeof(format) - 1)) { import_format = parse_import_format(argv[i] + sizeof(format) - 1); } + else if (!strncmp(argv[i], y_inverted, + sizeof(y_inverted) - 1)) { + if (is_true(argv[i] + sizeof(y_inverted) - 1)) + opts |= OPT_Y_INVERTED; + } else { print_usage_and_exit(); } @@ -890,7 +905,7 @@ main(int argc, char **argv) } display = create_display(is_immediate, import_format); - window = create_window(display, 256, 256, import_format); + window = create_window(display, 256, 256, import_format, opts); if (!window) return 1; From 2e24198974c83a7e4a7d6fec6bdaccb5775b875b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 16 Mar 2018 18:56:49 +0100 Subject: [PATCH 0375/1642] simple-dmabuf-drm: use opt bitmask instead of is_immediate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Günther Reviewed-by: Pekka Paalanen --- clients/simple-dmabuf-drm.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 1789db737..427597ab0 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -62,6 +62,7 @@ struct buffer; /* Possible options that affect the displayed image */ #define OPT_Y_INVERTED 1 /* contents has y axis inverted */ +#define OPT_IMMEDIATE 2 /* create wl_buffer immediately */ struct display { @@ -756,7 +757,7 @@ static const struct wl_registry_listener registry_listener = { }; static struct display * -create_display(int is_immediate, int format) +create_display(int opts, int format) { struct display *display; const char *extensions; @@ -769,7 +770,7 @@ create_display(int is_immediate, int format) display->display = wl_display_connect(NULL); assert(display->display); - display->req_dmabuf_immediate = is_immediate; + display->req_dmabuf_immediate = opts & OPT_IMMEDIATE; display->req_dmabuf_modifiers = (format == DRM_FORMAT_NV12); /* @@ -874,7 +875,6 @@ main(int argc, char **argv) struct sigaction sigint; struct display *display; struct window *window; - int is_immediate = 0; int opts = 0; int import_format = DRM_FORMAT_XRGB8888; int ret = 0, i = 0; @@ -886,8 +886,8 @@ main(int argc, char **argv) for (i = 1; i < argc; i++) { if (!strncmp(argv[i], import_mode, sizeof(import_mode) - 1)) { - is_immediate = is_true(argv[i] - + sizeof(import_mode) - 1); + if (is_true(argv[i] + sizeof(import_mode) - 1)) + opts |= OPT_IMMEDIATE; } else if (!strncmp(argv[i], format, sizeof(format) - 1)) { import_format = parse_import_format(argv[i] @@ -904,7 +904,7 @@ main(int argc, char **argv) } } - display = create_display(is_immediate, import_format); + display = create_display(opts, import_format); window = create_window(display, 256, 256, import_format, opts); if (!window) return 1; From 60970ec27cb906c9fd4ae023820f8c5be0b63487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 16 Mar 2018 18:56:50 +0100 Subject: [PATCH 0376/1642] simple-dmabuf-drm: use getopt_long MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Günther Reviewed-by: Pekka Paalanen --- clients/simple-dmabuf-drm.c | 51 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 427597ab0..4f26e4a93 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -38,6 +38,7 @@ #include #include #include +#include #include @@ -877,30 +878,32 @@ main(int argc, char **argv) struct window *window; int opts = 0; int import_format = DRM_FORMAT_XRGB8888; - int ret = 0, i = 0; - - if (argc > 1) { - static const char import_mode[] = "--import-immediate="; - static const char format[] = "--import-format="; - static const char y_inverted[] = "--y-inverted="; - for (i = 1; i < argc; i++) { - if (!strncmp(argv[i], import_mode, - sizeof(import_mode) - 1)) { - if (is_true(argv[i] + sizeof(import_mode) - 1)) - opts |= OPT_IMMEDIATE; - } - else if (!strncmp(argv[i], format, sizeof(format) - 1)) { - import_format = parse_import_format(argv[i] - + sizeof(format) - 1); - } - else if (!strncmp(argv[i], y_inverted, - sizeof(y_inverted) - 1)) { - if (is_true(argv[i] + sizeof(y_inverted) - 1)) - opts |= OPT_Y_INVERTED; - } - else { - print_usage_and_exit(); - } + int c, option_index, ret = 0; + + static struct option long_options[] = { + {"import-format", required_argument, 0, 'f' }, + {"import-immediate", required_argument, 0, 'i' }, + {"y-inverted", required_argument, 0, 'y' }, + {"help", no_argument , 0, 'h' }, + {0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, "hf:i:y:", + long_options, &option_index)) != -1) { + switch (c) { + case 'f': + import_format = parse_import_format(optarg); + break; + case 'i': + if (is_true(optarg)) + opts |= OPT_IMMEDIATE; + break; + case 'y': + if (is_true(optarg)) + opts |= OPT_Y_INVERTED; + break; + default: + print_usage_and_exit(); } } From 6b2fb180d99bb9d6deaddb1cdf735422d4dd5b93 Mon Sep 17 00:00:00 2001 From: Dima Ryazanov Date: Sun, 18 Mar 2018 00:20:29 -0400 Subject: [PATCH 0377/1642] Fix an uninitialized variable "has_discrete" gets set to true in if/else if, but gets left unset otherwise. So let's initialize it to false. (This was caught by valgrind.) Signed-off-by: Dima Ryazanov Reviewed-by: Pekka Paalanen --- libweston/compositor-wayland.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index f51f78dde..111c4c098 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1707,6 +1707,7 @@ input_handle_axis(void *data, struct wl_pointer *pointer, weston_event.axis = axis; weston_event.value = wl_fixed_to_double(value); + weston_event.has_discrete = false; if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL && input->vert.has_discrete) { From dc0e65413ef90a5d20a1fdfa3e1e454491c4900e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 19 Mar 2018 17:45:18 +0100 Subject: [PATCH 0378/1642] simple-dmabuf-drm: Always define ALIGN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Other backends might want to use it. Signed-off-by: Guido Günther Reviewed-by: Derek Foreman --- clients/simple-dmabuf-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 4f26e4a93..efe3b7f5d 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -65,6 +65,7 @@ struct buffer; #define OPT_Y_INVERTED 1 /* contents has y axis inverted */ #define OPT_IMMEDIATE 2 /* create wl_buffer immediately */ +#define ALIGN(v, a) ((v + a - 1) & ~(a - 1)) struct display { struct wl_display *display; @@ -215,7 +216,6 @@ intel_device_destroy(struct buffer *my_buf) #endif /* HAVE_LIBDRM_INTEL */ #ifdef HAVE_LIBDRM_FREEDRENO -#define ALIGN(v, a) ((v + a - 1) & ~(a - 1)) static int fd_alloc_bo(struct buffer *buf) From 3ed7a0000840253a233363e64308d93f49c48364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 19 Mar 2018 17:45:19 +0100 Subject: [PATCH 0379/1642] simple-dmabuf-drm: use appropriately sized buffer (freedreno) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use stride instead of width for buffer calculation. [Derek Foreman edited the commit log and removed the leftover initialization of 'size'] Signed-off-by: Guido Günther Reviewed-by: Derek Foreman --- clients/simple-dmabuf-drm.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index efe3b7f5d..2975f3a5e 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -221,14 +221,16 @@ static int fd_alloc_bo(struct buffer *buf) { int flags = DRM_FREEDRENO_GEM_CACHE_WCOMBINE; - int size = buf->width * buf->height * buf->bpp / 8; - buf->fd_dev = fd_device_new(buf->drm_fd); + int size; + buf->fd_dev = fd_device_new(buf->drm_fd); + buf->stride = ALIGN(buf->width, 32) * buf->bpp / 8; + size = buf->stride * buf->height; + buf->fd_dev = fd_device_new(buf->drm_fd); buf->fd_bo = fd_bo_new(buf->fd_dev, size, flags); if (!buf->fd_bo) return 0; - buf->stride = ALIGN(buf->width, 32) * buf->bpp / 8; return 1; } From dc4024627cffce95f35f27eb72f92b31b68bd2a0 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 19 Mar 2018 15:41:17 -0500 Subject: [PATCH 0380/1642] configure.ac: bump to version 3.0.92 for the beta release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 90ffc88d7..070e61b96 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [3]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [91]) +m4_define([weston_micro_version], [92]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [4]) From d2cb711d813e750b1e303e6200c027fd27a21f8e Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Mon, 19 Mar 2018 18:06:03 -0600 Subject: [PATCH 0381/1642] xwm: Fix memory leak Fix memory leak introduced by 6b58ea8c. weston_wm_handle_icon() was calling xcb_get_property_reply() without freeing the reply. Reviewed-by: Pekka Paalanen --- xwayland/window-manager.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index c307e1992..24e721326 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -1387,6 +1387,8 @@ weston_wm_handle_icon(struct weston_wm *wm, struct weston_wm_window *window) CAIRO_FORMAT_ARGB32, width, height, width * 4); + free(reply); + /* Bail out in case anything wrong happened during surface creation. */ if (cairo_surface_status(new_surface) != CAIRO_STATUS_SUCCESS) { cairo_surface_destroy(new_surface); From 9fe5d5fae9d41bb5f9ec070dbbc0567c738f4141 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 20 Mar 2018 10:12:37 +0200 Subject: [PATCH 0382/1642] Revert "xwm: Fix memory leak" This reverts commit d2cb711d813e750b1e303e6200c027fd27a21f8e. I missed the call to cairo_image_surface_create_for_data() which assumes the data will remain present until the cairo surface is destroyed. It seems the existence of data depends on the reply not being freed. This will need a more involved fix. Sorry, I noticed this just seconds after I pushed the patch. Signed-off-by: Pekka Paalanen --- xwayland/window-manager.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 24e721326..c307e1992 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -1387,8 +1387,6 @@ weston_wm_handle_icon(struct weston_wm *wm, struct weston_wm_window *window) CAIRO_FORMAT_ARGB32, width, height, width * 4); - free(reply); - /* Bail out in case anything wrong happened during surface creation. */ if (cairo_surface_status(new_surface) != CAIRO_STATUS_SUCCESS) { cairo_surface_destroy(new_surface); From 4d1cd36c9ea041688f92cd8981e43b5fe3b52409 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Fri, 23 Mar 2018 13:41:43 -0600 Subject: [PATCH 0383/1642] xwm: Fix memory leak A memory leak introduced by 6b58ea8c led to me finding a bigger leak, which is xwm was calling frame_create() without calling frame_destroy(). This meant that the associated icon_surface was not being destroyed, leaving the destroy handler for it broken. Here we fix this by calling frame_destroy() when the window is destroyed and free the reply in the icon_surface destroy handler. Reviewed-by: Derek Foreman Acked-by: Pekka Paalanen --- xwayland/window-manager.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index c307e1992..dad117fa4 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -1352,6 +1352,12 @@ weston_wm_window_schedule_repaint(struct weston_wm_window *window) weston_wm_window_do_repaint, window); } +static void +handle_icon_surface_destroy(void *data) +{ + free(data); +} + static void weston_wm_handle_icon(struct weston_wm *wm, struct weston_wm_window *window) { @@ -1371,16 +1377,20 @@ weston_wm_handle_icon(struct weston_wm *wm, struct weston_wm_window *window) length = xcb_get_property_value_length(reply); /* This is in 32-bit words, not in bytes. */ - if (length < 2) + if (length < 2) { + free(reply); return; + } data = xcb_get_property_value(reply); width = *data++; height = *data++; /* Some checks against malformed input. */ - if (width == 0 || height == 0 || length < 2 + width * height) + if (width == 0 || height == 0 || length < 2 + width * height) { + free(reply); return; + } new_surface = cairo_image_surface_create_for_data((unsigned char *)data, @@ -1390,9 +1400,13 @@ weston_wm_handle_icon(struct weston_wm *wm, struct weston_wm_window *window) /* Bail out in case anything wrong happened during surface creation. */ if (cairo_surface_status(new_surface) != CAIRO_STATUS_SUCCESS) { cairo_surface_destroy(new_surface); + free(reply); return; } + cairo_surface_set_user_data(new_surface, NULL, reply, + &handle_icon_surface_destroy); + if (window->frame) frame_set_icon(window->frame, new_surface); else /* We don’t have a frame yet */ @@ -1502,6 +1516,9 @@ weston_wm_window_destroy(struct weston_wm_window *window) window->frame_id = XCB_WINDOW_NONE; } + if (window->frame) + frame_destroy(window->frame); + if (window->surface_id) wl_list_remove(&window->link); From ff449f5ab13e465b497a059f5718d6c8037dec87 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Tue, 20 Mar 2018 15:28:22 +0100 Subject: [PATCH 0384/1642] compositor-drm: remove dead assigment in drm_fb_create_dumb ret is overwritten by drmModeAddFB2 call (Found by clang source code analyzer) Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 81ca67d60..c09c49bfd 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -891,8 +891,6 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, fb->height = height; fb->fd = b->drm.fd; - ret = -1; - handles[0] = fb->handle; pitches[0] = fb->stride; offsets[0] = 0; From 16ac6a0f9d084279758c55a87f5d8e3435a9e69b Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Tue, 20 Mar 2018 15:28:23 +0100 Subject: [PATCH 0385/1642] hmi-controller: remove dead assignments in add_launchers assigned values of x, y, ret and layout_surface are never read. (Found by clang source code analyzer) Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/hmi-controller.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index b13936f59..0e44df88b 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -1184,10 +1184,6 @@ ivi_hmi_controller_add_launchers(struct hmi_controller *hmi_ctrl, compare_launcher_info); wl_array_for_each(data, &launchers) { - x = 0; - y = 0; - ret = 0; - layout_surface = NULL; add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, sizeof(*add_surface_id)); From 3796b59e746f314cf533c1f8e41932bb40455e81 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Tue, 20 Mar 2018 15:28:24 +0100 Subject: [PATCH 0386/1642] input: fix use-after-free issue at pointer_cancel If the constraint is an one-shot constraint, constraint is freed in disable_pointer_constraint function. Therefore, we should not try to read freed memory at "switch (constraint->lifetime)" statement. The removed code is anyway superfluous. Because surface destroy signal is only removed, when constraint is an one-shot constraint. (Found by clang source code analyzer) Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- libweston/input.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/libweston/input.c b/libweston/input.c index 3e91c266a..a9d21cb51 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -4577,18 +4577,6 @@ confined_pointer_grab_pointer_cancel(struct weston_pointer_grab *grab) container_of(grab, struct weston_pointer_constraint, grab); disable_pointer_constraint(constraint); - - /* If this is a persistent constraint, re-add the surface destroy signal - * listener only if we are currently not destroying the surface. */ - switch (constraint->lifetime) { - case ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT: - if (constraint->surface->resource) - wl_signal_add(&constraint->surface->destroy_signal, - &constraint->surface_destroy_listener); - break; - case ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT: - break; - } } static const struct weston_pointer_grab_interface From 95c7095e138ed566c8d14d2ea5f41dbb109b6d5b Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Tue, 20 Mar 2018 15:29:39 +0100 Subject: [PATCH 0387/1642] gl-renderer: set num_images after import_simple_dmabuf we have to set num_images after import_simple_dmabuf call. Otherwise, egl_images will not be correctly referenced in gl_renderer_attach_dmabuf. (Found by clang source code analyzer) Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- libweston/gl-renderer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index a6b29a924..2c50d2da3 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -2216,6 +2216,7 @@ import_known_dmabuf(struct gl_renderer *gr, image->images[0] = import_simple_dmabuf(gr, &image->dmabuf->attributes); if (!image->images[0]) return false; + image->num_images = 1; break; case IMPORT_TYPE_GL_CONVERSION: From e479ed8ec22514bb608c5593fe963701398e62f6 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Tue, 20 Mar 2018 15:29:40 +0100 Subject: [PATCH 0388/1642] compositor: initialize ret in repaint_timer_handler If output_list of compositor is empty, value of ret is read without initialization. Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 67b5d2830..4816f33e6 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2457,7 +2457,7 @@ output_repaint_timer_handler(void *data) struct weston_output *output; struct timespec now; void *repaint_data = NULL; - int ret; + int ret = 0; weston_compositor_read_presentation_clock(compositor, &now); From 77db9316ebd75c57c210417fd059a6d377047b56 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Tue, 20 Mar 2018 15:29:41 +0100 Subject: [PATCH 0389/1642] ivi-shell: remove dead assignments in layer transition Signed-off-by: Emre Ucan Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-layout-transition.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ivi-shell/ivi-layout-transition.c b/ivi-shell/ivi-layout-transition.c index b887ff657..a223b58a9 100644 --- a/ivi-shell/ivi-layout-transition.c +++ b/ivi-shell/ivi-layout-transition.c @@ -843,10 +843,10 @@ ivi_layout_transition_fade_layer( uint32_t duration) { struct ivi_layout_transition *transition; - struct fade_layer_data *data = NULL; - wl_fixed_t fixed_opacity = 0.0; - double now_opacity = 0.0; - double remain = 0.0; + struct fade_layer_data *data; + wl_fixed_t fixed_opacity; + double now_opacity; + double remain; transition = get_transition_from_type_and_id( IVI_LAYOUT_TRANSITION_LAYER_FADE, @@ -858,7 +858,6 @@ ivi_layout_transition_fade_layer( /* FIXME */ fixed_opacity = layer->prop.opacity; now_opacity = wl_fixed_to_double(fixed_opacity); - remain = 0.0; data->is_fade_in = is_fade_in; data->start_alpha = now_opacity; From bef761796c2ada6344d227142af4a0f40b9760dd Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 27 Mar 2018 11:09:32 -0500 Subject: [PATCH 0390/1642] xwm: Fix two more icon related memory leaks Hopefully sort the last leaks introduced in commit 6b58ea8c The window could be destroyed before it had a frame but after it had an icon (I could trigger this with firefox), and the window could be assigned an icon twice before it had a frame (I could trigger this with terminology). The latter leak was Reported-by: Scott Moreau Signed-off-by: Derek Foreman Reviewed-by: Pekka Paalanen --- xwayland/window-manager.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index dad117fa4..7b98e1405 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -1404,6 +1404,9 @@ weston_wm_handle_icon(struct weston_wm *wm, struct weston_wm_window *window) return; } + if (window->icon_surface) + cairo_surface_destroy(window->icon_surface); + cairo_surface_set_user_data(new_surface, NULL, reply, &handle_icon_surface_destroy); @@ -1506,6 +1509,8 @@ weston_wm_window_destroy(struct weston_wm_window *window) wl_event_source_remove(window->repaint_source); if (window->cairo_surface) cairo_surface_destroy(window->cairo_surface); + if (window->icon_surface) + cairo_surface_destroy(window->icon_surface); if (window->frame_id) { xcb_reparent_window(wm->conn, window->id, wm->wm_window, 0, 0); From 676523396e19f630d073484bee5c9de81a733263 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 28 Mar 2018 09:33:56 -0500 Subject: [PATCH 0391/1642] Revert "xwm: do not include shadow in input region" This reverts commit 332d1892bbb380b32ff1c9f99d20184b447535dd. And re-introduces the bug it was intended to fix, see: https://lists.freedesktop.org/archives/wayland-devel/2017-December/036402.html Reverting this because it causes harm to all xwayland clients - the input region no longer gets adjusted when resizing windows. start an xterm, resize it larger, you can no longer interact with the new area of the window (including the server side decor). --- xwayland/window-manager.c | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 7b98e1405..62087941c 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -644,20 +644,6 @@ weston_wm_window_get_child_position(struct weston_wm_window *window, } } -static void -weston_wm_window_get_input_rect(struct weston_wm_window *window, - int32_t *x, int32_t *y, - int32_t *width, int32_t *height) -{ - if (!window->decorate) { - weston_wm_window_get_child_position(window, x, y); - *width = window->width; - *height = window->height; - } else { - frame_input_rect(window->frame, x, y, width, height); - } -} - static void weston_wm_window_send_configure_notify(struct weston_wm_window *window) { @@ -983,7 +969,6 @@ weston_wm_window_create_frame(struct weston_wm_window *window) { struct weston_wm *wm = window->wm; uint32_t values[3]; - xcb_rectangle_t rect; int x, y, width, height; int buttons = FRAME_BUTTON_CLOSE; @@ -1040,25 +1025,6 @@ weston_wm_window_create_frame(struct weston_wm_window *window) &wm->format_rgba, width, height); - weston_wm_window_get_input_rect(window, &x, &y, &width, &height); - rect.x = x; - rect.y = y; - rect.width = width; - rect.height = height; - - /* The window frame was created with position and size which include - * an offset for margins and shadow. Set the input region to ignore - * shadow. */ - xcb_shape_rectangles(wm->conn, - XCB_SHAPE_SO_SET, - XCB_SHAPE_SK_INPUT, - 0, - window->frame_id, - 0, - 0, - 1, - &rect); - hash_table_insert(wm->window_hash, window->frame_id, window); } From 0dc0df247188660c22f90ba5035da4dbe8e34fa5 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 30 Mar 2018 11:56:22 -0500 Subject: [PATCH 0392/1642] Partially revert "xwm: Add icon support to the frame" and friends This (partially) reverts commit bef761796c2ada6344d227142af4a0f40b9760dd. This (partially) reverts commit 4d1cd36c9ea041688f92cd8981e43b5fe3b52409. This (partially) reverts commit 44fc1be913ab2faad0414f50e51d58310302d065. This (partially) reverts commit 6b58ea8c43ac81e519bd418efbf24687a5d731b8. The new xwm icon code has proven to be leaky and incomplete, and while we have patches under consideration to fix the rest of its known problems they still require changes and review cycles. Currently the known leaks have been squashed, but it still picks wrong sized icons and does no scaling, which can lead to very strange rendering. At window close time the wrong sized icon appears above the window during fade out. This patch reverts the mostly solid bits and keeps the unfinished bits behind in favor of a simpler revert than removing the whole thing. Signed-off-by: Derek Foreman Acked-by: Daniel Stone Reviewed-by: Quentin Glidic --- xwayland/window-manager.c | 88 +-------------------------------------- 1 file changed, 2 insertions(+), 86 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 62087941c..06370b70f 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -138,8 +138,6 @@ struct weston_wm_window { xcb_window_t frame_id; struct frame *frame; cairo_surface_t *cairo_surface; - int icon; - cairo_surface_t *icon_surface; /* A temporary slot, to be passed to frame on creation */ uint32_t surface_id; struct weston_surface *surface; struct weston_desktop_xwayland_surface *shsurf; @@ -475,7 +473,6 @@ weston_wm_window_read_properties(struct weston_wm_window *window) { wm->atom.net_wm_state, TYPE_NET_WM_STATE, NULL }, { wm->atom.net_wm_window_type, XCB_ATOM_ATOM, F(type) }, { wm->atom.net_wm_name, XCB_ATOM_STRING, F(name) }, - { wm->atom.net_wm_icon, XCB_ATOM_CARDINAL, F(icon) }, { wm->atom.net_wm_pid, XCB_ATOM_CARDINAL, F(pid) }, { wm->atom.motif_wm_hints, TYPE_MOTIF_WM_HINTS, NULL }, { wm->atom.wm_client_machine, XCB_ATOM_WM_CLIENT_MACHINE, F(machine) }, @@ -976,10 +973,8 @@ weston_wm_window_create_frame(struct weston_wm_window *window) buttons |= FRAME_BUTTON_MAXIMIZE; window->frame = frame_create(window->wm->theme, - window->width, window->height, - buttons, window->name, - window->icon_surface); - window->icon_surface = NULL; + window->width, window->height, + buttons, window->name, NULL); frame_resize_inside(window->frame, window->width, window->height); weston_wm_window_get_frame_size(window, &width, &height); @@ -1318,70 +1313,6 @@ weston_wm_window_schedule_repaint(struct weston_wm_window *window) weston_wm_window_do_repaint, window); } -static void -handle_icon_surface_destroy(void *data) -{ - free(data); -} - -static void -weston_wm_handle_icon(struct weston_wm *wm, struct weston_wm_window *window) -{ - xcb_get_property_reply_t *reply; - xcb_get_property_cookie_t cookie; - uint32_t length; - uint32_t *data, width, height; - cairo_surface_t *new_surface; - - /* TODO: icons don’t have any specified order, we should pick the - * closest one to the target dimension instead of the first one. */ - - cookie = xcb_get_property(wm->conn, 0, window->id, - wm->atom.net_wm_icon, XCB_ATOM_ANY, 0, - UINT32_MAX); - reply = xcb_get_property_reply(wm->conn, cookie, NULL); - length = xcb_get_property_value_length(reply); - - /* This is in 32-bit words, not in bytes. */ - if (length < 2) { - free(reply); - return; - } - - data = xcb_get_property_value(reply); - width = *data++; - height = *data++; - - /* Some checks against malformed input. */ - if (width == 0 || height == 0 || length < 2 + width * height) { - free(reply); - return; - } - - new_surface = - cairo_image_surface_create_for_data((unsigned char *)data, - CAIRO_FORMAT_ARGB32, - width, height, width * 4); - - /* Bail out in case anything wrong happened during surface creation. */ - if (cairo_surface_status(new_surface) != CAIRO_STATUS_SUCCESS) { - cairo_surface_destroy(new_surface); - free(reply); - return; - } - - if (window->icon_surface) - cairo_surface_destroy(window->icon_surface); - - cairo_surface_set_user_data(new_surface, NULL, reply, - &handle_icon_surface_destroy); - - if (window->frame) - frame_set_icon(window->frame, new_surface); - else /* We don’t have a frame yet */ - window->icon_surface = new_surface; -} - static void weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *event) { @@ -1402,19 +1333,6 @@ weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *even read_and_dump_property(wm, property_notify->window, property_notify->atom); - if (property_notify->atom == wm->atom.net_wm_icon) { - if (property_notify->state != XCB_PROPERTY_DELETE) { - weston_wm_handle_icon(wm, window); - } else { - if (window->frame) - frame_set_icon(window->frame, NULL); - if (window->icon_surface) - cairo_surface_destroy(window->icon_surface); - window->icon_surface = NULL; - } - weston_wm_window_schedule_repaint(window); - } - if (property_notify->atom == wm->atom.net_wm_name || property_notify->atom == XCB_ATOM_WM_NAME) weston_wm_window_schedule_repaint(window); @@ -1475,8 +1393,6 @@ weston_wm_window_destroy(struct weston_wm_window *window) wl_event_source_remove(window->repaint_source); if (window->cairo_surface) cairo_surface_destroy(window->cairo_surface); - if (window->icon_surface) - cairo_surface_destroy(window->icon_surface); if (window->frame_id) { xcb_reparent_window(wm->conn, window->id, wm->wm_window, 0, 0); From 10fe82fe2d45f19e4df96e77d21fb11564c3398a Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 2 Apr 2018 13:00:01 -0500 Subject: [PATCH 0393/1642] configure.ac: bump to version 3.0.93 for the RC1 release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 070e61b96..ff394697e 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [3]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [92]) +m4_define([weston_micro_version], [93]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [4]) From ee96ce2fc2ac54f61316ecc357c2777f30da5f92 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 3 Apr 2018 19:38:09 +0100 Subject: [PATCH 0394/1642] terminal: Fix unintended fallthrough to cursor restore ef57a9b788 added support for window operations such as reporting the title in escape mode. It implemented this by which-window-op case, inside the existing which-escape-code case. Whilst it would break out of the former window-op case, it never broke out of the latter escape-code case. This would lead to window ops (such as reporting title) falling through to restoring the saved cursor position. This doesn't seem at all right, and also fixes a warning with GCC 8. Signed-off-by: Daniel Stone Reviewed-by: Derek Foreman --- clients/terminal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/clients/terminal.c b/clients/terminal.c index 16a449540..f792badcd 100644 --- a/clients/terminal.c +++ b/clients/terminal.c @@ -1686,6 +1686,7 @@ handle_escape(struct terminal *terminal) fprintf(stderr, "Unimplemented windowOp %d\n", args[0]); break; } + break; case 'u': /* Restore cursor location */ terminal->row = terminal->saved_row; terminal->column = terminal->saved_column; From e5f33d0112e98fe0ed2cd853807cd200d4824c31 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 9 Apr 2018 11:55:01 -0500 Subject: [PATCH 0395/1642] configure.ac: bump to version 4.0.0 for the official release --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index ff394697e..7aebbdbde 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ -m4_define([weston_major_version], [3]) +m4_define([weston_major_version], [4]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [93]) +m4_define([weston_micro_version], [0]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [4]) From 8eb71d1e2c2ed9a2905eb5561535f9a7678e7cfd Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 9 Apr 2018 13:13:40 -0500 Subject: [PATCH 0396/1642] configure.ac: Reopen master for regular development --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 7aebbdbde..d35784f1c 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [4]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [0]) +m4_define([weston_micro_version], [90]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [4]) From 61c4a730d2106c071667524b2e24325910fdec96 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 9 Apr 2018 16:13:09 -0500 Subject: [PATCH 0397/1642] configure.ac: Bump libweston version to match weston version configure won't work if weston's version is higher than libweston's. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index d35784f1c..cde4564ec 100644 --- a/configure.ac +++ b/configure.ac @@ -5,7 +5,7 @@ m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [4]) m4_define([libweston_minor_version], [0]) -m4_define([libweston_patch_version], [0]) +m4_define([libweston_patch_version], [90]) AC_PREREQ([2.64]) AC_INIT([weston], From 01f60211b2ff3d12bd8bc6a008ba07c30a666760 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 24 Mar 2017 15:39:24 +0200 Subject: [PATCH 0398/1642] libweston: introduce weston_head In order to support clone modes, libweston needs the concept of a head that is separate from weston_output. While weston_output manages buffers and the repaint state machine, weston_head will represent a single monitor. In the future it will be possible to have a single weston_output drive one or more weston_heads for a clone mode that shares the framebuffers between all cloned heads. All the fields that are obviously properties of the monitor are moved from weston_output into weston_head. As moving the fields requires one to touch all the backends for all the assingments, introduce setter functions for them while we are here. The setters are identical to the old assignments, for now. As a temporary measure, weston_output embeds a single head. Also the ugly casts in weston_head_set_monitor_strings() will be removed by a follow-up patch. Libweston major version is bumped, because weston_output struct layout is changed. v7: - Bump libweston major version. v6: - adapt to upstream changes in weston_output_set_transform() Signed-off-by: Pekka Paalanen v5 Reviewed-by: Derek Foreman v6 Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- compositor/cms-colord.c | 32 ++++----- configure.ac | 4 +- libweston/compositor-drm.c | 17 ++--- libweston/compositor-fbdev.c | 12 ++-- libweston/compositor-headless.c | 8 +-- libweston/compositor-rdp.c | 8 +-- libweston/compositor-wayland.c | 18 ++--- libweston/compositor-x11.c | 13 ++-- libweston/compositor.c | 116 +++++++++++++++++++++++++++----- libweston/compositor.h | 38 +++++++++-- 10 files changed, 193 insertions(+), 73 deletions(-) diff --git a/compositor/cms-colord.c b/compositor/cms-colord.c index 0daa2a7e9..f421773b3 100644 --- a/compositor/cms-colord.c +++ b/compositor/cms-colord.c @@ -102,22 +102,23 @@ edid_value_valid(const char *str) static gchar * get_output_id(struct cms_colord *cms, struct weston_output *o) { + struct weston_head *head = &o->head; const gchar *tmp; GString *device_id; /* see https://github.com/hughsie/colord/blob/master/doc/device-and-profile-naming-spec.txt * for format and allowed values */ device_id = g_string_new("xrandr"); - if (edid_value_valid(o->make)) { - tmp = g_hash_table_lookup(cms->pnp_ids, o->make); + if (edid_value_valid(head->make)) { + tmp = g_hash_table_lookup(cms->pnp_ids, head->make); if (tmp == NULL) - tmp = o->make; + tmp = head->make; g_string_append_printf(device_id, "-%s", tmp); } - if (edid_value_valid(o->model)) - g_string_append_printf(device_id, "-%s", o->model); - if (edid_value_valid(o->serial_number)) - g_string_append_printf(device_id, "-%s", o->serial_number); + if (edid_value_valid(head->model)) + g_string_append_printf(device_id, "-%s", head->model); + if (edid_value_valid(head->serial_number)) + g_string_append_printf(device_id, "-%s", head->serial_number); /* no EDID data, so use fallback */ if (strcmp(device_id->str, "xrandr") == 0) @@ -230,6 +231,7 @@ colord_notifier_output_destroy(struct wl_listener *listener, void *data) static void colord_output_created(struct cms_colord *cms, struct weston_output *o) { + struct weston_head *head = &o->head; CdDevice *device; const gchar *tmp; gchar *device_id; @@ -251,25 +253,25 @@ colord_output_created(struct cms_colord *cms, struct weston_output *o) g_hash_table_insert (device_props, g_strdup(CD_DEVICE_PROPERTY_COLORSPACE), g_strdup(cd_colorspace_to_string(CD_COLORSPACE_RGB))); - if (edid_value_valid(o->make)) { - tmp = g_hash_table_lookup(cms->pnp_ids, o->make); + if (edid_value_valid(head->make)) { + tmp = g_hash_table_lookup(cms->pnp_ids, head->make); if (tmp == NULL) - tmp = o->make; + tmp = head->make; g_hash_table_insert (device_props, g_strdup(CD_DEVICE_PROPERTY_VENDOR), g_strdup(tmp)); } - if (edid_value_valid(o->model)) { + if (edid_value_valid(head->model)) { g_hash_table_insert (device_props, g_strdup(CD_DEVICE_PROPERTY_MODEL), - g_strdup(o->model)); + g_strdup(head->model)); } - if (edid_value_valid(o->serial_number)) { + if (edid_value_valid(head->serial_number)) { g_hash_table_insert (device_props, g_strdup(CD_DEVICE_PROPERTY_SERIAL), - g_strdup(o->serial_number)); + g_strdup(head->serial_number)); } - if (o->connection_internal) { + if (head->connection_internal) { g_hash_table_insert (device_props, g_strdup (CD_DEVICE_PROPERTY_EMBEDDED), NULL); diff --git a/configure.ac b/configure.ac index cde4564ec..da3f73424 100644 --- a/configure.ac +++ b/configure.ac @@ -3,9 +3,9 @@ m4_define([weston_minor_version], [0]) m4_define([weston_micro_version], [90]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) -m4_define([libweston_major_version], [4]) +m4_define([libweston_major_version], [5]) m4_define([libweston_minor_version], [0]) -m4_define([libweston_patch_version], [90]) +m4_define([libweston_patch_version], [0]) AC_PREREQ([2.64]) AC_INIT([weston], diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c09c49bfd..4504c00c6 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4933,6 +4933,7 @@ create_output_for_connector(struct drm_backend *b, struct udev_device *drm_device) { struct drm_output *output; + struct weston_head *head; drmModeObjectPropertiesPtr props; struct drm_mode *drm_mode; char *name; @@ -4973,26 +4974,26 @@ create_output_for_connector(struct drm_backend *b, } drm_property_info_populate(b, connector_props, output->props_conn, WDRM_CONNECTOR__COUNT, props); + head = &output->base.head; find_and_parse_output_edid(b, output, props, &make, &model, &serial_number); - output->base.make = (char *)make; - output->base.model = (char *)model; - output->base.serial_number = (char *)serial_number; - output->base.subpixel = drm_subpixel_to_wayland(output->connector->subpixel); + weston_head_set_monitor_strings(head, make, model, serial_number); + weston_head_set_subpixel(head, + drm_subpixel_to_wayland(output->connector->subpixel)); drmModeFreeObjectProperties(props); if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) - output->base.connection_internal = true; + weston_head_set_internal(head); if (drm_output_init_gamma_size(output) < 0) goto err_output; - output->state_cur = drm_output_state_alloc(output, NULL); + weston_head_set_physical_size(head, output->connector->mmWidth, + output->connector->mmHeight); - output->base.mm_width = output->connector->mmWidth; - output->base.mm_height = output->connector->mmHeight; + output->state_cur = drm_output_state_alloc(output, NULL); for (i = 0; i < output->connector->count_modes; i++) { drm_mode = drm_output_add_mode(output, &output->connector->modes[i]); diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index c63b1fc1f..7db95d218 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -499,6 +499,7 @@ fbdev_output_create(struct fbdev_backend *backend, const char *device) { struct fbdev_output *output; + struct weston_head *head; int fb_fd; weston_log("Creating fbdev output.\n"); @@ -532,12 +533,13 @@ fbdev_output_create(struct fbdev_backend *backend, wl_list_insert(&output->base.mode_list, &output->mode.link); output->base.current_mode = &output->mode; - output->base.subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; - output->base.make = "unknown"; - output->base.model = output->fb_info.id; - output->base.mm_width = output->fb_info.width_mm; - output->base.mm_height = output->fb_info.height_mm; + head = &output->base.head; + weston_head_set_monitor_strings(head, "unknown", output->fb_info.id, + NULL); + weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_UNKNOWN); + weston_head_set_physical_size(head, output->fb_info.width_mm, + output->fb_info.height_mm); close(fb_fd); diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index 9307a36a4..d6ab9db95 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -185,6 +185,7 @@ headless_output_set_size(struct weston_output *base, int width, int height) { struct headless_output *output = to_headless_output(base); + struct weston_head *head = &output->base.head; int output_width, output_height; /* We can only be called once. */ @@ -204,12 +205,11 @@ headless_output_set_size(struct weston_output *base, wl_list_insert(&output->base.mode_list, &output->mode.link); output->base.current_mode = &output->mode; - output->base.make = "weston"; - output->base.model = "headless"; + + weston_head_set_monitor_strings(head, "weston", "headless", NULL); /* XXX: Calculate proper size. */ - output->base.mm_width = width; - output->base.mm_height = height; + weston_head_set_physical_size(head, width, height); output->base.start_repaint_loop = headless_output_start_repaint_loop; output->base.repaint = headless_output_repaint; diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index ee68e969b..4d74d40cd 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -482,6 +482,7 @@ rdp_output_set_size(struct weston_output *base, int width, int height) { struct rdp_output *output = to_rdp_output(base); + struct weston_head *head = &output->base.head; struct weston_mode *currentMode; struct weston_mode initMode; @@ -500,12 +501,11 @@ rdp_output_set_size(struct weston_output *base, return -1; output->base.current_mode = output->base.native_mode = currentMode; - output->base.make = "weston"; - output->base.model = "rdp"; + + weston_head_set_monitor_strings(head, "weston", "rdp", NULL); /* XXX: Calculate proper size. */ - output->base.mm_width = width; - output->base.mm_height = height; + weston_head_set_physical_size(head, width, height); output->base.start_repaint_loop = rdp_output_start_repaint_loop; output->base.repaint = rdp_output_repaint; diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 111c4c098..fbb04deac 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1313,6 +1313,7 @@ static int wayland_output_set_size(struct weston_output *base, int width, int height) { struct wayland_output *output = to_wayland_output(base); + struct weston_head *head = &output->base.head; int output_width, output_height; /* We can only be called once. */ @@ -1345,12 +1346,11 @@ wayland_output_set_size(struct weston_output *base, int width, int height) wl_list_insert(&output->base.mode_list, &output->mode.link); output->base.current_mode = &output->mode; - output->base.make = "wayland"; - output->base.model = "none"; + + weston_head_set_monitor_strings(head, "wayland", "none", NULL); /* XXX: Calculate proper size. */ - output->base.mm_width = width; - output->base.mm_height = height; + weston_head_set_physical_size(head, width, height); return 0; } @@ -1383,10 +1383,12 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, output->parent.output = poutput->global; - output->base.make = poutput->physical.make; - output->base.model = poutput->physical.model; - output->base.mm_width = poutput->physical.width; - output->base.mm_height = poutput->physical.height; + weston_head_set_monitor_strings(&output->base.head, + poutput->physical.make, + poutput->physical.model, NULL); + weston_head_set_physical_size(&output->base.head, + poutput->physical.width, + poutput->physical.height); wl_list_insert_list(&output->base.mode_list, &poutput->mode_list); wl_list_init(&poutput->mode_list); diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 14faeda03..de19fad8f 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -1065,6 +1065,8 @@ x11_output_set_size(struct weston_output *base, int width, int height) { struct x11_output *output = to_x11_output(base); struct x11_backend *b = to_x11_backend(base->compositor); + struct weston_head *head = &output->base.head; + xcb_screen_t *scrn = b->screen; int output_width, output_height; /* We can only be called once. */ @@ -1099,16 +1101,13 @@ x11_output_set_size(struct weston_output *base, int width, int height) wl_list_insert(&output->base.mode_list, &output->mode.link); output->base.current_mode = &output->mode; - output->base.make = "weston-X11"; - output->base.model = "none"; - output->base.native_mode = &output->native; output->base.native_scale = output->base.scale; - output->base.mm_width = width * b->screen->width_in_millimeters / - b->screen->width_in_pixels; - output->base.mm_height = height * b->screen->height_in_millimeters / - b->screen->height_in_pixels; + weston_head_set_monitor_strings(head, "weston-X11", "none", NULL); + weston_head_set_physical_size(head, + width * scrn->width_in_millimeters / scrn->width_in_pixels, + height * scrn->height_in_millimeters / scrn->height_in_pixels); return 0; } diff --git a/libweston/compositor.c b/libweston/compositor.c index 4816f33e6..3a50a33f6 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4315,6 +4315,7 @@ bind_output(struct wl_client *client, struct weston_output *output = data; struct weston_mode *mode; struct wl_resource *resource; + struct weston_head *head = &output->head; resource = wl_resource_create(client, &wl_output_interface, version, id); @@ -4329,10 +4330,10 @@ bind_output(struct wl_client *client, wl_output_send_geometry(resource, output->x, output->y, - output->mm_width, - output->mm_height, - output->subpixel, - output->make, output->model, + head->mm_width, + head->mm_height, + head->subpixel, + head->make, head->model, output->transform); if (version >= WL_OUTPUT_SCALE_SINCE_VERSION) wl_output_send_scale(resource, @@ -4365,6 +4366,85 @@ weston_output_from_resource(struct wl_resource *resource) return wl_resource_get_user_data(resource); } +/** Store monitor make, model and serial number + * + * \param head The head to modify. + * \param make The monitor make. If EDID is available, the PNP ID. Otherwise + * any string, or NULL for none. + * \param model The monitor model or name, or a made-up string, or NULL for + * none. + * \param serialno The monitor serial number, a made-up string, or NULL for + * none. + * + * \memberof weston_head + * \internal + */ +WL_EXPORT void +weston_head_set_monitor_strings(struct weston_head *head, + const char *make, + const char *model, + const char *serialno) +{ + head->make = (char *)make; + head->model = (char *)model; + head->serial_number = (char *)serialno; +} + +/** Store physical image size + * + * \param head The head to modify. + * \param mm_width Image area width in millimeters. + * \param mm_height Image area height in millimeters. + * + * \memberof weston_head + * \internal + */ +WL_EXPORT void +weston_head_set_physical_size(struct weston_head *head, + int32_t mm_width, int32_t mm_height) +{ + head->mm_width = mm_width; + head->mm_height = mm_height; +} + +/** Store monitor sub-pixel layout + * + * \param head The head to modify. + * \param sp Sub-pixel layout. The possible values are: + * - WL_OUTPUT_SUBPIXEL_UNKNOWN, + * - WL_OUTPUT_SUBPIXEL_NONE, + * - WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB, + * - WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR, + * - WL_OUTPUT_SUBPIXEL_VERTICAL_RGB, + * - WL_OUTPUT_SUBPIXEL_VERTICAL_BGR + * + * \memberof weston_head + * \internal + */ +WL_EXPORT void +weston_head_set_subpixel(struct weston_head *head, + enum wl_output_subpixel sp) +{ + head->subpixel = sp; +} + +/** Mark the monitor as internal + * + * This is used for embedded screens, like laptop panels. + * + * \param head The head to mark as internal. + * + * By default a head is external. The type is often inferred from the physical + * connector type. + * + * \memberof weston_head + * \internal + */ +WL_EXPORT void +weston_head_set_internal(struct weston_head *head) +{ + head->connection_internal = true; +} /* Move other outputs when one is resized so the space remains contiguous. */ static void @@ -4481,6 +4561,7 @@ weston_output_init_geometry(struct weston_output *output, int x, int y) WL_EXPORT void weston_output_move(struct weston_output *output, int x, int y) { + struct weston_head *head = &output->head; struct wl_resource *resource; output->move_x = x - output->x; @@ -4501,11 +4582,11 @@ weston_output_move(struct weston_output *output, int x, int y) wl_output_send_geometry(resource, output->x, output->y, - output->mm_width, - output->mm_height, - output->subpixel, - output->make, - output->model, + head->mm_width, + head->mm_height, + head->subpixel, + head->make, + head->model, output->transform); if (wl_resource_get_version(resource) >= WL_OUTPUT_DONE_SINCE_VERSION) @@ -4695,6 +4776,7 @@ weston_output_set_transform(struct weston_output *output, struct weston_seat *seat; pixman_region32_t old_region; int mid_x, mid_y; + struct weston_head *head = &output->head; if (!output->enabled && output->transform == UINT32_MAX) { output->transform = transform; @@ -4715,11 +4797,11 @@ weston_output_set_transform(struct weston_output *output, wl_output_send_geometry(resource, output->x, output->y, - output->mm_width, - output->mm_height, - output->subpixel, - output->make, - output->model, + head->mm_width, + head->mm_height, + head->subpixel, + head->make, + head->model, output->transform); if (wl_resource_get_version(resource) >= WL_OUTPUT_DONE_SINCE_VERSION) @@ -4766,6 +4848,8 @@ weston_output_init(struct weston_output *output, struct weston_compositor *compositor, const char *name) { + struct weston_head *head = &output->head; + output->compositor = compositor; output->destroying = 0; output->name = strdup(name); @@ -4775,8 +4859,8 @@ weston_output_init(struct weston_output *output, /* Add some (in)sane defaults which can be used * for checking if an output was properly configured */ - output->mm_width = 0; - output->mm_height = 0; + head->mm_width = 0; + head->mm_height = 0; output->scale = 0; /* Can't use -1 on uint32_t and 0 is valid enum value */ output->transform = UINT32_MAX; diff --git a/libweston/compositor.h b/libweston/compositor.h index 010f1fa8b..3af3e9ab9 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -147,6 +147,21 @@ enum dpms_enum { WESTON_DPMS_OFF }; +/** Represents a monitor + * + * This object represents a monitor (hardware backends like DRM) or a window + * (windowed nested backends). + */ +struct weston_head { + int32_t mm_width; /**< physical image width in mm */ + int32_t mm_height; /**< physical image height in mm */ + char *make; /**< monitor manufacturer (PNP ID) */ + char *model; /**< monitor model */ + char *serial_number; /**< monitor serial */ + uint32_t subpixel; /**< enum wl_output_subpixel */ + bool connection_internal; /**< embedded monitor (e.g. laptop) */ +}; + struct weston_output { uint32_t id; char *name; @@ -165,7 +180,6 @@ struct weston_output { struct wl_list animation_list; int32_t x, y, width, height; - int32_t mm_width, mm_height; /** Output area in global coordinates, simple rect */ pixman_region32_t region; @@ -199,8 +213,6 @@ struct weston_output { int destroying; struct wl_list feedback_list; - char *make, *model, *serial_number; - uint32_t subpixel; uint32_t transform; int32_t native_scale; int32_t current_scale; @@ -211,6 +223,8 @@ struct weston_output { struct weston_mode *original_mode; struct wl_list mode_list; + struct weston_head head; + void (*start_repaint_loop)(struct weston_output *output); int (*repaint)(struct weston_output *output, pixman_region32_t *damage, @@ -224,7 +238,6 @@ struct weston_output { void (*set_backlight)(struct weston_output *output, uint32_t value); void (*set_dpms)(struct weston_output *output, enum dpms_enum level); - bool connection_internal; uint16_t gamma_size; void (*set_gamma)(struct weston_output *output, uint16_t size, @@ -1936,6 +1949,23 @@ weston_seat_set_keyboard_focus(struct weston_seat *seat, int weston_compositor_load_xwayland(struct weston_compositor *compositor); +void +weston_head_set_monitor_strings(struct weston_head *head, + const char *make, + const char *model, + const char *serialno); + +void +weston_head_set_physical_size(struct weston_head *head, + int32_t mm_width, int32_t mm_height); + +void +weston_head_set_subpixel(struct weston_head *head, + enum wl_output_subpixel sp); + +void +weston_head_set_internal(struct weston_head *head); + void weston_output_set_scale(struct weston_output *output, int32_t scale); From 1b9bf598a57aa60115ecaba6ee547db80927c9bb Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 27 Mar 2017 12:15:38 +0300 Subject: [PATCH 0399/1642] libweston: move wl_output to weston_head The wl_output protocol interface exposes things like monitor make, model, sub-pixel layout and physical dimensions. Obviously wl_output is meant to represent a monitor. The abstraction of a monitor is weston_head. Therefore move the wl_output global and the bound resources list into weston_head. When clone mode gets implemented in the future, this means that monitors driven by the same CRTC will still be represented as separate wl_output globals. This allows to accurately represent the hardware. Clone mode that used separate, not frame-locked, CRTCs to drive two monitors as clones would necessarily also be exposed as separate wl_output since they have different timings. v6: - adapt to upstream changes in weston_output_set_transform() Signed-off-by: Pekka Paalanen v5 Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 41 +++++++++++++++++++++++++---------------- libweston/compositor.h | 5 +++-- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 3a50a33f6..7992870bb 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -81,6 +81,7 @@ static void weston_mode_switch_finish(struct weston_output *output, int scale_changed) { struct weston_seat *seat; + struct weston_head *head; struct wl_resource *resource; pixman_region32_t old_output_region; int version; @@ -129,8 +130,10 @@ static void weston_mode_switch_finish(struct weston_output *output, if (!mode_changed && !scale_changed) return; + head = &output->head; + /* notify clients of the changes */ - wl_resource_for_each(resource, &output->resource_list) { + wl_resource_for_each(resource, &head->resource_list) { if (mode_changed) { wl_output_send_mode(resource, output->current_mode->flags, @@ -340,12 +343,14 @@ weston_presentation_feedback_present( uint32_t flags) { struct wl_client *client = wl_resource_get_client(feedback->resource); + struct weston_head *head; struct wl_resource *o; uint32_t tv_sec_hi; uint32_t tv_sec_lo; uint32_t tv_nsec; - wl_resource_for_each(o, &output->resource_list) { + head = &output->head; + wl_resource_for_each(o, &head->resource_list) { if (wl_resource_get_client(o) != client) continue; @@ -922,7 +927,7 @@ weston_view_damage_below(struct weston_view *view) /** Send wl_surface.enter/leave events * * \param surface The surface. - * \param output The entered/left output. + * \param head A head of the entered/left output. * \param enter True if entered. * \param left True if left. * @@ -931,7 +936,7 @@ weston_view_damage_below(struct weston_view *view) */ static void weston_surface_send_enter_leave(struct weston_surface *surface, - struct weston_output *output, + struct weston_head *head, bool enter, bool leave) { @@ -941,7 +946,7 @@ weston_surface_send_enter_leave(struct weston_surface *surface, assert(enter != leave); client = wl_resource_get_client(surface->resource); - wl_resource_for_each(wloutput, &output->resource_list) { + wl_resource_for_each(wloutput, &head->resource_list) { if (wl_resource_get_client(wloutput) != client) continue; @@ -981,7 +986,7 @@ weston_surface_update_output_mask(struct weston_surface *es, uint32_t mask) if (!(output_bit & different)) continue; - weston_surface_send_enter_leave(es, output, + weston_surface_send_enter_leave(es, &output->head, output_bit & entered, output_bit & left); } @@ -4324,7 +4329,7 @@ bind_output(struct wl_client *client, return; } - wl_list_insert(&output->resource_list, wl_resource_get_link(resource)); + wl_list_insert(&head->resource_list, wl_resource_get_link(resource)); wl_resource_set_implementation(resource, &output_interface, data, unbind_resource); wl_output_send_geometry(resource, @@ -4578,7 +4583,7 @@ weston_output_move(struct weston_output *output, int x, int y) wl_signal_emit(&output->compositor->output_moved_signal, output); /* Notify clients of the change for output position. */ - wl_resource_for_each(resource, &output->resource_list) { + wl_resource_for_each(resource, &head->resource_list) { wl_output_send_geometry(resource, output->x, output->y, @@ -4612,6 +4617,7 @@ weston_compositor_add_output(struct weston_compositor *compositor, struct weston_output *output) { struct weston_view *view, *next; + struct weston_head *head; assert(!output->enabled); @@ -4629,9 +4635,10 @@ weston_compositor_add_output(struct weston_compositor *compositor, wl_list_insert(compositor->output_list.prev, &output->link); output->enabled = true; - output->global = wl_global_create(compositor->wl_display, - &wl_output_interface, 3, - output, bind_output); + head = &output->head; + head->global = wl_global_create(compositor->wl_display, + &wl_output_interface, 3, + output, bind_output); wl_signal_emit(&compositor->output_created_signal, output); @@ -4703,6 +4710,7 @@ weston_compositor_remove_output(struct weston_output *output) struct weston_compositor *compositor = output->compositor; struct wl_resource *resource; struct weston_view *view; + struct weston_head *head; assert(output->destroying); assert(output->enabled); @@ -4723,9 +4731,10 @@ weston_compositor_remove_output(struct weston_output *output) wl_signal_emit(&compositor->output_destroyed_signal, output); wl_signal_emit(&output->destroy_signal, output); - wl_global_destroy(output->global); - output->global = NULL; - wl_resource_for_each(resource, &output->resource_list) { + head = &output->head; + wl_global_destroy(head->global); + head->global = NULL; + wl_resource_for_each(resource, &head->resource_list) { wl_resource_set_destructor(resource, NULL); } @@ -4793,7 +4802,7 @@ weston_output_set_transform(struct weston_output *output, output->dirty = 1; /* Notify clients of the change for output transform. */ - wl_resource_for_each(resource, &output->resource_list) { + wl_resource_for_each(resource, &head->resource_list) { wl_output_send_geometry(resource, output->x, output->y, @@ -4967,7 +4976,7 @@ weston_output_enable(struct weston_output *output) wl_signal_init(&output->frame_signal); wl_signal_init(&output->destroy_signal); wl_list_init(&output->animation_list); - wl_list_init(&output->resource_list); + wl_list_init(&output->head.resource_list); wl_list_init(&output->feedback_list); /* Enable the output (set up the crtc or create a diff --git a/libweston/compositor.h b/libweston/compositor.h index 3af3e9ab9..9f9613cef 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -153,6 +153,9 @@ enum dpms_enum { * (windowed nested backends). */ struct weston_head { + struct wl_list resource_list; /**< wl_output protocol objects */ + struct wl_global *global; /**< wl_output global */ + int32_t mm_width; /**< physical image width in mm */ int32_t mm_height; /**< physical image height in mm */ char *make; /**< monitor manufacturer (PNP ID) */ @@ -169,8 +172,6 @@ struct weston_output { void *renderer_state; struct wl_list link; - struct wl_list resource_list; - struct wl_global *global; struct weston_compositor *compositor; /** From global to output buffer coordinates. */ From 0534762b219e7222493f92cbde3e9bde8e39170d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 27 Mar 2017 12:24:34 +0300 Subject: [PATCH 0400/1642] libweston: use head in wl_output global As a wl_output represents weston_head, use a weston_head pointer as the wl_output global's user data. Signed-off-by: Pekka Paalanen v5 Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 11 +++++++---- libweston/compositor.h | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 7992870bb..6f869327a 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4317,10 +4317,10 @@ static void bind_output(struct wl_client *client, void *data, uint32_t version, uint32_t id) { - struct weston_output *output = data; + struct weston_head *head = data; + struct weston_output *output = head->output; struct weston_mode *mode; struct wl_resource *resource; - struct weston_head *head = &output->head; resource = wl_resource_create(client, &wl_output_interface, version, id); @@ -4330,8 +4330,10 @@ bind_output(struct wl_client *client, } wl_list_insert(&head->resource_list, wl_resource_get_link(resource)); - wl_resource_set_implementation(resource, &output_interface, data, unbind_resource); + wl_resource_set_implementation(resource, &output_interface, output, + unbind_resource); + assert(output); wl_output_send_geometry(resource, output->x, output->y, @@ -4636,9 +4638,10 @@ weston_compositor_add_output(struct weston_compositor *compositor, output->enabled = true; head = &output->head; + head->output = output; head->global = wl_global_create(compositor->wl_display, &wl_output_interface, 3, - output, bind_output); + head, bind_output); wl_signal_emit(&compositor->output_created_signal, output); diff --git a/libweston/compositor.h b/libweston/compositor.h index 9f9613cef..129e75413 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -153,6 +153,8 @@ enum dpms_enum { * (windowed nested backends). */ struct weston_head { + struct weston_output *output; /**< the output driving this head */ + struct wl_list resource_list; /**< wl_output protocol objects */ struct wl_global *global; /**< wl_output global */ From 055c1137aebc1cd1fc7c8d1a653edb595734b88a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 27 Mar 2017 16:31:25 +0300 Subject: [PATCH 0401/1642] libweston: make wl_output point to weston_head The user data of a wl_resource representing a wl_output protocol object used to be a pointer to weston_output. Now that weston_output is being split, wl_output more accurately refers to weston_head which is a single monitor. Change the wl_output user data to point to weston_head. weston_output_from_resource() is replaced with weston_head_from_resource(). This change is not strictly necessary, but architecturally it is the right thing to do. In the future there might appear the need to refer to a specific head of a cloned pair, for instance. Signed-off-by: Pekka Paalanen v5 Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- compositor/weston-screenshooter.c | 2 +- desktop-shell/input-panel.c | 4 +++- desktop-shell/shell.c | 4 ++-- fullscreen-shell/fullscreen-shell.c | 4 ++-- ivi-shell/input-panel-ivi.c | 4 +++- libweston-desktop/wl-shell.c | 2 +- libweston-desktop/xdg-shell-v6.c | 2 +- libweston/compositor.c | 6 +++--- libweston/compositor.h | 4 ++-- tests/weston-test.c | 2 +- 10 files changed, 19 insertions(+), 15 deletions(-) diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c index f0bc0e1e0..70afed4a5 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -66,7 +66,7 @@ screenshooter_shoot(struct wl_client *client, struct wl_resource *buffer_resource) { struct weston_output *output = - weston_output_from_resource(output_resource); + weston_head_from_resource(output_resource)->output; struct weston_buffer *buffer = weston_buffer_from_resource(buffer_resource); diff --git a/desktop-shell/input-panel.c b/desktop-shell/input-panel.c index e6b1541aa..8292f20a6 100644 --- a/desktop-shell/input-panel.c +++ b/desktop-shell/input-panel.c @@ -270,11 +270,13 @@ input_panel_surface_set_toplevel(struct wl_client *client, struct input_panel_surface *input_panel_surface = wl_resource_get_user_data(resource); struct desktop_shell *shell = input_panel_surface->shell; + struct weston_head *head; wl_list_insert(&shell->input_panel.surfaces, &input_panel_surface->link); - input_panel_surface->output = weston_output_from_resource(output_resource); + head = weston_head_from_resource(output_resource); + input_panel_surface->output = head->output; input_panel_surface->panel = 0; } diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index ceb45c74d..b846e305f 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2969,7 +2969,7 @@ desktop_shell_set_background(struct wl_client *client, surface->committed = background_committed; surface->committed_private = shell; weston_surface_set_label_func(surface, background_get_label); - surface->output = weston_output_from_resource(output_resource); + surface->output = weston_head_from_resource(output_resource)->output; view->output = surface->output; sh_output = find_shell_output_from_weston_output(shell, surface->output); @@ -3066,7 +3066,7 @@ desktop_shell_set_panel(struct wl_client *client, surface->committed = panel_committed; surface->committed_private = shell; weston_surface_set_label_func(surface, panel_get_label); - surface->output = weston_output_from_resource(output_resource); + surface->output = weston_head_from_resource(output_resource)->output; view->output = surface->output; sh_output = find_shell_output_from_weston_output(shell, surface->output); diff --git a/fullscreen-shell/fullscreen-shell.c b/fullscreen-shell/fullscreen-shell.c index 6f4565a75..898847947 100644 --- a/fullscreen-shell/fullscreen-shell.c +++ b/fullscreen-shell/fullscreen-shell.c @@ -769,7 +769,7 @@ fullscreen_shell_present_surface(struct wl_client *client, } if (output_res) { - output = weston_output_from_resource(output_res); + output = weston_head_from_resource(output_res)->output; fsout = fs_output_for_output(output); fs_output_set_surface(fsout, surface, method, 0, 0); } else { @@ -813,7 +813,7 @@ fullscreen_shell_present_surface_for_mode(struct wl_client *client, struct weston_seat *seat; struct fs_output *fsout; - output = weston_output_from_resource(output_res); + output = weston_head_from_resource(output_res)->output; fsout = fs_output_for_output(output); if (surface_res == NULL) { diff --git a/ivi-shell/input-panel-ivi.c b/ivi-shell/input-panel-ivi.c index 0008a52d3..219494dc2 100644 --- a/ivi-shell/input-panel-ivi.c +++ b/ivi-shell/input-panel-ivi.c @@ -271,11 +271,13 @@ input_panel_surface_set_toplevel(struct wl_client *client, struct input_panel_surface *input_panel_surface = wl_resource_get_user_data(resource); struct ivi_shell *shell = input_panel_surface->shell; + struct weston_head *head; wl_list_insert(&shell->input_panel.surfaces, &input_panel_surface->link); - input_panel_surface->output = weston_output_from_resource(output_resource); + head = weston_head_from_resource(output_resource); + input_panel_surface->output = head->output; input_panel_surface->panel = 0; } diff --git a/libweston-desktop/wl-shell.c b/libweston-desktop/wl-shell.c index 8467dfb88..37720acbb 100644 --- a/libweston-desktop/wl-shell.c +++ b/libweston-desktop/wl-shell.c @@ -308,7 +308,7 @@ weston_desktop_wl_shell_surface_protocol_set_fullscreen(struct wl_client *wl_cli struct weston_output *output = NULL; if (output_resource != NULL) - output = weston_output_from_resource(output_resource); + output = weston_head_from_resource(output_resource)->output; weston_desktop_wl_shell_change_state(surface, FULLSCREEN, NULL, 0, 0); weston_desktop_api_fullscreen_requested(surface->desktop, dsurface, diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index 8fa01a324..ccdd19377 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -515,7 +515,7 @@ weston_desktop_xdg_toplevel_protocol_set_fullscreen(struct wl_client *wl_client, struct weston_output *output = NULL; if (output_resource != NULL) - output = weston_output_from_resource(output_resource); + output = weston_head_from_resource(output_resource)->output; weston_desktop_xdg_toplevel_ensure_added(toplevel); weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, diff --git a/libweston/compositor.c b/libweston/compositor.c index 6f869327a..81fb5aff5 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4330,7 +4330,7 @@ bind_output(struct wl_client *client, } wl_list_insert(&head->resource_list, wl_resource_get_link(resource)); - wl_resource_set_implementation(resource, &output_interface, output, + wl_resource_set_implementation(resource, &output_interface, head, unbind_resource); assert(output); @@ -4364,8 +4364,8 @@ bind_output(struct wl_client *client, * \return The backing object (user data) of a wl_resource representing a * wl_output protocol object. */ -WL_EXPORT struct weston_output * -weston_output_from_resource(struct wl_resource *resource) +WL_EXPORT struct weston_head * +weston_head_from_resource(struct wl_resource *resource) { assert(wl_resource_instance_of(resource, &wl_output_interface, &output_interface)); diff --git a/libweston/compositor.h b/libweston/compositor.h index 129e75413..194aa7488 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1995,8 +1995,8 @@ weston_output_disable(struct weston_output *output); void weston_pending_output_coldplug(struct weston_compositor *compositor); -struct weston_output * -weston_output_from_resource(struct wl_resource *resource); +struct weston_head * +weston_head_from_resource(struct wl_resource *resource); #ifdef __cplusplus } diff --git a/tests/weston-test.c b/tests/weston-test.c index 9a2fd2869..73409cace 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -530,7 +530,7 @@ capture_screenshot(struct wl_client *client, struct wl_resource *buffer_resource) { struct weston_output *output = - weston_output_from_resource(output_resource); + weston_head_from_resource(output_resource)->output; struct weston_buffer *buffer = weston_buffer_from_resource(buffer_resource); From 6528c0381deeb40353351288e116aaa89cd84cea Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 28 Mar 2017 15:27:10 +0300 Subject: [PATCH 0402/1642] libweston: refactor weston_mode_switch_finish Split out a new function. This is a pure refactoring, no change in behaviour. This helps a following patch that adds a loop over output->head_list. Signed-off-by: Pekka Paalanen v5 Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 57 ++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 81fb5aff5..ee30068db 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -76,15 +76,45 @@ weston_output_transform_scale_init(struct weston_output *output, static void weston_compositor_build_view_list(struct weston_compositor *compositor); -static void weston_mode_switch_finish(struct weston_output *output, - int mode_changed, - int scale_changed) +/** Send wl_output events for mode and scale changes + * + * \param head Send on all resources bound to this head. + * \param mode_changed If true, send the current mode. + * \param scale_changed If true, send the current scale. + */ +static void +weston_mode_switch_send_events(struct weston_head *head, + bool mode_changed, bool scale_changed) +{ + struct weston_output *output = head->output; + struct wl_resource *resource; + int version; + + wl_resource_for_each(resource, &head->resource_list) { + if (mode_changed) { + wl_output_send_mode(resource, + output->current_mode->flags, + output->current_mode->width, + output->current_mode->height, + output->current_mode->refresh); + } + + version = wl_resource_get_version(resource); + if (version >= WL_OUTPUT_SCALE_SINCE_VERSION && scale_changed) + wl_output_send_scale(resource, output->current_scale); + + if (version >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output_send_done(resource); + } +} + +static void +weston_mode_switch_finish(struct weston_output *output, + int mode_changed, int scale_changed) { struct weston_seat *seat; struct weston_head *head; - struct wl_resource *resource; pixman_region32_t old_output_region; - int version; pixman_region32_init(&old_output_region); pixman_region32_copy(&old_output_region, &output->region); @@ -133,22 +163,7 @@ static void weston_mode_switch_finish(struct weston_output *output, head = &output->head; /* notify clients of the changes */ - wl_resource_for_each(resource, &head->resource_list) { - if (mode_changed) { - wl_output_send_mode(resource, - output->current_mode->flags, - output->current_mode->width, - output->current_mode->height, - output->current_mode->refresh); - } - - version = wl_resource_get_version(resource); - if (version >= WL_OUTPUT_SCALE_SINCE_VERSION && scale_changed) - wl_output_send_scale(resource, output->current_scale); - - if (version >= WL_OUTPUT_DONE_SINCE_VERSION) - wl_output_send_done(resource); - } + weston_mode_switch_send_events(head, mode_changed, scale_changed); } From 7cdbabee9a0a6bf16d85fc4f4036f0d1c82ee756 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 28 Mar 2017 16:27:25 +0300 Subject: [PATCH 0403/1642] libweston: introduce weston_output::head_list The intention is that in the future backends will dynamically allocate weston_heads based on the resources they have. The lifetime of a weston_head will be independent of the lifetime of a weston_output it may be attached to. Backends allocate objects derived from weston_head, like they currently do for weston_output. Backend will choose when to destroy a weston_head. For clone mode, struct weston_output gains head_list member, which is the list of attached heads that will all show the same framebuffer. Since heads are growing out of weston_output, management functions are added. Detaching a head from an enabled output is allowed to accommodate disappearing heads. Attaching a head to an enabled output is disallowed because it may need hardware reconfiguration and testing, and so requires a weston_output_enable() call. As a temporary measure, we have one weston_head embedded in weston_output, so that backends can be migrated individually to the new allocation scheme. v8: - Do not send wp_presentation_feedback.sync_output events for multiple wl_output globals in weston_presentation_feedback_present(). v6: - adapt to upstream changes in weston_output_set_transform() - use wl_list_for_each_safe in weston_output_release() - removed weston_output_get_first_head() as it's not needed yet Signed-off-by: Pekka Paalanen v5 Reviewed-by: Derek Foreman v7 Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 236 +++++++++++++++++++++++++++++++---------- libweston/compositor.h | 4 +- 2 files changed, 184 insertions(+), 56 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index ee30068db..fce74f342 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -160,13 +160,12 @@ weston_mode_switch_finish(struct weston_output *output, if (!mode_changed && !scale_changed) return; - head = &output->head; - /* notify clients of the changes */ - weston_mode_switch_send_events(head, mode_changed, scale_changed); + wl_list_for_each(head, &output->head_list, output_link) + weston_mode_switch_send_events(head, + mode_changed, scale_changed); } - static void weston_compositor_reflow_outputs(struct weston_compositor *compositor, struct weston_output *resized_output, int delta_width); @@ -363,13 +362,22 @@ weston_presentation_feedback_present( uint32_t tv_sec_hi; uint32_t tv_sec_lo; uint32_t tv_nsec; + bool done = false; - head = &output->head; - wl_resource_for_each(o, &head->resource_list) { - if (wl_resource_get_client(o) != client) - continue; + wl_list_for_each(head, &output->head_list, output_link) { + wl_resource_for_each(o, &head->resource_list) { + if (wl_resource_get_client(o) != client) + continue; + + wp_presentation_feedback_send_sync_output(feedback->resource, o); + done = true; + } - wp_presentation_feedback_send_sync_output(feedback->resource, o); + /* For clone mode, send it for just one wl_output global, + * they are all equivalent anyway. + */ + if (done) + break; } timespec_to_proto(ts, &tv_sec_hi, &tv_sec_lo, &tv_nsec); @@ -989,6 +997,7 @@ weston_surface_update_output_mask(struct weston_surface *es, uint32_t mask) uint32_t left = es->output_mask & different; uint32_t output_bit; struct weston_output *output; + struct weston_head *head; es->output_mask = mask; if (es->resource == NULL) @@ -1001,9 +1010,11 @@ weston_surface_update_output_mask(struct weston_surface *es, uint32_t mask) if (!(output_bit & different)) continue; - weston_surface_send_enter_leave(es, &output->head, - output_bit & entered, - output_bit & left); + wl_list_for_each(head, &output->head_list, output_link) { + weston_surface_send_enter_leave(es, head, + output_bit & entered, + output_bit & left); + } } } @@ -4388,6 +4399,98 @@ weston_head_from_resource(struct wl_resource *resource) return wl_resource_get_user_data(resource); } +/** Initialize a pre-allocated weston_head + * + * \param head The head to initialize. + * + * The head will be safe to attach, detach and release. + * + * \memberof weston_head + * \internal + */ +static void +weston_head_init(struct weston_head *head) +{ + /* Add some (in)sane defaults which can be used + * for checking if an output was properly configured + */ + memset(head, 0, sizeof *head); + + wl_list_init(&head->output_link); + wl_list_init(&head->resource_list); +} + +/** Attach a head to an inactive output + * + * \param output The output to attach to. + * \param head The head that is not yet attached. + * \return 0 on success, -1 on failure. + * + * Attaches the given head to the output. All heads of an output are clones + * and share the resolution and timings. + * + * Cloning heads this way uses less resources than creating an output for + * each head, but is not always possible due to environment, driver and hardware + * limitations. + * + * On failure, the head remains unattached. Success of this function does not + * guarantee the output configuration is actually valid. The final checks are + * made on weston_output_enable(). + * + * \memberof weston_output + */ +static int +weston_output_attach_head(struct weston_output *output, + struct weston_head *head) +{ + if (output->enabled) + return -1; + + if (!wl_list_empty(&head->output_link)) + return -1; + + /* XXX: no support for multi-head yet */ + if (!wl_list_empty(&output->head_list)) + return -1; + + head->output = output; + wl_list_insert(output->head_list.prev, &head->output_link); + + return 0; +} + +/** Detach a head from its output + * + * \param head The head to detach. + * + * It is safe to detach a non-attached head. + * + * \memberof weston_head + */ +static void +weston_head_detach(struct weston_head *head) +{ + wl_list_remove(&head->output_link); + wl_list_init(&head->output_link); + head->output = NULL; +} + +/** Destroy a head + * + * \param head The head to be released. + * + * Destroys the head. The caller is responsible for freeing the memory pointed + * to by \c head. + * + * \memberof weston_head + * \internal + */ +static void +weston_head_release(struct weston_head *head) +{ + weston_head_detach(head); +} + /** Store monitor make, model and serial number * * \param head The head to modify. @@ -4583,8 +4686,9 @@ weston_output_init_geometry(struct weston_output *output, int x, int y) WL_EXPORT void weston_output_move(struct weston_output *output, int x, int y) { - struct weston_head *head = &output->head; + struct weston_head *head; struct wl_resource *resource; + int ver; output->move_x = x - output->x; output->move_y = y - output->y; @@ -4600,19 +4704,22 @@ weston_output_move(struct weston_output *output, int x, int y) wl_signal_emit(&output->compositor->output_moved_signal, output); /* Notify clients of the change for output position. */ - wl_resource_for_each(resource, &head->resource_list) { - wl_output_send_geometry(resource, - output->x, - output->y, - head->mm_width, - head->mm_height, - head->subpixel, - head->make, - head->model, - output->transform); - - if (wl_resource_get_version(resource) >= WL_OUTPUT_DONE_SINCE_VERSION) - wl_output_send_done(resource); + wl_list_for_each(head, &output->head_list, output_link) { + wl_resource_for_each(resource, &head->resource_list) { + wl_output_send_geometry(resource, + output->x, + output->y, + head->mm_width, + head->mm_height, + head->subpixel, + head->make, + head->model, + output->transform); + + ver = wl_resource_get_version(resource); + if (ver >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output_send_done(resource); + } } } @@ -4652,11 +4759,11 @@ weston_compositor_add_output(struct weston_compositor *compositor, wl_list_insert(compositor->output_list.prev, &output->link); output->enabled = true; - head = &output->head; - head->output = output; - head->global = wl_global_create(compositor->wl_display, - &wl_output_interface, 3, - head, bind_output); + wl_list_for_each(head, &output->head_list, output_link) { + head->global = wl_global_create(compositor->wl_display, + &wl_output_interface, 3, + head, bind_output); + } wl_signal_emit(&compositor->output_created_signal, output); @@ -4749,11 +4856,12 @@ weston_compositor_remove_output(struct weston_output *output) wl_signal_emit(&compositor->output_destroyed_signal, output); wl_signal_emit(&output->destroy_signal, output); - head = &output->head; - wl_global_destroy(head->global); - head->global = NULL; - wl_resource_for_each(resource, &head->resource_list) { - wl_resource_set_destructor(resource, NULL); + wl_list_for_each(head, &output->head_list, output_link) { + wl_global_destroy(head->global); + head->global = NULL; + + wl_resource_for_each(resource, &head->resource_list) + wl_resource_set_destructor(resource, NULL); } compositor->output_id_pool &= ~(1u << output->id); @@ -4803,7 +4911,8 @@ weston_output_set_transform(struct weston_output *output, struct weston_seat *seat; pixman_region32_t old_region; int mid_x, mid_y; - struct weston_head *head = &output->head; + struct weston_head *head; + int ver; if (!output->enabled && output->transform == UINT32_MAX) { output->transform = transform; @@ -4820,19 +4929,22 @@ weston_output_set_transform(struct weston_output *output, output->dirty = 1; /* Notify clients of the change for output transform. */ - wl_resource_for_each(resource, &head->resource_list) { - wl_output_send_geometry(resource, - output->x, - output->y, - head->mm_width, - head->mm_height, - head->subpixel, - head->make, - head->model, - output->transform); - - if (wl_resource_get_version(resource) >= WL_OUTPUT_DONE_SINCE_VERSION) - wl_output_send_done(resource); + wl_list_for_each(head, &output->head_list, output_link) { + wl_resource_for_each(resource, &head->resource_list) { + wl_output_send_geometry(resource, + output->x, + output->y, + head->mm_width, + head->mm_height, + head->subpixel, + head->make, + head->model, + output->transform); + + ver = wl_resource_get_version(resource); + if (ver >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output_send_done(resource); + } } /* we must ensure that pointers are inside output, otherwise they disappear */ @@ -4875,19 +4987,19 @@ weston_output_init(struct weston_output *output, struct weston_compositor *compositor, const char *name) { - struct weston_head *head = &output->head; - output->compositor = compositor; output->destroying = 0; output->name = strdup(name); wl_list_init(&output->link); output->enabled = false; + wl_list_init(&output->head_list); + + weston_head_init(&output->head); + /* Add some (in)sane defaults which can be used * for checking if an output was properly configured */ - head->mm_width = 0; - head->mm_height = 0; output->scale = 0; /* Can't use -1 on uint32_t and 0 is valid enum value */ output->transform = UINT32_MAX; @@ -4961,6 +5073,7 @@ weston_output_enable(struct weston_output *output) struct weston_compositor *c = output->compositor; struct weston_output *iterator; int x = 0, y = 0; + int ret; if (output->enabled) { weston_log("Error: attempt to enable an enabled output '%s'\n", @@ -4994,9 +5107,14 @@ weston_output_enable(struct weston_output *output) wl_signal_init(&output->frame_signal); wl_signal_init(&output->destroy_signal); wl_list_init(&output->animation_list); - wl_list_init(&output->head.resource_list); wl_list_init(&output->feedback_list); + /* XXX: Temporary until all backends are converted. */ + if (wl_list_empty(&output->head_list)) { + ret = weston_output_attach_head(output, &output->head); + assert(ret == 0); + } + /* Enable the output (set up the crtc or create a * window representing the output, set up the * renderer, etc) @@ -5088,6 +5206,8 @@ weston_pending_output_coldplug(struct weston_compositor *compositor) WL_EXPORT void weston_output_release(struct weston_output *output) { + struct weston_head *head, *tmp; + output->destroying = 1; if (output->enabled) @@ -5096,6 +5216,12 @@ weston_output_release(struct weston_output *output) pixman_region32_fini(&output->region); pixman_region32_fini(&output->previous_damage); wl_list_remove(&output->link); + + wl_list_for_each_safe(head, tmp, &output->head_list, output_link) + weston_head_detach(head); + + weston_head_release(&output->head); + free(output->name); } diff --git a/libweston/compositor.h b/libweston/compositor.h index 194aa7488..604792a5d 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -154,6 +154,7 @@ enum dpms_enum { */ struct weston_head { struct weston_output *output; /**< the output driving this head */ + struct wl_list output_link; /**< in weston_output::head_list */ struct wl_list resource_list; /**< wl_output protocol objects */ struct wl_global *global; /**< wl_output global */ @@ -226,7 +227,8 @@ struct weston_output { struct weston_mode *original_mode; struct wl_list mode_list; - struct weston_head head; + struct weston_head head; /**< head for unconverted backends */ + struct wl_list head_list; /**< List of driven weston_heads */ void (*start_repaint_loop)(struct weston_output *output); int (*repaint)(struct weston_output *output, From d9dcc6dc8f273b56faffed8d786058d622957f0a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 4 Dec 2017 15:28:13 +0200 Subject: [PATCH 0404/1642] libweston: properly orphan wl_output resources Remove the wl_resource in the head's resource list when we are removing the wl_output global. We sent global removal events to clients, the resources should become dummies until clients reap them. Reset user data so that clients triying to use dummy objects don't hit e.g. a freed head pointer. This fixes a theoretical issue: if an enabled output is disabled and then gets enabled again, mode changes and wl_surface.enter/leave would still attempt to use the dummy objects. If a client destroyed a dummy object, we don't have the destructor to remove it from the resource list, and libweston would hit freed memory. Signed-off-by: Pekka Paalanen v5 Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index fce74f342..767cb8008 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4384,6 +4384,28 @@ bind_output(struct wl_client *client, wl_output_send_done(resource); } +/** Remove the global wl_output protocol object + * + * \param head The head whose global to remove. + * + * Also orphans the wl_resources for this head (wl_output). + */ +static void +weston_head_remove_global(struct weston_head *head) +{ + struct wl_resource *resource, *tmp; + + if (head->global) + wl_global_destroy(head->global); + head->global = NULL; + + wl_resource_for_each_safe(resource, tmp, &head->resource_list) { + unbind_resource(resource); + wl_resource_set_destructor(resource, NULL); + wl_resource_set_user_data(resource, NULL); + } +} + /** Get the backing object of wl_output * * \param resource A wl_output protocol object. @@ -4833,7 +4855,6 @@ static void weston_compositor_remove_output(struct weston_output *output) { struct weston_compositor *compositor = output->compositor; - struct wl_resource *resource; struct weston_view *view; struct weston_head *head; @@ -4856,13 +4877,8 @@ weston_compositor_remove_output(struct weston_output *output) wl_signal_emit(&compositor->output_destroyed_signal, output); wl_signal_emit(&output->destroy_signal, output); - wl_list_for_each(head, &output->head_list, output_link) { - wl_global_destroy(head->global); - head->global = NULL; - - wl_resource_for_each(resource, &head->resource_list) - wl_resource_set_destructor(resource, NULL); - } + wl_list_for_each(head, &output->head_list, output_link) + weston_head_remove_global(head); compositor->output_id_pool &= ~(1u << output->id); output->id = 0xffffffff; /* invalid */ From 06f99efc9c417eefb8046bf64a84869d698a0be5 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 4 Apr 2017 16:26:23 +0300 Subject: [PATCH 0405/1642] libweston: strdup head make, model, serial_number Duplicate these strings to decouple their lifetime from whatever the backends used. This should prevent hard to catch use after frees and such problems in the future. Signed-off-by: Pekka Paalanen v5 Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 767cb8008..c3a94d356 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4511,6 +4511,10 @@ static void weston_head_release(struct weston_head *head) { weston_head_detach(head); + + free(head->make); + free(head->model); + free(head->serial_number); } /** Store monitor make, model and serial number @@ -4532,9 +4536,13 @@ weston_head_set_monitor_strings(struct weston_head *head, const char *model, const char *serialno) { - head->make = (char *)make; - head->model = (char *)model; - head->serial_number = (char *)serialno; + free(head->make); + free(head->model); + free(head->serial_number); + + head->make = make ? strdup(make) : NULL; + head->model = model ? strdup(model) : NULL; + head->serial_number = serialno ? strdup(serialno) : NULL; } /** Store physical image size From cf0a476b8e22950b2f4183b12b0690d8ab392d92 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 4 Apr 2017 16:36:07 +0300 Subject: [PATCH 0406/1642] cms-colord: find a good head The 'head' member of 'struct weston_output' is going to go unused and then disappear, so stop using it and find a head from the proper list. However, this leaves a problem in cms-colord: if you have multiple monitors driver with the same CRTC, what do you say to the color management system? The monitors could be different, but all the color LUTs etc. are in the CRTC and are shared, as is the framebuffer. Do the simple hack here and just use whatever head happens to be the first in the list. The warning is printed in get_output_id(), because if heads are added or removed while the output is enabled, the id could change. v6: - add weston_output_get_first_head(), at first use - add warning message for nr. heads > 1 Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- compositor/cms-colord.c | 17 +++++++++++++++-- libweston/compositor.c | 18 ++++++++++++++++++ libweston/compositor.h | 3 +++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/compositor/cms-colord.c b/compositor/cms-colord.c index f421773b3..b68e4921c 100644 --- a/compositor/cms-colord.c +++ b/compositor/cms-colord.c @@ -102,10 +102,20 @@ edid_value_valid(const char *str) static gchar * get_output_id(struct cms_colord *cms, struct weston_output *o) { - struct weston_head *head = &o->head; + struct weston_head *head; const gchar *tmp; GString *device_id; + /* XXX: What to do with multiple heads? + * This is potentially unstable, if head configuration is changed + * while the output is enabled. */ + head = weston_output_get_first_head(o); + + if (wl_list_length(&o->head_list) > 1) { + weston_log("colord: WARNING: multiple heads are not supported (output %s).\n", + o->name); + } + /* see https://github.com/hughsie/colord/blob/master/doc/device-and-profile-naming-spec.txt * for format and allowed values */ device_id = g_string_new("xrandr"); @@ -231,7 +241,7 @@ colord_notifier_output_destroy(struct wl_listener *listener, void *data) static void colord_output_created(struct cms_colord *cms, struct weston_output *o) { - struct weston_head *head = &o->head; + struct weston_head *head; CdDevice *device; const gchar *tmp; gchar *device_id; @@ -239,6 +249,9 @@ colord_output_created(struct cms_colord *cms, struct weston_output *o) GHashTable *device_props; struct cms_output *ocms; + /* XXX: What to do with multiple heads? */ + head = weston_output_get_first_head(o); + /* create device */ device_id = get_output_id(cms, o); weston_log("colord: output added %s\n", device_id); diff --git a/libweston/compositor.c b/libweston/compositor.c index c3a94d356..f5695f872 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5249,6 +5249,24 @@ weston_output_release(struct weston_output *output) free(output->name); } +/** When you need a head... + * + * This function is a hack, used until all code has been converted to become + * multi-head aware. + * + * \param output The weston_output whose head to get. + * \return The first head in the output's list. + */ +WL_EXPORT struct weston_head * +weston_output_get_first_head(struct weston_output *output) +{ + if (wl_list_empty(&output->head_list)) + return NULL; + + return container_of(output->head_list.next, + struct weston_head, output_link); +} + static void destroy_viewport(struct wl_resource *resource) { diff --git a/libweston/compositor.h b/libweston/compositor.h index 604792a5d..d606fc9b1 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -2000,6 +2000,9 @@ weston_pending_output_coldplug(struct weston_compositor *compositor); struct weston_head * weston_head_from_resource(struct wl_resource *resource); +struct weston_head * +weston_output_get_first_head(struct weston_output *output); + #ifdef __cplusplus } #endif From 9b02e4781a46d5188441d6743802d6e72a017801 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 14 Aug 2017 14:43:13 +0300 Subject: [PATCH 0407/1642] libweston: add name to weston_head Heads need to be named, so they can be referenced in logs and configuration sources. When clone mode is implemented, output and head names may differ. Signed-off-by: Pekka Paalanen Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 10 ++++++++-- libweston/compositor.h | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index f5695f872..5ae35d30a 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4424,14 +4424,18 @@ weston_head_from_resource(struct wl_resource *resource) /** Initialize a pre-allocated weston_head * * \param head The head to initialize. + * \param name The head name, e.g. the connector name or equivalent. * * The head will be safe to attach, detach and release. * + * The name is used in logs, and can be used by compositors as a configuration + * identifier. + * * \memberof weston_head * \internal */ static void -weston_head_init(struct weston_head *head) +weston_head_init(struct weston_head *head, const char *name) { /* Add some (in)sane defaults which can be used * for checking if an output was properly configured @@ -4440,6 +4444,7 @@ weston_head_init(struct weston_head *head) wl_list_init(&head->output_link); wl_list_init(&head->resource_list); + head->name = strdup(name); } /** Attach a head to an inactive output @@ -4515,6 +4520,7 @@ weston_head_release(struct weston_head *head) free(head->make); free(head->model); free(head->serial_number); + free(head->name); } /** Store monitor make, model and serial number @@ -5019,7 +5025,7 @@ weston_output_init(struct weston_output *output, wl_list_init(&output->head_list); - weston_head_init(&output->head); + weston_head_init(&output->head, name); /* Add some (in)sane defaults which can be used * for checking if an output was properly configured diff --git a/libweston/compositor.h b/libweston/compositor.h index d606fc9b1..e7d96c9b2 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -166,6 +166,8 @@ struct weston_head { char *serial_number; /**< monitor serial */ uint32_t subpixel; /**< enum wl_output_subpixel */ bool connection_internal; /**< embedded monitor (e.g. laptop) */ + + char *name; /**< head name, e.g. connector name */ }; struct weston_output { From 7fe858be814362d93a635eab50b77581e8e7db71 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 14 Aug 2017 15:45:14 +0300 Subject: [PATCH 0408/1642] libweston: add weston_head::connected Heads may be disconnected or connected and the compositor needs to be able to know the state to know which heads to take into use. Currently a single head is automatically created with an output, and outputs are only ever created as connected and destroyed on disconnection, so it suffices to set connected to true. In the future, backends are expected to create heads for both connected and disconnected connectors, so that a connector can be forced on without it being actually connected. v6: - split weston_head_is_enabled() to a new patch Signed-off-by: Pekka Paalanen Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 43 ++++++++++++++++++++++++++++++++++++++++++ libweston/compositor.h | 7 +++++++ 2 files changed, 50 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index 5ae35d30a..fd1439163 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4607,6 +4607,48 @@ weston_head_set_internal(struct weston_head *head) head->connection_internal = true; } +/** Store connector status + * + * \param head The head to modify. + * \param connected Whether the head is connected. + * + * Connectors are created as disconnected. This function can be used to + * set the connector status. + * + * The status should be set to true when a physical connector is connected to + * a video sink device like a monitor and to false when the connector is + * disconnected. For nested backends, the connection status should reflect the + * connection to the parent display server. + * + * \memberof weston_head + * \internal + */ +WL_EXPORT void +weston_head_set_connection_status(struct weston_head *head, bool connected) +{ + head->connected = connected; +} + +/** Is the head currently connected? + * + * \param head The head to query. + * \return Connection status. + * + * Returns true if the head is physically connected to a monitor, or in + * case of a nested backend returns true when there is a connection to the + * parent display server. + * + * This is independent from the head being enabled. + * + * \sa weston_head_is_enabled + * \memberof weston_head + */ +WL_EXPORT bool +weston_head_is_connected(struct weston_head *head) +{ + return head->connected; +} + /* Move other outputs when one is resized so the space remains contiguous. */ static void weston_compositor_reflow_outputs(struct weston_compositor *compositor, @@ -5026,6 +5068,7 @@ weston_output_init(struct weston_output *output, wl_list_init(&output->head_list); weston_head_init(&output->head, name); + weston_head_set_connection_status(&output->head, true); /* Add some (in)sane defaults which can be used * for checking if an output was properly configured diff --git a/libweston/compositor.h b/libweston/compositor.h index e7d96c9b2..8ce9a07fd 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -168,6 +168,7 @@ struct weston_head { bool connection_internal; /**< embedded monitor (e.g. laptop) */ char *name; /**< head name, e.g. connector name */ + bool connected; /**< is physically connected */ }; struct weston_output { @@ -1970,9 +1971,15 @@ void weston_head_set_subpixel(struct weston_head *head, enum wl_output_subpixel sp); +void +weston_head_set_connection_status(struct weston_head *head, bool connected); + void weston_head_set_internal(struct weston_head *head); +bool +weston_head_is_connected(struct weston_head *head); + void weston_output_set_scale(struct weston_output *output, int32_t scale); From 8e552fd3bf1158f61fdf9022bb2d5f1a24e9c014 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 15 Feb 2018 15:18:20 +0200 Subject: [PATCH 0409/1642] libweston: add weston_head_is_enabled() Enabled is orthogonal from connected. A connected head could be disabled, or a disconnected head could in the future be enabled. Compositors quite likely want to check if a head is already enabled before starting to take it into use. Signed-off-by: Pekka Paalanen Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 21 +++++++++++++++++++++ libweston/compositor.h | 3 +++ 2 files changed, 24 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index fd1439163..af32c8b99 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4649,6 +4649,27 @@ weston_head_is_connected(struct weston_head *head) return head->connected; } +/** Is the head currently enabled? + * + * \param head The head to query. + * \return Video status. + * + * Returns true if the head is currently transmitting a video stream. + * + * This is independent of the head being connected. + * + * \sa weston_head_is_connected + * \memberof weston_head + */ +WL_EXPORT bool +weston_head_is_enabled(struct weston_head *head) +{ + if (!head->output) + return false; + + return head->output->enabled; +} + /* Move other outputs when one is resized so the space remains contiguous. */ static void weston_compositor_reflow_outputs(struct weston_compositor *compositor, diff --git a/libweston/compositor.h b/libweston/compositor.h index 8ce9a07fd..a5d07beed 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1980,6 +1980,9 @@ weston_head_set_internal(struct weston_head *head); bool weston_head_is_connected(struct weston_head *head); +bool +weston_head_is_enabled(struct weston_head *head); + void weston_output_set_scale(struct weston_output *output, int32_t scale); From 1adcbacd5bc737fb639bcc292dad7a6867bac2f4 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 14 Aug 2017 16:05:35 +0300 Subject: [PATCH 0410/1642] libweston: add compositor list of heads weston_compositor needs to maintain a list of all available heads, so that a compositor can pick and choose which heads to take into or out of use at arbitrary times. The heads may be on or off, and connected or disconnected. Signed-off-by: Pekka Paalanen Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 75 ++++++++++++++++++++++++++++++++++++++++++ libweston/compositor.h | 8 +++++ 2 files changed, 83 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index af32c8b99..b928f89ca 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4442,11 +4442,79 @@ weston_head_init(struct weston_head *head, const char *name) */ memset(head, 0, sizeof *head); + wl_list_init(&head->compositor_link); wl_list_init(&head->output_link); wl_list_init(&head->resource_list); head->name = strdup(name); } +/** Register a new head + * + * \param compositor The compositor. + * \param head The head to register, must not be already registered. + * + * This signals the core that a new head has become available. + * + * \memberof weston_compositor + * \internal + */ +static void +weston_compositor_add_head(struct weston_compositor *compositor, + struct weston_head *head) +{ + assert(wl_list_empty(&head->compositor_link)); + assert(head->name); + + wl_list_insert(compositor->head_list.prev, &head->compositor_link); + head->compositor = compositor; +} + +/** Iterate over available heads + * + * \param compositor The compositor. + * \param item The iterator, or NULL for start. + * \return The next available head in the list. + * + * Returns all available heads, regardless of being connected or enabled. + * + * You can iterate over all heads as follows: + * \code + * struct weston_head *head = NULL; + * + * while ((head = weston_compositor_iterate_heads(compositor, head))) { + * ... + * } + * \endcode + * + * If you cause \c iter to be removed from the list, you cannot use it to + * continue iterating. Removing any other item is safe. + * + * \memberof weston_compositor + */ +WL_EXPORT struct weston_head * +weston_compositor_iterate_heads(struct weston_compositor *compositor, + struct weston_head *iter) +{ + struct wl_list *list = &compositor->head_list; + struct wl_list *node; + + assert(compositor); + assert(!iter || iter->compositor == compositor); + + if (iter) + node = iter->compositor_link.next; + else + node = list->next; + + assert(node); + assert(!iter || node != &iter->compositor_link); + + if (node == list) + return NULL; + + return container_of(node, struct weston_head, compositor_link); +} + /** Attach a head to an inactive output * * \param output The output to attach to. @@ -4521,6 +4589,8 @@ weston_head_release(struct weston_head *head) free(head->model); free(head->serial_number); free(head->name); + + wl_list_remove(&head->compositor_link); } /** Store monitor make, model and serial number @@ -5090,6 +5160,7 @@ weston_output_init(struct weston_output *output, weston_head_init(&output->head, name); weston_head_set_connection_status(&output->head, true); + weston_compositor_add_head(compositor, &output->head); /* Add some (in)sane defaults which can be used * for checking if an output was properly configured @@ -5713,6 +5784,7 @@ weston_compositor_create(struct wl_display *display, void *user_data) wl_list_init(&ec->seat_list); wl_list_init(&ec->pending_output_list); wl_list_init(&ec->output_list); + wl_list_init(&ec->head_list); wl_list_init(&ec->key_binding_list); wl_list_init(&ec->modifier_binding_list); wl_list_init(&ec->button_binding_list); @@ -5988,6 +6060,9 @@ weston_compositor_destroy(struct weston_compositor *compositor) if (compositor->backend) compositor->backend->destroy(compositor); + /* The backend is responsible for destroying the heads. */ + assert(wl_list_empty(&compositor->head_list)); + weston_plugin_api_destroy_list(compositor); free(compositor); diff --git a/libweston/compositor.h b/libweston/compositor.h index a5d07beed..a84080fda 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -153,6 +153,9 @@ enum dpms_enum { * (windowed nested backends). */ struct weston_head { + struct weston_compositor *compositor; /**< owning compositor */ + struct wl_list compositor_link; /**< in weston_compositor::head_list */ + struct weston_output *output; /**< the output driving this head */ struct wl_list output_link; /**< in weston_output::head_list */ @@ -920,6 +923,7 @@ struct weston_compositor { struct wl_list pending_output_list; struct wl_list output_list; + struct wl_list head_list; /* struct weston_head::compositor_link */ struct wl_list seat_list; struct wl_list layer_list; /* struct weston_layer::link */ struct wl_list view_list; /* struct weston_view::link */ @@ -1983,6 +1987,10 @@ weston_head_is_connected(struct weston_head *head); bool weston_head_is_enabled(struct weston_head *head); +struct weston_head * +weston_compositor_iterate_heads(struct weston_compositor *compositor, + struct weston_head *iter); + void weston_output_set_scale(struct weston_output *output, int32_t scale); From 37e6c9e5372cc244f7a09a7cce9c4a5c29b13e23 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 15 Aug 2017 13:00:02 +0300 Subject: [PATCH 0411/1642] libweston: add heads_changed_signal Add a hook for compositors to get a callback when heads are added or their connection status changes, to which compositors likely want to react to by enabling or disabling outputs (API for that to be added later). As many head changes as possible should be coalesced into a single heads_changed call. Therefore the callback is made from an idle task. This anticipates a future atomic output configuration API, where the global output configuration is tested and set atomically instead of one by one. weston_pending_output_coldplug() needs to manually execute the heads_changed call so that initial outputs are created before any plugins get their start-up idle tasks ran. This is especially important for ivi-shell which does not support output hotplug, and for tests to guarantee the expected outputs. v8: - Change the callback function pointer into a wl_signal. The API is changed and renamed. v6: - fix a typo - add comment in weston_pending_output_coldplug() Signed-off-by: Pekka Paalanen v6 Reviewed-by: Derek Foreman v6 Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 81 +++++++++++++++++++++++++++++++++++++++++- libweston/compositor.h | 6 ++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index b928f89ca..68bf11c62 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4448,12 +4448,44 @@ weston_head_init(struct weston_head *head, const char *name) head->name = strdup(name); } +/** Idle task for emitting heads_changed_signal */ +static void +weston_compositor_call_heads_changed(void *data) +{ + struct weston_compositor *compositor = data; + + compositor->heads_changed_source = NULL; + + wl_signal_emit(&compositor->heads_changed_signal, compositor); +} + +/** Schedule a call on idle to heads_changed callback + * + * \param compositor The Compositor. + * + * \memberof weston_compositor + * \internal + */ +static void +weston_compositor_schedule_heads_changed(struct weston_compositor *compositor) +{ + struct wl_event_loop *loop; + + if (compositor->heads_changed_source) + return; + + loop = wl_display_get_event_loop(compositor->wl_display); + compositor->heads_changed_source = wl_event_loop_add_idle(loop, + weston_compositor_call_heads_changed, compositor); +} + /** Register a new head * * \param compositor The compositor. * \param head The head to register, must not be already registered. * - * This signals the core that a new head has become available. + * This signals the core that a new head has become available, leading to + * heads_changed hook being called later. * * \memberof weston_compositor * \internal @@ -4467,6 +4499,30 @@ weston_compositor_add_head(struct weston_compositor *compositor, wl_list_insert(compositor->head_list.prev, &head->compositor_link); head->compositor = compositor; + weston_compositor_schedule_heads_changed(compositor); +} + +/** Adds a listener to be called when heads change + * + * \param compositor The compositor. + * \param listener The listener to add. + * + * The listener notify function argument is the \var compositor. + * + * The listener function will be called after heads are added or their + * connection status has changed. Several changes may be accumulated into a + * single call. The user is expected to iterate over the existing heads and + * check their statuses to find out what changed. + * + * \sa weston_compositor_iterate_heads, weston_head_is_connected, + * weston_head_is_enabled + * \memberof weston_compositor + */ +WL_EXPORT void +weston_compositor_add_heads_changed_listener(struct weston_compositor *compositor, + struct wl_listener *listener) +{ + wl_signal_add(&compositor->heads_changed_signal, listener); } /** Iterate over available heads @@ -4690,13 +4746,23 @@ weston_head_set_internal(struct weston_head *head) * disconnected. For nested backends, the connection status should reflect the * connection to the parent display server. * + * When the connection status changes, it schedules a call to the heads_changed + * hook. + * + * \sa weston_compositor_set_heads_changed_cb * \memberof weston_head * \internal */ WL_EXPORT void weston_head_set_connection_status(struct weston_head *head, bool connected) { + if (head->connected == connected) + return; + head->connected = connected; + + if (head->compositor) + weston_compositor_schedule_heads_changed(head->compositor); } /** Is the head currently connected? @@ -5354,6 +5420,15 @@ weston_pending_output_coldplug(struct weston_compositor *compositor) wl_list_for_each_safe(output, next, &compositor->pending_output_list, link) wl_signal_emit(&compositor->output_pending_signal, output); + + /* Execute the heads changed callback manually to ensure it is + * processed before any plugins get their start-up idle tasks ran. + * This ensures the plugins see all the initial outputs. + */ + if (compositor->heads_changed_source) { + wl_event_source_remove(compositor->heads_changed_source); + weston_compositor_call_heads_changed(compositor); + } } /** Uninitialize an output @@ -5751,6 +5826,7 @@ weston_compositor_create(struct wl_display *display, void *user_data) wl_signal_init(&ec->output_destroyed_signal); wl_signal_init(&ec->output_moved_signal); wl_signal_init(&ec->output_resized_signal); + wl_signal_init(&ec->heads_changed_signal); wl_signal_init(&ec->session_signal); ec->session_active = 1; @@ -6065,6 +6141,9 @@ weston_compositor_destroy(struct weston_compositor *compositor) weston_plugin_api_destroy_list(compositor); + if (compositor->heads_changed_source) + wl_event_source_remove(compositor->heads_changed_source); + free(compositor); } diff --git a/libweston/compositor.h b/libweston/compositor.h index a84080fda..f028b69e7 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -983,6 +983,8 @@ struct weston_compositor { /* Whether to let the compositor run without any input device. */ bool require_input; + struct wl_signal heads_changed_signal; + struct wl_event_source *heads_changed_source; }; struct weston_buffer { @@ -1991,6 +1993,10 @@ struct weston_head * weston_compositor_iterate_heads(struct weston_compositor *compositor, struct weston_head *iter); +void +weston_compositor_add_heads_changed_listener(struct weston_compositor *compositor, + struct wl_listener *listener); + void weston_output_set_scale(struct weston_output *output, int32_t scale); From 992a8cb38cf5208cc759ab5c617b19f85cd808a2 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 16 Aug 2017 10:39:17 +0300 Subject: [PATCH 0412/1642] libweston: new head-based output management API Introduce the API for users (compositors) to create an output from a head, attach and detach heads, and destroy outputs created this way. This also adds the backend-facing API to libweston. In the new API design, a backend creates heads, and the compositor chooses one or more heads (clone mode) to be driven by an output. In the future backends will be converted to not create outputs directly but only in the new create_output hook. The user subscribes to a heads_changed hook and arranges heads into outputs from there. Adding the API this way will allow frontends (main.c) and backends to be converted one by one. This adds compatiblity paths in weston_compositor_create_output_with_head() and weston_output_destroy() so that frontends can be converted first to call these, and then backends can be converted one by one to the new design. Afterwards, the compatibility paths will be removed along with weston_output::head. Currently heads can be added to a disabled output only. This is less than ideal for clone mode hotplug and should be improved on later. v4: Remove the wl_output global on head detach if output is enabled. Signed-off-by: Pekka Paalanen Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 187 +++++++++++++++++++++++++++++++++++++++-- libweston/compositor.h | 78 +++++++++++++++++ 2 files changed, 256 insertions(+), 9 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 68bf11c62..23e5ef0e1 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4434,7 +4434,7 @@ weston_head_from_resource(struct wl_resource *resource) * \memberof weston_head * \internal */ -static void +WL_EXPORT void weston_head_init(struct weston_head *head, const char *name) { /* Add some (in)sane defaults which can be used @@ -4490,7 +4490,7 @@ weston_compositor_schedule_heads_changed(struct weston_compositor *compositor) * \memberof weston_compositor * \internal */ -static void +WL_EXPORT void weston_compositor_add_head(struct weston_compositor *compositor, struct weston_head *head) { @@ -4571,6 +4571,52 @@ weston_compositor_iterate_heads(struct weston_compositor *compositor, return container_of(node, struct weston_head, compositor_link); } +/** Iterate over attached heads + * + * \param output The output whose heads to iterate. + * \param item The iterator, or NULL for start. + * \return The next attached head in the list. + * + * Returns all heads currently attached to the output. + * + * You can iterate over all heads as follows: + * \code + * struct weston_head *head = NULL; + * + * while ((head = weston_output_iterate_heads(output, head))) { + * ... + * } + * \endcode + * + * If you cause \c iter to be removed from the list, you cannot use it to + * continue iterating. Removing any other item is safe. + * + * \memberof weston_compositor + */ +WL_EXPORT struct weston_head * +weston_output_iterate_heads(struct weston_output *output, + struct weston_head *iter) +{ + struct wl_list *list = &output->head_list; + struct wl_list *node; + + assert(output); + assert(!iter || iter->output == output); + + if (iter) + node = iter->output_link.next; + else + node = list->next; + + assert(node); + assert(!iter || node != &iter->output_link); + + if (node == list) + return NULL; + + return container_of(node, struct weston_head, output_link); +} + /** Attach a head to an inactive output * * \param output The output to attach to. @@ -4590,7 +4636,7 @@ weston_compositor_iterate_heads(struct weston_compositor *compositor, * * \memberof weston_output */ -static int +WL_EXPORT int weston_output_attach_head(struct weston_output *output, struct weston_head *head) { @@ -4600,9 +4646,13 @@ weston_output_attach_head(struct weston_output *output, if (!wl_list_empty(&head->output_link)) return -1; - /* XXX: no support for multi-head yet */ - if (!wl_list_empty(&output->head_list)) + if (output->attach_head) { + if (output->attach_head(output, head) < 0) + return -1; + } else if (!wl_list_empty(&output->head_list)) { + /* No support for clones in the legacy path. */ return -1; + } head->output = output; wl_list_insert(output->head_list.prev, &head->output_link); @@ -4616,14 +4666,33 @@ weston_output_attach_head(struct weston_output *output, * * It is safe to detach a non-attached head. * + * If the head is attached to an enabled output and the output will be left + * with no heads, the output will be disabled. + * * \memberof weston_head + * \sa weston_output_disable */ -static void +WL_EXPORT void weston_head_detach(struct weston_head *head) { + struct weston_output *output = head->output; + wl_list_remove(&head->output_link); wl_list_init(&head->output_link); head->output = NULL; + + if (!output) + return; + + if (output->detach_head) + output->detach_head(output, head); + + if (output->enabled) { + weston_head_remove_global(head); + + if (wl_list_empty(&output->head_list)) + weston_output_disable(output); + } } /** Destroy a head @@ -4636,7 +4705,7 @@ weston_head_detach(struct weston_head *head) * \memberof weston_head * \internal */ -static void +WL_EXPORT void weston_head_release(struct weston_head *head) { weston_head_detach(head); @@ -4806,6 +4875,31 @@ weston_head_is_enabled(struct weston_head *head) return head->output->enabled; } +/** Get the name of a head + * + * \param head The head to query. + * \return The head's name, not NULL. + * + * The name depends on the backend. The DRM backend uses connector names, + * other backends may use hardcoded names or user-given names. + */ +WL_EXPORT const char * +weston_head_get_name(struct weston_head *head) +{ + return head->name; +} + +/** Get the output the head is attached to + * + * \param head The head to query. + * \return The output the head is attached to, or NULL if detached. + */ +WL_EXPORT struct weston_output * +weston_head_get_output(struct weston_head *head) +{ + return head->output; +} + /* Move other outputs when one is resized so the space remains contiguous. */ static void weston_compositor_reflow_outputs(struct weston_compositor *compositor, @@ -5225,8 +5319,11 @@ weston_output_init(struct weston_output *output, wl_list_init(&output->head_list); weston_head_init(&output->head, name); - weston_head_set_connection_status(&output->head, true); - weston_compositor_add_head(compositor, &output->head); + output->head.allocator_output = output; + if (!compositor->backend->create_output) { + weston_head_set_connection_status(&output->head, true); + weston_compositor_add_head(compositor, &output->head); + } /* Add some (in)sane defaults which can be used * for checking if an output was properly configured @@ -5465,6 +5562,78 @@ weston_output_release(struct weston_output *output) free(output->name); } +/** Create an output for an unused head + * + * \param compositor The compositor. + * \param head The head to attach to the output. + * \return A new \c weston_output, or NULL on failure. + * + * This creates a new weston_output that starts with the given head attached. + * The output inherits the name of the head. The head must not be already + * attached to another output. + * + * An output must be configured before it can be enabled. + * + * \memberof weston_compositor + */ +WL_EXPORT struct weston_output * +weston_compositor_create_output_with_head(struct weston_compositor *compositor, + struct weston_head *head) +{ + struct weston_output *output; + + if (head->allocator_output) { + /* XXX: compatibility path to be removed after all converted */ + output = head->allocator_output; + } else { + assert(compositor->backend->create_output); + output = compositor->backend->create_output(compositor, + head->name); + } + + if (!output) + return NULL; + + if (weston_output_attach_head(output, head) < 0) { + if (!head->allocator_output) + output->destroy(output); + + return NULL; + } + + return output; +} + +/** Destroy an output + * + * \param output The output to destroy. + * + * The heads attached to the given output are detached and become unused again. + * + * It is not necessary to explicitly destroy all outputs at compositor exit. + * weston_compositor_destroy() will automatically destroy any remaining + * outputs. + * + * \memberof weston_output + */ +WL_EXPORT void +weston_output_destroy(struct weston_output *output) +{ + struct weston_head *head; + + /* XXX: compatibility path to be removed after all converted */ + head = weston_output_get_first_head(output); + if (head->allocator_output) { + /* The old design: backend is responsible for destroying the + * output, so just undo create_output_with_head() + */ + weston_head_detach(head); + return; + } + + output->destroy(output); +} + /** When you need a head... * * This function is a hack, used until all code has been converted to become diff --git a/libweston/compositor.h b/libweston/compositor.h index f028b69e7..8b20c8c95 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -172,6 +172,8 @@ struct weston_head { char *name; /**< head name, e.g. connector name */ bool connected; /**< is physically connected */ + + struct weston_output *allocator_output; /**< XXX: to be removed */ }; struct weston_output { @@ -263,6 +265,33 @@ struct weston_output { int (*enable)(struct weston_output *output); int (*disable)(struct weston_output *output); + + /** Attach a head in the backend + * + * @param output The output to attach to. + * @param head The head to attach. + * @return 0 on success, -1 on failure. + * + * Do anything necessary to account for a new head being attached to + * the output, and check any conditions possible. On failure, both + * the head and the output must be left as before the call. + * + * Libweston core will add the head to the head_list after a successful + * call. + */ + int (*attach_head)(struct weston_output *output, + struct weston_head *head); + + /** Detach a head in the backend + * + * @param output The output to detach from. + * @param head The head to detach. + * + * Do any clean-up necessary to detach this head from the output. + * The head has already been removed from the output's head_list. + */ + void (*detach_head)(struct weston_output *output, + struct weston_head *head); }; enum weston_pointer_motion_mask { @@ -883,6 +912,21 @@ struct weston_backend { */ void (*repaint_flush)(struct weston_compositor *compositor, void *repaint_data); + + /** Allocate a new output + * + * @param compositor The compositor. + * @param name Name for the new output. + * + * Allocates a new output structure that embeds a weston_output, + * initializes it, and returns the pointer to the weston_output + * member. + * + * Must set weston_output members @c destroy, @c enable and @c disable. + */ + struct weston_output * + (*create_output)(struct weston_compositor *compositor, + const char *name); }; struct weston_desktop_xwayland; @@ -1963,6 +2007,16 @@ weston_seat_set_keyboard_focus(struct weston_seat *seat, int weston_compositor_load_xwayland(struct weston_compositor *compositor); +void +weston_head_init(struct weston_head *head, const char *name); + +void +weston_head_release(struct weston_head *head); + +void +weston_compositor_add_head(struct weston_compositor *compositor, + struct weston_head *head); + void weston_head_set_monitor_strings(struct weston_head *head, const char *make, @@ -1989,6 +2043,15 @@ weston_head_is_connected(struct weston_head *head); bool weston_head_is_enabled(struct weston_head *head); +const char * +weston_head_get_name(struct weston_head *head); + +struct weston_output * +weston_head_get_output(struct weston_head *head); + +void +weston_head_detach(struct weston_head *head); + struct weston_head * weston_compositor_iterate_heads(struct weston_compositor *compositor, struct weston_head *iter); @@ -1997,6 +2060,21 @@ void weston_compositor_add_heads_changed_listener(struct weston_compositor *compositor, struct wl_listener *listener); +struct weston_output * +weston_compositor_create_output_with_head(struct weston_compositor *compositor, + struct weston_head *head); + +void +weston_output_destroy(struct weston_output *output); + +int +weston_output_attach_head(struct weston_output *output, + struct weston_head *head); + +struct weston_head * +weston_output_iterate_heads(struct weston_output *output, + struct weston_head *iter); + void weston_output_set_scale(struct weston_output *output, int32_t scale); From 2e1bedb4ed2042264cbbf0ffb4d65de2ba21efd1 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 10 Oct 2017 11:21:58 +0300 Subject: [PATCH 0413/1642] libweston: add weston_head destroy signal Add support for subscribing to weston_head destruction. The primary use case for heads being destroyed arbitrarily is the DRM-backend with MST connectors, which may disappear on unplug. It is not just the connector becoming disconnected, it is the connector actually disappearing. The compositor needs to know about disappearing heads so that it has a chance to clean up "orphaned" outputs which do get disabled but still need destroying at run time. Shutdown would destroy them as well. Signed-off-by: Pekka Paalanen Reviewed-by: Derek Foreman Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 45 ++++++++++++++++++++++++++++++++++++++++++ libweston/compositor.h | 9 +++++++++ 2 files changed, 54 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index 23e5ef0e1..652d27c27 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4443,6 +4443,7 @@ weston_head_init(struct weston_head *head, const char *name) memset(head, 0, sizeof *head); wl_list_init(&head->compositor_link); + wl_signal_init(&head->destroy_signal); wl_list_init(&head->output_link); wl_list_init(&head->resource_list); head->name = strdup(name); @@ -4708,6 +4709,8 @@ weston_head_detach(struct weston_head *head) WL_EXPORT void weston_head_release(struct weston_head *head) { + wl_signal_emit(&head->destroy_signal, head); + weston_head_detach(head); free(head->make); @@ -4900,6 +4903,48 @@ weston_head_get_output(struct weston_head *head) return head->output; } +/** Add destroy callback for a head + * + * \param head The head to watch for. + * \param listener The listener to add. The \c notify member must be set. + * + * Heads may get destroyed for various reasons by the backends. If a head is + * attached to an output, the compositor should listen for head destruction + * and reconfigure or destroy the output if necessary. + * + * The destroy callbacks will be called on weston_head destruction before any + * automatic detaching from an associated weston_output and before any + * weston_head information is lost. + * + * The \c data argument to the notify callback is the weston_head being + * destroyed. + */ +WL_EXPORT void +weston_head_add_destroy_listener(struct weston_head *head, + struct wl_listener *listener) +{ + wl_signal_add(&head->destroy_signal, listener); +} + +/** Look up destroy listener for a head + * + * \param head The head to query. + * \param notify The notify function used used for the added destroy listener. + * \return The listener, or NULL if not found. + * + * This looks up the previously added destroy listener struct based on the + * notify function it has. The listener can be used to access user data + * through \c container_of(). + * + * \sa wl_signal_get() + */ +WL_EXPORT struct wl_listener * +weston_head_get_destroy_listener(struct weston_head *head, + wl_notify_func_t notify) +{ + return wl_signal_get(&head->destroy_signal, notify); +} + /* Move other outputs when one is resized so the space remains contiguous. */ static void weston_compositor_reflow_outputs(struct weston_compositor *compositor, diff --git a/libweston/compositor.h b/libweston/compositor.h index 8b20c8c95..b3b0eddad 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -155,6 +155,7 @@ enum dpms_enum { struct weston_head { struct weston_compositor *compositor; /**< owning compositor */ struct wl_list compositor_link; /**< in weston_compositor::head_list */ + struct wl_signal destroy_signal; /**< destroy callbacks */ struct weston_output *output; /**< the output driving this head */ struct wl_list output_link; /**< in weston_output::head_list */ @@ -2052,6 +2053,14 @@ weston_head_get_output(struct weston_head *head); void weston_head_detach(struct weston_head *head); +void +weston_head_add_destroy_listener(struct weston_head *head, + struct wl_listener *listener); + +struct wl_listener * +weston_head_get_destroy_listener(struct weston_head *head, + wl_notify_func_t notify); + struct weston_head * weston_compositor_iterate_heads(struct weston_compositor *compositor, struct weston_head *iter); From e19970fd6ce257c84e83da182754cdd5416744b2 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 28 Aug 2017 14:11:02 +0300 Subject: [PATCH 0414/1642] libweston: add weston_head_is_device_changed() API Reacting to DRM hotplug events is racy. It is theoretically possible to get hotplug events for a quick swap from one monitor to another and process both only after the new monitor is connected. Hence it is possible for display device information to change without going through a disconnected state for the head. To support such cases, add API to allow detecting it in the compositor. v6: - change str_null_neq() to str_null_eq() - rename weston_head_condition_device_changed() - move the condition from weston_head_set_device_changed() to the callers Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 89 ++++++++++++++++++++++++++++++++++++++++-- libweston/compositor.h | 7 ++++ 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 652d27c27..919a84a1d 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4721,6 +4721,28 @@ weston_head_release(struct weston_head *head) wl_list_remove(&head->compositor_link); } +static void +weston_head_set_device_changed(struct weston_head *head) +{ + head->device_changed = true; + + if (head->compositor) + weston_compositor_schedule_heads_changed(head->compositor); +} + +/** String equal comparison with NULLs being equal */ +static bool +str_null_eq(const char *a, const char *b) +{ + if (!a && !b) + return true; + + if (!!a != !!b) + return false; + + return strcmp(a, b) == 0; +} + /** Store monitor make, model and serial number * * \param head The head to modify. @@ -4731,6 +4753,8 @@ weston_head_release(struct weston_head *head) * \param serialno The monitor serial number, a made-up string, or NULL for * none. * + * This may set the device_changed flag. + * * \memberof weston_head * \internal */ @@ -4740,6 +4764,11 @@ weston_head_set_monitor_strings(struct weston_head *head, const char *model, const char *serialno) { + if (str_null_eq(head->make, make) && + str_null_eq(head->model, model) && + str_null_eq(head->serial_number, serialno)) + return; + free(head->make); free(head->model); free(head->serial_number); @@ -4747,6 +4776,8 @@ weston_head_set_monitor_strings(struct weston_head *head, head->make = make ? strdup(make) : NULL; head->model = model ? strdup(model) : NULL; head->serial_number = serialno ? strdup(serialno) : NULL; + + weston_head_set_device_changed(head); } /** Store physical image size @@ -4755,6 +4786,8 @@ weston_head_set_monitor_strings(struct weston_head *head, * \param mm_width Image area width in millimeters. * \param mm_height Image area height in millimeters. * + * This may set the device_changed flag. + * * \memberof weston_head * \internal */ @@ -4762,8 +4795,14 @@ WL_EXPORT void weston_head_set_physical_size(struct weston_head *head, int32_t mm_width, int32_t mm_height) { + if (head->mm_width == mm_width && + head->mm_height == mm_height) + return; + head->mm_width = mm_width; head->mm_height = mm_height; + + weston_head_set_device_changed(head); } /** Store monitor sub-pixel layout @@ -4777,6 +4816,8 @@ weston_head_set_physical_size(struct weston_head *head, * - WL_OUTPUT_SUBPIXEL_VERTICAL_RGB, * - WL_OUTPUT_SUBPIXEL_VERTICAL_BGR * + * This may set the device_changed flag. + * * \memberof weston_head * \internal */ @@ -4784,7 +4825,12 @@ WL_EXPORT void weston_head_set_subpixel(struct weston_head *head, enum wl_output_subpixel sp) { + if (head->subpixel == sp) + return; + head->subpixel = sp; + + weston_head_set_device_changed(head); } /** Mark the monitor as internal @@ -4819,7 +4865,7 @@ weston_head_set_internal(struct weston_head *head) * connection to the parent display server. * * When the connection status changes, it schedules a call to the heads_changed - * hook. + * hook and sets the device_changed flag. * * \sa weston_compositor_set_heads_changed_cb * \memberof weston_head @@ -4833,8 +4879,7 @@ weston_head_set_connection_status(struct weston_head *head, bool connected) head->connected = connected; - if (head->compositor) - weston_compositor_schedule_heads_changed(head->compositor); + weston_head_set_device_changed(head); } /** Is the head currently connected? @@ -4878,6 +4923,44 @@ weston_head_is_enabled(struct weston_head *head) return head->output->enabled; } +/** Has the device information changed? + * + * \param head The head to query. + * \return True if the device information has changed since last reset. + * + * The information about the connected display device, e.g. a monitor, may + * change without being disconnected in between. Changing information + * causes a call to the heads_changed hook. + * + * The information includes make, model, serial number, physical size, + * and sub-pixel type. The connection status is also included. + * + * \sa weston_head_reset_device_changed, weston_compositor_set_heads_changed_cb + * \memberof weston_head + */ +WL_EXPORT bool +weston_head_is_device_changed(struct weston_head *head) +{ + return head->device_changed; +} + +/** Acknowledge device information change + * + * \param head The head to acknowledge. + * + * Clears the device changed flag on this head. When a compositor has processed + * device information, it should call this to be able to notice further + * changes. + * + * \sa weston_head_is_device_changed + * \memberof weston_head + */ +WL_EXPORT void +weston_head_reset_device_changed(struct weston_head *head) +{ + head->device_changed = false; +} + /** Get the name of a head * * \param head The head to query. diff --git a/libweston/compositor.h b/libweston/compositor.h index b3b0eddad..3e113c476 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -170,6 +170,7 @@ struct weston_head { char *serial_number; /**< monitor serial */ uint32_t subpixel; /**< enum wl_output_subpixel */ bool connection_internal; /**< embedded monitor (e.g. laptop) */ + bool device_changed; /**< monitor information has changed */ char *name; /**< head name, e.g. connector name */ bool connected; /**< is physically connected */ @@ -2044,6 +2045,12 @@ weston_head_is_connected(struct weston_head *head); bool weston_head_is_enabled(struct weston_head *head); +bool +weston_head_is_device_changed(struct weston_head *head); + +void +weston_head_reset_device_changed(struct weston_head *head); + const char * weston_head_get_name(struct weston_head *head); From 3717e639ee59b57e513bb49b7d99f07eb0875941 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 17 Aug 2017 16:39:48 +0300 Subject: [PATCH 0415/1642] weston: move weston_output_enable() into callers Move the call out of wet_configure_windowed_output_from_config() and into its callers. This allows to migrate each frontend one by one. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- compositor/main.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 1e827884e..72b1d3328 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1007,8 +1007,6 @@ wet_configure_windowed_output_from_config(struct weston_output *output, return -1; } - weston_output_enable(output); - return 0; } @@ -1152,6 +1150,8 @@ headless_backend_output_configure(struct wl_listener *listener, void *data) if (wet_configure_windowed_output_from_config(output, &defaults) < 0) weston_log("Cannot configure output \"%s\".\n", output->name); + + weston_output_enable(output); } static int @@ -1370,6 +1370,8 @@ x11_backend_output_configure(struct wl_listener *listener, void *data) if (wet_configure_windowed_output_from_config(output, &defaults) < 0) weston_log("Cannot configure output \"%s\".\n", output->name); + + weston_output_enable(output); } static int @@ -1486,6 +1488,8 @@ wayland_backend_output_configure(struct wl_listener *listener, void *data) if (wet_configure_windowed_output_from_config(output, &defaults) < 0) weston_log("Cannot configure output \"%s\".\n", output->name); + + weston_output_enable(output); } static int From cd8a1a818291c7e70647babca2cb626f042cda6d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 17 Aug 2017 13:10:28 +0300 Subject: [PATCH 0416/1642] weston: migrate headless to head-based output API Migrate the headless frontend to use the new head-based output configuration API: listen for heads_changed, and process all heads. The simple_heads_changed() function is written to be able to cater for all backends. The rest will be migrated individually. The head destroy listeners are not exactly necessary, for headless anyway, but this is an example excercising the API. Also is_device_changed() check is mostly useful with DRM. v8: - replace weston_compositor_set_heads_changed_cb() with weston_compositor_add_heads_changed_listener() - fix comment on wet_head_tracker_create() v3: Print "Detected a monitor change" only for enabled heads. Signed-off-by: Pekka Paalanen v6 Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- compositor/main.c | 202 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 192 insertions(+), 10 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 72b1d3328..55be7cdd8 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -69,11 +69,20 @@ struct wet_output_config { uint32_t transform; }; +struct wet_compositor; + +struct wet_head_tracker { + struct wl_listener head_destroy_listener; +}; + struct wet_compositor { struct weston_config *config; struct wet_output_config *parsed_options; struct wl_listener pending_output_listener; bool drm_use_current_mode; + struct wl_listener heads_changed_listener; + int (*simple_output_configure)(struct weston_output *output); + bool init_failed; }; static FILE *weston_logfile = NULL; @@ -1010,6 +1019,181 @@ wet_configure_windowed_output_from_config(struct weston_output *output, return 0; } +static int +count_remaining_heads(struct weston_output *output, struct weston_head *to_go) +{ + struct weston_head *iter = NULL; + int n = 0; + + while ((iter = weston_output_iterate_heads(output, iter))) { + if (iter != to_go) + n++; + } + + return n; +} + +static void +wet_head_tracker_destroy(struct wet_head_tracker *track) +{ + wl_list_remove(&track->head_destroy_listener.link); + free(track); +} + +static void +handle_head_destroy(struct wl_listener *listener, void *data) +{ + struct weston_head *head = data; + struct weston_output *output; + struct wet_head_tracker *track = + container_of(listener, struct wet_head_tracker, + head_destroy_listener); + + wet_head_tracker_destroy(track); + + output = weston_head_get_output(head); + + /* On shutdown path, the output might be already gone. */ + if (!output) + return; + + if (count_remaining_heads(output, head) > 0) + return; + + weston_output_destroy(output); +} + +static struct wet_head_tracker * +wet_head_tracker_from_head(struct weston_head *head) +{ + struct wl_listener *lis; + + lis = weston_head_get_destroy_listener(head, handle_head_destroy); + if (!lis) + return NULL; + + return container_of(lis, struct wet_head_tracker, + head_destroy_listener); +} + +/* Listen for head destroy signal. + * + * If a head is destroyed and it was the last head on the output, we + * destroy the associated output. + * + * Do not bother destroying the head trackers on shutdown, the backend will + * destroy the heads which calls our handler to destroy the trackers. + */ +static void +wet_head_tracker_create(struct wet_compositor *compositor, + struct weston_head *head) +{ + struct wet_head_tracker *track; + + track = zalloc(sizeof *track); + if (!track) + return; + + track->head_destroy_listener.notify = handle_head_destroy; + weston_head_add_destroy_listener(head, &track->head_destroy_listener); +} + +static void +simple_head_enable(struct weston_compositor *compositor, struct weston_head *head) +{ + struct wet_compositor *wet = to_wet_compositor(compositor); + struct weston_output *output; + int ret = 0; + + output = weston_compositor_create_output_with_head(compositor, head); + if (!output) { + weston_log("Could not create an output for head \"%s\".\n", + weston_head_get_name(head)); + wet->init_failed = true; + + return; + } + + if (wet->simple_output_configure) + ret = wet->simple_output_configure(output); + if (ret < 0) { + weston_log("Cannot configure output \"%s\".\n", + weston_head_get_name(head)); + weston_output_destroy(output); + wet->init_failed = true; + + return; + } + + if (weston_output_enable(output) < 0) { + weston_log("Enabling output \"%s\" failed.\n", + weston_head_get_name(head)); + weston_output_destroy(output); + wet->init_failed = true; + + return; + } + + wet_head_tracker_create(wet, head); + + /* The weston_compositor will track and destroy the output on exit. */ +} + +static void +simple_head_disable(struct weston_head *head) +{ + struct weston_output *output; + struct wet_head_tracker *track; + + track = wet_head_tracker_from_head(head); + if (track) + wet_head_tracker_destroy(track); + + output = weston_head_get_output(head); + assert(output); + weston_output_destroy(output); +} + +static void +simple_heads_changed(struct wl_listener *listener, void *arg) +{ + struct weston_compositor *compositor = arg; + struct weston_head *head = NULL; + bool connected; + bool enabled; + bool changed; + + while ((head = weston_compositor_iterate_heads(compositor, head))) { + connected = weston_head_is_connected(head); + enabled = weston_head_is_enabled(head); + changed = weston_head_is_device_changed(head); + + if (connected && !enabled) { + simple_head_enable(compositor, head); + } else if (!connected && enabled) { + simple_head_disable(head); + } else if (enabled && changed) { + weston_log("Detected a monitor change on head '%s', " + "not bothering to do anything about it.\n", + weston_head_get_name(head)); + } + weston_head_reset_device_changed(head); + } +} + +static void +wet_set_simple_head_configurator(struct weston_compositor *compositor, + int (*fn)(struct weston_output *)) +{ + struct wet_compositor *wet = to_wet_compositor(compositor); + + wet->simple_output_configure = fn; + + wet->heads_changed_listener.notify = simple_heads_changed; + weston_compositor_add_heads_changed_listener(compositor, + &wet->heads_changed_listener); +} + static void configure_input_device(struct weston_compositor *compositor, struct libinput_device *device) @@ -1137,10 +1321,9 @@ load_drm_backend(struct weston_compositor *c, return ret; } -static void -headless_backend_output_configure(struct wl_listener *listener, void *data) +static int +headless_backend_output_configure(struct weston_output *output) { - struct weston_output *output = data; struct wet_output_config defaults = { .width = 1024, .height = 640, @@ -1148,10 +1331,7 @@ headless_backend_output_configure(struct wl_listener *listener, void *data) .transform = WL_OUTPUT_TRANSFORM_NORMAL }; - if (wet_configure_windowed_output_from_config(output, &defaults) < 0) - weston_log("Cannot configure output \"%s\".\n", output->name); - - weston_output_enable(output); + return wet_configure_windowed_output_from_config(output, &defaults); } static int @@ -1189,6 +1369,8 @@ load_headless_backend(struct weston_compositor *c, config.base.struct_version = WESTON_HEADLESS_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_headless_backend_config); + wet_set_simple_head_configurator(c, headless_backend_output_configure); + /* load the actual wayland backend and configure it */ ret = weston_compositor_load_backend(c, WESTON_BACKEND_HEADLESS, &config.base); @@ -1196,8 +1378,6 @@ load_headless_backend(struct weston_compositor *c, if (ret < 0) return ret; - wet_set_pending_output_handler(c, headless_backend_output_configure); - if (!no_outputs) { api = weston_windowed_output_get_api(c); @@ -1676,7 +1856,7 @@ int main(int argc, char *argv[]) struct wl_client *primary_client; struct wl_listener primary_client_destroyed; struct weston_seat *seat; - struct wet_compositor user_data; + struct wet_compositor user_data = { 0 }; int require_input; int32_t wait_for_debugger = 0; @@ -1791,6 +1971,8 @@ int main(int argc, char *argv[]) } weston_pending_output_coldplug(ec); + if (user_data.init_failed) + goto out; if (idle_time < 0) weston_config_section_get_int(section, "idle-time", &idle_time, -1); From 30465bdca66b6f2dd7a917d1d1724d62ec54201c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 17 Aug 2017 16:54:58 +0300 Subject: [PATCH 0417/1642] weston: migrate x11 to head-based output API Migrate the x11 frontend to use the new head-based output configuration API: listen for heads_changed, and process all heads. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- compositor/main.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 55be7cdd8..b21bd4674 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1537,10 +1537,9 @@ load_fbdev_backend(struct weston_compositor *c, return ret; } -static void -x11_backend_output_configure(struct wl_listener *listener, void *data) +static int +x11_backend_output_configure(struct weston_output *output) { - struct weston_output *output = data; struct wet_output_config defaults = { .width = 1024, .height = 600, @@ -1548,10 +1547,7 @@ x11_backend_output_configure(struct wl_listener *listener, void *data) .transform = WL_OUTPUT_TRANSFORM_NORMAL }; - if (wet_configure_windowed_output_from_config(output, &defaults) < 0) - weston_log("Cannot configure output \"%s\".\n", output->name); - - weston_output_enable(output); + return wet_configure_windowed_output_from_config(output, &defaults); } static int @@ -1587,6 +1583,8 @@ load_x11_backend(struct weston_compositor *c, config.base.struct_version = WESTON_X11_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_x11_backend_config); + wet_set_simple_head_configurator(c, x11_backend_output_configure); + /* load the actual backend and configure it */ ret = weston_compositor_load_backend(c, WESTON_BACKEND_X11, &config.base); @@ -1594,8 +1592,6 @@ load_x11_backend(struct weston_compositor *c, if (ret < 0) return ret; - wet_set_pending_output_handler(c, x11_backend_output_configure); - api = weston_windowed_output_get_api(c); if (!api) { From 46e2f024494c30d819c200bcaadc73c80e78d92a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 17 Aug 2017 16:59:53 +0300 Subject: [PATCH 0418/1642] weston: migrate wayland to head-based output API Migrate the Wayland frontend to use the new head-based output configuration API: listen for heads_changed, and process all heads. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- compositor/main.c | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index b21bd4674..335b7f269 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1642,19 +1642,9 @@ load_x11_backend(struct weston_compositor *c, return 0; } -static void -wayland_backend_output_configure_hotplug(struct wl_listener *listener, void *data) -{ - struct weston_output *output = data; - - /* This backend has all values hardcoded, so nothing can be configured here */ - weston_output_enable(output); -} - -static void -wayland_backend_output_configure(struct wl_listener *listener, void *data) +static int +wayland_backend_output_configure(struct weston_output *output) { - struct weston_output *output = data; struct wet_output_config defaults = { .width = 1024, .height = 640, @@ -1662,10 +1652,7 @@ wayland_backend_output_configure(struct wl_listener *listener, void *data) .transform = WL_OUTPUT_TRANSFORM_NORMAL }; - if (wet_configure_windowed_output_from_config(output, &defaults) < 0) - weston_log("Cannot configure output \"%s\".\n", output->name); - - weston_output_enable(output); + return wet_configure_windowed_output_from_config(output, &defaults); } static int @@ -1732,13 +1719,15 @@ load_wayland_backend(struct weston_compositor *c, if (api == NULL) { /* We will just assume if load_backend() finished cleanly and * windowed_output_api is not present that wayland backend is - * started with --sprawl or runs on fullscreen-shell. */ - wet_set_pending_output_handler(c, wayland_backend_output_configure_hotplug); + * started with --sprawl or runs on fullscreen-shell. + * In this case, all values are hardcoded, so nothing can be + * configured; simply create and enable an output. */ + wet_set_simple_head_configurator(c, NULL); return 0; } - wet_set_pending_output_handler(c, wayland_backend_output_configure); + wet_set_simple_head_configurator(c, wayland_backend_output_configure); section = NULL; while (weston_config_next_section(wc, §ion, §ion_name)) { From 33961bd78e63f3eeb19bf8428d40bc8f944c7c0c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 17 Aug 2017 17:05:29 +0300 Subject: [PATCH 0419/1642] weston: migrate fbdev to head-based output API Migrate the fbdev frontend to use the new head-based output configuration API: listen for heads_changed, and process all heads. v7: - remove unnecessary 'goto out' in load_fbdev_backend() Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by: Daniel Stone Acked-by: Derek Foreman --- compositor/main.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 335b7f269..2f0437bfb 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1487,10 +1487,9 @@ load_rdp_backend(struct weston_compositor *c, return ret; } -static void -fbdev_backend_output_configure(struct wl_listener *listener, void *data) +static int +fbdev_backend_output_configure(struct weston_output *output) { - struct weston_output *output = data; struct weston_config *wc = wet_get_config(output->compositor); struct weston_config_section *section; @@ -1499,7 +1498,7 @@ fbdev_backend_output_configure(struct wl_listener *listener, void *data) wet_output_set_transform(output, section, WL_OUTPUT_TRANSFORM_NORMAL, UINT32_MAX); weston_output_set_scale(output, 1); - weston_output_enable(output); + return 0; } static int @@ -1523,16 +1522,12 @@ load_fbdev_backend(struct weston_compositor *c, config.base.struct_size = sizeof(struct weston_fbdev_backend_config); config.configure_device = configure_input_device; + wet_set_simple_head_configurator(c, fbdev_backend_output_configure); + /* load the actual wayland backend and configure it */ ret = weston_compositor_load_backend(c, WESTON_BACKEND_FBDEV, &config.base); - if (ret < 0) - goto out; - - wet_set_pending_output_handler(c, fbdev_backend_output_configure); - -out: free(config.device); return ret; } From 63d58390cee370f39c7e1a8aebfbab1465f218cb Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 17 Aug 2017 17:09:01 +0300 Subject: [PATCH 0420/1642] weston: migrate RDP to head-based output API Migrate the RDP frontend to use the new head-based output configuration API: listen for heads_changed, and process all heads. v7: - remove unnecessary 'goto out' in load_rdp_backend() Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by: Daniel Stone Acked-by: Derek Foreman --- compositor/main.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 2f0437bfb..3885ff6cf 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1393,10 +1393,9 @@ load_headless_backend(struct weston_compositor *c, return 0; } -static void -rdp_backend_output_configure(struct wl_listener *listener, void *data) +static int +rdp_backend_output_configure(struct weston_output *output) { - struct weston_output *output = data; 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); @@ -1407,7 +1406,7 @@ rdp_backend_output_configure(struct wl_listener *listener, void *data) if (!api) { weston_log("Cannot use weston_rdp_output_api.\n"); - return; + return -1; } if (parsed_options->width) @@ -1422,10 +1421,10 @@ rdp_backend_output_configure(struct wl_listener *listener, void *data) if (api->output_set_size(output, width, height) < 0) { weston_log("Cannot configure output \"%s\" using weston_rdp_output_api.\n", output->name); - return; + return -1; } - weston_output_enable(output); + return 0; } static void @@ -1470,15 +1469,11 @@ load_rdp_backend(struct weston_compositor *c, parse_options(rdp_options, ARRAY_LENGTH(rdp_options), argc, argv); + wet_set_simple_head_configurator(c, rdp_backend_output_configure); + ret = weston_compositor_load_backend(c, WESTON_BACKEND_RDP, &config.base); - if (ret < 0) - goto out; - - wet_set_pending_output_handler(c, rdp_backend_output_configure); - -out: free(config.bind_address); free(config.rdp_key); free(config.server_cert); From 1394ac303e4a7bd02085496645f3c599f349b562 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 17 Aug 2017 17:13:08 +0300 Subject: [PATCH 0421/1642] weston: migrate DRM to head-based output API Migrate the DRM frontend to use the simple head-based output configurator, maintaining the exact same features and semantics as before. This is an intermediate step. It is unoptimal to create a weston_output just to turn it off, but the libweston implementation and the DRM backend require it for now. In the future, the DRM frontend will get its own configurator that does not create useless weston_outputs and supports clone mode by attaching multiple heads to the same weston_output. Clone mode is not yet supported by libweston/DRM. Until we remove the need to create a weston_output just to turn it "off", that is, disable it, we will hit simple_head_enable() for heads we have already disabled. As long as the DRM-backend conversion to the head-based API is not complete, attempting to create an output for a head again would lead to a crash. This problem does not exist right now, but it will after the patch "compositor-drm: start migration to head-based output API". Therefore, check if the head we are about to process is already attached, and do nothing if so. DRM outputs set to "off" are the only ones legitimately hitting this condition. This is the last frontend migrated, wet_set_pending_output_handler() is deleted as dead code. v9: - Add the workaround in simple_head_enable(). Signed-off-by: Pekka Paalanen v6 Reviewed-by: Ian Ray v7 Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- compositor/main.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 3885ff6cf..9306cb8b3 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -78,7 +78,6 @@ struct wet_head_tracker { struct wet_compositor { struct weston_config *config; struct wet_output_config *parsed_options; - struct wl_listener pending_output_listener; bool drm_use_current_mode; struct wl_listener heads_changed_listener; int (*simple_output_configure)(struct weston_output *output); @@ -361,16 +360,6 @@ to_wet_compositor(struct weston_compositor *compositor) return weston_compositor_get_user_data(compositor); } -static void -wet_set_pending_output_handler(struct weston_compositor *ec, - wl_notify_func_t handler) -{ - struct wet_compositor *compositor = to_wet_compositor(ec); - - compositor->pending_output_listener.notify = handler; - wl_signal_add(&ec->output_pending_signal, &compositor->pending_output_listener); -} - static struct wet_output_config * wet_init_parsed_options(struct weston_compositor *ec) { @@ -1105,6 +1094,12 @@ simple_head_enable(struct weston_compositor *compositor, struct weston_head *hea struct weston_output *output; int ret = 0; + /* Workaround for repeated DRM backend "off" setting. + * For any other case, we should not have an attached head that is not + * enabled. */ + if (weston_head_get_output(head)) + return; + output = weston_compositor_create_output_with_head(compositor, head); if (!output) { weston_log("Could not create an output for head \"%s\".\n", @@ -1125,6 +1120,10 @@ simple_head_enable(struct weston_compositor *compositor, struct weston_head *hea return; } + /* Escape hatch for DRM backend "off" setting. */ + if (ret > 0) + return; + if (weston_output_enable(output) < 0) { weston_log("Enabling output \"%s\" failed.\n", weston_head_get_name(head)); @@ -1218,10 +1217,9 @@ configure_input_device(struct weston_compositor *compositor, } } -static void -drm_backend_output_configure(struct wl_listener *listener, void *data) +static int +drm_backend_output_configure(struct weston_output *output) { - struct weston_output *output = data; struct weston_config *wc = wet_get_config(output->compositor); struct wet_compositor *wet = to_wet_compositor(output->compositor); struct weston_config_section *section; @@ -1236,7 +1234,7 @@ drm_backend_output_configure(struct wl_listener *listener, void *data) if (!api) { weston_log("Cannot use weston_drm_output_api.\n"); - return; + return -1; } section = weston_config_get_section(wc, "output", "name", output->name); @@ -1245,7 +1243,7 @@ drm_backend_output_configure(struct wl_listener *listener, void *data) if (strcmp(s, "off") == 0) { weston_output_disable(output); free(s); - return; + return 1; } else if (wet->drm_use_current_mode || strcmp(s, "current") == 0) { mode = WESTON_DRM_BACKEND_OUTPUT_CURRENT; } else if (strcmp(s, "preferred") != 0) { @@ -1257,7 +1255,7 @@ drm_backend_output_configure(struct wl_listener *listener, void *data) if (api->set_mode(output, mode, modeline) < 0) { weston_log("Cannot configure an output using weston_drm_output_api.\n"); free(modeline); - return; + return -1; } free(modeline); @@ -1275,7 +1273,7 @@ drm_backend_output_configure(struct wl_listener *listener, void *data) api->set_seat(output, seat); free(seat); - weston_output_enable(output); + return 0; } static int @@ -1310,11 +1308,11 @@ load_drm_backend(struct weston_compositor *c, config.base.struct_size = sizeof(struct weston_drm_backend_config); config.configure_device = configure_input_device; + wet_set_simple_head_configurator(c, drm_backend_output_configure); + ret = weston_compositor_load_backend(c, WESTON_BACKEND_DRM, &config.base); - wet_set_pending_output_handler(c, drm_backend_output_configure); - free(config.gbm_format); free(config.seat_id); From e868c3fc152c791e35748bfdd4419b7b890ee3dc Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 15:03:42 +0300 Subject: [PATCH 0422/1642] libweston: change windowed_output_api output_create to create_head Rename the function pointer to create_head() because that is what it does on backends that are converted to the head-based API. Update the documentation to match. Surprisingly this is not an ABI break, as the function behaviour and signature remain intact. Hence API_NAME is not bumped. This is only an API break, and main.c is fixed accordingly. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- compositor/main.c | 10 +++++----- libweston/windowed-output-api.h | 23 +++++++++++++---------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 9306cb8b3..372de49d5 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1384,7 +1384,7 @@ load_headless_backend(struct weston_compositor *c, return -1; } - if (api->output_create(c, "headless") < 0) + if (api->create_head(c, "headless") < 0) return -1; } @@ -1604,7 +1604,7 @@ load_x11_backend(struct weston_compositor *c, continue; } - if (api->output_create(c, output_name) < 0) { + if (api->create_head(c, output_name) < 0) { free(output_name); return -1; } @@ -1620,7 +1620,7 @@ load_x11_backend(struct weston_compositor *c, return -1; } - if (api->output_create(c, default_output) < 0) { + if (api->create_head(c, default_output) < 0) { free(default_output); return -1; } @@ -1736,7 +1736,7 @@ load_wayland_backend(struct weston_compositor *c, continue; } - if (api->output_create(c, output_name) < 0) { + if (api->create_head(c, output_name) < 0) { free(output_name); return -1; } @@ -1749,7 +1749,7 @@ load_wayland_backend(struct weston_compositor *c, if (asprintf(&output_name, "wayland%d", i) < 0) return -1; - if (api->output_create(c, output_name) < 0) { + if (api->create_head(c, output_name) < 0) { free(output_name); return -1; } diff --git a/libweston/windowed-output-api.h b/libweston/windowed-output-api.h index e0f78b4dc..388413f30 100644 --- a/libweston/windowed-output-api.h +++ b/libweston/windowed-output-api.h @@ -56,23 +56,26 @@ struct weston_windowed_output_api { int (*output_set_size)(struct weston_output *output, int width, int height); - /** Create a new windowed output. + /** Create a new windowed head. * * \param compositor The compositor instance. - * \param name Desired name for a new output. + * \param name Desired name for a new head, not NULL. * * Returns 0 on success, -1 on failure. * - * This creates a new output in the backend using this API. - * After this function is ran, the created output should be - * ready for configuration using the output_configure() and - * weston_output_set_{scale,transform}(). + * This creates a new head in the backend. The new head will + * be advertised in the compositor's head list and triggers a + * head_changed callback. * - * An optional name can be assigned to it, so it can be used - * by compositor to configure it. It can't be NULL. + * A new output can be created for the head. The output must be + * configured with output_set_size() and + * weston_output_set_{scale,transform}() before enabling it. + * + * \sa weston_compositor_set_heads_changed_cb(), + * weston_compositor_create_output_with_head() */ - int (*output_create)(struct weston_compositor *compositor, - const char *name); + int (*create_head)(struct weston_compositor *compositor, + const char *name); }; static inline const struct weston_windowed_output_api * From 8a8dcac4316049cf630483894466d3c057205676 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 17 Aug 2017 17:29:36 +0300 Subject: [PATCH 0423/1642] libweston: remove output_pending_signal The signal has been replaced with the heads_changed hook and is no longer useful. weston_pending_output_coldplug() is renamed to weston_compositor_flush_heads_changed() for two reasons: it better describes what it does now, and it serves as an obvious flag that libweston ABI has been broken. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- compositor/main.c | 2 +- libweston/compositor.c | 21 +++++---------------- libweston/compositor.h | 3 +-- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 372de49d5..f72d3ea1f 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1943,7 +1943,7 @@ int main(int argc, char *argv[]) goto out; } - weston_pending_output_coldplug(ec); + weston_compositor_flush_heads_changed(ec); if (user_data.init_failed) goto out; diff --git a/libweston/compositor.c b/libweston/compositor.c index 919a84a1d..d013273cd 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5470,9 +5470,6 @@ weston_output_init(struct weston_output *output, * \param output The weston_output object to add * \param compositor The compositor instance. * - * Also notifies the compositor that an output is pending for - * configuration. - * * The opposite of this operation is built into weston_output_release(). * * \memberof weston_output @@ -5487,7 +5484,6 @@ weston_compositor_add_pending_output(struct weston_output *output, wl_list_remove(&output->link); wl_list_insert(compositor->pending_output_list.prev, &output->link); - wl_signal_emit(&compositor->output_pending_signal, output); } /** Constructs a weston_output object that can be used by the compositor. @@ -5634,22 +5630,16 @@ weston_output_disable(struct weston_output *output) output->destroying = 0; } -/** Emits a signal to indicate that there are outputs waiting to be configured. +/** Forces a synchronous call to heads_changed hook * * \param compositor The compositor instance + * + * If there are new or changed heads, calls the heads_changed hook and + * returns after the hook returns. */ WL_EXPORT void -weston_pending_output_coldplug(struct weston_compositor *compositor) +weston_compositor_flush_heads_changed(struct weston_compositor *compositor) { - struct weston_output *output, *next; - - wl_list_for_each_safe(output, next, &compositor->pending_output_list, link) - wl_signal_emit(&compositor->output_pending_signal, output); - - /* Execute the heads changed callback manually to ensure it is - * processed before any plugins get their start-up idle tasks ran. - * This ensures the plugins see all the initial outputs. - */ if (compositor->heads_changed_source) { wl_event_source_remove(compositor->heads_changed_source); weston_compositor_call_heads_changed(compositor); @@ -6118,7 +6108,6 @@ weston_compositor_create(struct wl_display *display, void *user_data) wl_signal_init(&ec->hide_input_panel_signal); wl_signal_init(&ec->update_input_panel_signal); wl_signal_init(&ec->seat_created_signal); - wl_signal_init(&ec->output_pending_signal); wl_signal_init(&ec->output_created_signal); wl_signal_init(&ec->output_destroyed_signal); wl_signal_init(&ec->output_moved_signal); diff --git a/libweston/compositor.h b/libweston/compositor.h index 3e113c476..5ee7fdfd6 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -955,7 +955,6 @@ struct weston_compositor { struct wl_signal update_input_panel_signal; struct wl_signal seat_created_signal; - struct wl_signal output_pending_signal; struct wl_signal output_created_signal; struct wl_signal output_destroyed_signal; struct wl_signal output_moved_signal; @@ -2115,7 +2114,7 @@ void weston_output_disable(struct weston_output *output); void -weston_pending_output_coldplug(struct weston_compositor *compositor); +weston_compositor_flush_heads_changed(struct weston_compositor *compositor); struct weston_head * weston_head_from_resource(struct wl_resource *resource); From ddce54de3aab242f746c44bd4c09d6e04e987d06 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 23 Aug 2017 16:00:21 +0300 Subject: [PATCH 0424/1642] libweston: stop auto-adding the implicit head All frontends have been converted to the new head-based output management API, which means that weston_compositor_create_output_with_head() is calling weston_output_attach_head(). We will never hit the implicit attach anymore. Therefore we can now require that an output has at least one head when calling weston_output_enable(). An output without heads is useless. The auto-add code is removed as dead. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index d013273cd..0efd57074 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5489,7 +5489,7 @@ weston_compositor_add_pending_output(struct weston_output *output, /** Constructs a weston_output object that can be used by the compositor. * * \param output The weston_output object that needs to be enabled. Must not - * be enabled already. + * be enabled already. Must have at least one head attached. * * Output coordinates are calculated and each new output is by default * assigned to the right of previous one. @@ -5525,7 +5525,6 @@ weston_output_enable(struct weston_output *output) struct weston_compositor *c = output->compositor; struct weston_output *iterator; int x = 0, y = 0; - int ret; if (output->enabled) { weston_log("Error: attempt to enable an enabled output '%s'\n", @@ -5533,6 +5532,12 @@ weston_output_enable(struct weston_output *output) return -1; } + if (wl_list_empty(&output->head_list)) { + weston_log("Error: cannot enable output '%s' without heads.\n", + output->name); + return -1; + } + iterator = container_of(c->output_list.prev, struct weston_output, link); @@ -5561,12 +5566,6 @@ weston_output_enable(struct weston_output *output) wl_list_init(&output->animation_list); wl_list_init(&output->feedback_list); - /* XXX: Temporary until all backends are converted. */ - if (wl_list_empty(&output->head_list)) { - ret = weston_output_attach_head(output, &output->head); - assert(ret == 0); - } - /* Enable the output (set up the crtc or create a * window representing the output, set up the * renderer, etc) From ec25b0a844b8786fd20e107669c516f45372955a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 24 Aug 2017 16:08:49 +0300 Subject: [PATCH 0425/1642] libweston: assert make/model in weston_output_enable() Output make and model are not allowed to be NULL in the protocol, so ensure they are not forgotten when enabling an output. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index 0efd57074..547e826d6 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5524,6 +5524,7 @@ weston_output_enable(struct weston_output *output) { struct weston_compositor *c = output->compositor; struct weston_output *iterator; + struct weston_head *head; int x = 0, y = 0; if (output->enabled) { @@ -5538,6 +5539,11 @@ weston_output_enable(struct weston_output *output) return -1; } + wl_list_for_each(head, &output->head_list, output_link) { + assert(head->make); + assert(head->model); + } + iterator = container_of(c->output_list.prev, struct weston_output, link); From 586e1ac791e7a514b71db873b477b137f02aa0b4 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 14 Sep 2017 16:17:59 +0300 Subject: [PATCH 0426/1642] libweston: assert current_mode in weston_output_enable() The functions called here, particularly weston_output_transform_scale_init(), rely on current mode being set. The current mode must also be found in the mode list, though we don't explicitly check it here. current_mode not being set is a programmer error. It could be a backend bug, but it could also be a libweston user bug not calling a set size function. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index 547e826d6..2ec7f8e05 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5539,6 +5539,12 @@ weston_output_enable(struct weston_output *output) return -1; } + if (wl_list_empty(&output->mode_list) || !output->current_mode) { + weston_log("Error: no video mode for output '%s'.\n", + output->name); + return -1; + } + wl_list_for_each(head, &output->head_list, output_link) { assert(head->make); assert(head->model); From dcbcfc7f678d96d9c26514fa47d65d992bb5b48c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 26 Oct 2017 14:33:59 +0300 Subject: [PATCH 0427/1642] libweston: cancel idle_repaint on output destroy If the idle_repaint() callback has been scheduled when a weston_output gets destroyed, the callback will hit use-after-free. I have encountered this when migrating the wayland backend to the head-based API, using --sprawl, and closing/disconnecting one of the parent compositor outputs. Store the idle_repaint callback source, and destroy it in weston_output_release(), ensuring we don't get a stale call to start_repaint_loop later. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 8 +++++++- libweston/compositor.h | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 2ec7f8e05..4a1d568a9 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2592,6 +2592,7 @@ idle_repaint(void *data) assert(output->repaint_status == REPAINT_BEGIN_FROM_IDLE); output->repaint_status = REPAINT_AWAITING_COMPLETION; + output->idle_repaint_source = NULL; output->start_repaint_loop(output); } @@ -2716,7 +2717,9 @@ weston_output_schedule_repaint(struct weston_output *output) return; output->repaint_status = REPAINT_BEGIN_FROM_IDLE; - wl_event_loop_add_idle(loop, idle_repaint, output); + assert(!output->idle_repaint_source); + output->idle_repaint_source = wl_event_loop_add_idle(loop, idle_repaint, + output); TL_POINT("core_repaint_enter_loop", TLP_OUTPUT(output), TLP_END); } @@ -5676,6 +5679,9 @@ weston_output_release(struct weston_output *output) output->destroying = 1; + if (output->idle_repaint_source) + wl_event_source_remove(output->idle_repaint_source); + if (output->enabled) weston_compositor_remove_output(output); diff --git a/libweston/compositor.h b/libweston/compositor.h index 5ee7fdfd6..869740df5 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -216,6 +216,9 @@ struct weston_output { * next repaint should be run */ struct timespec next_repaint; + /** For cancelling the idle_repaint callback on output destruction. */ + struct wl_event_source *idle_repaint_source; + struct weston_output_zoom zoom; int dirty; struct wl_signal frame_signal; From fd4c57cb01d606f16e6b28c74b0b56e3fa29cec7 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 24 Aug 2017 16:40:17 +0300 Subject: [PATCH 0428/1642] compositor-headless: migrate to head-based output API Implement the head-based output API in this backend, and stop relying on the implicit weston_output::head. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-headless.c | 75 +++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index d6ab9db95..6cb4f2066 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -48,6 +48,10 @@ struct headless_backend { bool use_pixman; }; +struct headless_head { + struct weston_head base; +}; + struct headless_output { struct weston_output base; @@ -57,6 +61,12 @@ struct headless_output { pixman_image_t *image; }; +static inline struct headless_head * +to_headless_head(struct weston_head *base) +{ + return container_of(base, struct headless_head, base); +} + static inline struct headless_output * to_headless_output(struct weston_output *base) { @@ -185,7 +195,7 @@ headless_output_set_size(struct weston_output *base, int width, int height) { struct headless_output *output = to_headless_output(base); - struct weston_head *head = &output->base.head; + struct weston_head *head; int output_width, output_height; /* We can only be called once. */ @@ -194,6 +204,14 @@ headless_output_set_size(struct weston_output *base, /* Make sure we have scale set. */ assert(output->base.scale); + wl_list_for_each(head, &output->base.head_list, output_link) { + weston_head_set_monitor_strings(head, "weston", "headless", + NULL); + + /* XXX: Calculate proper size. */ + weston_head_set_physical_size(head, width, height); + } + output_width = width * output->base.scale; output_height = height * output->base.scale; @@ -206,11 +224,6 @@ headless_output_set_size(struct weston_output *base, output->base.current_mode = &output->mode; - weston_head_set_monitor_strings(head, "weston", "headless", NULL); - - /* XXX: Calculate proper size. */ - weston_head_set_physical_size(head, width, height); - output->base.start_repaint_loop = headless_output_start_repaint_loop; output->base.repaint = headless_output_repaint; output->base.assign_planes = NULL; @@ -221,9 +234,8 @@ headless_output_set_size(struct weston_output *base, return 0; } -static int -headless_output_create(struct weston_compositor *compositor, - const char *name) +static struct weston_output * +headless_output_create(struct weston_compositor *compositor, const char *name) { struct headless_output *output; @@ -231,33 +243,71 @@ headless_output_create(struct weston_compositor *compositor, assert(name); output = zalloc(sizeof *output); - if (output == NULL) - return -1; + if (!output) + return NULL; weston_output_init(&output->base, compositor, name); output->base.destroy = headless_output_destroy; output->base.disable = headless_output_disable; output->base.enable = headless_output_enable; + output->base.attach_head = NULL; weston_compositor_add_pending_output(&output->base, compositor); + return &output->base; +} + +static int +headless_head_create(struct weston_compositor *compositor, + const char *name) +{ + struct headless_head *head; + + /* name can't be NULL. */ + assert(name); + + head = zalloc(sizeof *head); + if (head == NULL) + return -1; + + weston_head_init(&head->base, name); + weston_head_set_connection_status(&head->base, true); + + /* Ideally all attributes of the head would be set here, so that the + * user has all the information when deciding to create outputs. + * We do not have those until set_size() time through. + */ + + weston_compositor_add_head(compositor, &head->base); + return 0; } +static void +headless_head_destroy(struct headless_head *head) +{ + weston_head_release(&head->base); + free(head); +} + static void headless_destroy(struct weston_compositor *ec) { struct headless_backend *b = to_headless_backend(ec); + struct weston_head *base, *next; weston_compositor_shutdown(ec); + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) + headless_head_destroy(to_headless_head(base)); + free(b); } static const struct weston_windowed_output_api api = { headless_output_set_size, - headless_output_create, + headless_head_create, }; static struct headless_backend * @@ -278,6 +328,7 @@ headless_backend_create(struct weston_compositor *compositor, goto err_free; b->base.destroy = headless_destroy; + b->base.create_output = headless_output_create; b->use_pixman = config->use_pixman; if (b->use_pixman) { From 388646506bd0eaa402de501d6cccf0773feefaf7 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 16:21:26 +0300 Subject: [PATCH 0429/1642] compositor-rdp: migrate to head-based output API Follow the starndard patttern as the other backends, headless and x11 in particular, to stop relying on the implicit weston_output::head. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-rdp.c | 64 +++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 4d74d40cd..693f136a0 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -130,6 +130,10 @@ struct rdp_peers_item { struct wl_list link; }; +struct rdp_head { + struct weston_head base; +}; + struct rdp_output { struct weston_output base; struct wl_event_source *finish_frame_timer; @@ -152,6 +156,12 @@ struct rdp_peer_context { }; typedef struct rdp_peer_context RdpPeerContext; +static inline struct rdp_head * +to_rdp_head(struct weston_head *base) +{ + return container_of(base, struct rdp_head, base); +} + static inline struct rdp_output * to_rdp_output(struct weston_output *base) { @@ -482,13 +492,20 @@ rdp_output_set_size(struct weston_output *base, int width, int height) { struct rdp_output *output = to_rdp_output(base); - struct weston_head *head = &output->base.head; + struct weston_head *head; struct weston_mode *currentMode; struct weston_mode initMode; /* We can only be called once. */ assert(!output->base.current_mode); + wl_list_for_each(head, &output->base.head_list, output_link) { + weston_head_set_monitor_strings(head, "weston", "rdp", NULL); + + /* XXX: Calculate proper size. */ + weston_head_set_physical_size(head, width, height); + } + wl_list_init(&output->peers); initMode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; @@ -502,11 +519,6 @@ rdp_output_set_size(struct weston_output *base, output->base.current_mode = output->base.native_mode = currentMode; - weston_head_set_monitor_strings(head, "weston", "rdp", NULL); - - /* XXX: Calculate proper size. */ - weston_head_set_physical_size(head, width, height); - output->base.start_repaint_loop = rdp_output_start_repaint_loop; output->base.repaint = rdp_output_repaint; output->base.assign_planes = NULL; @@ -576,33 +588,62 @@ rdp_output_destroy(struct weston_output *base) free(output); } -static int -rdp_backend_create_output(struct weston_compositor *compositor) +static struct weston_output * +rdp_output_create(struct weston_compositor *compositor, const char *name) { struct rdp_output *output; output = zalloc(sizeof *output); if (output == NULL) - return -1; + return NULL; - weston_output_init(&output->base, compositor, "rdp"); + weston_output_init(&output->base, compositor, name); output->base.destroy = rdp_output_destroy; output->base.disable = rdp_output_disable; output->base.enable = rdp_output_enable; + output->base.attach_head = NULL; weston_compositor_add_pending_output(&output->base, compositor); + return &output->base; +} + +static int +rdp_head_create(struct weston_compositor *compositor, const char *name) +{ + struct rdp_head *head; + + head = zalloc(sizeof *head); + if (!head) + return -1; + + weston_head_init(&head->base, name); + weston_head_set_connection_status(&head->base, true); + weston_compositor_add_head(compositor, &head->base); + return 0; } +static void +rdp_head_destroy(struct rdp_head *head) +{ + weston_head_release(&head->base); + free(head); +} + static void rdp_destroy(struct weston_compositor *ec) { struct rdp_backend *b = to_rdp_backend(ec); + struct weston_head *base, *next; int i; weston_compositor_shutdown(ec); + + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) + rdp_head_destroy(to_rdp_head(base)); + for (i = 0; i < MAX_FREERDP_FDS; i++) if (b->listener_events[i]) wl_event_source_remove(b->listener_events[i]); @@ -1298,6 +1339,7 @@ rdp_backend_create(struct weston_compositor *compositor, b->compositor = compositor; b->base.destroy = rdp_destroy; + b->base.create_output = rdp_output_create; b->rdp_key = config->rdp_key ? strdup(config->rdp_key) : NULL; b->no_clients_resize = config->no_clients_resize; @@ -1319,7 +1361,7 @@ rdp_backend_create(struct weston_compositor *compositor, if (pixman_renderer_init(compositor) < 0) goto err_compositor; - if (rdp_backend_create_output(compositor) < 0) + if (rdp_head_create(compositor, "rdp") < 0) goto err_compositor; compositor->capabilities |= WESTON_CAP_ARBITRARY_MODES; From 55916d54ceb0b94321946a4b497208992768cb9b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Sep 2017 16:19:02 +0300 Subject: [PATCH 0430/1642] compositor-fbdev: make re-enable less drastic Destroying the whole output in reenable would cause list walk corruption: the loop over output_list in session_notify() is not using wl_list_for_each_safe so output removal would break it. Creating a new output is also problematic as it needs the compositor to configure it, but that probably saved us from another list walk failure: adding the new output to be list while walking the list, possibly causing it to be destroyed and re-created ad infinitum. Instead of a complete destroy/create cycle, just do our internal disable/enable cycle. That will re-open the fbdev, re-read the parameters, re-create hw_surface, and reinitialize the renderer output. A problem with this is if fbdev_set_screen_info() fails. We do read the new parameters, but we don't communicate them to libweston core or old clients. However, it is hard to care: to trigger this path, one needs to VT-switch to another fbdev app which changes the fbdev parameters. That is quite difficult as VT-switching has been broken for a good while for fbdev-backend, at least with logind. Also fbdev_set_screen_info() would have to fail before one should be able to tell something is wrong. The real reason behind this patch, though, is the migration to the head-based output API. Destroying and re-creating an output really does not fit that design. Destroying and re-creating a head would be better, but again not testable in the current state. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-fbdev.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 7db95d218..af338625a 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -594,15 +594,15 @@ fbdev_output_reenable(struct fbdev_backend *backend, struct fbdev_output *output = to_fbdev_output(base); struct fbdev_screeninfo new_screen_info; int fb_fd; - char *device; weston_log("Re-enabling fbdev output.\n"); + assert(output->base.enabled); /* Create the frame buffer. */ fb_fd = fbdev_frame_buffer_open(output->device, &new_screen_info); if (fb_fd < 0) { weston_log("Creating frame buffer failed.\n"); - goto err; + return -1; } /* Check whether the frame buffer details have changed since we were @@ -616,27 +616,20 @@ fbdev_output_reenable(struct fbdev_backend *backend, close(fb_fd); - /* Remove and re-add the output so that resources depending on + /* Disable and enable the output so that resources depending on * the frame buffer X/Y resolution (such as the shadow buffer) * are re-initialised. */ - device = strdup(output->device); - fbdev_output_destroy(&output->base); - fbdev_output_create(backend, device); - free(device); - - return 0; + fbdev_output_disable(&output->base); + return fbdev_output_enable(&output->base); } /* Map the device if it has the same details as before. */ if (fbdev_frame_buffer_map(output, fb_fd) < 0) { weston_log("Mapping frame buffer failed.\n"); - goto err; + return -1; } return 0; - -err: - return -1; } static void From 2305812296a23bb7e3c1d36831700b233ac9036d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 14 Sep 2017 16:50:44 +0300 Subject: [PATCH 0431/1642] compositor-fbdev: migrate to head-based output API Implement the head-based output API in this backend, and stop relying on the implicit weston_output::head. The split between fbdev_head and fbdev_output is somewhat arbitrary. There is no hotplug or unplug, and there is always 1:1 relationship. Struct fbdev_screeninfo could have been split as well, but it would not have made much difference. I chose fbdev_output to carry the mmap details (buffer_length is now duplicated here), and fbdev_head to carry the display parameters and device node path. The device node identifies the head, similar to a connector. The backend init creates a head. The compositor uses it to create an output. Libweston core attaches the head automatically after creating the output. The attach hook is a suitable place to set up the video modes on the output as they are dictated by the head, it would be too late at enable() time. v7: - use name argument instead of hardcoded "fbdev" in fbdev_output_create() Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-fbdev.c | 189 ++++++++++++++++++++++++----------- 1 file changed, 133 insertions(+), 56 deletions(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index af338625a..de40c2191 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -78,6 +78,14 @@ struct fbdev_screeninfo { unsigned int refresh_rate; /* Hertz */ }; +struct fbdev_head { + struct weston_head base; + + /* Frame buffer details. */ + char *device; + struct fbdev_screeninfo fb_info; +}; + struct fbdev_output { struct fbdev_backend *backend; struct weston_output base; @@ -85,10 +93,9 @@ struct fbdev_output { struct weston_mode mode; struct wl_event_source *finish_frame_timer; - /* Frame buffer details. */ - char *device; - struct fbdev_screeninfo fb_info; - void *fb; /* length is fb_info.buffer_length */ + /* framebuffer mmap details */ + size_t buffer_length; + void *fb; /* pixman details. */ pixman_image_t *hw_surface; @@ -96,6 +103,12 @@ struct fbdev_output { static const char default_seat[] = "seat0"; +static inline struct fbdev_head * +to_fbdev_head(struct weston_head *base) +{ + return container_of(base, struct fbdev_head, base); +} + static inline struct fbdev_output * to_fbdev_output(struct weston_output *base) { @@ -108,6 +121,16 @@ to_fbdev_backend(struct weston_compositor *base) return container_of(base->backend, struct fbdev_backend, base); } +static struct fbdev_head * +fbdev_output_get_head(struct fbdev_output *output) +{ + if (wl_list_length(&output->base.head_list) != 1) + return NULL; + + return container_of(output->base.head_list.next, + struct fbdev_head, base.output_link); +} + static void fbdev_output_start_repaint_loop(struct weston_output *output) { @@ -368,13 +391,17 @@ fbdev_frame_buffer_open(const char *fb_dev, static int fbdev_frame_buffer_map(struct fbdev_output *output, int fd) { + struct fbdev_head *head; int retval = -1; + head = fbdev_output_get_head(output); + weston_log("Mapping fbdev frame buffer.\n"); /* Map the frame buffer. Write-only mode, since we don't want to read * anything back (because it's slow). */ - output->fb = mmap(NULL, output->fb_info.buffer_length, + output->buffer_length = head->fb_info.buffer_length; + output->fb = mmap(NULL, output->buffer_length, PROT_WRITE, MAP_SHARED, fd, 0); if (output->fb == MAP_FAILED) { weston_log("Failed to mmap frame buffer: %s\n", @@ -385,11 +412,11 @@ fbdev_frame_buffer_map(struct fbdev_output *output, int fd) /* Create a pixman image to wrap the memory mapped frame buffer. */ output->hw_surface = - pixman_image_create_bits(output->fb_info.pixel_format, - output->fb_info.x_resolution, - output->fb_info.y_resolution, + pixman_image_create_bits(head->fb_info.pixel_format, + head->fb_info.x_resolution, + head->fb_info.y_resolution, output->fb, - output->fb_info.line_length); + head->fb_info.line_length); if (output->hw_surface == NULL) { weston_log("Failed to create surface for frame buffer.\n"); goto out_unmap; @@ -400,7 +427,7 @@ fbdev_frame_buffer_map(struct fbdev_output *output, int fd) out_unmap: if (retval != 0 && output->fb != NULL) { - munmap(output->fb, output->fb_info.buffer_length); + munmap(output->fb, output->buffer_length); output->fb = NULL; } @@ -425,13 +452,37 @@ fbdev_frame_buffer_unmap(struct fbdev_output *output) pixman_image_unref(output->hw_surface); output->hw_surface = NULL; - if (munmap(output->fb, output->fb_info.buffer_length) < 0) + if (munmap(output->fb, output->buffer_length) < 0) weston_log("Failed to munmap frame buffer: %s\n", strerror(errno)); output->fb = NULL; } + +static int +fbdev_output_attach_head(struct weston_output *output_base, + struct weston_head *head_base) +{ + struct fbdev_output *output = to_fbdev_output(output_base); + struct fbdev_head *head = to_fbdev_head(head_base); + + /* Clones not supported. */ + if (!wl_list_empty(&output->base.head_list)) + return -1; + + /* only one static mode in list */ + output->mode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + output->mode.width = head->fb_info.x_resolution; + output->mode.height = head->fb_info.y_resolution; + output->mode.refresh = head->fb_info.refresh_rate; + wl_list_init(&output->base.mode_list); + wl_list_insert(&output->base.mode_list, &output->mode.link); + output->base.current_mode = &output->mode; + + return 0; +} + static void fbdev_output_destroy(struct weston_output *base); static int @@ -439,11 +490,14 @@ fbdev_output_enable(struct weston_output *base) { struct fbdev_output *output = to_fbdev_output(base); struct fbdev_backend *backend = to_fbdev_backend(base->compositor); + struct fbdev_head *head; int fb_fd; struct wl_event_loop *loop; + head = fbdev_output_get_head(output); + /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(output->device, &output->fb_info); + fb_fd = fbdev_frame_buffer_open(head->device, &head->fb_info); if (fb_fd < 0) { weston_log("Creating frame buffer failed.\n"); return -1; @@ -494,64 +548,80 @@ fbdev_output_disable(struct weston_output *base) return 0; } -static int -fbdev_output_create(struct fbdev_backend *backend, - const char *device) +static struct fbdev_head * +fbdev_head_create(struct fbdev_backend *backend, const char *device) { - struct fbdev_output *output; - struct weston_head *head; + struct fbdev_head *head; int fb_fd; - weston_log("Creating fbdev output.\n"); - - output = zalloc(sizeof *output); - if (output == NULL) - return -1; + head = zalloc(sizeof *head); + if (!head) + return NULL; - output->backend = backend; - output->device = strdup(device); + head->device = strdup(device); /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(device, &output->fb_info); + fb_fd = fbdev_frame_buffer_open(head->device, &head->fb_info); if (fb_fd < 0) { - weston_log("Creating frame buffer failed.\n"); + weston_log("Creating frame buffer head failed.\n"); goto out_free; } + close(fb_fd); - weston_output_init(&output->base, backend->compositor, "fbdev"); + weston_head_init(&head->base, "fbdev"); + weston_head_set_connection_status(&head->base, true); + weston_head_set_monitor_strings(&head->base, "unknown", + head->fb_info.id, NULL); + weston_head_set_subpixel(&head->base, WL_OUTPUT_SUBPIXEL_UNKNOWN); + weston_head_set_physical_size(&head->base, head->fb_info.width_mm, + head->fb_info.height_mm); - output->base.destroy = fbdev_output_destroy; - output->base.disable = fbdev_output_disable; - output->base.enable = fbdev_output_enable; + weston_compositor_add_head(backend->compositor, &head->base); - /* only one static mode in list */ - output->mode.flags = - WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; - output->mode.width = output->fb_info.x_resolution; - output->mode.height = output->fb_info.y_resolution; - output->mode.refresh = output->fb_info.refresh_rate; - wl_list_insert(&output->base.mode_list, &output->mode.link); + weston_log("Created head '%s' for device %s (%s)\n", + head->base.name, head->device, head->base.model); - output->base.current_mode = &output->mode; + return head; - head = &output->base.head; - weston_head_set_monitor_strings(head, "unknown", output->fb_info.id, - NULL); - weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_UNKNOWN); - weston_head_set_physical_size(head, output->fb_info.width_mm, - output->fb_info.height_mm); +out_free: + free(head->device); + free(head); - close(fb_fd); + return NULL; +} - weston_compositor_add_pending_output(&output->base, backend->compositor); +static void +fbdev_head_destroy(struct fbdev_head *head) +{ + weston_head_release(&head->base); + free(head->device); + free(head); +} - return 0; +static struct weston_output * +fbdev_output_create(struct weston_compositor *compositor, + const char *name) +{ + struct fbdev_output *output; -out_free: - free(output->device); - free(output); + weston_log("Creating fbdev output.\n"); - return -1; + output = zalloc(sizeof *output); + if (output == NULL) + return NULL; + + output->backend = to_fbdev_backend(compositor); + + weston_output_init(&output->base, compositor, name); + + output->base.destroy = fbdev_output_destroy; + output->base.disable = fbdev_output_disable; + output->base.enable = fbdev_output_enable; + output->base.attach_head = fbdev_output_attach_head; + + weston_compositor_add_pending_output(&output->base, compositor); + + return &output->base; } static void @@ -566,7 +636,6 @@ fbdev_output_destroy(struct weston_output *base) /* Remove the output. */ weston_output_release(&output->base); - free(output->device); free(output); } @@ -592,14 +661,17 @@ fbdev_output_reenable(struct fbdev_backend *backend, struct weston_output *base) { struct fbdev_output *output = to_fbdev_output(base); + struct fbdev_head *head; struct fbdev_screeninfo new_screen_info; int fb_fd; + head = fbdev_output_get_head(output); + weston_log("Re-enabling fbdev output.\n"); assert(output->base.enabled); /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(output->device, &new_screen_info); + fb_fd = fbdev_frame_buffer_open(head->device, &new_screen_info); if (fb_fd < 0) { weston_log("Creating frame buffer failed.\n"); return -1; @@ -607,9 +679,9 @@ fbdev_output_reenable(struct fbdev_backend *backend, /* Check whether the frame buffer details have changed since we were * disabled. */ - if (compare_screen_info (&output->fb_info, &new_screen_info) != 0) { + if (compare_screen_info(&head->fb_info, &new_screen_info) != 0) { /* Perform a mode-set to restore the old mode. */ - if (fbdev_set_screen_info(fb_fd, &output->fb_info) < 0) { + if (fbdev_set_screen_info(fb_fd, &head->fb_info) < 0) { weston_log("Failed to restore mode settings. " "Attempting to re-open output anyway.\n"); } @@ -636,12 +708,16 @@ static void fbdev_backend_destroy(struct weston_compositor *base) { struct fbdev_backend *backend = to_fbdev_backend(base); + struct weston_head *head, *next; udev_input_destroy(&backend->input); /* Destroy the output. */ weston_compositor_shutdown(base); + wl_list_for_each_safe(head, next, &base->head_list, compositor_link) + fbdev_head_destroy(to_fbdev_head(head)); + /* Chain up. */ weston_launcher_destroy(base->launcher); @@ -732,6 +808,7 @@ fbdev_backend_create(struct weston_compositor *compositor, } backend->base.destroy = fbdev_backend_destroy; + backend->base.create_output = fbdev_output_create; backend->prev_state = WESTON_COMPOSITOR_ACTIVE; @@ -740,7 +817,7 @@ fbdev_backend_create(struct weston_compositor *compositor, if (pixman_renderer_init(compositor) < 0) goto out_launcher; - if (fbdev_output_create(backend, param->device) < 0) + if (!fbdev_head_create(backend, param->device)) goto out_launcher; udev_input_init(&backend->input, compositor, backend->udev, From c900881e091966d189c98fa46d2cab02e3dcb796 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 15:58:10 +0300 Subject: [PATCH 0432/1642] compositor-x11: migrate to head-based output API Follow the standard pattern set by the headless backend which also uses the the window output API. Stops relying on the implicit weston_output::head. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-x11.c | 68 ++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index de19fad8f..78b0d7c60 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -116,6 +116,10 @@ struct x11_backend { } atom; }; +struct x11_head { + struct weston_head base; +}; + struct x11_output { struct weston_output base; @@ -142,6 +146,12 @@ struct window_delete_data { struct gl_renderer_interface *gl_renderer; +static inline struct x11_head * +to_x11_head(struct weston_head *base) +{ + return container_of(base, struct x11_head, base); +} + static inline struct x11_output * to_x11_output(struct weston_output *base) { @@ -1065,7 +1075,7 @@ x11_output_set_size(struct weston_output *base, int width, int height) { struct x11_output *output = to_x11_output(base); struct x11_backend *b = to_x11_backend(base->compositor); - struct weston_head *head = &output->base.head; + struct weston_head *head; xcb_screen_t *scrn = b->screen; int output_width, output_height; @@ -1087,6 +1097,13 @@ x11_output_set_size(struct weston_output *base, int width, int height) return -1; } + wl_list_for_each(head, &output->base.head_list, output_link) { + weston_head_set_monitor_strings(head, "weston-X11", "none", NULL); + weston_head_set_physical_size(head, + width * scrn->width_in_millimeters / scrn->width_in_pixels, + height * scrn->height_in_millimeters / scrn->height_in_pixels); + } + output_width = width * output->base.scale; output_height = height * output->base.scale; @@ -1104,17 +1121,11 @@ x11_output_set_size(struct weston_output *base, int width, int height) output->base.native_mode = &output->native; output->base.native_scale = output->base.scale; - weston_head_set_monitor_strings(head, "weston-X11", "none", NULL); - weston_head_set_physical_size(head, - width * scrn->width_in_millimeters / scrn->width_in_pixels, - height * scrn->height_in_millimeters / scrn->height_in_pixels); - return 0; } -static int -x11_output_create(struct weston_compositor *compositor, - const char *name) +static struct weston_output * +x11_output_create(struct weston_compositor *compositor, const char *name) { struct x11_output *output; @@ -1122,22 +1133,46 @@ x11_output_create(struct weston_compositor *compositor, assert(name); output = zalloc(sizeof *output); - if (output == NULL) { - perror("zalloc"); - return -1; - } + if (!output) + return NULL; weston_output_init(&output->base, compositor, name); output->base.destroy = x11_output_destroy; output->base.disable = x11_output_disable; output->base.enable = x11_output_enable; + output->base.attach_head = NULL; weston_compositor_add_pending_output(&output->base, compositor); + return &output->base; +} + +static int +x11_head_create(struct weston_compositor *compositor, const char *name) +{ + struct x11_head *head; + + assert(name); + + head = zalloc(sizeof *head); + if (!head) + return -1; + + weston_head_init(&head->base, name); + weston_head_set_connection_status(&head->base, true); + weston_compositor_add_head(compositor, &head->base); + return 0; } +static void +x11_head_destroy(struct x11_head *head) +{ + weston_head_release(&head->base); + free(head); +} + static struct x11_output * x11_backend_find_output(struct x11_backend *b, xcb_window_t window) { @@ -1745,12 +1780,16 @@ static void x11_destroy(struct weston_compositor *ec) { struct x11_backend *backend = to_x11_backend(ec); + struct weston_head *base, *next; wl_event_source_remove(backend->xcb_source); x11_input_destroy(backend); weston_compositor_shutdown(ec); /* destroys outputs, too */ + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) + x11_head_destroy(to_x11_head(base)); + XCloseDisplay(backend->dpy); free(backend); } @@ -1774,7 +1813,7 @@ init_gl_renderer(struct x11_backend *b) static const struct weston_windowed_output_api api = { x11_output_set_size, - x11_output_create, + x11_head_create, }; static struct x11_backend * @@ -1833,6 +1872,7 @@ x11_backend_create(struct weston_compositor *compositor, weston_log("Using %s renderer\n", config->use_pixman ? "pixman" : "gl"); b->base.destroy = x11_destroy; + b->base.create_output = x11_output_create; if (x11_input_create(b, config->no_input) < 0) { weston_log("Failed to create X11 input\n"); From 39069fa27c1a925fa36a63cd4fcc58e22baad8e9 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 26 Oct 2017 12:56:00 +0300 Subject: [PATCH 0433/1642] compositor-wayland: strict surface create/destroy Add safeguards to make it painfully obvious if we ever get the pairing of wayland_backend_create_output_surface() and wayland_backend_destroy_output_surface() wrong. Helps catching bugs. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-wayland.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index fbb04deac..82e52b90d 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -645,16 +645,25 @@ wayland_output_repaint_pixman(struct weston_output *output_base, static void wayland_backend_destroy_output_surface(struct wayland_output *output) { - if (output->parent.xdg_toplevel) + assert(output->parent.surface); + + if (output->parent.xdg_toplevel) { zxdg_toplevel_v6_destroy(output->parent.xdg_toplevel); + output->parent.xdg_toplevel = NULL; + } - if (output->parent.xdg_surface) + if (output->parent.xdg_surface) { zxdg_surface_v6_destroy(output->parent.xdg_surface); + output->parent.xdg_surface = NULL; + } - if (output->parent.shell_surface) + if (output->parent.shell_surface) { wl_shell_surface_destroy(output->parent.shell_surface); + output->parent.shell_surface = NULL; + } wl_surface_destroy(output->parent.surface); + output->parent.surface = NULL; } static void @@ -1139,6 +1148,8 @@ wayland_backend_create_output_surface(struct wayland_output *output) { struct wayland_backend *b = to_wayland_backend(output->base.compositor); + assert(!output->parent.surface); + output->parent.surface = wl_compositor_create_surface(b->parent.compositor); if (!output->parent.surface) From c80e954123dc824cbdf679328200a5440d78f1e6 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 25 Oct 2017 15:51:37 +0300 Subject: [PATCH 0434/1642] compositor-wayland: migrate to head-based output API Follow the standard pattern used in the headless and x11 backend migration, but also cater for the two other backend modes: --sprawl or fullscreen-shell, and --fullscreen. Stops relying on the implicit weston_output::head. Unlike other backends, this uses the attach_head hook to do the required head setup that is not possible to do without an output, but must be done before weston_output_enable(). This also requires the detach_head hook for the one case where the user attaches a --fullscreen head and then detaches it without enabling the output. It is a little awkward to fully initialize heads as late as attach, but aside from the --sprawl/fullscreen-shell case, there is not really a way to know the head properties without creating the parent wl_surface and configuring it. Heads/outputs created for parent outputs now have distinct names instead of all being called "wlparent". Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Acked-by: Derek Foreman --- libweston/compositor-wayland.c | 239 ++++++++++++++++++++++++--------- 1 file changed, 178 insertions(+), 61 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 82e52b90d..2473f0879 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -137,7 +137,7 @@ struct wayland_output { struct wayland_parent_output { struct wayland_backend *backend; /**< convenience */ - struct wayland_output *output; + struct wayland_head *head; struct wl_list link; struct wl_output *global; @@ -161,6 +161,11 @@ struct wayland_parent_output { struct weston_mode *current_mode; }; +struct wayland_head { + struct weston_head base; + struct wayland_parent_output *parent_output; +}; + struct wayland_shm_buffer { struct wayland_output *output; struct wl_list link; @@ -210,6 +215,12 @@ struct wayland_input { struct gl_renderer_interface *gl_renderer; +static inline struct wayland_head * +to_wayland_head(struct weston_head *base) +{ + return container_of(base, struct wayland_head, base); +} + static inline struct wayland_output * to_wayland_output(struct weston_output *base) { @@ -1275,9 +1286,59 @@ wayland_output_enable(struct weston_output *base) return -1; } -static struct wayland_output * -wayland_output_create_common(struct weston_compositor *compositor, - const char *name) +static int +wayland_output_setup_for_parent_output(struct wayland_output *output, + struct wayland_parent_output *poutput); + +static int +wayland_output_setup_fullscreen(struct wayland_output *output, + struct wayland_head *head); + +static int +wayland_output_attach_head(struct weston_output *output_base, + struct weston_head *head_base) +{ + struct wayland_backend *b = to_wayland_backend(output_base->compositor); + struct wayland_output *output = to_wayland_output(output_base); + struct wayland_head *head = to_wayland_head(head_base); + + if (!wl_list_empty(&output->base.head_list)) + return -1; + + if (head->parent_output) { + if (wayland_output_setup_for_parent_output(output, + head->parent_output) < 0) + return -1; + } else if (b->fullscreen) { + if (wayland_output_setup_fullscreen(output, head) < 0) + return -1; + } else { + /* A floating window, nothing to do. */ + } + + return 0; +} + +static void +wayland_output_detach_head(struct weston_output *output_base, + struct weston_head *head) +{ + struct wayland_output *output = to_wayland_output(output_base); + + /* Rely on the disable hook if the output was enabled. We do not + * support cloned heads, so detaching is guaranteed to disable the + * output. + */ + if (output->base.enabled) + return; + + /* undo setup fullscreen */ + if (output->parent.surface) + wayland_backend_destroy_output_surface(output); +} + +static struct weston_output * +wayland_output_create(struct weston_compositor *compositor, const char *name) { struct wayland_output *output; char *title; @@ -1302,29 +1363,87 @@ wayland_output_create_common(struct weston_compositor *compositor, output->base.destroy = wayland_output_destroy; output->base.disable = wayland_output_disable; output->base.enable = wayland_output_enable; + output->base.attach_head = wayland_output_attach_head; + output->base.detach_head = wayland_output_detach_head; + + weston_compositor_add_pending_output(&output->base, compositor); + + return &output->base; +} + +static struct wayland_head * +wayland_head_create(struct weston_compositor *compositor, const char *name) +{ + struct wayland_head *head; + + assert(name); + + head = zalloc(sizeof *head); + if (!head) + return NULL; + + weston_head_init(&head->base, name); + weston_head_set_connection_status(&head->base, true); + weston_compositor_add_head(compositor, &head->base); - return output; + return head; } static int -wayland_output_create(struct weston_compositor *compositor, const char *name) +wayland_head_create_windowed(struct weston_compositor *compositor, + const char *name) { - struct wayland_output *output; + if (!wayland_head_create(compositor, name)) + return -1; - output = wayland_output_create_common(compositor, name); - if (!output) + return 0; +} + +static int +wayland_head_create_for_parent_output(struct weston_compositor *compositor, + struct wayland_parent_output *poutput) +{ + struct wayland_head *head; + char name[100]; + int ret; + + ret = snprintf(name, sizeof(name), "wlparent-%d", poutput->id); + if (ret < 1 || (unsigned)ret >= sizeof(name)) return -1; - weston_compositor_add_pending_output(&output->base, compositor); + head = wayland_head_create(compositor, name); + if (!head) + return -1; + + assert(!poutput->head); + head->parent_output = poutput; + poutput->head = head; + + weston_head_set_monitor_strings(&head->base, + poutput->physical.make, + poutput->physical.model, NULL); + weston_head_set_physical_size(&head->base, + poutput->physical.width, + poutput->physical.height); return 0; } +static void +wayland_head_destroy(struct wayland_head *head) +{ + if (head->parent_output) + head->parent_output->head = NULL; + + weston_head_release(&head->base); + free(head); +} + static int wayland_output_set_size(struct weston_output *base, int width, int height) { struct wayland_output *output = to_wayland_output(base); - struct weston_head *head = &output->base.head; + struct weston_head *head; int output_width, output_height; /* We can only be called once. */ @@ -1345,6 +1464,13 @@ wayland_output_set_size(struct weston_output *base, int width, int height) return -1; } + wl_list_for_each(head, &output->base.head_list, output_link) { + weston_head_set_monitor_strings(head, "wayland", "none", NULL); + + /* XXX: Calculate proper size. */ + weston_head_set_physical_size(head, width, height); + } + output_width = width * output->base.scale; output_height = height * output->base.scale; @@ -1358,25 +1484,15 @@ wayland_output_set_size(struct weston_output *base, int width, int height) output->base.current_mode = &output->mode; - weston_head_set_monitor_strings(head, "wayland", "none", NULL); - - /* XXX: Calculate proper size. */ - weston_head_set_physical_size(head, width, height); - return 0; } static int -wayland_output_create_for_parent_output(struct wayland_backend *b, - struct wayland_parent_output *poutput) +wayland_output_setup_for_parent_output(struct wayland_output *output, + struct wayland_parent_output *poutput) { - struct wayland_output *output; struct weston_mode *mode; - output = wayland_output_create_common(b->compositor, "wlparent"); - if (!output) - return -1; - if (poutput->current_mode) { mode = poutput->current_mode; } else if (poutput->preferred_mode) { @@ -1386,7 +1502,7 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, struct weston_mode, link); } else { weston_log("No valid modes found. Skipping output.\n"); - goto out; + return -1; } output->base.scale = 1; @@ -1394,13 +1510,6 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, output->parent.output = poutput->global; - weston_head_set_monitor_strings(&output->base.head, - poutput->physical.make, - poutput->physical.model, NULL); - weston_head_set_physical_size(&output->base.head, - poutput->physical.width, - poutput->physical.height); - wl_list_insert_list(&output->base.mode_list, &poutput->mode_list); wl_list_init(&poutput->mode_list); @@ -1410,33 +1519,21 @@ wayland_output_create_for_parent_output(struct wayland_backend *b, /* output->mode is unused in this path. */ - weston_compositor_add_pending_output(&output->base, b->compositor); - return 0; - -out: - weston_output_release(&output->base); - free(output->title); - free(output); - - return -1; } static int -wayland_output_create_fullscreen(struct wayland_backend *b) +wayland_output_setup_fullscreen(struct wayland_output *output, + struct wayland_head *head) { - struct wayland_output *output; + struct wayland_backend *b = to_wayland_backend(output->base.compositor); int width = 0, height = 0; - output = wayland_output_create_common(b->compositor, "wayland-fullscreen"); - if (!output) - return -1; - output->base.scale = 1; output->base.transform = WL_OUTPUT_TRANSFORM_NORMAL; if (wayland_backend_create_output_surface(output) < 0) - goto err_surface; + return -1; /* What should size be set if conditional is false? */ if (b->parent.xdg_shell || b->parent.shell) { @@ -1456,16 +1553,15 @@ wayland_output_create_fullscreen(struct wayland_backend *b) if (wayland_output_set_size(&output->base, width, height) < 0) goto err_set_size; - weston_compositor_add_pending_output(&output->base, b->compositor); + /* The head is not attached yet, so set_size did not set these. */ + weston_head_set_monitor_strings(&head->base, "wayland", "none", NULL); + /* XXX: Calculate proper size. */ + weston_head_set_physical_size(&head->base, width, height); return 0; err_set_size: wayland_backend_destroy_output_surface(output); -err_surface: - weston_output_release(&output->base); - free(output->title); - free(output); return -1; } @@ -2286,16 +2382,32 @@ find_mode(struct wl_list *list, int32_t width, int32_t height, uint32_t refresh) return mode; } +static struct weston_output * +wayland_parent_output_get_enabled_output(struct wayland_parent_output *poutput) +{ + struct wayland_head *head = poutput->head; + + if (!head) + return NULL; + + if (!weston_head_is_enabled(&head->base)) + return NULL; + + return weston_head_get_output(&head->base); +} + static void wayland_parent_output_mode(void *data, struct wl_output *wl_output_proxy, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { struct wayland_parent_output *output = data; + struct weston_output *enabled_output; struct weston_mode *mode; - if (output->output) { - mode = find_mode(&output->output->base.mode_list, + enabled_output = wayland_parent_output_get_enabled_output(output); + if (enabled_output) { + mode = find_mode(&enabled_output->mode_list, width, height, refresh); if (!mode) return; @@ -2329,7 +2441,7 @@ output_sync_callback(void *data, struct wl_callback *callback, uint32_t unused) assert(output->backend->sprawl_across_outputs); - wayland_output_create_for_parent_output(output->backend, output); + wayland_head_create_for_parent_output(output->backend->compositor, output); } static const struct wl_callback_listener output_sync_listener = { @@ -2377,8 +2489,8 @@ wayland_parent_output_destroy(struct wayland_parent_output *output) if (output->sync_cb) wl_callback_destroy(output->sync_cb); - if (output->output) - wayland_output_destroy(&output->output->base); + if (output->head) + wayland_head_destroy(output->head); wl_output_destroy(output->global); free(output->physical.make); @@ -2483,11 +2595,15 @@ static void wayland_destroy(struct weston_compositor *ec) { struct wayland_backend *b = to_wayland_backend(ec); + struct weston_head *base, *next; wl_event_source_remove(b->parent.wl_source); weston_compositor_shutdown(ec); + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) + wayland_head_destroy(to_wayland_head(base)); + if (b->parent.shm) wl_shm_destroy(b->parent.shm); @@ -2642,6 +2758,7 @@ wayland_backend_create(struct weston_compositor *compositor, } b->base.destroy = wayland_destroy; + b->base.create_output = wayland_output_create; loop = wl_display_get_event_loop(compositor->wl_display); @@ -2686,7 +2803,7 @@ wayland_backend_destroy(struct wayland_backend *b) static const struct weston_windowed_output_api windowed_api = { wayland_output_set_size, - wayland_output_create, + wayland_head_create_windowed, }; static void @@ -2723,14 +2840,14 @@ weston_backend_init(struct weston_compositor *compositor, wl_display_roundtrip(b->parent.wl_display); wl_list_for_each(poutput, &b->parent.output_list, link) - wayland_output_create_for_parent_output(b, poutput); + wayland_head_create_for_parent_output(compositor, poutput); return 0; } if (new_config.fullscreen) { - if (wayland_output_create_fullscreen(b) < 0) { - weston_log("Unable to create a fullscreen output.\n"); + if (!wayland_head_create(compositor, "wayland-fullscreen")) { + weston_log("Unable to create a fullscreen head.\n"); goto err_outputs; } From c112f00bd15d657a16b78bf8a4346712d1303c40 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 28 Aug 2017 16:27:20 +0300 Subject: [PATCH 0435/1642] compositor-drm: start migration to head-based output API Hook up the libweston facing head-based output API by introducing struct drm_head, but leave it as a fake so that members can be migrated in pieces in follow-up patches. The DRM backend continues to create an output for each connected connector only, and during output creation it also creates a drm_head for it. This allows it to pretend it supports the head-based output API as long as there is only one head per output ever attached. create_output callback is fake, it will only look up the existing drm_output by the head name. Clones are not yet supported, hence max is defined to 1. This unfortunately introduces some temporary code that will be revomed later, but seems to be necessary to avoid a single big patch. v6: - add missing drm_head_destroy() call Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 135 +++++++++++++++++++++++++++++++++++-- 1 file changed, 129 insertions(+), 6 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 4504c00c6..d19f99c3c 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -83,6 +83,8 @@ #define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64 #endif +#define MAX_CLONED_CONNECTORS 1 + /** * Represents the values of an enum-type KMS property */ @@ -400,6 +402,13 @@ struct drm_plane { uint32_t formats[]; }; +struct drm_head { + struct weston_head base; + struct drm_backend *backend; + + struct drm_output *output; /* XXX: temporary */ +}; + struct drm_output { struct weston_output base; drmModeConnector *connector; @@ -475,6 +484,12 @@ wl_array_remove_uint32(struct wl_array *array, uint32_t elm) } } +static inline struct drm_head * +to_drm_head(struct weston_head *base) +{ + return container_of(base, struct drm_head, base); +} + static inline struct drm_output * to_drm_output(struct weston_output *base) { @@ -4393,6 +4408,16 @@ setup_output_seat_constraint(struct drm_backend *b, } } +static int +drm_output_attach_head(struct weston_output *output_base, + struct weston_head *head_base) +{ + if (wl_list_length(&output_base->head_list) >= MAX_CLONED_CONNECTORS) + return -1; + + return 0; +} + static int parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format) { @@ -4810,11 +4835,15 @@ drm_output_deinit(struct weston_output *base) b->state_invalid = true; } +static void +drm_head_destroy(struct drm_head *head); + static void drm_output_destroy(struct weston_output *base) { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); + struct weston_head *head = weston_output_get_first_head(base); if (output->page_flip_pending || output->vblank_pending || output->atomic_complete_pending) { @@ -4845,6 +4874,10 @@ drm_output_destroy(struct weston_output *base) drm_output_state_free(output->state_cur); free(output); + + /* XXX: temporary */ + if (head) + drm_head_destroy(to_drm_head(head)); } static int @@ -4868,6 +4901,19 @@ drm_output_disable(struct weston_output *base) return 0; } +static struct weston_output * +drm_output_create(struct weston_compositor *compositor, const char *name) +{ + struct drm_head *head; + + /* XXX: Temporary until we can have heads without an output */ + wl_list_for_each(head, &compositor->head_list, base.compositor_link) + if (strcmp(name, head->base.name) == 0) + return &head->output->base; + + return NULL; +} + /** * Update the list of unused connectors and CRTCs * @@ -4913,6 +4959,71 @@ drm_backend_update_unused_outputs(struct drm_backend *b, drmModeRes *resources) } } +/** + * Create a Weston head for a connector + * + * Given a DRM connector, create a matching drm_head structure and add it + * to Weston's head list. + * + * @param b Weston backend structure + * @param connector_id DRM connector ID for the head + * @param drm_device udev device pointer + * @returns The new head, or NULL on failure. + */ +static struct drm_head * +drm_head_create(struct drm_backend *backend, uint32_t connector_id, + struct udev_device *drm_device) +{ + struct drm_head *head; + drmModeConnector *connector; + char *name; + + head = zalloc(sizeof *head); + if (!head) + return NULL; + + connector = drmModeGetConnector(backend->drm.fd, connector_id); + if (!connector) + goto err_alloc; + + name = make_connector_name(connector); + if (!name) + goto err_alloc; + + weston_head_init(&head->base, name); + free(name); + + head->backend = backend; + + /* Unknown connection status is assumed disconnected. */ + weston_head_set_connection_status(&head->base, + connector->connection == DRM_MODE_CONNECTED); + + weston_compositor_add_head(backend->compositor, &head->base); + drmModeFreeConnector(connector); + + weston_log("DRM: found head '%s', connector %d %s.\n", + head->base.name, connector_id, + head->base.connected ? "connected" : "disconnected"); + + return head; + +err_alloc: + if (connector) + drmModeFreeConnector(connector); + + free(head); + + return NULL; +} + +static void +drm_head_destroy(struct drm_head *head) +{ + weston_head_release(&head->base); + free(head); +} + /** * Create a Weston output structure * @@ -4933,7 +5044,7 @@ create_output_for_connector(struct drm_backend *b, struct udev_device *drm_device) { struct drm_output *output; - struct weston_head *head; + struct drm_head *head; drmModeObjectPropertiesPtr props; struct drm_mode *drm_mode; char *name; @@ -4956,9 +5067,16 @@ create_output_for_connector(struct drm_backend *b, weston_output_init(&output->base, b->compositor, name); free(name); + /* XXX: temporary */ + head = drm_head_create(b, connector->connector_id, drm_device); + if (!head) + abort(); + head->output = output; + output->base.enable = drm_output_enable; output->base.destroy = drm_output_destroy; output->base.disable = drm_output_disable; + output->base.attach_head = drm_output_attach_head; output->destroy_pending = 0; output->disable_pending = 0; @@ -4974,23 +5092,22 @@ create_output_for_connector(struct drm_backend *b, } drm_property_info_populate(b, connector_props, output->props_conn, WDRM_CONNECTOR__COUNT, props); - head = &output->base.head; find_and_parse_output_edid(b, output, props, &make, &model, &serial_number); - weston_head_set_monitor_strings(head, make, model, serial_number); - weston_head_set_subpixel(head, + weston_head_set_monitor_strings(&head->base, make, model, serial_number); + weston_head_set_subpixel(&head->base, drm_subpixel_to_wayland(output->connector->subpixel)); drmModeFreeObjectProperties(props); if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) - weston_head_set_internal(head); + weston_head_set_internal(&head->base); if (drm_output_init_gamma_size(output) < 0) goto err_output; - weston_head_set_physical_size(head, output->connector->mmWidth, + weston_head_set_physical_size(&head->base, output->connector->mmWidth, output->connector->mmHeight); output->state_cur = drm_output_state_alloc(output, NULL); @@ -5008,6 +5125,7 @@ create_output_for_connector(struct drm_backend *b, return 0; err_output: + drm_head_destroy(head); drm_output_destroy(&output->base); return -1; /* no fallthrough! */ @@ -5189,6 +5307,7 @@ static void drm_destroy(struct weston_compositor *ec) { struct drm_backend *b = to_drm_backend(ec); + struct weston_head *base, *next; udev_input_destroy(&b->input); @@ -5201,6 +5320,9 @@ drm_destroy(struct weston_compositor *ec) weston_compositor_shutdown(ec); + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) + drm_head_destroy(to_drm_head(base)); + if (b->gbm) gbm_device_destroy(b->gbm); @@ -5718,6 +5840,7 @@ drm_backend_create(struct weston_compositor *compositor, b->base.repaint_begin = drm_repaint_begin; b->base.repaint_flush = drm_repaint_flush; b->base.repaint_cancel = drm_repaint_cancel; + b->base.create_output = drm_output_create; weston_setup_vt_switch_bindings(compositor); From 42c0e14808bfd39e1a2eea34f43f0b5fe20fbb16 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 27 Oct 2017 12:07:49 +0300 Subject: [PATCH 0436/1642] libweston: remove weston_output::head Remove the scaffolding that allowed backends to be converted one by one to the head-based API. Nothing is using these members anymore. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 36 +++--------------------------------- libweston/compositor.h | 3 --- 2 files changed, 3 insertions(+), 36 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 4a1d568a9..d149bb02a 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5449,13 +5449,6 @@ weston_output_init(struct weston_output *output, wl_list_init(&output->head_list); - weston_head_init(&output->head, name); - output->head.allocator_output = output; - if (!compositor->backend->create_output) { - weston_head_set_connection_status(&output->head, true); - weston_compositor_add_head(compositor, &output->head); - } - /* Add some (in)sane defaults which can be used * for checking if an output was properly configured */ @@ -5692,8 +5685,6 @@ weston_output_release(struct weston_output *output) wl_list_for_each_safe(head, tmp, &output->head_list, output_link) weston_head_detach(head); - weston_head_release(&output->head); - free(output->name); } @@ -5717,22 +5708,13 @@ weston_compositor_create_output_with_head(struct weston_compositor *compositor, { struct weston_output *output; - if (head->allocator_output) { - /* XXX: compatibility path to be removed after all converted */ - output = head->allocator_output; - } else { - assert(compositor->backend->create_output); - output = compositor->backend->create_output(compositor, - head->name); - } - + assert(compositor->backend->create_output); + output = compositor->backend->create_output(compositor, head->name); if (!output) return NULL; if (weston_output_attach_head(output, head) < 0) { - if (!head->allocator_output) - output->destroy(output); - + weston_output_destroy(output); return NULL; } @@ -5754,18 +5736,6 @@ weston_compositor_create_output_with_head(struct weston_compositor *compositor, WL_EXPORT void weston_output_destroy(struct weston_output *output) { - struct weston_head *head; - - /* XXX: compatibility path to be removed after all converted */ - head = weston_output_get_first_head(output); - if (head->allocator_output) { - /* The old design: backend is responsible for destroying the - * output, so just undo create_output_with_head() - */ - weston_head_detach(head); - return; - } - output->destroy(output); } diff --git a/libweston/compositor.h b/libweston/compositor.h index 869740df5..71e57755b 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -174,8 +174,6 @@ struct weston_head { char *name; /**< head name, e.g. connector name */ bool connected; /**< is physically connected */ - - struct weston_output *allocator_output; /**< XXX: to be removed */ }; struct weston_output { @@ -240,7 +238,6 @@ struct weston_output { struct weston_mode *original_mode; struct wl_list mode_list; - struct weston_head head; /**< head for unconverted backends */ struct wl_list head_list; /**< List of driven weston_heads */ void (*start_repaint_loop)(struct weston_output *output); From 3e8f20187857fbea8dba23241791a84ca4d707f9 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 2 Nov 2017 14:03:11 +0200 Subject: [PATCH 0437/1642] libweston: print head names on output enable This will be interesting to see when testing clone mode. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index d149bb02a..d5bbcb22d 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5482,6 +5482,32 @@ weston_compositor_add_pending_output(struct weston_output *output, wl_list_insert(compositor->pending_output_list.prev, &output->link); } +/** Create a string with the attached heads' names. + * + * The string must be free()'d. + */ +static char * +weston_output_create_heads_string(struct weston_output *output) +{ + FILE *fp; + char *str = NULL; + size_t size = 0; + struct weston_head *head; + const char *sep = ""; + + fp = open_memstream(&str, &size); + if (!fp) + return NULL; + + wl_list_for_each(head, &output->head_list, output_link) { + fprintf(fp, "%s%s", sep, head->name); + sep = ", "; + } + fclose(fp); + + return str; +} + /** Constructs a weston_output object that can be used by the compositor. * * \param output The weston_output object that needs to be enabled. Must not @@ -5521,6 +5547,7 @@ weston_output_enable(struct weston_output *output) struct weston_compositor *c = output->compositor; struct weston_output *iterator; struct weston_head *head; + char *head_names; int x = 0, y = 0; if (output->enabled) { @@ -5585,6 +5612,11 @@ weston_output_enable(struct weston_output *output) weston_compositor_add_output(output->compositor, output); + head_names = weston_output_create_heads_string(output); + weston_log("Output '%s' enabled with head(s) %s\n", + output->name, head_names); + free(head_names); + return 0; } From 1ae9d084770b5ff9257b3c6a114cde231720db13 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 2 Nov 2017 14:11:53 +0200 Subject: [PATCH 0438/1642] libweston: create/find output by name To let users pick an arbitrary name for an output, to be used as e.g. a configuration key, add API to create an output with a given name. For the same configuration purpose, add a search function as well. For the search function to be predictable, forbid creating multiple outputs with the same name. Previously, creating multiple outputs with the same name would have needed detatching to create outputs from the same head, now that is forbidden. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 56 ++++++++++++++++++++++++++++++++++++++++-- libweston/compositor.h | 8 ++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index d5bbcb22d..4e15c2e80 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5720,6 +5720,59 @@ weston_output_release(struct weston_output *output) free(output->name); } +/** Find an output by its given name + * + * \param compositor The compositor to search in. + * \param name The output name to search for. + * \return An existing output with the given name, or NULL if not found. + * + * \memberof weston_compositor + */ +WL_EXPORT struct weston_output * +weston_compositor_find_output_by_name(struct weston_compositor *compositor, + const char *name) +{ + struct weston_output *output; + + wl_list_for_each(output, &compositor->output_list, link) + if (strcmp(output->name, name) == 0) + return output; + + wl_list_for_each(output, &compositor->pending_output_list, link) + if (strcmp(output->name, name) == 0) + return output; + + return NULL; +} + +/** Create a named output + * + * \param compositor The compositor. + * \param name The name for the output. + * \return A new \c weston_output, or NULL on failure. + * + * This creates a new weston_output that starts with no heads attached. + * + * An output must be configured and it must have at least one head before + * it can be enabled. + * + * \memberof weston_compositor + */ +WL_EXPORT struct weston_output * +weston_compositor_create_output(struct weston_compositor *compositor, + const char *name) +{ + assert(compositor->backend->create_output); + + if (weston_compositor_find_output_by_name(compositor, name)) { + weston_log("Warning: attempted to create an output with a " + "duplicate name '%s'.\n", name); + return NULL; + } + + return compositor->backend->create_output(compositor, name); +} + /** Create an output for an unused head * * \param compositor The compositor. @@ -5740,8 +5793,7 @@ weston_compositor_create_output_with_head(struct weston_compositor *compositor, { struct weston_output *output; - assert(compositor->backend->create_output); - output = compositor->backend->create_output(compositor, head->name); + output = weston_compositor_create_output(compositor, head->name); if (!output) return NULL; diff --git a/libweston/compositor.h b/libweston/compositor.h index 71e57755b..c6083c7cc 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -2075,6 +2075,14 @@ void weston_compositor_add_heads_changed_listener(struct weston_compositor *compositor, struct wl_listener *listener); +struct weston_output * +weston_compositor_find_output_by_name(struct weston_compositor *compositor, + const char *name); + +struct weston_output * +weston_compositor_create_output(struct weston_compositor *compositor, + const char *name); + struct weston_output * weston_compositor_create_output_with_head(struct weston_compositor *compositor, struct weston_head *head); From 37b7c6ebb5418fb5048edaaad1e9e5f617613c8d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 7 Nov 2017 10:15:01 +0200 Subject: [PATCH 0439/1642] libweston: support user data on weston_output Support attaching custom data to a weston_output by the traditional destroy listener / wl_signal_get approach. Needs a new destroy signal, because user data lifetime should be the lifetime of the weston_output regradless of its enabled status. The old destroy signal is for output consumers that only care about enabled outputs in the system and gets emitted on disable, not on destroy. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 46 ++++++++++++++++++++++++++++++++++++++++++ libweston/compositor.h | 11 +++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 4e15c2e80..c4410e681 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5445,6 +5445,7 @@ weston_output_init(struct weston_output *output, output->destroying = 0; output->name = strdup(name); wl_list_init(&output->link); + wl_signal_init(&output->user_destroy_signal); output->enabled = false; wl_list_init(&output->head_list); @@ -5685,6 +5686,49 @@ weston_compositor_flush_heads_changed(struct weston_compositor *compositor) } } +/** Add destroy callback for an output + * + * \param output The output to watch. + * \param listener The listener to add. The \c notify member must be set. + * + * The listener callback will be called when user destroys an output. This + * may be delayed by a backend in some cases. The main purpose of the + * listener is to allow hooking up custom data to the output. The custom data + * can be fetched via weston_output_get_destroy_listener() followed by + * container_of(). + * + * The \c data argument to the notify callback is the weston_output being + * destroyed. + * + * @note This is for the final destruction of an output, not when it gets + * disabled. If you want to keep track of enabled outputs, this is not it. + */ +WL_EXPORT void +weston_output_add_destroy_listener(struct weston_output *output, + struct wl_listener *listener) +{ + wl_signal_add(&output->user_destroy_signal, listener); +} + +/** Look up destroy listener for an output + * + * \param output The output to query. + * \param notify The notify function used used for the added destroy listener. + * \return The listener, or NULL if not found. + * + * This looks up the previously added destroy listener struct based on the + * notify function it has. The listener can be used to access user data + * through \c container_of(). + * + * \sa wl_signal_get() weston_output_add_destroy_listener() + */ +WL_EXPORT struct wl_listener * +weston_output_get_destroy_listener(struct weston_output *output, + wl_notify_func_t notify) +{ + return wl_signal_get(&output->user_destroy_signal, notify); +} + /** Uninitialize an output * * Removes the output from the list of enabled outputs if necessary, but @@ -5704,6 +5748,8 @@ weston_output_release(struct weston_output *output) output->destroying = 1; + wl_signal_emit(&output->user_destroy_signal, output); + if (output->idle_repaint_source) wl_event_source_remove(output->idle_repaint_source); diff --git a/libweston/compositor.h b/libweston/compositor.h index c6083c7cc..f3137dea2 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -180,6 +180,9 @@ struct weston_output { uint32_t id; char *name; + /** Matches the lifetime from the user perspective */ + struct wl_signal user_destroy_signal; + void *renderer_state; struct wl_list link; @@ -220,7 +223,7 @@ struct weston_output { struct weston_output_zoom zoom; int dirty; struct wl_signal frame_signal; - struct wl_signal destroy_signal; + struct wl_signal destroy_signal; /**< sent when disabled */ int move_x, move_y; struct timespec frame_time; /* presentation timestamp */ uint64_t msc; /* media stream counter */ @@ -1808,6 +1811,12 @@ weston_output_activate_zoom(struct weston_output *output, void weston_output_move(struct weston_output *output, int x, int y); +void +weston_output_add_destroy_listener(struct weston_output *output, + struct wl_listener *listener); +struct wl_listener * +weston_output_get_destroy_listener(struct weston_output *output, + wl_notify_func_t notify); void weston_output_release(struct weston_output *output); void From dcac351dc5710a073e81fc816c7054203f6b0588 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 8 Dec 2017 14:13:34 +0200 Subject: [PATCH 0440/1642] libweston: allow attaching heads to enabled outputs Move the responsibility of ensuring the head will work in the enabled output to the backends. A compositor cannot enable an output without heads, and removing the last head from an output automatically disables the output, so attaching a new head to an enabled output is only possible for clone mode. Backends headless, rdp, and x11 forbid clone mode by not having an attach_head hook implemented; fbdev and wayland explicitly deny clone mode. Only the DRM backend is affected by this change and even that not yet because MAX_CLONED_CONNECTORS is 1 in the DRM backend. Also ensure a global is created for the head when attached to an enabled output, and log it. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index c4410e681..14fa12620 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -76,6 +76,9 @@ weston_output_transform_scale_init(struct weston_output *output, static void weston_compositor_build_view_list(struct weston_compositor *compositor); +static char * +weston_output_create_heads_string(struct weston_output *output); + /** Send wl_output events for mode and scale changes * * \param head Send on all resources bound to this head. @@ -4387,6 +4390,14 @@ bind_output(struct wl_client *client, wl_output_send_done(resource); } +static void +weston_head_add_global(struct weston_head *head) +{ + head->global = wl_global_create(head->compositor->wl_display, + &wl_output_interface, 3, + head, bind_output); +} + /** Remove the global wl_output protocol object * * \param head The head whose global to remove. @@ -4621,7 +4632,7 @@ weston_output_iterate_heads(struct weston_output *output, return container_of(node, struct weston_head, output_link); } -/** Attach a head to an inactive output +/** Attach a head to an output * * \param output The output to attach to. * \param head The head that is not yet attached. @@ -4636,7 +4647,7 @@ weston_output_iterate_heads(struct weston_output *output, * * On failure, the head remains unattached. Success of this function does not * guarantee the output configuration is actually valid. The final checks are - * made on weston_output_enable(). + * made on weston_output_enable() unless the output was already enabled. * * \memberof weston_output */ @@ -4644,8 +4655,7 @@ WL_EXPORT int weston_output_attach_head(struct weston_output *output, struct weston_head *head) { - if (output->enabled) - return -1; + char *head_names; if (!wl_list_empty(&head->output_link)) return -1; @@ -4661,6 +4671,15 @@ weston_output_attach_head(struct weston_output *output, head->output = output; wl_list_insert(output->head_list.prev, &head->output_link); + if (output->enabled) { + weston_head_add_global(head); + + head_names = weston_output_create_heads_string(output); + weston_log("Output '%s' updated to have head(s) %s\n", + output->name, head_names); + free(head_names); + } + return 0; } @@ -5219,11 +5238,8 @@ weston_compositor_add_output(struct weston_compositor *compositor, wl_list_insert(compositor->output_list.prev, &output->link); output->enabled = true; - wl_list_for_each(head, &output->head_list, output_link) { - head->global = wl_global_create(compositor->wl_display, - &wl_output_interface, 3, - head, bind_output); - } + wl_list_for_each(head, &output->head_list, output_link) + weston_head_add_global(head); wl_signal_emit(&compositor->output_created_signal, output); From a010699dcf2924f6f8219f92461b99821f00423e Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 8 Dec 2017 16:11:17 +0200 Subject: [PATCH 0441/1642] libweston: log head detach on enabled output Helps debugging. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 14fa12620..a9de4ac33 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4699,6 +4699,7 @@ WL_EXPORT void weston_head_detach(struct weston_head *head) { struct weston_output *output = head->output; + char *head_names; wl_list_remove(&head->output_link); wl_list_init(&head->output_link); @@ -4713,8 +4714,16 @@ weston_head_detach(struct weston_head *head) if (output->enabled) { weston_head_remove_global(head); - if (wl_list_empty(&output->head_list)) + if (wl_list_empty(&output->head_list)) { + weston_log("Output '%s' no heads left, disabling.\n", + output->name); weston_output_disable(output); + } else { + head_names = weston_output_create_heads_string(output); + weston_log("Output '%s' updated to have head(s) %s\n", + output->name, head_names); + free(head_names); + } } } From 54cc47cf306f4baf90f349343ad662161faba1fa Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 31 Aug 2017 11:58:41 +0300 Subject: [PATCH 0442/1642] compositor-drm: drm_output_find_by_connector from head_list Switch drm_output_find_by_connector() to search for the output by iterating the compositor's head_list. drm_head_find_by_connector() will be useful later on its own. As of "compositor-drm: start migration to head-based output API" the head list is guaranteed to contain all created drm_outputs through the automatically created drm_head. This simplifies the code a little, introduces drm_head_find_by_connector(), and works towards the eventual removal of drm_output_find_by_connector(). Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index d19f99c3c..f34e71051 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -803,21 +803,31 @@ drm_output_find_by_crtc(struct drm_backend *b, uint32_t crtc_id) return NULL; } -static struct drm_output * -drm_output_find_by_connector(struct drm_backend *b, uint32_t connector_id) +static struct drm_head * +drm_head_find_by_connector(struct drm_backend *backend, uint32_t connector_id) { - struct drm_output *output; + struct weston_head *base; + struct drm_head *head; - wl_list_for_each(output, &b->compositor->output_list, base.link) { - if (output->connector_id == connector_id) - return output; + wl_list_for_each(base, + &backend->compositor->head_list, compositor_link) { + head = to_drm_head(base); + if (head->output && head->output->connector_id == connector_id) + return head; } - wl_list_for_each(output, &b->compositor->pending_output_list, - base.link) { - if (output->connector_id == connector_id) - return output; - } + return NULL; +} + +static struct drm_output * +drm_output_find_by_connector(struct drm_backend *b, uint32_t connector_id) +{ + struct drm_head *head; + + /* XXX: like the old version, this counts both enabled and disabled outputs */ + head = drm_head_find_by_connector(b, connector_id); + if (head && head->output) + return head->output; return NULL; } From aeca2b5aad28eccbc4ed46d9422bd640f6dbf8d7 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 13 Feb 2018 15:02:44 +0200 Subject: [PATCH 0443/1642] compositor-drm: use head_find_by_connector in update_unused_outputs Making this function not depend on drm_head::output field through drm_output_find_by_connector() will later allow to remove the drm_head::output field before removing the unused_connectors array. This helps keeping the commit more fine-grained. drm_backend_update_unused_outputs() was only interested in enabled outputs. The new code is 100% equivalent to the old code. The difference is that weston_head::output is only set for attached heads. A connector cannot be in use if it is not attached to an output. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index f34e71051..0f0b6f068 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4941,11 +4941,11 @@ drm_backend_update_unused_outputs(struct drm_backend *b, drmModeRes *resources) wl_array_init(&b->unused_connectors); for (i = 0; i < resources->count_connectors; i++) { - struct drm_output *output; + struct drm_head *head; uint32_t *connector_id; - output = drm_output_find_by_connector(b, resources->connectors[i]); - if (output && output->base.enabled) + head = drm_head_find_by_connector(b, resources->connectors[i]); + if (head && weston_head_is_enabled(&head->base)) continue; connector_id = wl_array_add(&b->unused_connectors, From a0a3746a8a4febb5a807f6961a11cabb989f4277 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 31 Aug 2017 15:41:57 +0300 Subject: [PATCH 0444/1642] compositor-drm: find disconnects from head_list Instead of iterating output_list and pending_output_list, iterate head_list to find outputs whose connectors have been disconnected. This helps a following patch to move connector fields from drm_output to drm_head. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 0f0b6f068..51f4f7388 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -5197,7 +5197,8 @@ update_outputs(struct drm_backend *b, struct udev_device *drm_device) { drmModeConnector *connector; drmModeRes *resources; - struct drm_output *output, *next; + struct weston_head *base, *next; + struct drm_head *head; uint32_t *connected; int i; @@ -5238,30 +5239,17 @@ update_outputs(struct drm_backend *b, struct udev_device *drm_device) weston_log("connector %d connected\n", connector_id); } - wl_list_for_each_safe(output, next, &b->compositor->output_list, - base.link) { + wl_list_for_each_safe(base, next, + &b->compositor->head_list, compositor_link) { bool disconnected = true; - for (i = 0; i < resources->count_connectors; i++) { - if (connected[i] == output->connector_id) { - disconnected = false; - break; - } - } + head = to_drm_head(base); - if (!disconnected) + if (!head->output) continue; - weston_log("connector %d disconnected\n", output->connector_id); - drm_output_destroy(&output->base); - } - - wl_list_for_each_safe(output, next, &b->compositor->pending_output_list, - base.link) { - bool disconnected = true; - for (i = 0; i < resources->count_connectors; i++) { - if (connected[i] == output->connector_id) { + if (connected[i] == head->output->connector_id) { disconnected = false; break; } @@ -5270,8 +5258,8 @@ update_outputs(struct drm_backend *b, struct udev_device *drm_device) if (!disconnected) continue; - weston_log("connector %d disconnected\n", output->connector_id); - drm_output_destroy(&output->base); + weston_log("connector %d disconnected\n", head->output->connector_id); + drm_output_destroy(&head->output->base); } drm_backend_update_unused_outputs(b, resources); From ce72424d296aa70d1e056a64a26a8bea29a1fa39 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 4 Sep 2017 12:21:24 +0300 Subject: [PATCH 0445/1642] compositor-drm: move backlight into drm_head Backlight is driven per connector, hence it belongs in struct drm_head. weston_output::set_backlight() API is remains per output so far. There is no UI to control backlights per head and adding one would be difficult for an output that has multiple cloned heads. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 51f4f7388..4aca6acae 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -407,6 +407,8 @@ struct drm_head { struct drm_backend *backend; struct drm_output *output; /* XXX: temporary */ + + struct backlight *backlight; }; struct drm_output { @@ -423,8 +425,6 @@ struct drm_output { /* Holds the properties for the CRTC */ struct drm_property_info props_crtc[WDRM_CRTC__COUNT]; - struct backlight *backlight; - int vblank_pending; int page_flip_pending; int atomic_complete_pending; @@ -3808,12 +3808,12 @@ drm_subpixel_to_wayland(int drm_value) /* returns a value between 0-255 range, where higher is brighter */ static uint32_t -drm_get_backlight(struct drm_output *output) +drm_get_backlight(struct drm_head *head) { long brightness, max_brightness, norm; - brightness = backlight_get_brightness(output->backlight); - max_brightness = backlight_get_max_brightness(output->backlight); + brightness = backlight_get_brightness(head->backlight); + max_brightness = backlight_get_max_brightness(head->backlight); /* convert it on a scale of 0 to 255 */ norm = (brightness * 255)/(max_brightness); @@ -3825,21 +3825,21 @@ drm_get_backlight(struct drm_output *output) static void drm_set_backlight(struct weston_output *output_base, uint32_t value) { - struct drm_output *output = to_drm_output(output_base); + struct drm_head *head = to_drm_head(weston_output_get_first_head(output_base)); long max_brightness, new_brightness; - if (!output->backlight) + if (!head->backlight) return; if (value > 255) return; - max_brightness = backlight_get_max_brightness(output->backlight); + max_brightness = backlight_get_max_brightness(head->backlight); /* get denormalized value */ new_brightness = (value * max_brightness) / 255; - backlight_set_brightness(output->backlight, new_brightness); + backlight_set_brightness(head->backlight, new_brightness); } /** @@ -4745,6 +4745,7 @@ drm_output_enable(struct weston_output *base) { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); + struct drm_head *head = to_drm_head(weston_output_get_first_head(base)); struct weston_mode *m; if (b->pageflip_timeout) @@ -4760,11 +4761,11 @@ drm_output_enable(struct weston_output *base) goto err; } - if (output->backlight) { + if (head->backlight) { weston_log("Initialized backlight, device %s\n", - output->backlight->path); + head->backlight->path); output->base.set_backlight = drm_set_backlight; - output->base.backlight_current = drm_get_backlight(output); + output->base.backlight_current = drm_get_backlight(head); } else { weston_log("Failed to initialize backlight\n"); } @@ -4877,9 +4878,6 @@ drm_output_destroy(struct weston_output *base) drm_property_info_free(output->props_conn, WDRM_CONNECTOR__COUNT); drmModeFreeConnector(output->connector); - if (output->backlight) - backlight_destroy(output->backlight); - assert(!output->state_last); drm_output_state_free(output->state_cur); @@ -5005,6 +5003,8 @@ drm_head_create(struct drm_backend *backend, uint32_t connector_id, head->backend = backend; + head->backlight = backlight_init(drm_device, connector->connector_type); + /* Unknown connection status is assumed disconnected. */ weston_head_set_connection_status(&head->base, connector->connection == DRM_MODE_CONNECTED); @@ -5031,6 +5031,10 @@ static void drm_head_destroy(struct drm_head *head) { weston_head_release(&head->base); + + if (head->backlight) + backlight_destroy(head->backlight); + free(head); } @@ -5070,9 +5074,6 @@ create_output_for_connector(struct drm_backend *b, output->connector = connector; output->connector_id = connector->connector_id; - output->backlight = backlight_init(drm_device, - connector->connector_type); - name = make_connector_name(connector); weston_output_init(&output->base, b->compositor, name); free(name); From c1e89ba2f4f0a29ddfe5b99e9cd8efaec19a6371 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 31 Aug 2017 16:18:48 +0300 Subject: [PATCH 0446/1642] compositor-drm: move connector fields into drm_head Move the connector related fields from drm_output to the drm_head. A drm_head represents a connector for now. The code in drm_head_create() to update connector data, monitor information, etc. is moved into a new function. This will be useful when DRM-backend starts creating heads for all connectors regardless of their connection status and will need to update them on hotplug events. While incurring the churn to move several fields into struct drm_head, also refactor out drm_head_assign_connector_info(). This function is needed later when drm_heads will exist regardless of connected status, as every hotplug event will need to update the state of all connectors. At that point we will also start handling connector changes that do not go through an intermediate disconnected state. This refactoring is trivial enough to be in this patch to reduce the total amount of changes to be reviewed. v6: - adapt to the new places of updating unused_connectors - free connector in create_output_for_connector() error path Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 206 +++++++++++++++++++++---------------- 1 file changed, 120 insertions(+), 86 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 4aca6acae..701a40c21 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -408,20 +408,22 @@ struct drm_head { struct drm_output *output; /* XXX: temporary */ + drmModeConnector *connector; + uint32_t connector_id; + struct drm_edid edid; + + /* Holds the properties for the connector */ + struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT]; + struct backlight *backlight; }; struct drm_output { struct weston_output base; - drmModeConnector *connector; uint32_t crtc_id; /* object ID to pass to DRM functions */ int pipe; /* index of CRTC in resource array / bitmasks */ - uint32_t connector_id; - struct drm_edid edid; - /* Holds the properties for the connector */ - struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT]; /* Holds the properties for the CRTC */ struct drm_property_info props_crtc[WDRM_CRTC__COUNT]; @@ -812,7 +814,7 @@ drm_head_find_by_connector(struct drm_backend *backend, uint32_t connector_id) wl_list_for_each(base, &backend->compositor->head_list, compositor_link) { head = to_drm_head(base); - if (head->output && head->output->connector_id == connector_id) + if (head->connector_id == connector_id) return head; } @@ -1815,10 +1817,11 @@ static int drm_output_apply_state_legacy(struct drm_output_state *state) { struct drm_output *output = state->output; + struct drm_head *head = to_drm_head(weston_output_get_first_head(&output->base)); struct drm_backend *backend = to_drm_backend(output->base.compositor); struct drm_plane *scanout_plane = output->scanout_plane; struct drm_property_info *dpms_prop = - &output->props_conn[WDRM_CONNECTOR_DPMS]; + &head->props_conn[WDRM_CONNECTOR_DPMS]; struct drm_plane_state *scanout_state; struct drm_plane_state *ps; struct drm_plane *p; @@ -1861,7 +1864,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) } ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, 0, 0, 0, - &output->connector_id, 0, NULL); + &head->connector_id, 0, NULL); if (ret) weston_log("drmModeSetCrtc failed disabling: %m\n"); @@ -1896,7 +1899,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, scanout_state->fb->fb_id, 0, 0, - &output->connector_id, 1, + &head->connector_id, 1, &mode->mode_info); if (ret) { weston_log("set mode failed: %m\n"); @@ -1969,7 +1972,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) if (dpms_prop->prop_id && state->dpms != output->state_cur->dpms) { ret = drmModeConnectorSetProperty(backend->drm.fd, - output->connector_id, + head->connector_id, dpms_prop->prop_id, state->dpms); if (ret) { @@ -2005,16 +2008,16 @@ crtc_add_prop(drmModeAtomicReq *req, struct drm_output *output, } static int -connector_add_prop(drmModeAtomicReq *req, struct drm_output *output, +connector_add_prop(drmModeAtomicReq *req, struct drm_head *head, enum wdrm_connector_property prop, uint64_t val) { - struct drm_property_info *info = &output->props_conn[prop]; + struct drm_property_info *info = &head->props_conn[prop]; int ret; if (info->prop_id == 0) return -1; - ret = drmModeAtomicAddProperty(req, output->connector_id, + ret = drmModeAtomicAddProperty(req, head->connector_id, info->prop_id, val); return (ret <= 0) ? -1 : 0; } @@ -2058,6 +2061,7 @@ drm_output_apply_state_atomic(struct drm_output_state *state, uint32_t *flags) { struct drm_output *output = state->output; + struct drm_head *head = to_drm_head(weston_output_get_first_head(&output->base)); struct drm_backend *backend = to_drm_backend(output->base.compositor); struct drm_plane_state *plane_state; struct drm_mode *current_mode = to_drm_mode(output->base.current_mode); @@ -2074,13 +2078,12 @@ drm_output_apply_state_atomic(struct drm_output_state *state, ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, current_mode->blob_id); ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 1); - ret |= connector_add_prop(req, output, WDRM_CONNECTOR_CRTC_ID, + ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, output->crtc_id); } else { ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, 0); ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 0); - ret |= connector_add_prop(req, output, WDRM_CONNECTOR_CRTC_ID, - 0); + ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, 0); } if (ret != 0) { @@ -4296,8 +4299,7 @@ edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length) /** Parse monitor make, model and serial from EDID * - * \param b The backend instance. - * \param output The output whose \c drm_edid to fill in. + * \param head The head whose \c drm_edid to fill in. * \param props The DRM connector properties to get the EDID from. * \param make[out] The monitor make (PNP ID). * \param model[out] The monitor model (name). @@ -4306,10 +4308,10 @@ edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length) * Each of \c *make, \c *model and \c *serial_number are set only if the * information is found in the EDID. The pointers they are set to must not * be free()'d explicitly, instead they get implicitly freed when the - * \c drm_output is destroyed. + * \c drm_head is destroyed. */ static void -find_and_parse_output_edid(struct drm_backend *b, struct drm_output *output, +find_and_parse_output_edid(struct drm_head *head, drmModeObjectPropertiesPtr props, const char **make, const char **model, @@ -4320,29 +4322,29 @@ find_and_parse_output_edid(struct drm_backend *b, struct drm_output *output, int rc; blob_id = - drm_property_get_value(&output->props_conn[WDRM_CONNECTOR_EDID], + drm_property_get_value(&head->props_conn[WDRM_CONNECTOR_EDID], props, 0); if (!blob_id) return; - edid_blob = drmModeGetPropertyBlob(b->drm.fd, blob_id); + edid_blob = drmModeGetPropertyBlob(head->backend->drm.fd, blob_id); if (!edid_blob) return; - rc = edid_parse(&output->edid, + rc = edid_parse(&head->edid, edid_blob->data, edid_blob->length); if (!rc) { weston_log("EDID data '%s', '%s', '%s'\n", - output->edid.pnp_id, - output->edid.monitor_name, - output->edid.serial_number); - if (output->edid.pnp_id[0] != '\0') - *make = output->edid.pnp_id; - if (output->edid.monitor_name[0] != '\0') - *model = output->edid.monitor_name; - if (output->edid.serial_number[0] != '\0') - *serial_number = output->edid.serial_number; + head->edid.pnp_id, + head->edid.monitor_name, + head->edid.serial_number); + if (head->edid.pnp_id[0] != '\0') + *make = head->edid.pnp_id; + if (head->edid.monitor_name[0] != '\0') + *model = head->edid.monitor_name; + if (head->edid.serial_number[0] != '\0') + *serial_number = head->edid.serial_number; } drmModeFreePropertyBlob(edid_blob); } @@ -4569,11 +4571,12 @@ drm_output_set_mode(struct weston_output *base, { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); + struct drm_head *head = to_drm_head(weston_output_get_first_head(base)); struct drm_mode *current; drmModeModeInfo crtc_mode; - if (connector_get_current_mode(output->connector, b->drm.fd, &crtc_mode) < 0) + if (connector_get_current_mode(head->connector, b->drm.fd, &crtc_mode) < 0) return -1; current = drm_output_choose_initial_mode(b, output, mode, modeline, &crtc_mode); @@ -4788,11 +4791,11 @@ drm_output_enable(struct weston_output *base) &output->scanout_plane->base, &b->compositor->primary_plane); - wl_array_remove_uint32(&b->unused_connectors, output->connector_id); + wl_array_remove_uint32(&b->unused_connectors, head->connector_id); wl_array_remove_uint32(&b->unused_crtcs, output->crtc_id); weston_log("Output %s, (connector %d, crtc %d)\n", - output->base.name, output->connector_id, output->crtc_id); + output->base.name, head->connector_id, output->crtc_id); wl_list_for_each(m, &output->base.mode_list, link) weston_log_continue(STAMP_SPACE "mode %dx%d@%.1f%s%s%s\n", m->width, m->height, m->refresh / 1000.0, @@ -4800,7 +4803,7 @@ drm_output_enable(struct weston_output *base) ", preferred" : "", m->flags & WL_OUTPUT_MODE_CURRENT ? ", current" : "", - output->connector->count_modes == 0 ? + head->connector->count_modes == 0 ? ", built-in" : ""); return 0; @@ -4813,6 +4816,7 @@ static void drm_output_deinit(struct weston_output *base) { struct drm_output *output = to_drm_output(base); + struct drm_head *head = to_drm_head(weston_output_get_first_head(base)); struct drm_backend *b = to_drm_backend(base->compositor); uint32_t *unused; @@ -4838,7 +4842,7 @@ drm_output_deinit(struct weston_output *base) } unused = wl_array_add(&b->unused_connectors, sizeof(*unused)); - *unused = output->connector_id; + *unused = head->connector_id; unused = wl_array_add(&b->unused_crtcs, sizeof(*unused)); *unused = output->crtc_id; @@ -4875,9 +4879,6 @@ drm_output_destroy(struct weston_output *base) drm_output_fini_crtc(output); - drm_property_info_free(output->props_conn, WDRM_CONNECTOR__COUNT); - drmModeFreeConnector(output->connector); - assert(!output->state_last); drm_output_state_free(output->state_cur); @@ -4967,6 +4968,62 @@ drm_backend_update_unused_outputs(struct drm_backend *b, drmModeRes *resources) } } +/** Replace connector data and monitor information + * + * @param head The head to update. + * @param connector The connector data to be owned by the head, must match + * the head's connector ID. + * @return 0 on success, -1 on failure. + * + * Takes ownership of @c connector on success, not on failure. + * + * May schedule a heads changed call. + */ +static int +drm_head_assign_connector_info(struct drm_head *head, + drmModeConnector *connector) +{ + drmModeObjectProperties *props; + const char *make = "unknown"; + const char *model = "unknown"; + const char *serial_number = "unknown"; + + assert(connector); + assert(head->connector_id == connector->connector_id); + + props = drmModeObjectGetProperties(head->backend->drm.fd, + head->connector_id, + DRM_MODE_OBJECT_CONNECTOR); + if (!props) { + weston_log("Error: failed to get connector '%s' properties\n", + head->base.name); + return -1; + } + + if (head->connector) + drmModeFreeConnector(head->connector); + head->connector = connector; + + drm_property_info_populate(head->backend, connector_props, + head->props_conn, + WDRM_CONNECTOR__COUNT, props); + find_and_parse_output_edid(head, props, &make, &model, &serial_number); + weston_head_set_monitor_strings(&head->base, make, model, serial_number); + weston_head_set_subpixel(&head->base, + drm_subpixel_to_wayland(head->connector->subpixel)); + + weston_head_set_physical_size(&head->base, head->connector->mmWidth, + head->connector->mmHeight); + + drmModeFreeObjectProperties(props); + + /* Unknown connection status is assumed disconnected. */ + weston_head_set_connection_status(&head->base, + head->connector->connection == DRM_MODE_CONNECTED); + + return 0; +} + /** * Create a Weston head for a connector * @@ -5001,23 +5058,29 @@ drm_head_create(struct drm_backend *backend, uint32_t connector_id, weston_head_init(&head->base, name); free(name); + head->connector_id = connector_id; head->backend = backend; head->backlight = backlight_init(drm_device, connector->connector_type); - /* Unknown connection status is assumed disconnected. */ - weston_head_set_connection_status(&head->base, - connector->connection == DRM_MODE_CONNECTED); + if (drm_head_assign_connector_info(head, connector) < 0) + goto err_init; + + if (head->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || + head->connector->connector_type == DRM_MODE_CONNECTOR_eDP) + weston_head_set_internal(&head->base); weston_compositor_add_head(backend->compositor, &head->base); - drmModeFreeConnector(connector); weston_log("DRM: found head '%s', connector %d %s.\n", - head->base.name, connector_id, + head->base.name, head->connector_id, head->base.connected ? "connected" : "disconnected"); return head; +err_init: + weston_head_release(&head->base); + err_alloc: if (connector) drmModeFreeConnector(connector); @@ -5032,6 +5095,9 @@ drm_head_destroy(struct drm_head *head) { weston_head_release(&head->base); + drm_property_info_free(head->props_conn, WDRM_CONNECTOR__COUNT); + drmModeFreeConnector(head->connector); + if (head->backlight) backlight_destroy(head->backlight); @@ -5059,31 +5125,21 @@ create_output_for_connector(struct drm_backend *b, { struct drm_output *output; struct drm_head *head; - drmModeObjectPropertiesPtr props; struct drm_mode *drm_mode; - char *name; - const char *make = "unknown"; - const char *model = "unknown"; - const char *serial_number = "unknown"; int i; output = zalloc(sizeof *output); if (output == NULL) goto err_init; - output->connector = connector; - output->connector_id = connector->connector_id; - - name = make_connector_name(connector); - weston_output_init(&output->base, b->compositor, name); - free(name); - /* XXX: temporary */ head = drm_head_create(b, connector->connector_id, drm_device); if (!head) abort(); head->output = output; + weston_output_init(&output->base, b->compositor, head->base.name); + output->base.enable = drm_output_enable; output->base.destroy = drm_output_destroy; output->base.disable = drm_output_disable; @@ -5095,36 +5151,13 @@ create_output_for_connector(struct drm_backend *b, if (drm_output_init_crtc(output, resources, connector) < 0) goto err_output; - props = drmModeObjectGetProperties(b->drm.fd, connector->connector_id, - DRM_MODE_OBJECT_CONNECTOR); - if (!props) { - weston_log("failed to get connector properties\n"); - goto err_output; - } - drm_property_info_populate(b, connector_props, output->props_conn, - WDRM_CONNECTOR__COUNT, props); - find_and_parse_output_edid(b, output, props, - &make, &model, &serial_number); - weston_head_set_monitor_strings(&head->base, make, model, serial_number); - weston_head_set_subpixel(&head->base, - drm_subpixel_to_wayland(output->connector->subpixel)); - - drmModeFreeObjectProperties(props); - - if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || - output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) - weston_head_set_internal(&head->base); - if (drm_output_init_gamma_size(output) < 0) goto err_output; - weston_head_set_physical_size(&head->base, output->connector->mmWidth, - output->connector->mmHeight); - output->state_cur = drm_output_state_alloc(output, NULL); - for (i = 0; i < output->connector->count_modes; i++) { - drm_mode = drm_output_add_mode(output, &output->connector->modes[i]); + for (i = 0; i < head->connector->count_modes; i++) { + drm_mode = drm_output_add_mode(output, &head->connector->modes[i]); if (!drm_mode) { weston_log("failed to add mode\n"); goto err_output; @@ -5133,13 +5166,14 @@ create_output_for_connector(struct drm_backend *b, weston_compositor_add_pending_output(&output->base, b->compositor); + /* drm_head_create() made its own connector */ + drmModeFreeConnector(connector); + return 0; err_output: drm_head_destroy(head); drm_output_destroy(&output->base); - return -1; - /* no fallthrough! */ err_init: drmModeFreeConnector(connector); @@ -5250,7 +5284,7 @@ update_outputs(struct drm_backend *b, struct udev_device *drm_device) continue; for (i = 0; i < resources->count_connectors; i++) { - if (connected[i] == head->output->connector_id) { + if (connected[i] == head->connector_id) { disconnected = false; break; } @@ -5259,7 +5293,7 @@ update_outputs(struct drm_backend *b, struct udev_device *drm_device) if (!disconnected) continue; - weston_log("connector %d disconnected\n", head->output->connector_id); + weston_log("connector %d disconnected\n", head->connector_id); drm_output_destroy(&head->output->base); } From 663d5e9b12b96d670bd598afcc4d5921c827492a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 8 Sep 2017 13:32:40 +0300 Subject: [PATCH 0447/1642] compositor-drm: allocate CRTC on enable() A drm_output needs a CRTC only when it is in use. Allocating a CRTC on creation of drm_output will reserve the CRTC regardless of whether the output is actually used or not. This may cause creating other drm_outputs to fail if there are not enough CRTCs. Instead, allocate the CRTC on drm_output enable() time. A drm_output will have a valid CRTC only while it is enabled. This allows us to create drm_output objects arbitrarily and without a head assignment, which is required by the head-based output API for the backends. The assigned heads will be known only at enable() time. Now drm_output_enable() has to call drmModeGetResources() to be able to find a suitable CRTC. We might want to cache the resources somewhere, but that is it topic for another patch. v4: Force resetting unused CRTCs on fini. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 43 ++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 701a40c21..90c6f24da 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4698,6 +4698,8 @@ drm_output_init_crtc(struct drm_output *output, drm_output_find_special_plane(b, output, WDRM_PLANE_TYPE_CURSOR); + wl_array_remove_uint32(&b->unused_crtcs, output->crtc_id); + return 0; err_crtc: @@ -4717,6 +4719,7 @@ static void drm_output_fini_crtc(struct drm_output *output) { struct drm_backend *b = to_drm_backend(output->base.compositor); + uint32_t *unused; if (!b->universal_planes && !b->shutting_down) { /* With universal planes, the 'special' planes are allocated at @@ -4738,6 +4741,15 @@ drm_output_fini_crtc(struct drm_output *output) } drm_property_info_free(output->props_crtc, WDRM_CRTC__COUNT); + + assert(output->crtc_id != 0); + + unused = wl_array_add(&b->unused_crtcs, sizeof(*unused)); + *unused = output->crtc_id; + + /* Force resetting unused CRTCs */ + b->state_invalid = true; + output->crtc_id = 0; output->cursor_plane = NULL; output->scanout_plane = NULL; @@ -4750,6 +4762,21 @@ drm_output_enable(struct weston_output *base) struct drm_backend *b = to_drm_backend(base->compositor); struct drm_head *head = to_drm_head(weston_output_get_first_head(base)); struct weston_mode *m; + drmModeRes *resources; + int ret; + + resources = drmModeGetResources(b->drm.fd); + if (!resources) { + weston_log("drmModeGetResources failed\n"); + return -1; + } + ret = drm_output_init_crtc(output, resources, head->connector); + drmModeFreeResources(resources); + if (ret < 0) + return -1; + + if (drm_output_init_gamma_size(output) < 0) + goto err; if (b->pageflip_timeout) drm_output_pageflip_timer_create(output); @@ -4792,7 +4819,6 @@ drm_output_enable(struct weston_output *base) &b->compositor->primary_plane); wl_array_remove_uint32(&b->unused_connectors, head->connector_id); - wl_array_remove_uint32(&b->unused_crtcs, output->crtc_id); weston_log("Output %s, (connector %d, crtc %d)\n", output->base.name, head->connector_id, output->crtc_id); @@ -4809,6 +4835,8 @@ drm_output_enable(struct weston_output *base) return 0; err: + drm_output_fini_crtc(output); + return -1; } @@ -4843,11 +4871,8 @@ drm_output_deinit(struct weston_output *base) unused = wl_array_add(&b->unused_connectors, sizeof(*unused)); *unused = head->connector_id; - unused = wl_array_add(&b->unused_crtcs, sizeof(*unused)); - *unused = output->crtc_id; - /* Force programming unused connectors and crtcs. */ - b->state_invalid = true; + drm_output_fini_crtc(output); } static void @@ -4877,8 +4902,6 @@ drm_output_destroy(struct weston_output *base) weston_output_release(&output->base); - drm_output_fini_crtc(output); - assert(!output->state_last); drm_output_state_free(output->state_cur); @@ -5148,12 +5171,6 @@ create_output_for_connector(struct drm_backend *b, output->destroy_pending = 0; output->disable_pending = 0; - if (drm_output_init_crtc(output, resources, connector) < 0) - goto err_output; - - if (drm_output_init_gamma_size(output) < 0) - goto err_output; - output->state_cur = drm_output_state_alloc(output, NULL); for (i = 0; i < head->connector->count_modes; i++) { From 9790f8759985f88ef91abe35629fe2fefc6b481f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 27 Oct 2017 13:52:12 +0300 Subject: [PATCH 0448/1642] compositor-drm: simplify drm_output_find_by_crtc() As CRTC is allocated on output enable and deallocated on output disable, there cannot be any matches in find-by-crtc from the pending_output_list. Remove the loop over pending_output_list as never finding anything by definition. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 90c6f24da..add07c06f 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -796,12 +796,6 @@ drm_output_find_by_crtc(struct drm_backend *b, uint32_t crtc_id) return output; } - wl_list_for_each(output, &b->compositor->pending_output_list, - base.link) { - if (output->crtc_id == crtc_id) - return output; - } - return NULL; } From f857046559abc36e250540455a6f6c82fe87479f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 27 Oct 2017 14:06:51 +0300 Subject: [PATCH 0449/1642] compositor-drm: simplify drm_output_find_special_plane() As these planes are allocated on output enable and freed on output disable, there cannot be a match in the pending_output_list. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index add07c06f..65f4378d4 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3596,14 +3596,6 @@ drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, /* On some platforms, primary/cursor planes can roam * between different CRTCs, so make sure we don't claim the * same plane for two outputs. */ - wl_list_for_each(tmp, &b->compositor->pending_output_list, - base.link) { - if (tmp->cursor_plane == plane || - tmp->scanout_plane == plane) { - found_elsewhere = true; - break; - } - } wl_list_for_each(tmp, &b->compositor->output_list, base.link) { if (tmp->cursor_plane == plane || From 13d233edba558b6be642e3ec3544073120f53888 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 11 Sep 2017 14:06:11 +0300 Subject: [PATCH 0450/1642] compositor-drm: get current mode on head discovery The inherited mode is the video mode on the connector when we have not yet reconfigured the connector, if set. Get the inherited mode the moment we create a drm_head, not when we determine the mode for a drm_output. This way we are sure to read all inherited modes before we reconfigure a single CRTC. Enabling one output may grab the CRTC from another connector, overwriting whatever mode that connector might have had. The inherited mode is stored in drm_head, where we can keep it for the lifetime of the head, rather than relying on re-loading it from the kernel at set_mode() time. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 65f4378d4..f54257cd6 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -416,6 +416,8 @@ struct drm_head { struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT]; struct backlight *backlight; + + drmModeModeInfo inherited_mode; /**< Original mode on the connector */ }; struct drm_output { @@ -4560,12 +4562,9 @@ drm_output_set_mode(struct weston_output *base, struct drm_head *head = to_drm_head(weston_output_get_first_head(base)); struct drm_mode *current; - drmModeModeInfo crtc_mode; - - if (connector_get_current_mode(head->connector, b->drm.fd, &crtc_mode) < 0) - return -1; - current = drm_output_choose_initial_mode(b, output, mode, modeline, &crtc_mode); + current = drm_output_choose_initial_mode(b, output, mode, modeline, + &head->inherited_mode); if (!current) return -1; @@ -5079,6 +5078,13 @@ drm_head_create(struct drm_backend *backend, uint32_t connector_id, head->connector->connector_type == DRM_MODE_CONNECTOR_eDP) weston_head_set_internal(&head->base); + if (connector_get_current_mode(head->connector, backend->drm.fd, + &head->inherited_mode) < 0) { + weston_log("Failed to retrieve current mode from connector %d.\n", + head->connector_id); + /* Continue, inherited_mode was memset to zero. */ + } + weston_compositor_add_head(backend->compositor, &head->base); weston_log("DRM: found head '%s', connector %d %s.\n", From 4be248550259a6794157e9bd46d17a1f66f37a72 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 11 Sep 2017 15:01:12 +0300 Subject: [PATCH 0451/1642] compositor-drm: move mode list to set_mode() Move the initialization of the drm_output mode list to drm_output_set_mode() time. Once we stop creating the drm_head with the drm_output, there will not be a head to get the mode list from at drm_output creation time. Furthermore, once DRM-backend starts supporting more than one head per output, the combined mode list to be exposed to clients (and the compositor?) must be constructed with all heads attached. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 54 ++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index f54257cd6..df31f87e4 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4439,6 +4439,43 @@ parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format) return ret; } +/** Rewrite the output's mode list + * + * @param output The output. + * @return 0 on success, -1 on failure. + * + * Destroy all existing modes in the list, and reconstruct a new list from + * scratch, based on the currently attached heads. + * + * On failure the output's mode list may contain some modes. + */ +static int +drm_output_update_modelist_from_heads(struct drm_output *output) +{ + struct drm_backend *backend = to_drm_backend(output->base.compositor); + struct weston_head *head_base; + struct drm_head *head; + struct drm_mode *mode; + int i; + + assert(!output->base.enabled); + + drm_mode_list_destroy(backend, &output->base.mode_list); + + /* XXX: needs a strategy for combining mode lists from multiple heads */ + head_base = weston_output_get_first_head(&output->base); + assert(head_base); + head = to_drm_head(head_base); + + for (i = 0; i < head->connector->count_modes; i++) { + mode = drm_output_add_mode(output, &head->connector->modes[i]); + if (!mode) + return -1; + } + + return 0; +} + /** * Choose suitable mode for an output * @@ -4563,6 +4600,9 @@ drm_output_set_mode(struct weston_output *base, struct drm_mode *current; + if (drm_output_update_modelist_from_heads(output) < 0) + return -1; + current = drm_output_choose_initial_mode(b, output, mode, modeline, &head->inherited_mode); if (!current) @@ -5140,8 +5180,6 @@ create_output_for_connector(struct drm_backend *b, { struct drm_output *output; struct drm_head *head; - struct drm_mode *drm_mode; - int i; output = zalloc(sizeof *output); if (output == NULL) @@ -5165,14 +5203,6 @@ create_output_for_connector(struct drm_backend *b, output->state_cur = drm_output_state_alloc(output, NULL); - for (i = 0; i < head->connector->count_modes; i++) { - drm_mode = drm_output_add_mode(output, &head->connector->modes[i]); - if (!drm_mode) { - weston_log("failed to add mode\n"); - goto err_output; - } - } - weston_compositor_add_pending_output(&output->base, b->compositor); /* drm_head_create() made its own connector */ @@ -5180,10 +5210,6 @@ create_output_for_connector(struct drm_backend *b, return 0; -err_output: - drm_head_destroy(head); - drm_output_destroy(&output->base); - err_init: drmModeFreeConnector(connector); return -1; From d2e6242e3ca07f291c3b2ce11ab23fd483096e59 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 8 Sep 2017 15:48:07 +0300 Subject: [PATCH 0452/1642] compositor-drm: create heads for all connectors In previous patches, all the appropriate fields from drm_output have been moved into drm_head, and resource allocation has been moved away from drm_output creation. It is time to throw the switch: this patch disconnects the drm_output and drm_head lifetimes. Previously a drm_output was created for a connected connector and destroyed on disconnection. A drm_head was tied to the drm_output lifetime just to accommodate the head-based output configuration API temporarily. Now all connectors will get a head created regardless of their connection status. Heads are created and destroyed as connectors appear and disappear (MST), not when they get connected or disconnected. This should allow the compositor to force-enable a disconnected connector. An "empty" drm_output is created with weston_backend::create_output() hook. This now follows the intent of the head-based output configuration API. On hotplug events, all connectors' information is updated regardless of their connection status changes. It is theoretically possible for a monitor to change without going through a disconnected state in between. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 190 +++++++++++++------------------------ 1 file changed, 67 insertions(+), 123 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index df31f87e4..711668e57 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -406,8 +406,6 @@ struct drm_head { struct weston_head base; struct drm_backend *backend; - struct drm_output *output; /* XXX: temporary */ - drmModeConnector *connector; uint32_t connector_id; struct drm_edid edid; @@ -817,19 +815,6 @@ drm_head_find_by_connector(struct drm_backend *backend, uint32_t connector_id) return NULL; } -static struct drm_output * -drm_output_find_by_connector(struct drm_backend *b, uint32_t connector_id) -{ - struct drm_head *head; - - /* XXX: like the old version, this counts both enabled and disabled outputs */ - head = drm_head_find_by_connector(b, connector_id); - if (head && head->output) - return head->output; - - return NULL; -} - static void drm_fb_destroy(struct drm_fb *fb) { @@ -4908,7 +4893,6 @@ drm_output_destroy(struct weston_output *base) { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); - struct weston_head *head = weston_output_get_first_head(base); if (output->page_flip_pending || output->vblank_pending || output->atomic_complete_pending) { @@ -4931,10 +4915,6 @@ drm_output_destroy(struct weston_output *base) drm_output_state_free(output->state_cur); free(output); - - /* XXX: temporary */ - if (head) - drm_head_destroy(to_drm_head(head)); } static int @@ -4958,19 +4938,6 @@ drm_output_disable(struct weston_output *base) return 0; } -static struct weston_output * -drm_output_create(struct weston_compositor *compositor, const char *name) -{ - struct drm_head *head; - - /* XXX: Temporary until we can have heads without an output */ - wl_list_for_each(head, &compositor->head_list, base.compositor_link) - if (strcmp(name, head->base.name) == 0) - return &head->output->base; - - return NULL; -} - /** * Update the list of unused connectors and CRTCs * @@ -5072,6 +5039,31 @@ drm_head_assign_connector_info(struct drm_head *head, return 0; } +/** Update connector and monitor information + * + * @param head The head to update. + * + * Re-reads the DRM property lists for the connector and updates monitor + * information and connection status. This may schedule a heads changed call + * to the user. + */ +static void +drm_head_update_info(struct drm_head *head) +{ + drmModeConnector *connector; + + connector = drmModeGetConnector(head->backend->drm.fd, + head->connector_id); + if (!connector) { + weston_log("DRM: getting connector info for '%s' failed.\n", + head->base.name); + return; + } + + if (drm_head_assign_connector_info(head, connector) < 0) + drmModeFreeConnector(connector); +} + /** * Create a Weston head for a connector * @@ -5162,36 +5154,27 @@ drm_head_destroy(struct drm_head *head) /** * Create a Weston output structure * - * Given a DRM connector, create a matching drm_output structure and add it - * to Weston's output list. It also takes ownership of the connector, which - * is released when output is destroyed. + * Create an "empty" drm_output. This is the implementation of + * weston_backend::create_output. * - * @param b Weston backend structure - * @param resources DRM resources for this device - * @param connector DRM connector to use for this new output - * @param drm_device udev device pointer - * @returns 0 on success, or -1 on failure + * Creating an output is usually followed by drm_output_attach_head() + * and drm_output_enable() to make use of it. + * + * @param compositor The compositor instance. + * @param name Name for the new output. + * @returns The output, or NULL on failure. */ -static int -create_output_for_connector(struct drm_backend *b, - drmModeRes *resources, - drmModeConnector *connector, - struct udev_device *drm_device) +static struct weston_output * +drm_output_create(struct weston_compositor *compositor, const char *name) { + struct drm_backend *b = to_drm_backend(compositor); struct drm_output *output; - struct drm_head *head; output = zalloc(sizeof *output); if (output == NULL) - goto err_init; - - /* XXX: temporary */ - head = drm_head_create(b, connector->connector_id, drm_device); - if (!head) - abort(); - head->output = output; + return NULL; - weston_output_init(&output->base, b->compositor, head->base.name); + weston_output_init(&output->base, compositor, name); output->base.enable = drm_output_enable; output->base.destroy = drm_output_destroy; @@ -5205,20 +5188,13 @@ create_output_for_connector(struct drm_backend *b, weston_compositor_add_pending_output(&output->base, b->compositor); - /* drm_head_create() made its own connector */ - drmModeFreeConnector(connector); - - return 0; - -err_init: - drmModeFreeConnector(connector); - return -1; + return &output->base; } static int -create_outputs(struct drm_backend *b, struct udev_device *drm_device) +drm_backend_create_heads(struct drm_backend *b, struct udev_device *drm_device) { - drmModeConnector *connector; + struct drm_head *head; drmModeRes *resources; int i; @@ -5234,42 +5210,28 @@ create_outputs(struct drm_backend *b, struct udev_device *drm_device) b->max_height = resources->max_height; for (i = 0; i < resources->count_connectors; i++) { - int ret; - - connector = drmModeGetConnector(b->drm.fd, - resources->connectors[i]); - if (connector == NULL) - continue; + uint32_t connector_id = resources->connectors[i]; - if (connector->connection == DRM_MODE_CONNECTED) { - ret = create_output_for_connector(b, resources, - connector, drm_device); - if (ret < 0) - weston_log("failed to create new connector\n"); - } else { - drmModeFreeConnector(connector); + head = drm_head_create(b, connector_id, drm_device); + if (!head) { + weston_log("DRM: failed to create head for connector %d.\n", + connector_id); } } drm_backend_update_unused_outputs(b, resources); - if (wl_list_empty(&b->compositor->output_list) && - wl_list_empty(&b->compositor->pending_output_list)) - weston_log("No currently active connector found.\n"); - drmModeFreeResources(resources); return 0; } static void -update_outputs(struct drm_backend *b, struct udev_device *drm_device) +drm_backend_update_heads(struct drm_backend *b, struct udev_device *drm_device) { - drmModeConnector *connector; drmModeRes *resources; struct weston_head *base, *next; struct drm_head *head; - uint32_t *connected; int i; resources = drmModeGetResources(b->drm.fd); @@ -5278,63 +5240,45 @@ update_outputs(struct drm_backend *b, struct udev_device *drm_device) return; } - connected = calloc(resources->count_connectors, sizeof(uint32_t)); - if (!connected) { - drmModeFreeResources(resources); - return; - } - - /* collect new connects */ + /* collect new connectors that have appeared, e.g. MST */ for (i = 0; i < resources->count_connectors; i++) { uint32_t connector_id = resources->connectors[i]; - connector = drmModeGetConnector(b->drm.fd, connector_id); - if (connector == NULL) - continue; - - if (connector->connection != DRM_MODE_CONNECTED) { - drmModeFreeConnector(connector); - continue; - } - - connected[i] = connector_id; - - if (drm_output_find_by_connector(b, connector_id)) { - drmModeFreeConnector(connector); - continue; + head = drm_head_find_by_connector(b, connector_id); + if (head) { + drm_head_update_info(head); + } else { + head = drm_head_create(b, connector_id, drm_device); + if (!head) + weston_log("DRM: failed to create head for hot-added connector %d.\n", + connector_id); } - - create_output_for_connector(b, resources, - connector, drm_device); - weston_log("connector %d connected\n", connector_id); } + /* Remove connectors that have disappeared. */ wl_list_for_each_safe(base, next, &b->compositor->head_list, compositor_link) { - bool disconnected = true; + bool removed = true; head = to_drm_head(base); - if (!head->output) - continue; - for (i = 0; i < resources->count_connectors; i++) { - if (connected[i] == head->connector_id) { - disconnected = false; + if (resources->connectors[i] == head->connector_id) { + removed = false; break; } } - if (!disconnected) + if (!removed) continue; - weston_log("connector %d disconnected\n", head->connector_id); - drm_output_destroy(&head->output->base); + weston_log("DRM: head '%s' (connector %d) disappeared.\n", + head->base.name, head->connector_id); + drm_head_destroy(head); } drm_backend_update_unused_outputs(b, resources); - free(connected); drmModeFreeResources(resources); } @@ -5364,7 +5308,7 @@ udev_drm_event(int fd, uint32_t mask, void *data) event = udev_monitor_receive_device(b->udev_monitor); if (udev_event_is_hotplug(b, event)) - update_outputs(b, event); + drm_backend_update_heads(b, event); udev_device_unref(event); @@ -5922,8 +5866,8 @@ drm_backend_create(struct weston_compositor *compositor, goto err_sprite; } - if (create_outputs(b, drm_device) < 0) { - weston_log("failed to create output for %s\n", b->drm.filename); + if (drm_backend_create_heads(b, drm_device) < 0) { + weston_log("Failed to create heads for %s\n", b->drm.filename); goto err_udev_input; } From eacec815a3637c88874dba1b6513b51c47fb25b1 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 12 Sep 2017 13:43:51 +0300 Subject: [PATCH 0453/1642] compositor-drm: remove unused_connectors array Replace the unused_connectors array by iterating through the head list instead. A head that is not enabled (attached to an enabled output) is basically an unused connector. All connectors regardless of their status have a drm_head. This has the nice effect that drm_pending_state_apply_atomic() does not need to re-query the connector properties every time, they can be simply looked up in the drm_head. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 61 ++++++++------------------------------ 1 file changed, 12 insertions(+), 49 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 711668e57..e3eb0e559 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -252,8 +252,7 @@ struct drm_backend { bool state_invalid; - /* Connector and CRTC IDs not used by any enabled output. */ - struct wl_array unused_connectors; + /* CRTC IDs not used by any enabled output. */ struct wl_array unused_crtcs; int cursors_are_broken; @@ -2124,47 +2123,36 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, return -1; if (b->state_invalid) { + struct weston_head *head_base; + struct drm_head *head; uint32_t *unused; int err; /* If we need to reset all our state (e.g. because we've * just started, or just been VT-switched in), explicitly * disable all the CRTCs and connectors we aren't using. */ - wl_array_for_each(unused, &b->unused_connectors) { - struct drm_property_info infos[WDRM_CONNECTOR__COUNT]; + wl_list_for_each(head_base, + &b->compositor->head_list, compositor_link) { struct drm_property_info *info; - drmModeObjectProperties *props; - - memset(infos, 0, sizeof(infos)); - props = drmModeObjectGetProperties(b->drm.fd, - *unused, - DRM_MODE_OBJECT_CONNECTOR); - if (!props) { - ret = -1; + if (weston_head_is_enabled(head_base)) continue; - } - drm_property_info_populate(b, connector_props, infos, - WDRM_CONNECTOR__COUNT, - props); - drmModeFreeObjectProperties(props); + head = to_drm_head(head_base); - info = &infos[WDRM_CONNECTOR_CRTC_ID]; - err = drmModeAtomicAddProperty(req, *unused, + info = &head->props_conn[WDRM_CONNECTOR_CRTC_ID]; + err = drmModeAtomicAddProperty(req, head->connector_id, info->prop_id, 0); if (err <= 0) ret = -1; - info = &infos[WDRM_CONNECTOR_DPMS]; + info = &head->props_conn[WDRM_CONNECTOR_DPMS]; if (info->prop_id > 0) - err = drmModeAtomicAddProperty(req, *unused, + err = drmModeAtomicAddProperty(req, head->connector_id, info->prop_id, DRM_MODE_DPMS_OFF); if (err <= 0) ret = -1; - - drm_property_info_free(infos, WDRM_CONNECTOR__COUNT); } wl_array_for_each(unused, &b->unused_crtcs) { @@ -4828,8 +4816,6 @@ drm_output_enable(struct weston_output *base) &output->scanout_plane->base, &b->compositor->primary_plane); - wl_array_remove_uint32(&b->unused_connectors, head->connector_id); - weston_log("Output %s, (connector %d, crtc %d)\n", output->base.name, head->connector_id, output->crtc_id); wl_list_for_each(m, &output->base.mode_list, link) @@ -4854,9 +4840,7 @@ static void drm_output_deinit(struct weston_output *base) { struct drm_output *output = to_drm_output(base); - struct drm_head *head = to_drm_head(weston_output_get_first_head(base)); struct drm_backend *b = to_drm_backend(base->compositor); - uint32_t *unused; if (b->use_pixman) drm_output_fini_pixman(output); @@ -4879,9 +4863,6 @@ drm_output_deinit(struct weston_output *base) } } - unused = wl_array_add(&b->unused_connectors, sizeof(*unused)); - *unused = head->connector_id; - drm_output_fini_crtc(output); } @@ -4941,7 +4922,7 @@ drm_output_disable(struct weston_output *base) /** * Update the list of unused connectors and CRTCs * - * This keeps the unused_connectors and unused_crtcs arrays up to date. + * This keeps the unused_crtc arrays up to date. * * @param b Weston backend structure * @param resources DRM resources for this device @@ -4951,22 +4932,6 @@ drm_backend_update_unused_outputs(struct drm_backend *b, drmModeRes *resources) { int i; - wl_array_release(&b->unused_connectors); - wl_array_init(&b->unused_connectors); - - for (i = 0; i < resources->count_connectors; i++) { - struct drm_head *head; - uint32_t *connector_id; - - head = drm_head_find_by_connector(b, resources->connectors[i]); - if (head && weston_head_is_enabled(&head->base)) - continue; - - connector_id = wl_array_add(&b->unused_connectors, - sizeof(*connector_id)); - *connector_id = resources->connectors[i]; - } - wl_array_release(&b->unused_crtcs); wl_array_init(&b->unused_crtcs); @@ -5344,7 +5309,6 @@ drm_destroy(struct weston_compositor *ec) weston_launcher_destroy(ec->launcher); wl_array_release(&b->unused_crtcs); - wl_array_release(&b->unused_connectors); close(b->drm.fd); free(b->drm.filename); @@ -5778,7 +5742,6 @@ drm_backend_create(struct weston_compositor *compositor, b->state_invalid = true; b->drm.fd = -1; wl_array_init(&b->unused_crtcs); - wl_array_init(&b->unused_connectors); /* * KMS support for hardware planes cannot properly synchronize From 02aeb5c66ed7a6ff7f5603268d65d36398de27dd Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 12 Sep 2017 16:02:01 +0300 Subject: [PATCH 0454/1642] compositor-drm: drm_output_apply_state_legacy heads Fix this function to support more than one head per output. v9: - Change { connectors, 0 } to { NULL, 0 } in drmModeSetCrtc() args. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index e3eb0e559..b9898e4d5 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1797,18 +1797,24 @@ static int drm_output_apply_state_legacy(struct drm_output_state *state) { struct drm_output *output = state->output; - struct drm_head *head = to_drm_head(weston_output_get_first_head(&output->base)); struct drm_backend *backend = to_drm_backend(output->base.compositor); struct drm_plane *scanout_plane = output->scanout_plane; - struct drm_property_info *dpms_prop = - &head->props_conn[WDRM_CONNECTOR_DPMS]; + struct drm_property_info *dpms_prop; struct drm_plane_state *scanout_state; struct drm_plane_state *ps; struct drm_plane *p; struct drm_mode *mode; + struct drm_head *head; + uint32_t connectors[MAX_CLONED_CONNECTORS]; + int n_conn = 0; struct timespec now; int ret = 0; + wl_list_for_each(head, &output->base.head_list, base.output_link) { + assert(n_conn < MAX_CLONED_CONNECTORS); + connectors[n_conn++] = head->connector_id; + } + /* If disable_planes is set then assign_planes() wasn't * called for this render, so we could still have a stale * cursor plane set up. @@ -1844,7 +1850,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) } ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, 0, 0, 0, - &head->connector_id, 0, NULL); + NULL, 0, NULL); if (ret) weston_log("drmModeSetCrtc failed disabling: %m\n"); @@ -1879,7 +1885,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, scanout_state->fb->fb_id, 0, 0, - &head->connector_id, 1, + connectors, n_conn, &mode->mode_info); if (ret) { weston_log("set mode failed: %m\n"); @@ -1950,14 +1956,20 @@ drm_output_apply_state_legacy(struct drm_output_state *state) } } - if (dpms_prop->prop_id && state->dpms != output->state_cur->dpms) { - ret = drmModeConnectorSetProperty(backend->drm.fd, - head->connector_id, - dpms_prop->prop_id, - state->dpms); - if (ret) { - weston_log("DRM: DPMS: failed property set for %s\n", - output->base.name); + if (state->dpms != output->state_cur->dpms) { + wl_list_for_each(head, &output->base.head_list, base.output_link) { + dpms_prop = &head->props_conn[WDRM_CONNECTOR_DPMS]; + if (dpms_prop->prop_id == 0) + continue; + + ret = drmModeConnectorSetProperty(backend->drm.fd, + head->connector_id, + dpms_prop->prop_id, + state->dpms); + if (ret) { + weston_log("DRM: DPMS: failed property set for %s\n", + head->base.name); + } } } From 2f66130594e01c6057a1d7f28ec236a3a743632d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 12 Sep 2017 16:07:32 +0300 Subject: [PATCH 0455/1642] compositor-drm: drm_output_apply_state_atomic heads Fix this function to support more than one head per output. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index b9898e4d5..f10e90d0c 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2053,10 +2053,10 @@ drm_output_apply_state_atomic(struct drm_output_state *state, uint32_t *flags) { struct drm_output *output = state->output; - struct drm_head *head = to_drm_head(weston_output_get_first_head(&output->base)); struct drm_backend *backend = to_drm_backend(output->base.compositor); struct drm_plane_state *plane_state; struct drm_mode *current_mode = to_drm_mode(output->base.current_mode); + struct drm_head *head; int ret = 0; if (state->dpms != output->state_cur->dpms) @@ -2070,12 +2070,17 @@ drm_output_apply_state_atomic(struct drm_output_state *state, ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, current_mode->blob_id); ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 1); - ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, - output->crtc_id); + + wl_list_for_each(head, &output->base.head_list, base.output_link) { + ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, + output->crtc_id); + } } else { ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, 0); ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 0); - ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, 0); + + wl_list_for_each(head, &output->base.head_list, base.output_link) + ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, 0); } if (ret != 0) { From ecc8cce45d22b891053deede03e6a654803c593c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 12 Sep 2017 16:14:31 +0300 Subject: [PATCH 0456/1642] compositor-drm: drm_set_backlight heads Fix this function to support more than one head per output. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index f10e90d0c..9aaf90380 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3806,21 +3806,24 @@ drm_get_backlight(struct drm_head *head) static void drm_set_backlight(struct weston_output *output_base, uint32_t value) { - struct drm_head *head = to_drm_head(weston_output_get_first_head(output_base)); + struct drm_output *output = to_drm_output(output_base); + struct drm_head *head; long max_brightness, new_brightness; - if (!head->backlight) - return; - if (value > 255) return; - max_brightness = backlight_get_max_brightness(head->backlight); + wl_list_for_each(head, &output->base.head_list, base.output_link) { + if (!head->backlight) + return; - /* get denormalized value */ - new_brightness = (value * max_brightness) / 255; + max_brightness = backlight_get_max_brightness(head->backlight); - backlight_set_brightness(head->backlight, new_brightness); + /* get denormalized value */ + new_brightness = (value * max_brightness) / 255; + + backlight_set_brightness(head->backlight, new_brightness); + } } /** From 456dc73777d985776317958f5d99c9bcb985d435 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 9 Nov 2017 15:10:11 +0200 Subject: [PATCH 0457/1642] compositor-drm: unify head status logging Previously the log contained one line for EDID data and another line for the head, and you just had to know they belong together. Make it more obvious to read by putting both head and EDID info on the same line. We no longer print EDID data every time it is parsed (on every hotplug event), but only if it changes. I did take a shortcut here and use weston_head::device_changed as the print condition which relies on the compositor clearing it, but a failure to do so just means we print stuff even if it didn't change. Head info updates also print the head info and not just the EDID data. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 9aaf90380..750f458fe 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4316,10 +4316,6 @@ find_and_parse_output_edid(struct drm_head *head, edid_blob->data, edid_blob->length); if (!rc) { - weston_log("EDID data '%s', '%s', '%s'\n", - head->edid.pnp_id, - head->edid.monitor_name, - head->edid.serial_number); if (head->edid.pnp_id[0] != '\0') *make = head->edid.pnp_id; if (head->edid.monitor_name[0] != '\0') @@ -5024,6 +5020,21 @@ drm_head_assign_connector_info(struct drm_head *head, return 0; } +static void +drm_head_log_info(struct drm_head *head, const char *msg) +{ + if (head->base.connected) { + weston_log("DRM: head '%s' %s, connector %d is connected, " + "EDID make '%s', model '%s', serial '%s'\n", + head->base.name, msg, head->connector_id, + head->base.make, head->base.model, + head->base.serial_number ?: ""); + } else { + weston_log("DRM: head '%s' %s, connector %d is disconnected.\n", + head->base.name, msg, head->connector_id); + } +} + /** Update connector and monitor information * * @param head The head to update. @@ -5047,6 +5058,9 @@ drm_head_update_info(struct drm_head *head) if (drm_head_assign_connector_info(head, connector) < 0) drmModeFreeConnector(connector); + + if (head->base.device_changed) + drm_head_log_info(head, "updated"); } /** @@ -5103,10 +5117,7 @@ drm_head_create(struct drm_backend *backend, uint32_t connector_id, } weston_compositor_add_head(backend->compositor, &head->base); - - weston_log("DRM: found head '%s', connector %d %s.\n", - head->base.name, head->connector_id, - head->base.connected ? "connected" : "disconnected"); + drm_head_log_info(head, "found"); return head; From f005f25d62e048877dee96564a6b8c25a2b8752a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 10 Nov 2017 16:34:39 +0200 Subject: [PATCH 0458/1642] compositor-drm: combine mode list from heads A single list of modes needs to be combined from the mode lists in each attached head. We could just concatenate the lists, but that might introduce duplicates. Try to avoid duplicates instead by using partially fuzzy matching. When a duplicate is found, try to figure out which is more suitable to use in place of both. If one has the preferred flag and the other doesn't, take the preferred one. Otherwise use the one already in the list. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 114 +++++++++++++++++++++++++++++++++---- 1 file changed, 104 insertions(+), 10 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 750f458fe..9840f3314 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4428,6 +4428,101 @@ parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format) return ret; } +static uint32_t +u32distance(uint32_t a, uint32_t b) +{ + if (a < b) + return b - a; + else + return a - b; +} + +/** Choose equivalent mode + * + * If the two modes are not equivalent, return NULL. + * Otherwise return the mode that is more likely to work in place of both. + * + * None of the fuzzy matching criteria in this function have any justification. + * + * typedef struct _drmModeModeInfo { + * uint32_t clock; + * uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew; + * uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan; + * + * uint32_t vrefresh; + * + * uint32_t flags; + * uint32_t type; + * char name[DRM_DISPLAY_MODE_LEN]; + * } drmModeModeInfo, *drmModeModeInfoPtr; + */ +static const drmModeModeInfo * +drm_mode_pick_equivalent(const drmModeModeInfo *a, const drmModeModeInfo *b) +{ + uint32_t refresh_a, refresh_b; + + if (a->hdisplay != b->hdisplay || a->vdisplay != b->vdisplay) + return NULL; + + if (a->flags != b->flags) + return NULL; + + /* kHz */ + if (u32distance(a->clock, b->clock) > 500) + return NULL; + + refresh_a = drm_refresh_rate_mHz(a); + refresh_b = drm_refresh_rate_mHz(b); + if (u32distance(refresh_a, refresh_b) > 50) + return NULL; + + if ((a->type ^ b->type) & DRM_MODE_TYPE_PREFERRED) { + if (a->type & DRM_MODE_TYPE_PREFERRED) + return a; + else + return b; + } + + return a; +} + +/* If the given mode info is not already in the list, add it. + * If it is in the list, either keep the existing or replace it, + * depending on which one is "better". + */ +static int +drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info) +{ + struct weston_mode *base; + struct drm_mode *mode; + struct drm_backend *backend; + const drmModeModeInfo *chosen = NULL; + + assert(info); + + wl_list_for_each(base, &output->base.mode_list, link) { + mode = to_drm_mode(base); + chosen = drm_mode_pick_equivalent(&mode->mode_info, info); + if (chosen) + break; + } + + if (chosen == info) { + backend = to_drm_backend(output->base.compositor); + drm_output_destroy_mode(backend, mode); + chosen = NULL; + } + + if (!chosen) { + mode = drm_output_add_mode(output, info); + if (!mode) + return -1; + } + /* else { the equivalent mode is already in the list } */ + + return 0; +} + /** Rewrite the output's mode list * * @param output The output. @@ -4444,22 +4539,21 @@ drm_output_update_modelist_from_heads(struct drm_output *output) struct drm_backend *backend = to_drm_backend(output->base.compositor); struct weston_head *head_base; struct drm_head *head; - struct drm_mode *mode; int i; + int ret; assert(!output->base.enabled); drm_mode_list_destroy(backend, &output->base.mode_list); - /* XXX: needs a strategy for combining mode lists from multiple heads */ - head_base = weston_output_get_first_head(&output->base); - assert(head_base); - head = to_drm_head(head_base); - - for (i = 0; i < head->connector->count_modes; i++) { - mode = drm_output_add_mode(output, &head->connector->modes[i]); - if (!mode) - return -1; + wl_list_for_each(head_base, &output->base.head_list, output_link) { + head = to_drm_head(head_base); + for (i = 0; i < head->connector->count_modes; i++) { + ret = drm_output_try_add_mode(output, + &head->connector->modes[i]); + if (ret < 0) + return -1; + } } return 0; From f8b850d4e10f7aafc34bb9d2d174424fd00c36fe Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 15 Nov 2017 12:51:01 +0200 Subject: [PATCH 0459/1642] compositor-drm: backlight control for all heads If an output has multiple (cloned) heads, it should be enough for any head to support backlight control for DRM-backend to expose it. Inspect all attached heads for backlight control and improve the logging. Pick the initial backlight level from whatever happens to be the "first" head, because it's simple. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 9840f3314..6164e025a 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3826,6 +3826,35 @@ drm_set_backlight(struct weston_output *output_base, uint32_t value) } } +static void +drm_output_init_backlight(struct drm_output *output) +{ + struct weston_head *base; + struct drm_head *head; + + output->base.set_backlight = NULL; + + wl_list_for_each(base, &output->base.head_list, output_link) { + head = to_drm_head(base); + + if (head->backlight) { + weston_log("Initialized backlight for head '%s', device %s\n", + head->base.name, head->backlight->path); + + if (!output->base.set_backlight) { + output->base.set_backlight = drm_set_backlight; + output->base.backlight_current = + drm_get_backlight(head); + } + } + } + + if (!output->base.set_backlight) { + weston_log("No backlight control for output '%s'\n", + output->base.name); + } +} + /** * Power output on or off * @@ -4899,14 +4928,7 @@ drm_output_enable(struct weston_output *base) goto err; } - if (head->backlight) { - weston_log("Initialized backlight, device %s\n", - head->backlight->path); - output->base.set_backlight = drm_set_backlight; - output->base.backlight_current = drm_get_backlight(head); - } else { - weston_log("Failed to initialize backlight\n"); - } + drm_output_init_backlight(output); output->base.start_repaint_loop = drm_output_start_repaint_loop; output->base.repaint = drm_output_repaint; From c0eb25464be592555ee20a1a843e44e9ee4f52dc Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 15 Nov 2017 13:37:18 +0200 Subject: [PATCH 0460/1642] compositor-drm: update video mode printing Stop using a head for printing the mode list, because there could be multiple heads. We already gather the mode list from all heads. No need to print the connector id here, because it is logged with DRM heads, and core prints the head names on output enable. The "built-in" flag seemed dead, because it could only be printed if the kernel provided no modes. If we want more detailed info on where modes come from, we would need to inspect mode_info or add new flags to drm_mode or weston_mode. Add printing the pixel clock, because that is used by the video mode duplicate removal code. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 6164e025a..32b9182a3 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4892,13 +4892,31 @@ drm_output_fini_crtc(struct drm_output *output) output->scanout_plane = NULL; } +static void +drm_output_print_modes(struct drm_output *output) +{ + struct weston_mode *m; + struct drm_mode *dm; + + wl_list_for_each(m, &output->base.mode_list, link) { + dm = to_drm_mode(m); + + weston_log_continue(STAMP_SPACE "%dx%d@%.1f%s%s, %.1f MHz\n", + m->width, m->height, m->refresh / 1000.0, + m->flags & WL_OUTPUT_MODE_PREFERRED ? + ", preferred" : "", + m->flags & WL_OUTPUT_MODE_CURRENT ? + ", current" : "", + dm->mode_info.clock / 1000.0); + } +} + static int drm_output_enable(struct weston_output *base) { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); struct drm_head *head = to_drm_head(weston_output_get_first_head(base)); - struct weston_mode *m; drmModeRes *resources; int ret; @@ -4948,17 +4966,9 @@ drm_output_enable(struct weston_output *base) &output->scanout_plane->base, &b->compositor->primary_plane); - weston_log("Output %s, (connector %d, crtc %d)\n", - output->base.name, head->connector_id, output->crtc_id); - wl_list_for_each(m, &output->base.mode_list, link) - weston_log_continue(STAMP_SPACE "mode %dx%d@%.1f%s%s%s\n", - m->width, m->height, m->refresh / 1000.0, - m->flags & WL_OUTPUT_MODE_PREFERRED ? - ", preferred" : "", - m->flags & WL_OUTPUT_MODE_CURRENT ? - ", current" : "", - head->connector->count_modes == 0 ? - ", built-in" : ""); + weston_log("Output %s (crtc %d) video modes:\n", + output->base.name, output->crtc_id); + drm_output_print_modes(output); return 0; From 9c03a7c4abe0e7084c91d4bf68dc239475be95d0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 28 Nov 2017 14:30:10 +0200 Subject: [PATCH 0461/1642] compositor-drm: introduce drm_head_read_current_setup() Rename connector_get_current_mode() because it will be useful for storing not just the current mode on creating a head. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 32b9182a3..663a3e371 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4678,15 +4678,16 @@ drm_output_choose_initial_mode(struct drm_backend *backend, } static int -connector_get_current_mode(drmModeConnector *connector, int drm_fd, - drmModeModeInfo *mode) +drm_head_read_current_setup(struct drm_head *head, struct drm_backend *backend) { + drmModeModeInfo *mode = &head->inherited_mode; + int drm_fd = backend->drm.fd; drmModeEncoder *encoder; drmModeCrtc *crtc; /* Get the current mode on the crtc that's currently driving * this connector. */ - encoder = drmModeGetEncoder(drm_fd, connector->encoder_id); + encoder = drmModeGetEncoder(drm_fd, head->connector->encoder_id); memset(mode, 0, sizeof *mode); if (encoder != NULL) { crtc = drmModeGetCrtc(drm_fd, encoder->crtc_id); @@ -5235,8 +5236,7 @@ drm_head_create(struct drm_backend *backend, uint32_t connector_id, head->connector->connector_type == DRM_MODE_CONNECTOR_eDP) weston_head_set_internal(&head->base); - if (connector_get_current_mode(head->connector, backend->drm.fd, - &head->inherited_mode) < 0) { + if (drm_head_read_current_setup(head, backend) < 0) { weston_log("Failed to retrieve current mode from connector %d.\n", head->connector_id); /* Continue, inherited_mode was memset to zero. */ From 6fae2be9fb9d476837cc79ae66575492c3bb24f3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 28 Nov 2017 14:33:52 +0200 Subject: [PATCH 0462/1642] compositor-drm: no need to clear inherited_mode The head was just zalloc()'d, there is no need to memset it to zero. If a function fails, it is preferable it leaves the output arguments untouched. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 663a3e371..23fd8876e 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4680,7 +4680,6 @@ drm_output_choose_initial_mode(struct drm_backend *backend, static int drm_head_read_current_setup(struct drm_head *head, struct drm_backend *backend) { - drmModeModeInfo *mode = &head->inherited_mode; int drm_fd = backend->drm.fd; drmModeEncoder *encoder; drmModeCrtc *crtc; @@ -4688,14 +4687,13 @@ drm_head_read_current_setup(struct drm_head *head, struct drm_backend *backend) /* Get the current mode on the crtc that's currently driving * this connector. */ encoder = drmModeGetEncoder(drm_fd, head->connector->encoder_id); - memset(mode, 0, sizeof *mode); if (encoder != NULL) { crtc = drmModeGetCrtc(drm_fd, encoder->crtc_id); drmModeFreeEncoder(encoder); if (crtc == NULL) return -1; if (crtc->mode_valid) - *mode = crtc->mode; + head->inherited_mode = crtc->mode; drmModeFreeCrtc(crtc); } @@ -5239,7 +5237,7 @@ drm_head_create(struct drm_backend *backend, uint32_t connector_id, if (drm_head_read_current_setup(head, backend) < 0) { weston_log("Failed to retrieve current mode from connector %d.\n", head->connector_id); - /* Continue, inherited_mode was memset to zero. */ + /* Not fatal. */ } weston_compositor_add_head(backend->compositor, &head->base); From bb659c8e192d498846d3f641b6a7462506c24ca0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 18 Apr 2017 14:43:08 +0300 Subject: [PATCH 0463/1642] libinput: remove evdev_device::devnode Struct 'evdev_device' has field 'devnode' which is initialized to NULL, never assigned, and finally free()'d. Therefore it is useless. Remove the dead field. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- libweston/libinput-device.c | 1 - libweston/libinput-device.h | 1 - 2 files changed, 2 deletions(-) diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index 1b9fc9c83..9b2577b6c 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -596,7 +596,6 @@ evdev_device_destroy(struct evdev_device *device) wl_list_remove(&device->output_destroy_listener.link); wl_list_remove(&device->link); libinput_device_unref(device->device); - free(device->devnode); free(device->output_name); free(device); } diff --git a/libweston/libinput-device.h b/libweston/libinput-device.h index 5041a4aa6..8e9f56085 100644 --- a/libweston/libinput-device.h +++ b/libweston/libinput-device.h @@ -47,7 +47,6 @@ struct evdev_device { struct wl_list link; struct weston_output *output; struct wl_listener output_destroy_listener; - char *devnode; char *output_name; int fd; }; From dfc9d3b847b002f1139589e43a50b152b9aa2d3f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 18 Apr 2017 12:14:32 +0300 Subject: [PATCH 0464/1642] libinput: note if calibrating without an output Print a note that calibration got skipped if the input device supports a calibration matrix but there is no associated output to compute it from. Helps with debugging touchscreen calibration issues. The code is reorganized and commented a bit, but this does not change the behaviour. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- libweston/libinput-device.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index 9b2577b6c..48bfd6f18 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -463,21 +463,29 @@ evdev_device_set_calibration(struct evdev_device *device) float calibration[6]; enum libinput_config_status status; - if (!device->output) + if (!libinput_device_config_calibration_has_matrix(device->device)) + return; + + /* If LIBINPUT_CALIBRATION_MATRIX was set to non-identity, we will not + * override it with WL_CALIBRATION. It also means we don't need an + * output to load a calibration. */ + if (libinput_device_config_calibration_get_default_matrix( + device->device, + calibration) != 0) + return; + + if (!device->output) { + weston_log("input device %s has no enabled output associated " + "(%s named), skipping calibration for now.\n", + sysname, device->output_name ?: "none"); return; + } width = device->output->width; height = device->output->height; if (width == 0 || height == 0) return; - /* If libinput has a pre-set calibration matrix, don't override it */ - if (!libinput_device_config_calibration_has_matrix(device->device) || - libinput_device_config_calibration_get_default_matrix( - device->device, - calibration) != 0) - return; - udev = udev_new(); if (!udev) return; From ed51b624161434b058f8b1f39cd0ed6dbec79622 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 21 Mar 2018 14:30:04 +0200 Subject: [PATCH 0465/1642] libinput: deprecate WL_CALIBRATION The udev property WL_CALIBRATION is an old way of giving Weston a touchscreen calibration matrix. It is Weston-specific. The recommended way of setting up a calibration is to use the udev property LIBINPUT_CALIBRATION_MATRIX, which libinput will load automatically and therefore applies to all libinput using display servers and applications. The syntax of WL_CALIBRATION and LIBINPUT_CALIBRATION_MATRIX is different as well: WL_CALIBRATION uses pixels as the translation part units, which makes the values depend on the output resolution. LIBINPUT_CALIBRATION_MATRIX on the other hand uses normalized units. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- libweston/libinput-device.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index 48bfd6f18..64d99f754 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -500,6 +500,13 @@ evdev_device_set_calibration(struct evdev_device *device) udev_device_get_property_value(udev_device, "WL_CALIBRATION"); + if (calibration_values) { + weston_log("Warning: input device %s has WL_CALIBRATION property set. " + "Support for it will be removed in the future. " + "Please use LIBINPUT_CALIBRATION_MATRIX instead.\n", + sysname); + } + if (!calibration_values || sscanf(calibration_values, "%f %f %f %f %f %f", &calibration[0], From 6632b6dc725ade9daa7136cc7096eaeea3c29d9d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 18 Apr 2017 12:22:12 +0300 Subject: [PATCH 0466/1642] libinput: log input device to output associations Helps admins ensure the configuration is correct. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- libweston/libinput-device.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index 64d99f754..d391d63e8 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -554,6 +554,12 @@ evdev_device_set_output(struct evdev_device *device, device->output_destroy_listener.notify = NULL; } + weston_log("associating input device %s with output %s " + "(%s by udev)\n", + libinput_device_get_sysname(device->device), + output->name, + device->output_name ?: "none"); + device->output = output; device->output_destroy_listener.notify = notify_output_destroy; wl_signal_add(&output->destroy_signal, From 018e6ade21592bfad9d544408ccfe58134b865e4 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 18 Apr 2017 12:22:12 +0300 Subject: [PATCH 0467/1642] libinput: make setting the same output a no-op In the future evdev_device_set_output() will start getting called more often, redundantly. Short-circuit the setting if the chosen output is already set for an input device. This reduces churn in the logs. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- libweston/libinput-device.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index d391d63e8..62f4cee83 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -549,6 +549,9 @@ void evdev_device_set_output(struct evdev_device *device, struct weston_output *output) { + if (device->output == output) + return; + if (device->output_destroy_listener.notify) { wl_list_remove(&device->output_destroy_listener.link); device->output_destroy_listener.notify = NULL; From 98d50cc3112dcd6e32118bdc7adc266773ace1cc Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 20 Apr 2017 11:38:06 +0300 Subject: [PATCH 0468/1642] libinput: allow evdev_device_set_output(dev, NULL) Removing the output association from an evdev_device is more than just setting the pointer to NULL, one also needs to remove the destroy listener and flag the destroy listener as unused (notify == NULL). evdev_device_set_output() can already remove associations, so let it also handle an assignment to NULL output. Fix notify_output_destroy() to handle removing an association correctly. Previously, the listener was left "used", which would mean the next call to evdev_device_set_output() would have wl_list_remove()'d, accessing freed memory. This could be triggered by having a touchscreen with a specified output association, and unplugging then re-plugging the corresponding output. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- libweston/libinput-device.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index 62f4cee83..dbbaae32e 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -442,7 +442,7 @@ notify_output_destroy(struct wl_listener *listener, void *data) struct weston_output, link); evdev_device_set_output(device, output); } else { - device->output = NULL; + evdev_device_set_output(device, NULL); } } @@ -557,6 +557,14 @@ evdev_device_set_output(struct evdev_device *device, device->output_destroy_listener.notify = NULL; } + if (!output) { + weston_log("output for input device %s removed\n", + libinput_device_get_sysname(device->device)); + + device->output = NULL; + return; + } + weston_log("associating input device %s with output %s " "(%s by udev)\n", libinput_device_get_sysname(device->device), From fd02efbc84a3c963a79bb74e5b1cd0e54fee74da Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 19 Mar 2018 14:55:41 +0200 Subject: [PATCH 0469/1642] libinput: use head names for output matching Associating input devices with weston_outputs by the output name fails when one output has several heads. We need to match against head names instead of output names to be able to find all names. This fixes touchscreen output association in shared-CRTC clone mode when outputs or input devices appear or disappear. Even though notify_output_create() is called only when new outputs appear, the implementation is prepared to also remove output associations. This will be handy in the future when this function will handle also head detaching from an output that remains enabled. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- libweston/libinput-seat.c | 51 +++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/libweston/libinput-seat.c b/libweston/libinput-seat.c index 953f62059..3cece3a16 100644 --- a/libweston/libinput-seat.c +++ b/libweston/libinput-seat.c @@ -58,6 +58,27 @@ get_udev_seat(struct udev_input *input, struct libinput_device *device) return udev_seat_get_named(input, seat_name); } +static struct weston_output * +output_find_by_head_name(struct weston_compositor *compositor, + const char *head_name) +{ + struct weston_output *output; + struct weston_head *head; + + if (!head_name) + return NULL; + + /* only enabled outputs */ + wl_list_for_each(output, &compositor->output_list, link) { + wl_list_for_each(head, &output->head_list, output_link) { + if (strcmp(head_name, head->name) == 0) + return output; + } + } + + return NULL; +} + static void device_added(struct udev_input *input, struct libinput_device *libinput_device) { @@ -95,11 +116,10 @@ device_added(struct udev_input *input, struct libinput_device *libinput_device) output_name = libinput_device_get_output_name(libinput_device); if (output_name) { device->output_name = strdup(output_name); - wl_list_for_each(output, &c->output_list, link) - if (output->name && - strcmp(output->name, device->output_name) == 0) - evdev_device_set_output(device, output); - } else if (device->output == NULL && !wl_list_empty(&c->output_list)) { + output = output_find_by_head_name(c, output_name); + evdev_device_set_output(device, output); + } else if (!wl_list_empty(&c->output_list)) { + /* default assignment to an arbitrary output */ output = container_of(c->output_list.next, struct weston_output, link); evdev_device_set_output(device, output); @@ -363,15 +383,26 @@ notify_output_create(struct wl_listener *listener, void *data) output_create_listener); struct evdev_device *device; struct weston_output *output = data; + struct weston_output *found; wl_list_for_each(device, &seat->devices_list, link) { - if (device->output_name && - strcmp(output->name, device->output_name) == 0) { - evdev_device_set_output(device, output); + /* If we find any input device without an associated output + * or an output name to associate with, just tie it with the + * output we got here - the default assingment. + */ + if (!device->output_name) { + if (!device->output) + evdev_device_set_output(device, output); + + continue; } - if (device->output_name == NULL && device->output == NULL) - evdev_device_set_output(device, output); + /* Update all devices' output associations, may they gain or + * lose it. + */ + found = output_find_by_head_name(output->compositor, + device->output_name); + evdev_device_set_output(device, found); } } From 8dc6db8c76ac3065d1c37b5a7fb375651f6dab49 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 20 Mar 2018 13:29:40 +0200 Subject: [PATCH 0470/1642] libweston: require connected heads for input devices The use case driving this change is a clone mode setup, where the user is hotplugging or unplugging a cloned touchscreen. Even if the output and head are force-enabled, the touch device should still follow the connector connection status. If there is no video signal for the touchscreen (disconnected connector), then the touch input should be ignored as well. When the output is force-enabled, we need to trigger output_heads_changed from connector status changes. If the head or output are not force-enabled, the compositor will likely attach and detach the head as appropriate. In clone mode, the attach or detach needs to trigger output_heads_changed directly. In other cases, it may be handled through the output getting enabled or disabled which are different signals. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- libweston/compositor.c | 30 ++++++++++++++++++++++++++++++ libweston/compositor.h | 8 ++++++++ libweston/libinput-seat.c | 39 ++++++++++++++++++++++++++++++++++----- libweston/libinput-seat.h | 1 + 4 files changed, 73 insertions(+), 5 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index a9de4ac33..dbf61efe9 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4463,15 +4463,40 @@ weston_head_init(struct weston_head *head, const char *name) head->name = strdup(name); } +/** Send output heads changed signal + * + * \param output The output that changed. + * + * Notify that the enabled output gained and/or lost heads, or that the + * associated heads may have changed their connection status. This does not + * include cases where the output becomes enabled or disabled. The registered + * callbacks are called after the change has successfully happened. + * + * If connection status change causes the compositor to attach or detach a head + * to an enabled output, the registered callbacks may be called multiple times. + */ +static void +weston_output_emit_heads_changed(struct weston_output *output) +{ + wl_signal_emit(&output->compositor->output_heads_changed_signal, + output); +} + /** Idle task for emitting heads_changed_signal */ static void weston_compositor_call_heads_changed(void *data) { struct weston_compositor *compositor = data; + struct weston_head *head; compositor->heads_changed_source = NULL; wl_signal_emit(&compositor->heads_changed_signal, compositor); + + wl_list_for_each(head, &compositor->head_list, compositor_link) { + if (head->output && head->output->enabled) + weston_output_emit_heads_changed(head->output); + } } /** Schedule a call on idle to heads_changed callback @@ -4678,6 +4703,8 @@ weston_output_attach_head(struct weston_output *output, weston_log("Output '%s' updated to have head(s) %s\n", output->name, head_names); free(head_names); + + weston_output_emit_heads_changed(output); } return 0; @@ -4723,6 +4750,8 @@ weston_head_detach(struct weston_head *head) weston_log("Output '%s' updated to have head(s) %s\n", output->name, head_names); free(head_names); + + weston_output_emit_heads_changed(output); } } } @@ -6255,6 +6284,7 @@ weston_compositor_create(struct wl_display *display, void *user_data) wl_signal_init(&ec->output_moved_signal); wl_signal_init(&ec->output_resized_signal); wl_signal_init(&ec->heads_changed_signal); + wl_signal_init(&ec->output_heads_changed_signal); wl_signal_init(&ec->session_signal); ec->session_active = 1; diff --git a/libweston/compositor.h b/libweston/compositor.h index f3137dea2..ca234075a 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -963,6 +963,11 @@ struct weston_compositor { struct wl_signal output_moved_signal; struct wl_signal output_resized_signal; /* callback argument: resized output */ + /* Signal for output changes triggered by configuration from frontend + * or head state changes from backend. + */ + struct wl_signal output_heads_changed_signal; /* arg: weston_output */ + struct wl_signal session_signal; int session_active; @@ -1031,6 +1036,9 @@ struct weston_compositor { /* Whether to let the compositor run without any input device. */ bool require_input; + /* Signal for a backend to inform a frontend about possible changes + * in head status. + */ struct wl_signal heads_changed_signal; struct wl_event_source *heads_changed_source; }; diff --git a/libweston/libinput-seat.c b/libweston/libinput-seat.c index 3cece3a16..ac1e8e99d 100644 --- a/libweston/libinput-seat.c +++ b/libweston/libinput-seat.c @@ -68,9 +68,16 @@ output_find_by_head_name(struct weston_compositor *compositor, if (!head_name) return NULL; - /* only enabled outputs */ + /* Only enabled outputs with connected heads. + * This means force-enabled outputs but with disconnected heads + * will be ignored; if the touchscreen doesn't have a video signal, + * touching it is meaningless. + */ wl_list_for_each(output, &compositor->output_list, link) { wl_list_for_each(head, &output->head_list, output_link) { + if (!weston_head_is_connected(head)) + continue; + if (strcmp(head_name, head->name) == 0) return output; } @@ -377,12 +384,9 @@ udev_seat_led_update(struct weston_seat *seat_base, enum weston_led leds) } static void -notify_output_create(struct wl_listener *listener, void *data) +udev_seat_output_changed(struct udev_seat *seat, struct weston_output *output) { - struct udev_seat *seat = container_of(listener, struct udev_seat, - output_create_listener); struct evdev_device *device; - struct weston_output *output = data; struct weston_output *found; wl_list_for_each(device, &seat->devices_list, link) { @@ -406,6 +410,26 @@ notify_output_create(struct wl_listener *listener, void *data) } } +static void +notify_output_create(struct wl_listener *listener, void *data) +{ + struct udev_seat *seat = container_of(listener, struct udev_seat, + output_create_listener); + struct weston_output *output = data; + + udev_seat_output_changed(seat, output); +} + +static void +notify_output_heads_changed(struct wl_listener *listener, void *data) +{ + struct udev_seat *seat = container_of(listener, struct udev_seat, + output_heads_listener); + struct weston_output *output = data; + + udev_seat_output_changed(seat, output); +} + static struct udev_seat * udev_seat_create(struct udev_input *input, const char *seat_name) { @@ -423,6 +447,10 @@ udev_seat_create(struct udev_input *input, const char *seat_name) wl_signal_add(&c->output_created_signal, &seat->output_create_listener); + seat->output_heads_listener.notify = notify_output_heads_changed; + wl_signal_add(&c->output_heads_changed_signal, + &seat->output_heads_listener); + wl_list_init(&seat->devices_list); return seat; @@ -440,6 +468,7 @@ udev_seat_destroy(struct udev_seat *seat) udev_seat_remove_devices(seat); weston_seat_release(&seat->base); wl_list_remove(&seat->output_create_listener.link); + wl_list_remove(&seat->output_heads_listener.link); free(seat); } diff --git a/libweston/libinput-seat.h b/libweston/libinput-seat.h index 65c9b64ba..8c6a5bf7f 100644 --- a/libweston/libinput-seat.h +++ b/libweston/libinput-seat.h @@ -39,6 +39,7 @@ struct udev_seat { struct weston_seat base; struct wl_list devices_list; struct wl_listener output_create_listener; + struct wl_listener output_heads_listener; }; typedef void (*udev_configure_device_t)(struct weston_compositor *compositor, From 1bbf58ff5858bbb571c5e7d872efdc5b408a722f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 20 Mar 2018 14:45:36 +0200 Subject: [PATCH 0471/1642] libinput: do not switch output associations on disable If an input device is associated to an output that then gets disabled, there is no case where associating to a different output would be correct. The output association is used for absolute positioned input devices, and an input device like a touchscreen cannot ever be automatically valid for more than one possible output - the touchscreen display device. Therefore do not automatically reassing implicitly associated input devices to another output. This removes some log spam on shutdown. In fact, if there can be more than one output at any time, absolute input devices must be explicitly configured to associate with the correct output, or the results are essentially undefined in any case. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- libweston/libinput-device.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index dbbaae32e..e1738613d 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -434,16 +434,8 @@ notify_output_destroy(struct wl_listener *listener, void *data) struct evdev_device *device = container_of(listener, struct evdev_device, output_destroy_listener); - struct weston_compositor *c = device->seat->compositor; - struct weston_output *output; - - if (!device->output_name && !wl_list_empty(&c->output_list)) { - output = container_of(c->output_list.next, - struct weston_output, link); - evdev_device_set_output(device, output); - } else { - evdev_device_set_output(device, NULL); - } + + evdev_device_set_output(device, NULL); } /** From d61da83cb0923e3bcfc960ad322d9ab5e1c4af45 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 5 Feb 2018 16:20:51 +0200 Subject: [PATCH 0472/1642] man: document WESTON_LIBINPUT_LOG_PRIORITY env Commit c81c4241d9c9fc5f60c08177dd8a33ae4e4ddca1 added this environment variable. Document it. It applies also to the fbdev-backend but that has no man page. v2: - Rewording by Peter Hutterer. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- man/weston-drm.man | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/weston-drm.man b/man/weston-drm.man index 75d79021c..6518563ad 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -113,6 +113,11 @@ instead of using the current tty. .SH ENVIRONMENT . .TP +.B WESTON_LIBINPUT_LOG_PRIORITY +The minimum libinput verbosity level to be printed to Weston's log. +Valid values are +.BR debug ", " info ", and " error ". Default is " info . +.TP .B WESTON_TTY_FD The file descriptor (integer) of the opened tty where .B weston From f39249ad589cc82345af7924659fce64511f47e0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 26 Feb 2018 14:14:14 +0200 Subject: [PATCH 0473/1642] tests: add test_seat_release() for symmetry Add test_seat_release() as the counterpart of test_seat_init() instead of open-coding it. This helps adding more code to test_seat_release() later. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- tests/weston-test.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/weston-test.c b/tests/weston-test.c index 73409cace..ae08b02f5 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -80,6 +80,9 @@ test_client_sigchld(struct weston_process *process, int status) static int test_seat_init(struct weston_test *test) { + assert(!test->is_seat_initialized && + "Trying to add already added test seat"); + /* create our own seat */ weston_seat_init(&test->seat, test->compositor, "test-seat"); test->is_seat_initialized = true; @@ -93,6 +96,16 @@ test_seat_init(struct weston_test *test) return 0; } +static void +test_seat_release(struct weston_test *test) +{ + assert(test->is_seat_initialized && + "Trying to release already released test seat"); + test->is_seat_initialized = false; + weston_seat_release(&test->seat); + memset(&test->seat, 0, sizeof test->seat); +} + static struct weston_seat * get_seat(struct weston_test *test) { @@ -270,10 +283,7 @@ device_release(struct wl_client *client, } else if (strcmp(device, "touch") == 0) { weston_seat_release_touch(seat); } else if (strcmp(device, "seat") == 0) { - assert(test->is_seat_initialized && - "Trying to release already released test seat"); - weston_seat_release(seat); - test->is_seat_initialized = false; + test_seat_release(test); } else { assert(0 && "Unsupported device"); } @@ -293,8 +303,6 @@ device_add(struct wl_client *client, } else if (strcmp(device, "touch") == 0) { weston_seat_init_touch(seat); } else if (strcmp(device, "seat") == 0) { - assert(!test->is_seat_initialized && - "Trying to add already added test seat"); test_seat_init(test); } else { assert(0 && "Unsupported device"); From 925788fc76499290329863c0954275b74955b3a5 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 19 Apr 2018 14:20:01 +0300 Subject: [PATCH 0474/1642] Update copyrights for Collabora and General Electric Company MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Looking at the diff statistics of the changes authored by me and landed since 4.0.0 release points out these files as having major changes. Update the copyright holders accordingly, as both clone mode and touchscreen calibration related patches are copyright both Collabora and GE. I have kept the redundant "Copyright ©" form only to keep things consistent, even when either the word or the mark would be enough. Signed-off-by: Pekka Paalanen Acked-by: Ian Ray Reviewed-by: Daniel Stone --- compositor/main.c | 3 ++- libweston/compositor-drm.c | 2 ++ libweston/compositor.c | 3 ++- libweston/compositor.h | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index f72d3ea1f..d1bc80621 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1,9 +1,10 @@ /* * Copyright © 2010-2011 Intel Corporation * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2012-2015 Collabora, Ltd. + * Copyright © 2012-2018 Collabora, Ltd. * Copyright © 2010-2011 Benjamin Franzke * Copyright © 2013 Jason Ekstrand + * Copyright © 2017, 2018 General Electric Company * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 23fd8876e..92e4cc451 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1,6 +1,8 @@ /* * Copyright © 2008-2011 Kristian Høgsberg * Copyright © 2011 Intel Corporation + * Copyright © 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/libweston/compositor.c b/libweston/compositor.c index dbf61efe9..747c55fa0 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -1,7 +1,8 @@ /* * Copyright © 2010-2011 Intel Corporation * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2012-2015 Collabora, Ltd. + * Copyright © 2012-2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/libweston/compositor.h b/libweston/compositor.h index ca234075a..26527c61e 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1,6 +1,7 @@ /* * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2012 Collabora, Ltd. + * Copyright © 2012, 2017, 2018 Collabora, Ltd. + * Copyright © 2017, 2018 General Electric Company * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the From 1b043b16b5804f47f2cbfde80c867f3527bbe7bc Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 26 Feb 2018 14:55:32 +0200 Subject: [PATCH 0475/1642] libweston: unexport weston_{pointer,keyboard,touch}_{create,destroy}() We have weston_seat_{init,release}_{pointer,keyboard,touch}() as the backend-facing API. There is no need to expose the create/destroy functions which have been for internal use only for quite a long time. Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer v1 Tested-by: Matt Hoosier --- libweston/compositor.h | 12 ------------ libweston/input.c | 12 ++++++------ 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/libweston/compositor.h b/libweston/compositor.h index 26527c61e..f113e4f32 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -483,10 +483,6 @@ weston_pointer_motion_to_abs(struct weston_pointer *pointer, struct weston_pointer_motion_event *event, wl_fixed_t *x, wl_fixed_t *y); -struct weston_pointer * -weston_pointer_create(struct weston_seat *seat); -void -weston_pointer_destroy(struct weston_pointer *pointer); void weston_pointer_send_motion(struct weston_pointer *pointer, const struct timespec *time, @@ -531,10 +527,6 @@ weston_pointer_set_default_grab(struct weston_pointer *pointer, void weston_pointer_constraint_destroy(struct weston_pointer_constraint *constraint); -struct weston_keyboard * -weston_keyboard_create(void); -void -weston_keyboard_destroy(struct weston_keyboard *keyboard); void weston_keyboard_set_focus(struct weston_keyboard *keyboard, struct weston_surface *surface); @@ -563,10 +555,6 @@ weston_keyboard_send_modifiers(struct weston_keyboard *keyboard, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); -struct weston_touch * -weston_touch_create(void); -void -weston_touch_destroy(struct weston_touch *touch); void weston_touch_set_focus(struct weston_touch *touch, struct weston_view *view); diff --git a/libweston/input.c b/libweston/input.c index a9d21cb51..833ce22e9 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -1136,7 +1136,7 @@ weston_pointer_reset_state(struct weston_pointer *pointer) static void weston_pointer_handle_output_destroy(struct wl_listener *listener, void *data); -WL_EXPORT struct weston_pointer * +static struct weston_pointer * weston_pointer_create(struct weston_seat *seat) { struct weston_pointer *pointer; @@ -1175,7 +1175,7 @@ weston_pointer_create(struct weston_seat *seat) return pointer; } -WL_EXPORT void +static void weston_pointer_destroy(struct weston_pointer *pointer) { struct weston_pointer_client *pointer_client, *tmp; @@ -1209,7 +1209,7 @@ weston_pointer_set_default_grab(struct weston_pointer *pointer, &default_pointer_grab_interface; } -WL_EXPORT struct weston_keyboard * +static struct weston_keyboard * weston_keyboard_create(void) { struct weston_keyboard *keyboard; @@ -1235,7 +1235,7 @@ weston_keyboard_create(void) static void weston_xkb_info_destroy(struct weston_xkb_info *xkb_info); -WL_EXPORT void +static void weston_keyboard_destroy(struct weston_keyboard *keyboard) { struct wl_resource *resource; @@ -1268,7 +1268,7 @@ weston_touch_reset_state(struct weston_touch *touch) touch->num_tp = 0; } -WL_EXPORT struct weston_touch * +static struct weston_touch * weston_touch_create(void) { struct weston_touch *touch; @@ -1292,7 +1292,7 @@ weston_touch_create(void) return touch; } -WL_EXPORT void +static void weston_touch_destroy(struct weston_touch *touch) { struct wl_resource *resource; From 8549e16531970c247243202a45adacd5709d4986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= Date: Tue, 28 Nov 2017 20:42:47 -0500 Subject: [PATCH 0476/1642] libweston: fix weston_touch_start_grab() arg name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compositor.c has 'touch', so use 'touch' here as well. It is not a device to begin with. Signed-off-by: Louis-Francis Ratté-Boulianne Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer v1 Tested-by: Matt Hoosier --- libweston/compositor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor.h b/libweston/compositor.h index f113e4f32..30041b7fe 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -559,7 +559,7 @@ void weston_touch_set_focus(struct weston_touch *touch, struct weston_view *view); void -weston_touch_start_grab(struct weston_touch *device, +weston_touch_start_grab(struct weston_touch *touch, struct weston_touch_grab *grab); void weston_touch_end_grab(struct weston_touch *touch); From 27cc4816ae60dc256bbcf12cf78d6d0d36004a82 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 20 Nov 2017 13:31:06 +0200 Subject: [PATCH 0477/1642] compositor-drm: rewrite crtc picking for clone mode To support shared-CRTC clone mode, the chosen CRTC needs to support driving all the attached connectors. Replace the old algorithm with a new one that takes into account all associated connectors. Ideally it should use possible_clones mask to check which encoders (and therefore connectors) actually can be in a cloned set. However, the DRM documentation says about possible_clones and possible_crtcs masks both: "In reality almost every driver gets this wrong." - https://01.org/linuxgraphics/gfx-docs/drm/gpu/drm-kms.html#c.drm_encoder Looking at a target device and its kernel where clone mode is desired, possible_clones is indeed self-conflicting and would not allow cloning at all. Therefore the implemented algorithm replaces the checking of possible_clones with luck. It even goes out of its way to find any CRTC for a configuration, even if not advertised by the kernel as not supported. Libweston would need infrastructure to allow trial-and-error CRTC allocation: rather than picking one CRTC in advance and do or die, it should try all available CRTCs one by one. Unfortunately that is not yet possible, so this patch implements what it can. It is also the DRM upstream opinion that trial-and-error with ATOMIC_TEST would be the way to go. Unlike the old algorithm, the new algorithm prefers routings that were in place when Weston started instead of when enabling an output. When you never temporarily disable an output, this makes no difference. Signed-off-by: Pekka Paalanen Acked-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 184 ++++++++++++++++++++++++++----------- 1 file changed, 131 insertions(+), 53 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 92e4cc451..d983e1c3d 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -417,6 +417,7 @@ struct drm_head { struct backlight *backlight; drmModeModeInfo inherited_mode; /**< Original mode on the connector */ + uint32_t inherited_crtc_id; /**< Original CRTC assignment */ }; struct drm_output { @@ -3975,51 +3976,6 @@ make_connector_name(const drmModeConnector *con) return name; } -static int -find_crtc_for_connector(struct drm_backend *b, - drmModeRes *resources, drmModeConnector *connector) -{ - drmModeEncoder *encoder; - int i, j; - int ret = -1; - - for (j = 0; j < connector->count_encoders; j++) { - uint32_t possible_crtcs, encoder_id, crtc_id; - - encoder = drmModeGetEncoder(b->drm.fd, connector->encoders[j]); - if (encoder == NULL) { - weston_log("Failed to get encoder.\n"); - continue; - } - encoder_id = encoder->encoder_id; - possible_crtcs = encoder->possible_crtcs; - crtc_id = encoder->crtc_id; - drmModeFreeEncoder(encoder); - - for (i = 0; i < resources->count_crtcs; i++) { - if (!(possible_crtcs & (1 << i))) - continue; - - if (drm_output_find_by_crtc(b, resources->crtcs[i])) - continue; - - /* Try to preserve the existing - * CRTC -> encoder -> connector routing; it makes - * initialisation faster, and also since we have a - * very dumb picking algorithm, may preserve a better - * choice. */ - if (!connector->encoder_id || - (encoder_id == connector->encoder_id && - crtc_id == resources->crtcs[i])) - return i; - - ret = i; - } - } - - return ret; -} - static void drm_output_fini_cursor_egl(struct drm_output *output) { unsigned int i; @@ -4690,8 +4646,11 @@ drm_head_read_current_setup(struct drm_head *head, struct drm_backend *backend) * this connector. */ encoder = drmModeGetEncoder(drm_fd, head->connector->encoder_id); if (encoder != NULL) { + head->inherited_crtc_id = encoder->crtc_id; + crtc = drmModeGetCrtc(drm_fd, encoder->crtc_id); drmModeFreeEncoder(encoder); + if (crtc == NULL) return -1; if (crtc->mode_valid) @@ -4778,14 +4737,134 @@ drm_output_init_gamma_size(struct drm_output *output) return 0; } +static uint32_t +drm_head_get_possible_crtcs_mask(struct drm_head *head) +{ + uint32_t possible_crtcs = 0; + drmModeEncoder *encoder; + int i; + + for (i = 0; i < head->connector->count_encoders; i++) { + encoder = drmModeGetEncoder(head->backend->drm.fd, + head->connector->encoders[i]); + if (!encoder) + continue; + + possible_crtcs |= encoder->possible_crtcs; + drmModeFreeEncoder(encoder); + } + + return possible_crtcs; +} + +static int +drm_crtc_get_index(drmModeRes *resources, uint32_t crtc_id) +{ + int i; + + for (i = 0; i < resources->count_crtcs; i++) { + if (resources->crtcs[i] == crtc_id) + return i; + } + + assert(0 && "unknown crtc id"); + return -1; +} + +/** Pick a CRTC that might be able to drive all attached connectors + * + * @param output The output whose attached heads to include. + * @param resources The DRM KMS resources. + * @return CRTC index, or -1 on failure or not found. + */ +static int +drm_output_pick_crtc(struct drm_output *output, drmModeRes *resources) +{ + struct drm_backend *backend; + struct weston_head *base; + struct drm_head *head; + uint32_t possible_crtcs = 0xffffffff; + int existing_crtc[32]; + unsigned j, n = 0; + uint32_t crtc_id; + int best_crtc_index = -1; + int i; + + backend = to_drm_backend(output->base.compositor); + + /* This algorithm ignores drmModeEncoder::possible_clones restriction, + * because it is more often set wrong than not in the kernel. */ + + /* Accumulate a mask of possible crtcs and find existing routings. */ + wl_list_for_each(base, &output->base.head_list, output_link) { + head = to_drm_head(base); + + possible_crtcs &= drm_head_get_possible_crtcs_mask(head); + + crtc_id = head->inherited_crtc_id; + if (crtc_id > 0 && n < ARRAY_LENGTH(existing_crtc)) + existing_crtc[n++] = drm_crtc_get_index(resources, + crtc_id); + } + + /* Find a crtc that could drive each connector individually at least, + * and prefer existing routings. */ + for (i = 0; i < resources->count_crtcs; i++) { + crtc_id = resources->crtcs[i]; + + /* Could the crtc not drive each connector? */ + if (!(possible_crtcs & (1 << i))) + continue; + + /* Is the crtc already in use? */ + if (drm_output_find_by_crtc(backend, crtc_id)) + continue; + + /* Try to preserve the existing CRTC -> connector routing; + * it makes initialisation faster, and also since we have a + * very dumb picking algorithm, may preserve a better + * choice. */ + for (j = 0; j < n; j++) { + if (existing_crtc[j] == i) + return i; + } + + best_crtc_index = i; + } + + if (best_crtc_index != -1) + return best_crtc_index; + + /* Likely possible_crtcs was empty due to asking for clones, + * but since the DRM documentation says the kernel lies, let's + * pick one crtc anyway. Trial and error is the only way to + * be sure if something doesn't work. */ + + /* First pick any existing assignment. */ + for (j = 0; j < n; j++) { + crtc_id = resources->crtcs[existing_crtc[j]]; + if (!drm_output_find_by_crtc(backend, crtc_id)) + return existing_crtc[j]; + } + + /* Otherwise pick any available crtc. */ + for (i = 0; i < resources->count_crtcs; i++) { + crtc_id = resources->crtcs[i]; + + if (!drm_output_find_by_crtc(backend, crtc_id)) + return i; + } + + return -1; +} + /** Allocate a CRTC for the output * * @param output The output with no allocated CRTC. * @param resources DRM KMS resources. - * @param connector The DRM KMS connector data. * @return 0 on success, -1 on failure. * - * Finds a free CRTC that can drive the given connector, reserves the CRTC + * Finds a free CRTC that might drive the attached connectors, reserves the CRTC * for the output, and loads the CRTC properties. * * Populates the cursor and scanout planes. @@ -4793,8 +4872,7 @@ drm_output_init_gamma_size(struct drm_output *output) * On failure, the output remains without a CRTC. */ static int -drm_output_init_crtc(struct drm_output *output, - drmModeRes *resources, drmModeConnector *connector) +drm_output_init_crtc(struct drm_output *output, drmModeRes *resources) { struct drm_backend *b = to_drm_backend(output->base.compositor); drmModeObjectPropertiesPtr props; @@ -4802,9 +4880,10 @@ drm_output_init_crtc(struct drm_output *output, assert(output->crtc_id == 0); - i = find_crtc_for_connector(b, resources, connector); + i = drm_output_pick_crtc(output, resources); if (i < 0) { - weston_log("No usable crtc/encoder pair for connector.\n"); + weston_log("Output '%s': No available CRTCs.\n", + output->base.name); return -1; } @@ -4917,7 +4996,6 @@ drm_output_enable(struct weston_output *base) { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); - struct drm_head *head = to_drm_head(weston_output_get_first_head(base)); drmModeRes *resources; int ret; @@ -4926,7 +5004,7 @@ drm_output_enable(struct weston_output *base) weston_log("drmModeGetResources failed\n"); return -1; } - ret = drm_output_init_crtc(output, resources, head->connector); + ret = drm_output_init_crtc(output, resources); drmModeFreeResources(resources); if (ret < 0) return -1; From db4c7d7237ac297cfdd22b9a1d856d9999399ff2 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 28 Nov 2017 16:11:00 +0200 Subject: [PATCH 0478/1642] compositor-drm: preserve CRTC routing harder If we are processing a connector that does not have an existing routing, it is possible we pick a CRTC that was previously routed to a connector we have not enabled yet. If that happens, the latter connector cannot preserve its routing. Check that no other connector we might enable later had this CRTC before. Signed-off-by: Pekka Paalanen Acked-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index d983e1c3d..851446bea 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4788,7 +4788,9 @@ drm_output_pick_crtc(struct drm_output *output, drmModeRes *resources) unsigned j, n = 0; uint32_t crtc_id; int best_crtc_index = -1; + int fallback_crtc_index = -1; int i; + bool match; backend = to_drm_backend(output->base.compositor); @@ -4829,12 +4831,37 @@ drm_output_pick_crtc(struct drm_output *output, drmModeRes *resources) return i; } - best_crtc_index = i; + /* Check if any other head had existing routing to this CRTC. + * If they did, this is not the best CRTC as it might be needed + * for another output we haven't enabled yet. */ + match = false; + wl_list_for_each(base, &backend->compositor->head_list, + compositor_link) { + head = to_drm_head(base); + + if (head->base.output == &output->base) + continue; + + if (weston_head_is_enabled(&head->base)) + continue; + + if (head->inherited_crtc_id == crtc_id) { + match = true; + break; + } + } + if (!match) + best_crtc_index = i; + + fallback_crtc_index = i; } if (best_crtc_index != -1) return best_crtc_index; + if (fallback_crtc_index != -1) + return fallback_crtc_index; + /* Likely possible_crtcs was empty due to asking for clones, * but since the DRM documentation says the kernel lies, let's * pick one crtc anyway. Trial and error is the only way to From 7f853799e2ec2c43c6d45e399ae575b86ef97e8a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 29 Nov 2017 14:33:33 +0200 Subject: [PATCH 0479/1642] compositor-drm: head detach requires a modeset When a head is detached from an enabled output, that output needs to go through a modeset (drmModeSetCrtc() / ATOMIC_ALLOW_MODESET) so that the connector is actually removed from the CRTC. This has not yet been a problem, because an output could only have one head at a time, and would be automatically disabled on detach. It would be a problem with clone mode. Signed-off-by: Pekka Paalanen Acked-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 851446bea..9e88962c0 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4394,6 +4394,23 @@ drm_output_attach_head(struct weston_output *output_base, return 0; } +static void +drm_output_detach_head(struct weston_output *output_base, + struct weston_head *head_base) +{ + struct drm_backend *b = to_drm_backend(output_base->compositor); + + if (!output_base->enabled) + return; + + /* Need to go through modeset to drop connectors that should no longer + * be driven. */ + /* XXX: Ideally we'd do this per-output, not globally. */ + b->state_invalid = true; + + weston_output_schedule_repaint(output_base); +} + static int parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format) { @@ -5407,6 +5424,7 @@ drm_output_create(struct weston_compositor *compositor, const char *name) output->base.destroy = drm_output_destroy; output->base.disable = drm_output_disable; output->base.attach_head = drm_output_attach_head; + output->base.detach_head = drm_output_detach_head; output->destroy_pending = 0; output->disable_pending = 0; From d5f98d8932c6b3502b44ff5a3554ab7f80457231 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 8 Dec 2017 14:45:00 +0200 Subject: [PATCH 0480/1642] compositor-drm: head attach requires a modeset For the attach on an enabled output to have an effect, we need to go through drmModeSetCrtc or ATOMIC_ALLOW_MODESET. v9: - Add another XXX comment. Signed-off-by: Pekka Paalanen Acked-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 9e88962c0..592ee76aa 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4388,9 +4388,27 @@ static int drm_output_attach_head(struct weston_output *output_base, struct weston_head *head_base) { + struct drm_backend *b = to_drm_backend(output_base->compositor); + if (wl_list_length(&output_base->head_list) >= MAX_CLONED_CONNECTORS) return -1; + if (!output_base->enabled) + return 0; + + /* XXX: ensure the configuration will work. + * This is actually impossible without major infrastructure + * work. */ + + /* Need to go through modeset to add connectors. */ + /* XXX: Ideally we'd do this per-output, not globally. */ + /* XXX: Doing it globally, what guarantees another output's update + * will not clear the flag before this output is updated? + */ + b->state_invalid = true; + + weston_output_schedule_repaint(output_base); + return 0; } From dfc0683ba017e7d25a22aa5fc5b8703a58ea829b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 20 Nov 2017 14:04:38 +0200 Subject: [PATCH 0481/1642] compositor-drm: allow shared-CRTC cloning Allow cloning up to 4 connectors from the same CRTC. All the implementation bits support more than one head per output already. Four is just an arbitary number, small but unlikely to ever be the limiting factor in cloning since hardware is usually very restricted. Signed-off-by: Pekka Paalanen Acked-by: Derek Foreman Reviewed-by: Daniel Stone --- libweston/compositor-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 592ee76aa..287431eb8 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -85,7 +85,7 @@ #define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64 #endif -#define MAX_CLONED_CONNECTORS 1 +#define MAX_CLONED_CONNECTORS 4 /** * Represents the values of an enum-type KMS property From 3ffc6876cadec282a5df4e7c7b2ba9faf4e70dd5 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 2 Nov 2017 15:38:24 +0200 Subject: [PATCH 0482/1642] weston: store weston_compositor in wet_compositor This makes it easier to just pass wet_compositor around and take the weston_compositor from it. It feels weird to go from weston_compositor to wet_compositor all the time in internal functions. It's necessary in callbacks that cannot carry wet_compositor, but otherwise it is awkward. Signed-off-by: Pekka Paalanen Acked-by: Derek Foreman Reviewed-by: Daniel Stone --- compositor/main.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index d1bc80621..4cad6d802 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -77,6 +77,7 @@ struct wet_head_tracker { }; struct wet_compositor { + struct weston_compositor *compositor; struct weston_config *config; struct wet_output_config *parsed_options; bool drm_use_current_mode; @@ -1089,9 +1090,8 @@ wet_head_tracker_create(struct wet_compositor *compositor, } static void -simple_head_enable(struct weston_compositor *compositor, struct weston_head *head) +simple_head_enable(struct wet_compositor *wet, struct weston_head *head) { - struct wet_compositor *wet = to_wet_compositor(compositor); struct weston_output *output; int ret = 0; @@ -1101,7 +1101,8 @@ simple_head_enable(struct weston_compositor *compositor, struct weston_head *hea if (weston_head_get_output(head)) return; - output = weston_compositor_create_output_with_head(compositor, head); + output = weston_compositor_create_output_with_head(wet->compositor, + head); if (!output) { weston_log("Could not create an output for head \"%s\".\n", weston_head_get_name(head)); @@ -1158,18 +1159,19 @@ static void simple_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; bool connected; bool enabled; bool changed; - while ((head = weston_compositor_iterate_heads(compositor, head))) { + while ((head = weston_compositor_iterate_heads(wet->compositor, head))) { connected = weston_head_is_connected(head); enabled = weston_head_is_enabled(head); changed = weston_head_is_device_changed(head); if (connected && !enabled) { - simple_head_enable(compositor, head); + simple_head_enable(wet, head); } else if (!connected && enabled) { simple_head_disable(head); } else if (enabled && changed) { @@ -1926,6 +1928,7 @@ int main(int argc, char *argv[]) } ec = weston_compositor_create(display, &user_data); + user_data.compositor = ec; if (ec == NULL) { weston_log("fatal: failed to create compositor\n"); goto out; From 03dc95a131fda672c6fc6c1a1a9ba6e2bb0c88a0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 2 Nov 2017 15:42:51 +0200 Subject: [PATCH 0483/1642] weston: use wet.compositor consistently in main() Rename user_data to wet, because it is called wet everywhere else. Drop the local variable ec, because that is available as wet.compositor. This models a little better that wet_compositor owns weston_compositor, and not the other way around. Signed-off-by: Pekka Paalanen Acked-by: Derek Foreman Reviewed-by: Daniel Stone --- compositor/main.c | 50 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 4cad6d802..54fa312f6 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1809,7 +1809,6 @@ int main(int argc, char *argv[]) int ret = EXIT_FAILURE; char *cmdline; struct wl_display *display; - struct weston_compositor *ec; struct wl_event_source *signals[4]; struct wl_event_loop *loop; int i, fd; @@ -1832,7 +1831,7 @@ int main(int argc, char *argv[]) struct wl_client *primary_client; struct wl_listener primary_client_destroyed; struct weston_seat *seat; - struct wet_compositor user_data = { 0 }; + struct wet_compositor wet = { 0 }; int require_input; int32_t wait_for_debugger = 0; @@ -1905,8 +1904,8 @@ int main(int argc, char *argv[]) if (load_configuration(&config, noconfig, config_file) < 0) goto out_signals; - user_data.config = config; - user_data.parsed_options = NULL; + wet.config = config; + wet.parsed_options = NULL; section = weston_config_get_section(config, "core", NULL, NULL); @@ -1927,28 +1926,27 @@ int main(int argc, char *argv[]) backend = weston_choose_default_backend(); } - ec = weston_compositor_create(display, &user_data); - user_data.compositor = ec; - if (ec == NULL) { + wet.compositor = weston_compositor_create(display, &wet); + if (wet.compositor == NULL) { weston_log("fatal: failed to create compositor\n"); goto out; } - segv_compositor = ec; + segv_compositor = wet.compositor; - if (weston_compositor_init_config(ec, config) < 0) + if (weston_compositor_init_config(wet.compositor, config) < 0) goto out; weston_config_section_get_bool(section, "require-input", &require_input, true); - ec->require_input = require_input; + wet.compositor->require_input = require_input; - if (load_backend(ec, backend, &argc, argv, config) < 0) { + if (load_backend(wet.compositor, backend, &argc, argv, config) < 0) { weston_log("fatal: failed to create compositor backend\n"); goto out; } - weston_compositor_flush_heads_changed(ec); - if (user_data.init_failed) + weston_compositor_flush_heads_changed(wet.compositor); + if (wet.init_failed) goto out; if (idle_time < 0) @@ -1956,11 +1954,11 @@ int main(int argc, char *argv[]) if (idle_time < 0) idle_time = 300; /* default idle timeout, in seconds */ - ec->idle_time = idle_time; - ec->default_pointer_grab = NULL; - ec->exit = handle_exit; + wet.compositor->idle_time = idle_time; + wet.compositor->default_pointer_grab = NULL; + wet.compositor->exit = handle_exit; - weston_compositor_log_capabilities(ec); + weston_compositor_log_capabilities(wet.compositor); server_socket = getenv("WAYLAND_SERVER_SOCKET"); if (server_socket) { @@ -1989,28 +1987,28 @@ int main(int argc, char *argv[]) weston_config_section_get_string(section, "shell", &shell, "desktop-shell.so"); - if (wet_load_shell(ec, shell, &argc, argv) < 0) + if (wet_load_shell(wet.compositor, shell, &argc, argv) < 0) goto out; weston_config_section_get_string(section, "modules", &modules, ""); - if (load_modules(ec, modules, &argc, argv, &xwayland) < 0) + if (load_modules(wet.compositor, modules, &argc, argv, &xwayland) < 0) goto out; - if (load_modules(ec, option_modules, &argc, argv, &xwayland) < 0) + if (load_modules(wet.compositor, option_modules, &argc, argv, &xwayland) < 0) goto out; if (!xwayland) weston_config_section_get_bool(section, "xwayland", &xwayland, false); if (xwayland) { - if (wet_load_xwayland(ec) < 0) + if (wet_load_xwayland(wet.compositor) < 0) goto out; } section = weston_config_get_section(config, "keyboard", NULL, NULL); weston_config_section_get_bool(section, "numlock-on", &numlock_on, 0); if (numlock_on) { - wl_list_for_each(seat, &ec->seat_list, link) { + wl_list_for_each(seat, &wet.compositor->seat_list, link) { struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); @@ -2026,7 +2024,7 @@ int main(int argc, char *argv[]) if (argc > 1) goto out; - weston_compositor_wake(ec); + weston_compositor_wake(wet.compositor); wl_display_run(display); @@ -2036,13 +2034,13 @@ int main(int argc, char *argv[]) * that want to indicate failure status to * testing infrastructure above */ - ret = ec->exit_code; + ret = wet.compositor->exit_code; out: /* free(NULL) is valid, and it won't be NULL if it's used */ - free(user_data.parsed_options); + free(wet.parsed_options); - weston_compositor_destroy(ec); + weston_compositor_destroy(wet.compositor); out_signals: for (i = ARRAY_LENGTH(signals) - 1; i >= 0; i--) From e7a52fbb7d86c6810346ae0f058fb44a84d0754e Mon Sep 17 00:00:00 2001 From: Semi Malinen Date: Thu, 26 Apr 2018 11:08:10 +0200 Subject: [PATCH 0484/1642] libweston: add weston_view_set_output() Instead of desktop shell assigning view outputs directly, use a new method, weston_view_set_output(). The method can set up an output destroy listener to make sure that views do not have stale output pointers. Without this patch it is possible to end up in a scenario where, e.g. configure_static_view() accesses memory that has already been freed. The scenario can be provoked by repeatedly plugging and unplugging a display. The faulty memory accesses are reported by valgrind. Signed-off-by: Semi Malinen Signed-off-by: Fabien Lahoudere Reviewed-by: Pekka Paalanen --- desktop-shell/shell.c | 11 +++++++---- libweston/compositor.c | 43 ++++++++++++++++++++++++++++++++++++++++-- libweston/compositor.h | 4 ++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index b846e305f..1e153cb53 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -580,7 +580,7 @@ create_focus_surface(struct weston_compositor *ec, free(fsurf); return NULL; } - fsurf->view->output = output; + weston_view_set_output(fsurf->view, output); fsurf->view->is_mapped = true; weston_surface_set_size(surface, output->width, output->height); @@ -2464,7 +2464,7 @@ map(struct desktop_shell *shell, struct shell_surface *shsurf, shsurf->view->is_mapped = true; if (shsurf->state.maximized) { surface->output = shsurf->output; - shsurf->view->output = shsurf->output; + weston_view_set_output(shsurf->view, shsurf->output); } if (!shell->locked) { @@ -2881,6 +2881,9 @@ configure_static_view(struct weston_view *ev, struct weston_layer *layer, int x, { struct weston_view *v, *next; + if (!ev->output) + return; + wl_list_for_each_safe(v, next, &layer->view_list.link, layer_link.link) { if (v->output == ev->output && v != ev) { weston_view_unmap(v); @@ -2970,7 +2973,7 @@ desktop_shell_set_background(struct wl_client *client, surface->committed_private = shell; weston_surface_set_label_func(surface, background_get_label); surface->output = weston_head_from_resource(output_resource)->output; - view->output = surface->output; + weston_view_set_output(view, surface->output); sh_output = find_shell_output_from_weston_output(shell, surface->output); if (sh_output->background_surface) { @@ -3067,7 +3070,7 @@ desktop_shell_set_panel(struct wl_client *client, surface->committed_private = shell; weston_surface_set_label_func(surface, panel_get_label); surface->output = weston_head_from_resource(output_resource)->output; - view->output = surface->output; + weston_view_set_output(view, surface->output); sh_output = find_shell_output_from_weston_output(shell, surface->output); if (sh_output->panel_surface) { diff --git a/libweston/compositor.c b/libweston/compositor.c index 747c55fa0..6a300e0dd 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -1022,6 +1022,45 @@ weston_surface_update_output_mask(struct weston_surface *es, uint32_t mask) } } +static void +notify_view_output_destroy(struct wl_listener *listener, void *data) +{ + struct weston_view *view = + container_of(listener, + struct weston_view, output_destroy_listener); + + view->output = NULL; + view->output_destroy_listener.notify = NULL; +} + +/** Set the primary output of the view + * + * \param view The view whose primary output to set + * \param output The new primary output for the view + * + * Set \a output to be the primary output of the \a view. + * + * Notice that the assignment may be temporary; the primary output could be + * automatically changed. Hence, one cannot rely on the value persisting. + * + * Passing NULL as /a output will set the primary output to NULL. + */ +WL_EXPORT void +weston_view_set_output(struct weston_view *view, struct weston_output *output) +{ + if (view->output_destroy_listener.notify) { + wl_list_remove(&view->output_destroy_listener.link); + view->output_destroy_listener.notify = NULL; + } + view->output = output; + if (output) { + view->output_destroy_listener.notify = + notify_view_output_destroy; + wl_signal_add(&output->destroy_signal, + &view->output_destroy_listener); + } +} + /** Recalculate which output(s) the surface has views displayed on * * \param es The surface to remap to outputs @@ -1113,7 +1152,7 @@ weston_view_assign_output(struct weston_view *ev) } pixman_region32_fini(®ion); - ev->output = new_output; + weston_view_set_output(ev, new_output); ev->output_mask = mask; weston_surface_assign_output(ev->surface); @@ -1823,7 +1862,7 @@ weston_view_unmap(struct weston_view *view) return; weston_view_damage_below(view); - view->output = NULL; + weston_view_set_output(view, NULL); view->plane = NULL; view->is_mapped = false; weston_layer_entry_remove(&view->layer_link); diff --git a/libweston/compositor.h b/libweston/compositor.h index 30041b7fe..47337d8a8 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1183,6 +1183,7 @@ struct weston_view { * view, inheriting the primary output for related views in shells, etc. */ struct weston_output *output; + struct wl_listener output_destroy_listener; /* * A more complete representation of all outputs this surface is @@ -1397,6 +1398,9 @@ enum weston_activate_flag { void weston_version(int *major, int *minor, int *micro); +void +weston_view_set_output(struct weston_view *view, struct weston_output *output); + void weston_view_update_transform(struct weston_view *view); From 99f8c085594dba25006fa29fbfb09b60105bbaf9 Mon Sep 17 00:00:00 2001 From: Semi Malinen Date: Wed, 2 May 2018 11:10:32 +0200 Subject: [PATCH 0485/1642] desktop-shell: detect stale shell surface outputs When displays are hot (un)plugged, it may happen that a shell surface is left with a stale pointer to an output that has already been freed. Add an output destroy listener to catch such situations and set the output pointer to NULL. Signed-off-by: Semi Malinen Signed-off-by: Fabien Lahoudere Reviewed-by: Pekka Paalanen --- desktop-shell/shell.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 1e153cb53..64db89fc2 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -128,6 +128,7 @@ struct shell_surface { struct weston_output *fullscreen_output; struct weston_output *output; + struct wl_listener output_destroy_listener; struct surface_state { bool fullscreen; @@ -1933,6 +1934,17 @@ shell_surface_update_layer(struct shell_surface *shsurf) shell_surface_update_child_surface_layers(shsurf); } +static void +notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct shell_surface *shsurf = + container_of(listener, + struct shell_surface, output_destroy_listener); + + shsurf->output = NULL; + shsurf->output_destroy_listener.notify = NULL; +} + static void shell_surface_set_output(struct shell_surface *shsurf, struct weston_output *output) @@ -1948,6 +1960,18 @@ shell_surface_set_output(struct shell_surface *shsurf, shsurf->output = es->output; else shsurf->output = get_default_output(es->compositor); + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + if (!shsurf->output) + return; + + shsurf->output_destroy_listener.notify = notify_output_destroy; + wl_signal_add(&shsurf->output->destroy_signal, + &shsurf->output_destroy_listener); } static void @@ -1986,7 +2010,7 @@ unset_maximized(struct shell_surface *shsurf) weston_desktop_surface_get_surface(shsurf->desktop_surface); /* undo all maximized things here */ - shsurf->output = get_default_output(surface->compositor); + shell_surface_set_output(shsurf, get_default_output(surface->compositor)); if (shsurf->saved_position_valid) weston_view_set_position(shsurf->view, @@ -2348,7 +2372,8 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, shsurf->fullscreen.black_view = NULL; wl_list_init(&shsurf->fullscreen.transform.link); - shsurf->output = get_default_output(shsurf->shell->compositor); + shell_surface_set_output( + shsurf, get_default_output(shsurf->shell->compositor)); wl_signal_init(&shsurf->destroy_signal); From 99628000cbb20113dae3e2bd8833e512286ad308 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 22 May 2018 12:48:35 +0300 Subject: [PATCH 0486/1642] desktop-shell: fix output destroy signal list corruption This issue was introduced by "desktop-shell: detect stale shell surface outputs" which forgot to remove the output destroy listener when shell_surface is destroyed, leading to memory corruption. This was fairly easy to trigger by opening and closing an application window a few times. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray --- desktop-shell/shell.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 64db89fc2..0e50c97b3 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2303,6 +2303,12 @@ fade_out_done_idle_cb(void *data) struct shell_surface *shsurf = data; weston_surface_destroy(shsurf->view->surface); + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + free(shsurf); } @@ -2420,6 +2426,12 @@ desktop_surface_removed(struct weston_desktop_surface *desktop_surface, fade_out_done, shsurf); } else { weston_view_destroy(shsurf->view); + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + free(shsurf); } } From 944fae8887642982473e1a541b8b37aef5c61d51 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 22 May 2018 13:15:58 +0300 Subject: [PATCH 0487/1642] libweston: fix output destroy signal list corruption This issue was likely introduced by "libweston: add weston_view_set_output()" which forgot to ensure the output destroy listener is removed when weston_view is destroyed, leading to freed memory being left into the list. This was quite easy to trigger by opening and closing an application window a few times, leading various memory corruption symptoms. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray --- libweston/compositor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index 6a300e0dd..619e88b3a 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -1931,6 +1931,7 @@ weston_view_destroy(struct weston_view *view) pixman_region32_fini(&view->transform.opaque); weston_view_set_transform_parent(view, NULL); + weston_view_set_output(view, NULL); wl_list_remove(&view->surface_link); From acf50c3d96a5ab7b393a064e10b6e4affbd7236f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 23 Apr 2018 11:44:56 +0200 Subject: [PATCH 0488/1642] pixman,drm: do not composite previous damage Pixman-renderer uses a single internal shadow buffer. It is enough to composite the current damage into shadow, but the copy to hw buffer needs to include the previous damage because of double-buffering in DRM-backend. This patch lets pixman-renderer do exactly that without compositing also the previous damage on DRM-renderer. Arguably weston_output should not have field previous_damage to begin with, because it implies double-buffering, which e.g. EGL does not guarantee. It would be better for each backend explicitly always provide any extra damage that should be copied to hw. Signed-off-by: Pekka Paalanen Signed-off-by: Fabien Lahoudere Reviewed-by: Ian Ray --- libweston/compositor-drm.c | 16 ++++------------ libweston/pixman-renderer.c | 35 ++++++++++++++++++++++++++++++----- libweston/pixman-renderer.h | 7 ++++++- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 287431eb8..52b5dd70c 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1673,25 +1673,17 @@ drm_output_render_pixman(struct drm_output_state *state, { struct drm_output *output = state->output; struct weston_compositor *ec = output->base.compositor; - pixman_region32_t total_damage, previous_damage; - - pixman_region32_init(&total_damage); - pixman_region32_init(&previous_damage); - - pixman_region32_copy(&previous_damage, damage); - - pixman_region32_union(&total_damage, damage, &output->previous_damage); - pixman_region32_copy(&output->previous_damage, &previous_damage); output->current_image ^= 1; pixman_renderer_output_set_buffer(&output->base, output->image[output->current_image]); + pixman_renderer_output_set_hw_extra_damage(&output->base, + &output->previous_damage); - ec->renderer->repaint_output(&output->base, &total_damage); + ec->renderer->repaint_output(&output->base, damage); - pixman_region32_fini(&total_damage); - pixman_region32_fini(&previous_damage); + pixman_region32_copy(&output->previous_damage, damage); return drm_fb_ref(output->dumb[output->current_image]); } diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c index f7366cf60..cf43b15d2 100644 --- a/libweston/pixman-renderer.c +++ b/libweston/pixman-renderer.c @@ -41,6 +41,7 @@ struct pixman_output_state { void *shadow_buffer; pixman_image_t *shadow_image; pixman_image_t *hw_buffer; + pixman_region32_t *hw_extra_damage; }; struct pixman_surface_state { @@ -555,15 +556,29 @@ copy_to_hw_buffer(struct weston_output *output, pixman_region32_t *region) static void pixman_renderer_repaint_output(struct weston_output *output, - pixman_region32_t *output_damage) + pixman_region32_t *output_damage) { struct pixman_output_state *po = get_output_state(output); + pixman_region32_t hw_damage; - if (!po->hw_buffer) - return; + if (!po->hw_buffer) { + po->hw_extra_damage = NULL; + return; + } + + pixman_region32_init(&hw_damage); + if (po->hw_extra_damage) { + pixman_region32_union(&hw_damage, + po->hw_extra_damage, output_damage); + po->hw_extra_damage = NULL; + } else { + pixman_region32_copy(&hw_damage, output_damage); + } repaint_surfaces(output, output_damage); - copy_to_hw_buffer(output, output_damage); + + copy_to_hw_buffer(output, &hw_damage); + pixman_region32_fini(&hw_damage); pixman_region32_copy(&output->previous_damage, output_damage); wl_signal_emit(&output->frame_signal, output); @@ -862,7 +877,8 @@ pixman_renderer_init(struct weston_compositor *ec) } WL_EXPORT void -pixman_renderer_output_set_buffer(struct weston_output *output, pixman_image_t *buffer) +pixman_renderer_output_set_buffer(struct weston_output *output, + pixman_image_t *buffer) { struct pixman_output_state *po = get_output_state(output); @@ -876,6 +892,15 @@ pixman_renderer_output_set_buffer(struct weston_output *output, pixman_image_t * } } +WL_EXPORT void +pixman_renderer_output_set_hw_extra_damage(struct weston_output *output, + pixman_region32_t *extra_damage) +{ + struct pixman_output_state *po = get_output_state(output); + + po->hw_extra_damage = extra_damage; +} + WL_EXPORT int pixman_renderer_output_create(struct weston_output *output) { diff --git a/libweston/pixman-renderer.h b/libweston/pixman-renderer.h index 1b42f14ff..f19e14779 100644 --- a/libweston/pixman-renderer.h +++ b/libweston/pixman-renderer.h @@ -34,7 +34,12 @@ int pixman_renderer_output_create(struct weston_output *output); void -pixman_renderer_output_set_buffer(struct weston_output *output, pixman_image_t *buffer); +pixman_renderer_output_set_buffer(struct weston_output *output, + pixman_image_t *buffer); + +void +pixman_renderer_output_set_hw_extra_damage(struct weston_output *output, + pixman_region32_t *extra_damage); void pixman_renderer_output_destroy(struct weston_output *output); From 26ded94aa0969f826b296cde3a40deb1871811de Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 23 Apr 2018 11:44:57 +0200 Subject: [PATCH 0489/1642] pixman: make shadow buffer optional Add a flag to pixman-renderer for initializing the output with a shadow framebuffer. All backends were getting the shadow implcitly, so all backends are modified to ask for the shadow explicitly. Using a shadow buffer is usually beneficial, because read-modify-write cycles (blending) into a scanout-capable buffer may be very slow. The scanout framebuffer may also have reduced color depth, making blending and read-back produce inferior results. In some use cases though the shadow buffer might be just an extra copy hurting more than it helps. Whether it helps or hurts depends on the platform and the workload. Therefore let the backends control whether pixman-renderer uses a shadow buffer for an output or not. Signed-off-by: Pekka Paalanen Signed-off-by: Fabien Lahoudere Reviewed-by: Ian Ray --- libweston/compositor-drm.c | 3 +- libweston/compositor-fbdev.c | 3 +- libweston/compositor-headless.c | 3 +- libweston/compositor-rdp.c | 5 ++- libweston/compositor-wayland.c | 3 +- libweston/compositor-x11.c | 6 ++- libweston/pixman-renderer.c | 68 +++++++++++++++++++-------------- libweston/pixman-renderer.h | 6 ++- 8 files changed, 60 insertions(+), 37 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 52b5dd70c..bcf8962f5 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4109,7 +4109,8 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) goto err; } - if (pixman_renderer_output_create(&output->base) < 0) + if (pixman_renderer_output_create(&output->base, + PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) goto err; pixman_region32_init_rect(&output->previous_damage, diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index de40c2191..a78f6fab6 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -511,7 +511,8 @@ fbdev_output_enable(struct weston_output *base) output->base.start_repaint_loop = fbdev_output_start_repaint_loop; output->base.repaint = fbdev_output_repaint; - if (pixman_renderer_output_create(&output->base) < 0) + if (pixman_renderer_output_create(&output->base, + PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) goto out_hw_surface; loop = wl_display_get_event_loop(backend->compositor->wl_display); diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index 6cb4f2066..61a5bd93e 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -172,7 +172,8 @@ headless_output_enable(struct weston_output *base) output->image_buf, output->base.current_mode->width * 4); - if (pixman_renderer_output_create(&output->base) < 0) + if (pixman_renderer_output_create(&output->base, + PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) goto err_renderer; pixman_renderer_output_set_buffer(&output->base, diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 693f136a0..fd0651afb 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -460,7 +460,7 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) output->current_mode->flags |= WL_OUTPUT_MODE_CURRENT; pixman_renderer_output_destroy(output); - pixman_renderer_output_create(output); + pixman_renderer_output_create(output, PIXMAN_RENDERER_OUTPUT_USE_SHADOW); new_shadow_buffer = pixman_image_create_bits(PIXMAN_x8r8g8b8, target_mode->width, target_mode->height, 0, target_mode->width * 4); @@ -546,7 +546,8 @@ rdp_output_enable(struct weston_output *base) return -1; } - if (pixman_renderer_output_create(&output->base) < 0) { + if (pixman_renderer_output_create(&output->base, + PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) { pixman_image_unref(output->shadow_surface); return -1; } diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 2473f0879..56ed474a5 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -782,7 +782,8 @@ wayland_output_init_gl_renderer(struct wayland_output *output) static int wayland_output_init_pixman_renderer(struct wayland_output *output) { - return pixman_renderer_output_create(&output->base); + return pixman_renderer_output_create(&output->base, + PIXMAN_RENDERER_OUTPUT_USE_SHADOW); } static void diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 78b0d7c60..4a9d068f6 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -849,7 +849,8 @@ x11_output_switch_mode(struct weston_output *base, struct weston_mode *mode) return -1; } - if (pixman_renderer_output_create(&output->base) < 0) { + if (pixman_renderer_output_create(&output->base, + PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) { weston_log("Failed to create pixman renderer for output\n"); x11_output_deinit_shm(b, output); return -1; @@ -1021,7 +1022,8 @@ x11_output_enable(struct weston_output *base) weston_log("Failed to initialize SHM for the X11 output\n"); goto err; } - if (pixman_renderer_output_create(&output->base) < 0) { + if (pixman_renderer_output_create(&output->base, + PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) { weston_log("Failed to create pixman renderer for output\n"); x11_output_deinit_shm(b, output); goto err; diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c index cf43b15d2..1ea275b49 100644 --- a/libweston/pixman-renderer.c +++ b/libweston/pixman-renderer.c @@ -337,13 +337,19 @@ repaint_region(struct weston_view *ev, struct weston_output *output, struct pixman_surface_state *ps = get_surface_state(ev->surface); struct pixman_output_state *po = get_output_state(output); struct weston_buffer_viewport *vp = &ev->surface->buffer_viewport; + pixman_image_t *target_image; pixman_transform_t transform; pixman_filter_t filter; pixman_image_t *mask_image; pixman_color_t mask = { 0, }; - /* Clip rendering to the damaged output region */ - pixman_image_set_clip_region32(po->shadow_image, repaint_output); + if (po->shadow_image) + target_image = po->shadow_image; + else + target_image = po->hw_buffer; + + /* Clip rendering to the damaged output region */ + pixman_image_set_clip_region32(target_image, repaint_output); pixman_renderer_compute_transform(&transform, ev, output); @@ -363,11 +369,11 @@ repaint_region(struct weston_view *ev, struct weston_output *output, } if (source_clip) - composite_clipped(ps->image, mask_image, po->shadow_image, + composite_clipped(ps->image, mask_image, target_image, &transform, filter, source_clip); else composite_whole(pixman_op, ps->image, mask_image, - po->shadow_image, &transform, filter); + target_image, &transform, filter); if (mask_image) pixman_image_unref(mask_image); @@ -379,14 +385,14 @@ repaint_region(struct weston_view *ev, struct weston_output *output, pixman_image_composite32(PIXMAN_OP_OVER, pr->debug_color, /* src */ NULL /* mask */, - po->shadow_image, /* dest */ + target_image, /* dest */ 0, 0, /* src_x, src_y */ 0, 0, /* mask_x, mask_y */ 0, 0, /* dest_x, dest_y */ - pixman_image_get_width (po->shadow_image), /* width */ - pixman_image_get_height (po->shadow_image) /* height */); + pixman_image_get_width (target_image), /* width */ + pixman_image_get_height (target_image) /* height */); - pixman_image_set_clip_region32 (po->shadow_image, NULL); + pixman_image_set_clip_region32(target_image, NULL); } static void @@ -575,9 +581,12 @@ pixman_renderer_repaint_output(struct weston_output *output, pixman_region32_copy(&hw_damage, output_damage); } - repaint_surfaces(output, output_damage); - - copy_to_hw_buffer(output, &hw_damage); + if (po->shadow_image) { + repaint_surfaces(output, output_damage); + copy_to_hw_buffer(output, &hw_damage); + } else { + repaint_surfaces(output, &hw_damage); + } pixman_region32_fini(&hw_damage); pixman_region32_copy(&output->previous_damage, output_damage); @@ -902,7 +911,7 @@ pixman_renderer_output_set_hw_extra_damage(struct weston_output *output, } WL_EXPORT int -pixman_renderer_output_create(struct weston_output *output) +pixman_renderer_output_create(struct weston_output *output, uint32_t flags) { struct pixman_output_state *po; int w, h; @@ -911,25 +920,27 @@ pixman_renderer_output_create(struct weston_output *output) if (po == NULL) return -1; - /* set shadow image transformation */ - w = output->current_mode->width; - h = output->current_mode->height; + if (flags & PIXMAN_RENDERER_OUTPUT_USE_SHADOW) { + /* set shadow image transformation */ + w = output->current_mode->width; + h = output->current_mode->height; - po->shadow_buffer = malloc(w * h * 4); + po->shadow_buffer = malloc(w * h * 4); - if (!po->shadow_buffer) { - free(po); - return -1; - } + if (!po->shadow_buffer) { + free(po); + return -1; + } - po->shadow_image = - pixman_image_create_bits(PIXMAN_x8r8g8b8, w, h, - po->shadow_buffer, w * 4); + po->shadow_image = + pixman_image_create_bits(PIXMAN_x8r8g8b8, w, h, + po->shadow_buffer, w * 4); - if (!po->shadow_image) { - free(po->shadow_buffer); - free(po); - return -1; + if (!po->shadow_image) { + free(po->shadow_buffer); + free(po); + return -1; + } } output->renderer_state = po; @@ -942,7 +953,8 @@ pixman_renderer_output_destroy(struct weston_output *output) { struct pixman_output_state *po = get_output_state(output); - pixman_image_unref(po->shadow_image); + if (po->shadow_image) + pixman_image_unref(po->shadow_image); if (po->hw_buffer) pixman_image_unref(po->hw_buffer); diff --git a/libweston/pixman-renderer.h b/libweston/pixman-renderer.h index f19e14779..7a5f72901 100644 --- a/libweston/pixman-renderer.h +++ b/libweston/pixman-renderer.h @@ -30,8 +30,12 @@ int pixman_renderer_init(struct weston_compositor *ec); +enum pixman_renderer_output_flags { + PIXMAN_RENDERER_OUTPUT_USE_SHADOW = (1 << 0), +}; + int -pixman_renderer_output_create(struct weston_output *output); +pixman_renderer_output_create(struct weston_output *output, uint32_t flags); void pixman_renderer_output_set_buffer(struct weston_output *output, From dee412d174e3180ec8999c9d473b36ff2118abcf Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 23 Apr 2018 11:44:58 +0200 Subject: [PATCH 0490/1642] compositor-drm: expose global shadow flag for pixman Allow global control of the pixman shadow buffers. The compositor can choose whether all output use or do not use a shadow buffer with the pixman renderer. The option is added to the end of struct weston_drm_backend_config to avoid bumping WESTON_DRM_BACKEND_CONFIG_VERSION. Signed-off-by: Pekka Paalanen Signed-off-by: Fabien Lahoudere Reviewed-by: Ian Ray --- libweston/compositor-drm.c | 15 ++++++++++++--- libweston/compositor-drm.h | 3 +++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index bcf8962f5..8b1ea66d9 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -263,6 +263,7 @@ struct drm_backend { bool atomic_modeset; int use_pixman; + bool use_pixman_shadow; struct udev_input input; @@ -4082,6 +4083,7 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) uint32_t format = output->gbm_format; uint32_t pixman_format; unsigned int i; + uint32_t flags = 0; switch (format) { case GBM_FORMAT_XRGB8888: @@ -4109,9 +4111,14 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) goto err; } - if (pixman_renderer_output_create(&output->base, - PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) - goto err; + if (b->use_pixman_shadow) + flags |= PIXMAN_RENDERER_OUTPUT_USE_SHADOW; + + if (pixman_renderer_output_create(&output->base, flags) < 0) + goto err; + + weston_log("DRM: output %s %s shadow framebuffer.\n", output->base.name, + b->use_pixman_shadow ? "uses" : "does not use"); pixman_region32_init_rect(&output->previous_damage, output->base.x, output->base.y, output->base.width, output->base.height); @@ -6048,6 +6055,7 @@ drm_backend_create(struct weston_compositor *compositor, b->compositor = compositor; b->use_pixman = config->use_pixman; b->pageflip_timeout = config->pageflip_timeout; + b->use_pixman_shadow = config->use_pixman_shadow; compositor->backend = &b->base; @@ -6207,6 +6215,7 @@ drm_backend_create(struct weston_compositor *compositor, static void config_init_to_defaults(struct weston_drm_backend_config *config) { + config->use_pixman_shadow = true; } WL_EXPORT int diff --git a/libweston/compositor-drm.h b/libweston/compositor-drm.h index 68f93eabc..532222938 100644 --- a/libweston/compositor-drm.h +++ b/libweston/compositor-drm.h @@ -146,6 +146,9 @@ struct weston_drm_backend_config { * based on seat names and boot_vga to find the right device. */ char *specific_device; + + /** Use shadow buffer if using Pixman-renderer. */ + bool use_pixman_shadow; }; #ifdef __cplusplus From 325ff4cba1e9c9bc15c36b960cf892c7633ce4dd Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 23 Apr 2018 11:44:59 +0200 Subject: [PATCH 0491/1642] main: add setting for DRM/pixman shadow framebuffer Allows to control the Pixman-renderer shadow framebuffer usage from weston.ini. It defaults to enabled, and whether it is a good idea to disable or not depends on the platform and the workload. Signed-off-by: Pekka Paalanen Signed-off-by: Fabien Lahoudere Reviewed-by: Ian Ray --- compositor/main.c | 3 +++ man/weston-drm.man | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index 54fa312f6..1092204f8 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1286,6 +1286,7 @@ load_drm_backend(struct weston_compositor *c, struct weston_drm_backend_config config = {{ 0, }}; struct weston_config_section *section; struct wet_compositor *wet = to_wet_compositor(c); + int use_shadow; int ret = 0; wet->drm_use_current_mode = false; @@ -1306,6 +1307,8 @@ load_drm_backend(struct weston_compositor *c, NULL); weston_config_section_get_uint(section, "pageflip-timeout", &config.pageflip_timeout, 0); + weston_config_section_get_bool(section, "pixman-shadow", &use_shadow, 1); + config.use_pixman_shadow = use_shadow; config.base.struct_version = WESTON_DRM_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_drm_backend_config); diff --git a/man/weston-drm.man b/man/weston-drm.man index 6518563ad..d4cb75a77 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -79,6 +79,10 @@ Transform for the output, which can be rotated in 90-degree steps and possibly flipped. Possible values are .BR normal ", " 90 ", " 180 ", " 270 ", " .BR flipped ", " flipped-90 ", " flipped-180 ", and " flipped-270 . +.TP +\fBpixman-shadow\fR=\fIboolean\fR +If using the Pixman-renderer, use shadow framebuffers. Defaults to +.BR true . . .\" *************************************************************** .SH OPTIONS From 99372bab4ce6555526edbf2c8ac114ac3c8acdfb Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 2 May 2018 10:21:55 +0200 Subject: [PATCH 0492/1642] desktop-shell: handle NULL output in get_output_work_area() This is a tentative crash fix for a case where there are no enabled weston_outputs at all. Let get_output_work_area() return a zero area if the given output is NULL. If there is no output, there is no area. Unfortunately we cannot return "no position" but have to use 0,0 instead. In send_configure_for_surface(), this causes a maximized surface to receive width=0 and height=0 in the configure event, which means the client is free to choose the size. There is no correct size to send for maximizing for no output. In constrain_position(), this has no effect. The interactive move of a surface is restricted to not go below the panel, so even if a user managed to move a surface without an output, it just prevents the surface moving beyond y=0. In weston_view_set_initial_position(), get_output_work_area() will not be called with NULL output anyway. In set_maximized_position(), this makes it behave as if the output was at 0,0 which is the default position of the first output. Signed-off-by: Pekka Paalanen Signed-off-by: Fabien Lahoudere Reviewed-by: Ian Ray --- desktop-shell/shell.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 0e50c97b3..386177c32 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -342,6 +342,15 @@ get_output_work_area(struct desktop_shell *shell, { int32_t panel_width = 0, panel_height = 0; + if (!output) { + area->x = 0; + area->y = 0; + area->width = 0; + area->height = 0; + + return; + } + area->x = output->x; area->y = output->y; From 30aa59759ae9a3629c5f4e6a1d9532c41620328b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 2 May 2018 10:21:56 +0200 Subject: [PATCH 0493/1642] desktop-shell: handle NULL output in center_on_output() This is a tentative crash fix for a case where there are no enabled weston_outputs at all. If no output is given, just put the surface at 0,0. At least it should become mostly visible if an output is plugged in, if not centered. Signed-off-by: Pekka Paalanen Signed-off-by: Fabien Lahoudere Reviewed-by: Ian Ray --- desktop-shell/shell.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 386177c32..f87151d9a 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -4190,6 +4190,11 @@ center_on_output(struct weston_view *view, struct weston_output *output) int32_t surf_x, surf_y, width, height; float x, y; + if (!output) { + weston_view_set_position(view, 0, 0); + return; + } + surface_subsurfaces_boundingbox(view->surface, &surf_x, &surf_y, &width, &height); x = output->x + (output->width - width) / 2 - surf_x / 2; From 87860c20ee51ccaff97849eb1e483b4d52ac22bc Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 2 May 2018 10:21:57 +0200 Subject: [PATCH 0494/1642] desktop-shell: do not lower_fullscreen_layer(s, NULL) In activate, do not call lower_fullscreen_layer() at all if the output is NULL. It should not do anything in that case, per the existing comment. This is a tentative crash fix for a case where there are no enabled weston_outputs at all. Signed-off-by: Pekka Paalanen Signed-off-by: Fabien Lahoudere Reviewed-by: Ian Ray --- desktop-shell/shell.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index f87151d9a..fb2d5e853 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -3775,7 +3775,8 @@ activate(struct desktop_shell *shell, struct weston_view *view, /* Only demote fullscreen surfaces on the output of activated shsurf. * Leave fullscreen surfaces on unrelated outputs alone. */ - lower_fullscreen_layer(shell, shsurf->output); + if (shsurf->output) + lower_fullscreen_layer(shell, shsurf->output); weston_view_activate(view, seat, flags); From c63acc4cb891e6b89f76ceddfda0a49cd8bda2bd Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 2 May 2018 10:21:58 +0200 Subject: [PATCH 0495/1642] desktop-shell: survive NULL output in shell_configure_fullscreen() Running 'weston-simple-egl -f -b' (fullscreen, unthrottled) caused a crash in shell_ensure_fullscreen_black_view() due to shsurf->fullscreen_output being NULL. Also shell_configure_fullscreen() could crash on that condition. Fix shell_configure_fullscreen() to bail out with minimal work if there is no fullscreen_output. It is unclear if anything will cause a reconfiguration when an output is plugged in. Signed-off-by: Pekka Paalanen Signed-off-by: Fabien Lahoudere Reviewed-by: Ian Ray --- desktop-shell/shell.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index fb2d5e853..42fc27c6a 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2176,6 +2176,13 @@ shell_configure_fullscreen(struct shell_surface *shsurf) weston_layer_entry_insert(&shsurf->shell->fullscreen_layer.view_list, &shsurf->view->layer_link); + if (!shsurf->fullscreen_output) { + /* If there is no output, there's not much we can do. + * Position the window somewhere, whatever. */ + weston_view_set_position(shsurf->view, 0, 0); + return; + } + shell_ensure_fullscreen_black_view(shsurf); surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y, From b0a8317bcbba05d521aeb6227389c7826eaae8a3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 4 May 2018 12:53:54 +0200 Subject: [PATCH 0496/1642] xwm: dump properties of type CARDINAL Add code to dump CARDINAL arrays from properties. Useful for debugging. Signed-off-by: Pekka Paalanen Signed-off-by: Fabien Lahoudere Reviewed-by: Ian Ray --- xwayland/window-manager.c | 66 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 06370b70f..061ce1741 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -377,6 +378,67 @@ xcb_cursor_library_load_cursor(struct weston_wm *wm, const char *file) return cursor; } +static unsigned +dump_cardinal_array_elem(FILE *fp, unsigned format, + void *arr, unsigned len, unsigned ind) +{ + const char *comma; + + /* If more than 16 elements, print 0-14, ..., last */ + if (ind > 14 && ind < len - 1) { + fprintf(fp, ", ..."); + return len - 1; + } + + comma = ind ? ", " : ""; + + switch (format) { + case 32: + fprintf(fp, "%s%" PRIu32, comma, ((uint32_t *)arr)[ind]); + break; + case 16: + fprintf(fp, "%s%" PRIu16, comma, ((uint16_t *)arr)[ind]); + break; + case 8: + fprintf(fp, "%s%" PRIu8, comma, ((uint8_t *)arr)[ind]); + break; + default: + fprintf(fp, "%s???", comma); + } + + return ind + 1; +} + +static void +dump_cardinal_array(xcb_get_property_reply_t *reply) +{ + unsigned i = 0; + FILE *fp; + void *arr; + char *str = NULL; + size_t size = 0; + + assert(reply->type == XCB_ATOM_CARDINAL); + + fp = open_memstream(&str, &size); + if (!fp) + return; + + arr = xcb_get_property_value(reply); + + fprintf(fp, "["); + while (i < reply->value_len) + i = dump_cardinal_array_elem(fp, reply->format, + arr, reply->value_len, i); + fprintf(fp, "]"); + + if (fclose(fp) != 0) + return; + + wm_log_continue("%s\n", str); + free(str); +} + void dump_property(struct weston_wm *wm, xcb_atom_t property, xcb_get_property_reply_t *reply) @@ -403,7 +465,7 @@ dump_property(struct weston_wm *wm, incr_value = xcb_get_property_value(reply); wm_log_continue("%d\n", *incr_value); } else if (reply->type == wm->atom.utf8_string || - reply->type == wm->atom.string) { + reply->type == wm->atom.string) { text_value = xcb_get_property_value(reply); if (reply->value_len > 40) len = 40; @@ -424,6 +486,8 @@ dump_property(struct weston_wm *wm, width += wm_log_continue("%s", name); } wm_log_continue("\n"); + } else if (reply->type == XCB_ATOM_CARDINAL) { + dump_cardinal_array(reply); } else { wm_log_continue("huh?\n"); } From e0e39b66edff8a86d1e03c102ec9027da79a33cf Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 4 May 2018 12:53:55 +0200 Subject: [PATCH 0497/1642] xwm: dump properties of type WINDOW Very useful for TRANSIENT_FOR property debugging. Signed-off-by: Pekka Paalanen Signed-off-by: Fabien Lahoudere Reviewed-by: Ian Ray --- xwayland/window-manager.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 061ce1741..2b3defb70 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -446,6 +446,7 @@ dump_property(struct weston_wm *wm, int32_t *incr_value; const char *text_value, *name; xcb_atom_t *atom_value; + xcb_window_t *window_value; int width, len; uint32_t i; @@ -488,6 +489,9 @@ dump_property(struct weston_wm *wm, wm_log_continue("\n"); } else if (reply->type == XCB_ATOM_CARDINAL) { dump_cardinal_array(reply); + } else if (reply->type == XCB_ATOM_WINDOW && reply->format == 32) { + window_value = xcb_get_property_value(reply); + wm_log_continue("win %u\n", *window_value); } else { wm_log_continue("huh?\n"); } From 029583e56e6eaad4139e39b4cf908158ab8cc91a Mon Sep 17 00:00:00 2001 From: David Fort Date: Sun, 27 May 2018 23:56:43 +0200 Subject: [PATCH 0498/1642] rdp-compositor: fix compilation against FreeRDP 2.0.0 rc2 The SURFACE_BITS_COMMAND struct has changed and some members have been moved in the bmp field. Reviewed-by: Pekka Paalanen Tested-by: Pekka Paalanen --- configure.ac | 9 ++++- libweston/compositor-rdp.c | 69 ++++++++++++++++++++++++-------------- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/configure.ac b/configure.ac index da3f73424..1dce05fa2 100644 --- a/configure.ac +++ b/configure.ac @@ -264,10 +264,17 @@ if test x$enable_rdp_compositor = xyes; then [], [PKG_CHECK_MODULES(RDP_COMPOSITOR, [freerdp >= 1.1.0],[])] ) - SAVED_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $RDP_COMPOSITOR_CFLAGS" + AC_CHECK_HEADERS([freerdp/version.h]) + AC_CHECK_MEMBER([SURFACE_BITS_COMMAND.bmp], + [AC_DEFINE([HAVE_SURFACE_BITS_BMP], [1], [SURFACE_BITS_CMD has bmp field])], + [], + [[#include ]] + ) + + CPPFLAGS="$SAVED_CPPFLAGS" fi diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index fd0651afb..134e7298f 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -66,6 +66,22 @@ #define FREERDP_CB_RETURN(V) return TRUE #endif +#ifdef HAVE_SURFACE_BITS_BMP +#define SURFACE_BPP(cmd) cmd->bmp.bpp +#define SURFACE_CODECID(cmd) cmd->bmp.codecID +#define SURFACE_WIDTH(cmd) cmd->bmp.width +#define SURFACE_HEIGHT(cmd) cmd->bmp.height +#define SURFACE_BITMAP_DATA(cmd) cmd->bmp.bitmapData +#define SURFACE_BITMAP_DATA_LEN(cmd) cmd->bmp.bitmapDataLength +#else +#define SURFACE_BPP(cmd) cmd->bpp +#define SURFACE_CODECID(cmd) cmd->codecID +#define SURFACE_WIDTH(cmd) cmd->width +#define SURFACE_HEIGHT(cmd) cmd->height +#define SURFACE_BITMAP_DATA(cmd) cmd->bitmapData +#define SURFACE_BITMAP_DATA_LEN(cmd) cmd->bitmapDataLength +#endif + #include #include #include @@ -200,10 +216,10 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p cmd->destTop = damage->extents.y1; cmd->destRight = damage->extents.x2; cmd->destBottom = damage->extents.y2; - cmd->bpp = 32; - cmd->codecID = peer->settings->RemoteFxCodecId; - cmd->width = width; - cmd->height = height; + SURFACE_BPP(cmd) = 32; + SURFACE_CODECID(cmd) = peer->settings->RemoteFxCodecId; + SURFACE_WIDTH(cmd) = width; + SURFACE_HEIGHT(cmd) = height; ptr = pixman_image_get_data(image) + damage->extents.x1 + damage->extents.y1 * (pixman_image_get_stride(image) / sizeof(uint32_t)); @@ -226,8 +242,8 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p pixman_image_get_stride(image) ); - cmd->bitmapDataLength = Stream_GetPosition(context->encode_stream); - cmd->bitmapData = Stream_Buffer(context->encode_stream); + SURFACE_BITMAP_DATA_LEN(cmd) = Stream_GetPosition(context->encode_stream); + SURFACE_BITMAP_DATA(cmd) = Stream_Buffer(context->encode_stream); update->SurfaceBits(update->context, cmd); } @@ -253,23 +269,26 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p #else memset(cmd, 0, sizeof(*cmd)); #endif + cmd->destLeft = damage->extents.x1; cmd->destTop = damage->extents.y1; cmd->destRight = damage->extents.x2; cmd->destBottom = damage->extents.y2; - cmd->bpp = 32; - cmd->codecID = peer->settings->NSCodecId; - cmd->width = width; - cmd->height = height; + SURFACE_BPP(cmd) = 32; + SURFACE_CODECID(cmd) = peer->settings->NSCodecId; + SURFACE_WIDTH(cmd) = width; + SURFACE_HEIGHT(cmd) = height; ptr = pixman_image_get_data(image) + damage->extents.x1 + damage->extents.y1 * (pixman_image_get_stride(image) / sizeof(uint32_t)); nsc_compose_message(context->nsc_context, context->encode_stream, (BYTE *)ptr, - cmd->width, cmd->height, + width, height, pixman_image_get_stride(image)); - cmd->bitmapDataLength = Stream_GetPosition(context->encode_stream); - cmd->bitmapData = Stream_Buffer(context->encode_stream); + + SURFACE_BITMAP_DATA_LEN(cmd) = Stream_GetPosition(context->encode_stream); + SURFACE_BITMAP_DATA(cmd) = Stream_Buffer(context->encode_stream); + update->SurfaceBits(update->context, cmd); } @@ -306,16 +325,16 @@ rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_p update->SurfaceFrameMarker(peer->context, marker); memset(cmd, 0, sizeof(*cmd)); - cmd->bpp = 32; - cmd->codecID = 0; + SURFACE_BPP(cmd) = 32; + SURFACE_CODECID(cmd) = 0; for (i = 0; i < nrects; i++, rect++) { /*weston_log("rect(%d,%d, %d,%d)\n", rect->x1, rect->y1, rect->x2, rect->y2);*/ cmd->destLeft = rect->x1; cmd->destRight = rect->x2; - cmd->width = rect->x2 - rect->x1; + SURFACE_WIDTH(cmd) = rect->x2 - rect->x1; - heightIncrement = peer->settings->MultifragMaxRequestSize / (16 + cmd->width * 4); + heightIncrement = peer->settings->MultifragMaxRequestSize / (16 + SURFACE_WIDTH(cmd) * 4); remainingHeight = rect->y2 - rect->y1; top = rect->y1; @@ -323,21 +342,21 @@ rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_p subrect.x2 = rect->x2; while (remainingHeight) { - cmd->height = (remainingHeight > heightIncrement) ? heightIncrement : remainingHeight; + SURFACE_HEIGHT(cmd) = (remainingHeight > heightIncrement) ? heightIncrement : remainingHeight; cmd->destTop = top; - cmd->destBottom = top + cmd->height; - cmd->bitmapDataLength = cmd->width * cmd->height * 4; - cmd->bitmapData = (BYTE *)realloc(cmd->bitmapData, cmd->bitmapDataLength); + cmd->destBottom = top + SURFACE_HEIGHT(cmd); + SURFACE_BITMAP_DATA_LEN(cmd) = SURFACE_WIDTH(cmd) * SURFACE_HEIGHT(cmd) * 4; + SURFACE_BITMAP_DATA(cmd) = (BYTE *)realloc(SURFACE_BITMAP_DATA(cmd), SURFACE_BITMAP_DATA_LEN(cmd)); subrect.y1 = top; - subrect.y2 = top + cmd->height; - pixman_image_flipped_subrect(&subrect, image, cmd->bitmapData); + subrect.y2 = top + SURFACE_HEIGHT(cmd); + pixman_image_flipped_subrect(&subrect, image, SURFACE_BITMAP_DATA(cmd)); /*weston_log("* sending (%d,%d, %d,%d)\n", subrect.x1, subrect.y1, subrect.x2, subrect.y2); */ update->SurfaceBits(peer->context, cmd); - remainingHeight -= cmd->height; - top += cmd->height; + remainingHeight -= SURFACE_HEIGHT(cmd); + top += SURFACE_HEIGHT(cmd); } } From 324cdf294d05c7f2e97827a7655084f7baa1365e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 20 Mar 2018 09:41:58 +0100 Subject: [PATCH 0499/1642] simple-dmabuf-drm: 0 is a valid fd (freedreno) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: Eric Engestrom Signed-off-by: Guido Günther Reviewed-by: Pekka Paalanen --- clients/simple-dmabuf-drm.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 2975f3a5e..19e8dbb10 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -244,10 +244,7 @@ static int fd_bo_export_to_prime(struct buffer *buf) { buf->dmabuf_fd = fd_bo_dmabuf(buf->fd_bo); - if (buf->dmabuf_fd > 0) - return 0; - - return 1; + return buf->dmabuf_fd < 0; } static int From f9f5953b2defb9e51f0a97a96e57fb10d829b6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 20 Mar 2018 09:41:59 +0100 Subject: [PATCH 0500/1642] simple-dmabuf-drm: simplify fd_map_bo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Günther Reviewed-by: Pekka Paalanen --- clients/simple-dmabuf-drm.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 19e8dbb10..cb04622f7 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -251,11 +251,7 @@ static int fd_map_bo(struct buffer *buf) { buf->mmap = fd_bo_map(buf->fd_bo); - - if (buf->mmap != NULL) - return 1; - - return 0; + return buf->mmap != NULL; } static void From bff906304fc3175daad08072a14cda1002c3810f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 20 Mar 2018 09:42:00 +0100 Subject: [PATCH 0501/1642] simple-dmabuf-drm: support etnaviv drm as well MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: Derek Foreman Signed-off-by: Guido Günther Reviewed-by: Pekka Paalanen --- Makefile.am | 1 + clients/simple-dmabuf-drm.c | 68 +++++++++++++++++++++++++++++++++++++ configure.ac | 5 ++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 69ca6cba7..64a8006c5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -629,6 +629,7 @@ nodist_weston_simple_dmabuf_drm_SOURCES = \ weston_simple_dmabuf_drm_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_DRM_CLIENT_CFLAGS) weston_simple_dmabuf_drm_LDADD = $(SIMPLE_DMABUF_DRM_CLIENT_LIBS) \ $(LIBDRM_PLATFORM_FREEDRENO_LIBS) \ + $(LIBDRM_PLATFORM_ETNAVIV_LIBS) \ $(LIBDRM_PLATFORM_INTEL_LIBS) \ libshared.la endif diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index cb04622f7..e7a7b0ed8 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -39,6 +39,7 @@ #include #include #include +#include #include @@ -49,6 +50,9 @@ #ifdef HAVE_LIBDRM_FREEDRENO #include #endif +#ifdef HAVE_LIBDRM_ETNAVIV +#include +#endif #include #include @@ -108,6 +112,10 @@ struct buffer { struct fd_device *fd_dev; struct fd_bo *fd_bo; #endif /* HAVE_LIBDRM_FREEDRENO */ +#if HAVE_LIBDRM_ETNAVIV + struct etna_device *etna_dev; + struct etna_bo *etna_bo; +#endif /* HAVE_LIBDRM_ETNAVIV */ uint32_t gem_handle; int dmabuf_fd; @@ -265,6 +273,56 @@ fd_device_destroy(struct buffer *buf) fd_device_del(buf->fd_dev); } #endif /* HAVE_LIBDRM_FREEDRENO */ +#ifdef HAVE_LIBDRM_ETNAVIV + +static int +etna_alloc_bo(struct buffer *buf) +{ + int flags = DRM_ETNA_GEM_CACHE_WC; + int size; + + buf->stride = ALIGN(buf->width, 32) * buf->bpp / 8; + size = buf->stride * buf->height; + buf->etna_dev = etna_device_new(buf->drm_fd); + buf->etna_bo = etna_bo_new(buf->etna_dev, size, flags); + + return buf->etna_bo != NULL; +} + +static void +etna_free_bo(struct buffer *buf) +{ + etna_bo_del(buf->etna_bo); +} + +static int +etna_bo_export_to_prime(struct buffer *buf) +{ + buf->dmabuf_fd = etna_bo_dmabuf(buf->etna_bo); + return buf->dmabuf_fd < 0; +} + +static int +etna_map_bo(struct buffer *buf) +{ + buf->mmap = etna_bo_map(buf->etna_bo); + return buf->mmap != NULL; +} + +static void +etna_unmap_bo(struct buffer *buf) +{ + if (munmap(buf->mmap, buf->stride * buf->height) < 0) + fprintf(stderr, "Failed to unmap buffer: %s", strerror(errno)); + buf->mmap = NULL; +} + +static void +etna_device_destroy(struct buffer *buf) +{ + etna_device_del(buf->etna_dev); +} +#endif /* HAVE_LIBDRM_ENTAVIV */ static void fill_content(struct buffer *my_buf) @@ -332,6 +390,16 @@ drm_device_init(struct buffer *buf) dev->unmap_bo = fd_unmap_bo; dev->device_destroy = fd_device_destroy; } +#endif +#ifdef HAVE_LIBDRM_ETNAVIV + else if (!strcmp(dev->name, "etnaviv")) { + dev->alloc_bo = etna_alloc_bo; + dev->free_bo = etna_free_bo; + dev->export_bo_to_prime = etna_bo_export_to_prime; + dev->map_bo = etna_map_bo; + dev->unmap_bo = etna_unmap_bo; + dev->device_destroy = etna_device_destroy; + } #endif else { fprintf(stderr, "Error: drm device %s unsupported.\n", diff --git a/configure.ac b/configure.ac index 1dce05fa2..ba11b6c8b 100644 --- a/configure.ac +++ b/configure.ac @@ -400,11 +400,14 @@ if ! test "x$enable_simple_dmabuf_drm_client" = "xno"; then PKG_CHECK_MODULES(LIBDRM_PLATFORM_INTEL, [libdrm_intel], AC_DEFINE([HAVE_LIBDRM_INTEL], [1], [Build intel dmabuf client]) have_simple_dmabuf_drm_client=yes, [true]) + PKG_CHECK_MODULES(LIBDRM_PLATFORM_ETNAVIV, [libdrm_etnaviv], + AC_DEFINE([HAVE_LIBDRM_ETNAVIV], [1], [Build etnaviv dmabuf client]) have_simple_dmabuf_drm_client=yes, + [true]) if test "x$have_simple_dmabuf_drm_client" != "xyes" -o \ "x$have_simple_dmabuf_libs" = "xno" && \ test "x$enable_simple_dmabuf_drm_client" = "xyes"; then - AC_MSG_ERROR([DRM dmabuf client explicitly enabled, but libdrm_intel or libdrm_freedreno not found]) + AC_MSG_ERROR([DRM dmabuf client explicitly enabled, but none of libdrm_{intel,freedreno,etnaviv} found]) fi if test "x$have_simple_dmabuf_drm_client" = "xyes" -a "x$have_simple_dmabuf_libs" = "xyes"; then From f7d035b28c07fa38b85f9ef084f302e4f9354c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 20 Mar 2018 11:36:36 +0100 Subject: [PATCH 0502/1642] simple-dmabuf-drm: drop superfluous declaration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit variable is defined in simple-dmabuf-drm.h Signed-off-by: Guido Günther Reviewed-by: Pekka Paalanen --- clients/simple-dmabuf-drm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index e7a7b0ed8..32c3032c8 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -62,7 +62,6 @@ #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" -extern const unsigned nv12_tiled[]; struct buffer; /* Possible options that affect the displayed image */ From 577b34641278ce68ed4927fe0369933cc64573fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 20 Mar 2018 11:36:37 +0100 Subject: [PATCH 0503/1642] simple-dmabuf-drm: don't exit from create_display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only exit from main so control flow is in one place. Signed-off-by: Guido Günther Reviewed-by: Pekka Paalanen --- clients/simple-dmabuf-drm.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 32c3032c8..725b35d99 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -828,7 +828,7 @@ create_display(int opts, int format) display = malloc(sizeof *display); if (display == NULL) { fprintf(stderr, "out of memory\n"); - exit(1); + return NULL; } display->display = wl_display_connect(NULL); assert(display->display); @@ -851,7 +851,7 @@ create_display(int opts, int format) wl_display_roundtrip(display->display); if (display->dmabuf == NULL) { fprintf(stderr, "No zwp_linux_dmabuf global\n"); - exit(1); + return NULL; } wl_display_roundtrip(display->display); @@ -860,7 +860,7 @@ create_display(int opts, int format) (format == DRM_FORMAT_NV12 && (!display->nv12_format_found || !display->nv12_modifier_found))) { fprintf(stderr, "requested format is not available\n"); - exit(1); + return NULL; } return display; @@ -970,6 +970,8 @@ main(int argc, char **argv) } display = create_display(opts, import_format); + if (!display) + return 1; window = create_window(display, 256, 256, import_format, opts); if (!window) return 1; From 74742e0525826f5f31b24a7a5444e6f88fc91f81 Mon Sep 17 00:00:00 2001 From: Matt Hoosier Date: Fri, 4 May 2018 09:26:34 -0500 Subject: [PATCH 0504/1642] log: improve handling of use-before-init Rather than segfaulting by attempting to traverse an initially null log handler pointer, explicitly print a message and abort. Signed-off-by: Matt Hoosier Reviewed-by: Ian Ray [Pekka: coding style fix] Signed-off-by: Pekka Paalanen --- libweston/log.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/libweston/log.c b/libweston/log.c index 7d99a95d0..d9bdbf8ca 100644 --- a/libweston/log.c +++ b/libweston/log.c @@ -36,8 +36,28 @@ #include "compositor.h" -static log_func_t log_handler = 0; -static log_func_t log_continue_handler = 0; +static int +default_log_handler(const char *fmt, va_list ap); + +static log_func_t log_handler = default_log_handler; +static log_func_t log_continue_handler = default_log_handler; + +/** Sentinel log message handler + * + * This function is used as the default handler for log messages. It + * exists only to issue a noisy reminder to the user that a real handler + * must be installed prior to issuing logging calls. The process is + * immediately aborted after the reminder is printed. + * + * \param fmt The format string. Ignored. + * \param va The variadic argument list. Ignored. + */ +static int +default_log_handler(const char *fmt, va_list ap) +{ + fprintf(stderr, "weston_log_set_handler() must be called before using of weston_log().\n"); + abort(); +} /** Install the log handler * From da1888356e66c6b6b4d1c4d8a81e5916ffa41019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 28 May 2018 17:41:57 +0200 Subject: [PATCH 0505/1642] simple-dmabuf-drm: support DRM_FORMAT_LINEAR for NV12 as well MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes --import-format=NV12 testable on e.g. intel We only set nv12_format_found to true if we found that format and at least one understood modifier. Store modifier verbatim instead of using a boolean flag. Last advertised and supported modifier currently wins. The NV12 DRM_FORMAT_LINEAR image should be green in the upper left corner and white in the lower right. Signed-off-by: Guido Günther Reviewed-by: Pekka Paalanen --- clients/simple-dmabuf-drm.c | 55 +++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 725b35d99..df96758c9 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -79,7 +79,7 @@ struct display { struct zwp_linux_dmabuf_v1 *dmabuf; int xrgb8888_format_found; int nv12_format_found; - int nv12_modifier_found; + uint64_t nv12_modifier; int req_dmabuf_immediate; int req_dmabuf_modifiers; }; @@ -324,7 +324,7 @@ etna_device_destroy(struct buffer *buf) #endif /* HAVE_LIBDRM_ENTAVIV */ static void -fill_content(struct buffer *my_buf) +fill_content(struct buffer *my_buf, uint64_t modifier) { int x = 0, y = 0; uint32_t *pix; @@ -332,11 +332,31 @@ fill_content(struct buffer *my_buf) assert(my_buf->mmap); if (my_buf->format == DRM_FORMAT_NV12) { - pix = (uint32_t *) my_buf->mmap; - for (y = 0; y < my_buf->height; y++) - memcpy(&pix[y * my_buf->width / 4], - &nv12_tiled[my_buf->width * y / 4], - my_buf->width); + if (modifier == DRM_FORMAT_MOD_SAMSUNG_64_32_TILE) { + pix = (uint32_t *) my_buf->mmap; + for (y = 0; y < my_buf->height; y++) + memcpy(&pix[y * my_buf->width / 4], + &nv12_tiled[my_buf->width * y / 4], + my_buf->width); + } + else if (modifier == DRM_FORMAT_MOD_LINEAR) { + uint8_t *pix8; + /* first plane: Y (2/3 of the buffer) */ + for (y = 0; y < my_buf->height * 2/3; y++) { + pix8 = my_buf->mmap + y * my_buf->stride; + for (x = 0; x < my_buf->width; x++) + *pix8++ = x % 0xff; + } + /* second plane (CbCr) is half the size of Y + plane (last 1/3 of the buffer) */ + for (y = my_buf->height * 2/3; y < my_buf->height; y++) { + pix8 = my_buf->mmap + y * my_buf->stride; + for (x = 0; x < my_buf->width; x+=2) { + *pix8++ = x % 256; + *pix8++ = y % 256; + } + } + } } else { for (y = 0; y < my_buf->height; y++) { @@ -481,7 +501,7 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, /* adjust height for allocation of NV12 Y and UV planes */ buffer->height = height * 3 / 2; buffer->bpp = 8; - modifier = DRM_FORMAT_MOD_SAMSUNG_64_32_TILE; + modifier = display->nv12_modifier; break; default: buffer->height = height; @@ -498,7 +518,7 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, fprintf(stderr, "map_bo failed\n"); goto error2; } - fill_content(buffer); + fill_content(buffer, modifier); drm_dev->unmap_bo(buffer); if (drm_dev->export_bo_to_prime(buffer) != 0) { @@ -746,10 +766,13 @@ dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, d->xrgb8888_format_found = 1; break; case DRM_FORMAT_NV12: - d->nv12_format_found = 1; - if (modifier == DRM_FORMAT_MOD_SAMSUNG_64_32_TILE) - d->nv12_modifier_found = 1; - break; + switch (modifier) { + case DRM_FORMAT_MOD_SAMSUNG_64_32_TILE: + case DRM_FORMAT_MOD_LINEAR: + d->nv12_format_found = 1; + d->nv12_modifier = modifier; + break; + } default: break; } @@ -857,12 +880,10 @@ create_display(int opts, int format) wl_display_roundtrip(display->display); if ((format == DRM_FORMAT_XRGB8888 && !display->xrgb8888_format_found) || - (format == DRM_FORMAT_NV12 && (!display->nv12_format_found || - !display->nv12_modifier_found))) { + (format == DRM_FORMAT_NV12 && !display->nv12_format_found)) { fprintf(stderr, "requested format is not available\n"); return NULL; } - return display; } @@ -902,7 +923,7 @@ print_usage_and_exit(void) "\t'--y-inverted=<>'\n\t\t0 to not pass Y_INVERTED flag," "\n\t\t1 to pass Y_INVERTED flag\n" "\t'--import-format=<>'\n\t\tXRGB to import dmabuf as XRGB8888," - "\n\t\tNV12 to import as multi plane NV12 with tiling modifier\n"); + "\n\t\tNV12 to import as multi plane NV12\n"); exit(0); } From e0dc5d47cb5f29deec495efd958fcd5f6f833389 Mon Sep 17 00:00:00 2001 From: Dima Ryazanov Date: Thu, 10 May 2018 00:53:38 -0700 Subject: [PATCH 0506/1642] Fix a crash when unlocking or unconfining a pointer In GNOME (but not in Weston), if a window loses focus, the client first receives the focus event, then the unlock/unconfine event. This causes toytoolkit to dereference a NULL window when unlocking or unconfining the pointer. To repro: - Run weston-confine - Click the window - Alt-Tab away from it Result: [1606837.869] wl_keyboard@19.modifiers(63944, 524352, 0, 0, 0) [1606837.926] wl_keyboard@19.leave(63945, wl_surface@15) [1606837.945] wl_pointer@18.leave(63946, wl_surface@15) [1606837.956] wl_pointer@18.frame() [1606837.961] zwp_confined_pointer_v1@26.unconfined() Segmentation fault (core dumped) To fix this, get the input from the window instead of the other way around. Signed-off-by: Dima Ryazanov Reviewed-by: Pekka Paalanen --- clients/window.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/clients/window.c b/clients/window.c index bcf2b017c..dee4455f1 100644 --- a/clients/window.c +++ b/clients/window.c @@ -286,6 +286,7 @@ struct window { confined_pointer_unconfined_handler_t pointer_unconfined_handler; struct zwp_confined_pointer_v1 *confined_pointer; + struct input *confined_input; struct widget *confined_widget; bool confined; @@ -4788,8 +4789,8 @@ static void locked_pointer_locked(void *data, struct zwp_locked_pointer_v1 *locked_pointer) { - struct input *input = data; - struct window *window = input->pointer_focus; + struct window *window = data; + struct input *input = window->locked_input; window->pointer_locked = true; @@ -4804,8 +4805,8 @@ static void locked_pointer_unlocked(void *data, struct zwp_locked_pointer_v1 *locked_pointer) { - struct input *input = data; - struct window *window = input->pointer_focus; + struct window *window = data; + struct input *input = window->locked_input; window_unlock_pointer(window); @@ -4860,7 +4861,7 @@ window_lock_pointer(struct window *window, struct input *input) ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); zwp_locked_pointer_v1_add_listener(locked_pointer, &locked_pointer_listener, - input); + window); window->locked_input = input; window->locked_pointer = locked_pointer; @@ -4902,8 +4903,8 @@ static void confined_pointer_confined(void *data, struct zwp_confined_pointer_v1 *confined_pointer) { - struct input *input = data; - struct window *window = input->pointer_focus; + struct window *window = data; + struct input *input = window->confined_input; window->confined = true; @@ -4918,8 +4919,8 @@ static void confined_pointer_unconfined(void *data, struct zwp_confined_pointer_v1 *confined_pointer) { - struct input *input = data; - struct window *window = input->pointer_focus; + struct window *window = data; + struct input *input = window->confined_input; window_unconfine_pointer(window); @@ -4984,8 +4985,9 @@ window_confine_pointer_to_rectangles(struct window *window, zwp_confined_pointer_v1_add_listener(confined_pointer, &confined_pointer_listener, - input); + window); + window->confined_input = input; window->confined_pointer = confined_pointer; window->confined_widget = NULL; @@ -5046,6 +5048,7 @@ window_unconfine_pointer(struct window *window) zwp_confined_pointer_v1_destroy(window->confined_pointer); window->confined_pointer = NULL; window->confined = false; + window->confined_input = NULL; } static void From ccdc81d609b38424f38bcdcb015acf69e9a009c4 Mon Sep 17 00:00:00 2001 From: Markus Ongyerth Date: Mon, 30 Apr 2018 11:35:49 +0200 Subject: [PATCH 0507/1642] weston-info: Add support for tablet-unstable-v2 This now prints each tablet seat with at least one tablet/pad/tool attached. For each tablet seat, each tablet, pad and tool is printed with as much detail about the device as the protocol provides. Seat info is stored to be referenced, because the protocol requires to request a tablet_seat for each wl_seat and it's not guaranteed that the tablet_v2_manager is available when seats are advertised. Signed-off-by: Markus Ongyerth Acked-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- Makefile.am | 14 +- clients/weston-info.c | 845 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 854 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index 64a8006c5..d9c03fef1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -825,11 +825,13 @@ weston_simple_im_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) weston_info_SOURCES = \ clients/weston-info.c \ shared/helpers.h -nodist_weston_info_SOURCES = \ - protocol/presentation-time-protocol.c \ - protocol/presentation-time-client-protocol.h \ - protocol/linux-dmabuf-unstable-v1-protocol.c \ - protocol/linux-dmabuf-unstable-v1-client-protocol.h +nodist_weston_info_SOURCES = \ + protocol/presentation-time-protocol.c \ + protocol/presentation-time-client-protocol.h \ + protocol/linux-dmabuf-unstable-v1-protocol.c \ + protocol/linux-dmabuf-unstable-v1-client-protocol.h \ + protocol/tablet-unstable-v2-protocol.c \ + protocol/tablet-unstable-v2-client-protocol.h weston_info_LDADD = $(WESTON_INFO_LIBS) libshared.la weston_info_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) @@ -889,6 +891,8 @@ BUILT_SOURCES += \ protocol/ivi-application-client-protocol.h \ protocol/linux-dmabuf-unstable-v1-protocol.c \ protocol/linux-dmabuf-unstable-v1-client-protocol.h \ + protocol/tablet-unstable-v2-protocol.c \ + protocol/tablet-unstable-v2-client-protocol.h \ protocol/input-timestamps-unstable-v1-protocol.c \ protocol/input-timestamps-unstable-v1-client-protocol.h diff --git a/clients/weston-info.c b/clients/weston-info.c index 386bd412b..6606117a9 100644 --- a/clients/weston-info.c +++ b/clients/weston-info.c @@ -41,6 +41,7 @@ #include "shared/zalloc.h" #include "presentation-time-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "tablet-unstable-v2-client-protocol.h" typedef void (*print_info_t)(void *info); typedef void (*destroy_info_t)(void *info); @@ -113,6 +114,7 @@ struct linux_dmabuf_info { struct seat_info { struct global_info global; + struct wl_list global_link; struct wl_seat *seat; struct weston_info *info; @@ -123,6 +125,75 @@ struct seat_info { int32_t repeat_delay; }; +struct tablet_v2_path { + struct wl_list link; + char *path; +}; + +struct tablet_tool_info { + struct wl_list link; + struct zwp_tablet_tool_v2 *tool; + + uint64_t hardware_serial; + uint64_t hardware_id_wacom; + enum zwp_tablet_tool_v2_type type; + + bool has_tilt; + bool has_pressure; + bool has_distance; + bool has_rotation; + bool has_slider; + bool has_wheel; +}; + +struct tablet_pad_group_info { + struct wl_list link; + struct zwp_tablet_pad_group_v2 *group; + + uint32_t modes; + size_t button_count; + int *buttons; + size_t strips; + size_t rings; +}; + +struct tablet_pad_info { + struct wl_list link; + struct zwp_tablet_pad_v2 *pad; + + uint32_t buttons; + struct wl_list paths; + struct wl_list groups; +}; + +struct tablet_info { + struct wl_list link; + struct zwp_tablet_v2 *tablet; + + char *name; + uint32_t vid, pid; + struct wl_list paths; +}; + +struct tablet_seat_info { + struct wl_list link; + + struct zwp_tablet_seat_v2 *seat; + struct seat_info *seat_info; + + struct wl_list tablets; + struct wl_list tools; + struct wl_list pads; +}; + +struct tablet_v2_info { + struct global_info global; + struct zwp_tablet_manager_v2 *manager; + struct weston_info *info; + + struct wl_list seats; +}; + struct presentation_info { struct global_info global; struct wp_presentation *presentation; @@ -136,6 +207,10 @@ struct weston_info { struct wl_list infos; bool roundtrip_needed; + + /* required for tablet-unstable-v2 */ + struct wl_list seats; + struct tablet_v2_info *tablet_info; }; static void @@ -455,6 +530,767 @@ destroy_seat_info(void *data) if (seat->name != NULL) free(seat->name); + + wl_list_remove(&seat->global_link); +} + +static const char * +tablet_tool_type_to_str(enum zwp_tablet_tool_v2_type type) +{ + switch (type) { + case ZWP_TABLET_TOOL_V2_TYPE_PEN: + return "pen"; + case ZWP_TABLET_TOOL_V2_TYPE_ERASER: + return "eraser"; + case ZWP_TABLET_TOOL_V2_TYPE_BRUSH: + return "brush"; + case ZWP_TABLET_TOOL_V2_TYPE_PENCIL: + return "pencil"; + case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH: + return "airbrush"; + case ZWP_TABLET_TOOL_V2_TYPE_FINGER: + return "finger"; + case ZWP_TABLET_TOOL_V2_TYPE_MOUSE: + return "mouse"; + case ZWP_TABLET_TOOL_V2_TYPE_LENS: + return "lens"; + } + + return "Unknown type"; +} + +static void +print_tablet_tool_info(const struct tablet_tool_info *info) +{ + printf("\t\ttablet_tool: %s\n", tablet_tool_type_to_str(info->type)); + if (info->hardware_serial) { + printf("\t\t\thardware serial: %lx\n", info->hardware_serial); + } + if (info->hardware_id_wacom) { + printf("\t\t\thardware wacom: %lx\n", info->hardware_id_wacom); + } + + printf("\t\t\tcapabilities:"); + + if (info->has_tilt) { + printf(" tilt"); + } + if (info->has_pressure) { + printf(" pressure"); + } + if (info->has_distance) { + printf(" distance"); + } + if (info->has_rotation) { + printf(" rotation"); + } + if (info->has_slider) { + printf(" slider"); + } + if (info->has_wheel) { + printf(" wheel"); + } + printf("\n"); +} + +static void +destroy_tablet_tool_info(struct tablet_tool_info *info) +{ + wl_list_remove(&info->link); + zwp_tablet_tool_v2_destroy(info->tool); + free(info); +} + +static void +print_tablet_pad_group_info(const struct tablet_pad_group_info *info) +{ + size_t i; + printf("\t\t\tgroup:\n"); + printf("\t\t\t\tmodes: %u\n", info->modes); + printf("\t\t\t\tstrips: %zu\n", info->strips); + printf("\t\t\t\trings: %zu\n", info->rings); + printf("\t\t\t\tbuttons:"); + + for (i = 0; i < info->button_count; ++i) { + printf(" %d", info->buttons[i]); + } + + printf("\n"); +} + +static void +destroy_tablet_pad_group_info(struct tablet_pad_group_info *info) +{ + wl_list_remove(&info->link); + zwp_tablet_pad_group_v2_destroy(info->group); + + if (info->buttons) { + free(info->buttons); + } + free(info); +} + +static void +print_tablet_pad_info(const struct tablet_pad_info *info) +{ + const struct tablet_v2_path *path; + const struct tablet_pad_group_info *group; + + printf("\t\tpad:\n"); + printf("\t\t\tbuttons: %u\n", info->buttons); + + wl_list_for_each(path, &info->paths, link) { + printf("\t\t\tpath: %s\n", path->path); + } + + wl_list_for_each(group, &info->groups, link) { + print_tablet_pad_group_info(group); + } +} + +static void +destroy_tablet_pad_info(struct tablet_pad_info *info) +{ + struct tablet_v2_path *path; + struct tablet_v2_path *tmp_path; + struct tablet_pad_group_info *group; + struct tablet_pad_group_info *tmp_group; + + wl_list_remove(&info->link); + zwp_tablet_pad_v2_destroy(info->pad); + + wl_list_for_each_safe(path, tmp_path, &info->paths, link) { + wl_list_remove(&path->link); + free(path->path); + free(path); + } + + wl_list_for_each_safe(group, tmp_group, &info->groups, link) { + destroy_tablet_pad_group_info(group); + } + + free(info); +} + +static void +print_tablet_info(const struct tablet_info *info) +{ + const struct tablet_v2_path *path; + + printf("\t\ttablet: %s\n", info->name); + printf("\t\t\tvendor: %u\n", info->vid); + printf("\t\t\tproduct: %u\n", info->pid); + + wl_list_for_each(path, &info->paths, link) { + printf("\t\t\tpath: %s\n", path->path); + } +} + +static void +destroy_tablet_info(struct tablet_info *info) +{ + struct tablet_v2_path *path; + struct tablet_v2_path *tmp; + + wl_list_remove(&info->link); + zwp_tablet_v2_destroy(info->tablet); + + if (info->name) { + free(info->name); + } + + wl_list_for_each_safe(path, tmp, &info->paths, link) { + wl_list_remove(&path->link); + free(path->path); + free(path); + } + + free(info); +} + +static void +print_tablet_seat_info(const struct tablet_seat_info *info) +{ + const struct tablet_info *tablet; + const struct tablet_pad_info *pad; + const struct tablet_tool_info *tool; + + printf("\ttablet_seat: %s\n", info->seat_info->name); + + wl_list_for_each(tablet, &info->tablets, link) { + print_tablet_info(tablet); + } + + wl_list_for_each(pad, &info->pads, link) { + print_tablet_pad_info(pad); + } + + wl_list_for_each(tool, &info->tools, link) { + print_tablet_tool_info(tool); + } +} + +static void +destroy_tablet_seat_info(struct tablet_seat_info *info) +{ + struct tablet_info *tablet; + struct tablet_info *tmp_tablet; + struct tablet_pad_info *pad; + struct tablet_pad_info *tmp_pad; + struct tablet_tool_info *tool; + struct tablet_tool_info *tmp_tool; + + wl_list_remove(&info->link); + zwp_tablet_seat_v2_destroy(info->seat); + + wl_list_for_each_safe(tablet, tmp_tablet, &info->tablets, link) { + destroy_tablet_info(tablet); + } + + wl_list_for_each_safe(pad, tmp_pad, &info->pads, link) { + destroy_tablet_pad_info(pad); + } + + wl_list_for_each_safe(tool, tmp_tool, &info->tools, link) { + destroy_tablet_tool_info(tool); + } + + free(info); +} + +static void +print_tablet_v2_info(void *data) +{ + struct tablet_v2_info *info = data; + struct tablet_seat_info *seat; + print_global_info(data); + + wl_list_for_each(seat, &info->seats, link) { + /* Skip tablet_seats without a tablet, they are irrelevant */ + if (wl_list_empty(&seat->pads) && + wl_list_empty(&seat->tablets) && + wl_list_empty(&seat->tools)) { + continue; + } + + print_tablet_seat_info(seat); + } +} + +static void +destroy_tablet_v2_info(void *data) +{ + struct tablet_v2_info *info = data; + struct tablet_seat_info *seat; + struct tablet_seat_info *tmp; + + zwp_tablet_manager_v2_destroy(info->manager); + + wl_list_for_each_safe(seat, tmp, &info->seats, link) { + destroy_tablet_seat_info(seat); + } +} + +static void +handle_tablet_v2_tablet_tool_done(void *data, struct zwp_tablet_tool_v2 *tool) +{ + /* don't bother waiting for this; there's no good reason a + * compositor will wait more than one roundtrip before sending + * these initial events. */ +} + +static void +handle_tablet_v2_tablet_tool_removed(void *data, struct zwp_tablet_tool_v2 *tool) +{ + /* don't bother waiting for this; we never make any request either way. */ +} + +static void +handle_tablet_v2_tablet_tool_type(void *data, struct zwp_tablet_tool_v2 *tool, + uint32_t tool_type) +{ + struct tablet_tool_info *info = data; + info->type = tool_type; +} + +static void +handle_tablet_v2_tablet_tool_hardware_serial(void *data, + struct zwp_tablet_tool_v2 *tool, + uint32_t serial_hi, + uint32_t serial_lo) +{ + struct tablet_tool_info *info = data; + + info->hardware_serial = ((uint64_t) serial_hi) << 32 | + (uint64_t) serial_lo; +} + +static void +handle_tablet_v2_tablet_tool_hardware_id_wacom(void *data, + struct zwp_tablet_tool_v2 *tool, + uint32_t id_hi, uint32_t id_lo) +{ + struct tablet_tool_info *info = data; + + info->hardware_id_wacom = ((uint64_t) id_hi) << 32 | (uint64_t) id_lo; +} + +static void +handle_tablet_v2_tablet_tool_capability(void *data, + struct zwp_tablet_tool_v2 *tool, + uint32_t capability) +{ + struct tablet_tool_info *info = data; + enum zwp_tablet_tool_v2_capability cap = capability; + + switch(cap) { + case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT: + info->has_tilt = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE: + info->has_pressure = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE: + info->has_distance = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION: + info->has_rotation = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER: + info->has_slider = true; + break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL: + info->has_wheel = true; + break; + } +} + +static void +handle_tablet_v2_tablet_tool_proximity_in(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t serial, struct zwp_tablet_v2 *tablet, + struct wl_surface *surface) +{ + +} + +static void +handle_tablet_v2_tablet_tool_proximity_out(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) +{ + +} + +static void +handle_tablet_v2_tablet_tool_down(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t serial) +{ + +} + +static void +handle_tablet_v2_tablet_tool_up(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) +{ + +} + + +static void +handle_tablet_v2_tablet_tool_motion(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + wl_fixed_t x, + wl_fixed_t y) +{ + +} + +static void +handle_tablet_v2_tablet_tool_pressure(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t pressure) +{ + +} + +static void +handle_tablet_v2_tablet_tool_distance(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t distance) +{ + +} + +static void +handle_tablet_v2_tablet_tool_tilt(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + wl_fixed_t tilt_x, + wl_fixed_t tilt_y) +{ + +} + +static void +handle_tablet_v2_tablet_tool_rotation(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + wl_fixed_t degrees) +{ + +} + +static void +handle_tablet_v2_tablet_tool_slider(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + int32_t position) +{ + +} + +static void +handle_tablet_v2_tablet_tool_wheel(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + wl_fixed_t degrees, + int32_t clicks) +{ + +} + +static void +handle_tablet_v2_tablet_tool_button(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t serial, + uint32_t button, + uint32_t state) +{ + +} + +static void +handle_tablet_v2_tablet_tool_frame(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t time) +{ + +} + +static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { + .removed = handle_tablet_v2_tablet_tool_removed, + .done = handle_tablet_v2_tablet_tool_done, + .type = handle_tablet_v2_tablet_tool_type, + .hardware_serial = handle_tablet_v2_tablet_tool_hardware_serial, + .hardware_id_wacom = handle_tablet_v2_tablet_tool_hardware_id_wacom, + .capability = handle_tablet_v2_tablet_tool_capability, + + .proximity_in = handle_tablet_v2_tablet_tool_proximity_in, + .proximity_out = handle_tablet_v2_tablet_tool_proximity_out, + .down = handle_tablet_v2_tablet_tool_down, + .up = handle_tablet_v2_tablet_tool_up, + + .motion = handle_tablet_v2_tablet_tool_motion, + .pressure = handle_tablet_v2_tablet_tool_pressure, + .distance = handle_tablet_v2_tablet_tool_distance, + .tilt = handle_tablet_v2_tablet_tool_tilt, + .rotation = handle_tablet_v2_tablet_tool_rotation, + .slider = handle_tablet_v2_tablet_tool_slider, + .wheel = handle_tablet_v2_tablet_tool_wheel, + .button = handle_tablet_v2_tablet_tool_button, + .frame = handle_tablet_v2_tablet_tool_frame, +}; + +static void add_tablet_v2_tablet_tool_info(void *data, + struct zwp_tablet_seat_v2 *tablet_seat_v2, + struct zwp_tablet_tool_v2 *tool) +{ + struct tablet_seat_info *tablet_seat = data; + struct tablet_tool_info *tool_info = xzalloc(sizeof *tool_info); + + tool_info->tool = tool; + wl_list_insert(&tablet_seat->tools, &tool_info->link); + + zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, tool_info); +} + +static void +handle_tablet_v2_tablet_pad_group_mode_switch(void *data, + struct zwp_tablet_pad_group_v2 *zwp_tablet_pad_group_v2, + uint32_t time, uint32_t serial, uint32_t mode) +{ + /* This shouldn't ever happen */ +} + +static void +handle_tablet_v2_tablet_pad_group_done(void *data, + struct zwp_tablet_pad_group_v2 *group) +{ + /* don't bother waiting for this; there's no good reason a + * compositor will wait more than one roundtrip before sending + * these initial events. */ +} + +static void +handle_tablet_v2_tablet_pad_group_modes(void *data, + struct zwp_tablet_pad_group_v2 *group, + uint32_t modes) +{ + struct tablet_pad_group_info *info = data; + info->modes = modes; +} + +static void +handle_tablet_v2_tablet_pad_group_buttons(void *data, + struct zwp_tablet_pad_group_v2 *group, + struct wl_array *buttons) +{ + struct tablet_pad_group_info *info = data; + + info->button_count = buttons->size / sizeof(int); + info->buttons = xzalloc(buttons->size); + memcpy(info->buttons, buttons->data, buttons->size); +} + +static void +handle_tablet_v2_tablet_pad_group_ring(void *data, + struct zwp_tablet_pad_group_v2 *group, + struct zwp_tablet_pad_ring_v2 *ring) +{ + struct tablet_pad_group_info *info = data; + ++info->rings; + + zwp_tablet_pad_ring_v2_destroy(ring); +} + +static void +handle_tablet_v2_tablet_pad_group_strip(void *data, + struct zwp_tablet_pad_group_v2 *group, + struct zwp_tablet_pad_strip_v2 *strip) +{ + struct tablet_pad_group_info *info = data; + ++info->strips; + + zwp_tablet_pad_strip_v2_destroy(strip); +} + +static const struct zwp_tablet_pad_group_v2_listener tablet_pad_group_listener = { + .buttons = handle_tablet_v2_tablet_pad_group_buttons, + .modes = handle_tablet_v2_tablet_pad_group_modes, + .ring = handle_tablet_v2_tablet_pad_group_ring, + .strip = handle_tablet_v2_tablet_pad_group_strip, + .done = handle_tablet_v2_tablet_pad_group_done, + .mode_switch = handle_tablet_v2_tablet_pad_group_mode_switch, +}; + +static void +handle_tablet_v2_tablet_pad_group(void *data, + struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, + struct zwp_tablet_pad_group_v2 *pad_group) +{ + struct tablet_pad_info *pad_info = data; + struct tablet_pad_group_info *group = xzalloc(sizeof *group); + + wl_list_insert(&pad_info->groups, &group->link); + group->group = pad_group; + zwp_tablet_pad_group_v2_add_listener(pad_group, + &tablet_pad_group_listener, group); +} + +static void +handle_tablet_v2_tablet_pad_path(void *data, struct zwp_tablet_pad_v2 *pad, + const char *path) +{ + struct tablet_pad_info *pad_info = data; + struct tablet_v2_path *path_elem = xzalloc(sizeof *path_elem); + path_elem->path = xstrdup(path); + + wl_list_insert(&pad_info->paths, &path_elem->link); +} + +static void +handle_tablet_v2_tablet_pad_buttons(void *data, struct zwp_tablet_pad_v2 *pad, + uint32_t buttons) +{ + struct tablet_pad_info *pad_info = data; + + pad_info->buttons = buttons; +} + +static void +handle_tablet_v2_tablet_pad_done(void *data, struct zwp_tablet_pad_v2 *pad) +{ + /* don't bother waiting for this; there's no good reason a + * compositor will wait more than one roundtrip before sending + * these initial events. */ +} + +static void +handle_tablet_v2_tablet_pad_removed(void *data, struct zwp_tablet_pad_v2 *pad) +{ + /* don't bother waiting for this; We never make any request that's not + * allowed to be issued either way. */ +} + +static void +handle_tablet_v2_tablet_pad_button(void *data, struct zwp_tablet_pad_v2 *pad, + uint32_t time, uint32_t button, uint32_t state) +{ + /* we don't have a surface, so this can't ever happen */ +} + +static void +handle_tablet_v2_tablet_pad_enter(void *data, struct zwp_tablet_pad_v2 *pad, + uint32_t serial, + struct zwp_tablet_v2 *tablet, + struct wl_surface *surface) +{ + /* we don't have a surface, so this can't ever happen */ +} + +static void +handle_tablet_v2_tablet_pad_leave(void *data, struct zwp_tablet_pad_v2 *pad, + uint32_t serial, struct wl_surface *surface) +{ + /* we don't have a surface, so this can't ever happen */ +} + +static const struct zwp_tablet_pad_v2_listener tablet_pad_listener = { + .group = handle_tablet_v2_tablet_pad_group, + .path = handle_tablet_v2_tablet_pad_path, + .buttons = handle_tablet_v2_tablet_pad_buttons, + .done = handle_tablet_v2_tablet_pad_done, + .removed = handle_tablet_v2_tablet_pad_removed, + .button = handle_tablet_v2_tablet_pad_button, + .enter = handle_tablet_v2_tablet_pad_enter, + .leave = handle_tablet_v2_tablet_pad_leave, +}; + +static void add_tablet_v2_tablet_pad_info(void *data, + struct zwp_tablet_seat_v2 *tablet_seat_v2, + struct zwp_tablet_pad_v2 *pad) +{ + struct tablet_seat_info *tablet_seat = data; + struct tablet_pad_info *pad_info = xzalloc(sizeof *pad_info); + + wl_list_init(&pad_info->paths); + wl_list_init(&pad_info->groups); + pad_info->pad = pad; + wl_list_insert(&tablet_seat->pads, &pad_info->link); + + zwp_tablet_pad_v2_add_listener(pad, &tablet_pad_listener, pad_info); +} + +static void +handle_tablet_v2_tablet_name(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, + const char *name) +{ + struct tablet_info *tablet_info = data; + tablet_info->name = xstrdup(name); +} + +static void +handle_tablet_v2_tablet_path(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, + const char *path) +{ + struct tablet_info *tablet_info = data; + struct tablet_v2_path *path_elem = xzalloc(sizeof *path_elem); + path_elem->path = xstrdup(path); + + wl_list_insert(&tablet_info->paths, &path_elem->link); +} + +static void +handle_tablet_v2_tablet_id(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, + uint32_t vid, uint32_t pid) +{ + struct tablet_info *tablet_info = data; + + tablet_info->vid = vid; + tablet_info->pid = pid; +} + +static void +handle_tablet_v2_tablet_done(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) +{ + /* don't bother waiting for this; there's no good reason a + * compositor will wait more than one roundtrip before sending + * these initial events. */ +} + +static void +handle_tablet_v2_tablet_removed(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) +{ + /* don't bother waiting for this; We never make any request that's not + * allowed to be issued either way. */ +} + +static const struct zwp_tablet_v2_listener tablet_listener = { + .name = handle_tablet_v2_tablet_name, + .id = handle_tablet_v2_tablet_id, + .path = handle_tablet_v2_tablet_path, + .done = handle_tablet_v2_tablet_done, + .removed = handle_tablet_v2_tablet_removed +}; + +static void +add_tablet_v2_tablet_info(void *data, struct zwp_tablet_seat_v2 *tablet_seat_v2, + struct zwp_tablet_v2 *tablet) +{ + struct tablet_seat_info *tablet_seat = data; + struct tablet_info *tablet_info = xzalloc(sizeof *tablet_info); + + wl_list_init(&tablet_info->paths); + tablet_info->tablet = tablet; + wl_list_insert(&tablet_seat->tablets, &tablet_info->link); + + zwp_tablet_v2_add_listener(tablet, &tablet_listener, tablet_info); +} + +static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { + .tablet_added = add_tablet_v2_tablet_info, + .pad_added = add_tablet_v2_tablet_pad_info, + .tool_added = add_tablet_v2_tablet_tool_info, +}; + +static void +add_tablet_seat_info(struct tablet_v2_info *tablet_info, struct seat_info *seat) +{ + struct tablet_seat_info *tablet_seat = xzalloc(sizeof *tablet_seat); + + wl_list_insert(&tablet_info->seats, &tablet_seat->link); + tablet_seat->seat = zwp_tablet_manager_v2_get_tablet_seat( + tablet_info->manager, seat->seat); + zwp_tablet_seat_v2_add_listener(tablet_seat->seat, + &tablet_seat_listener, tablet_seat); + + wl_list_init(&tablet_seat->pads); + wl_list_init(&tablet_seat->tablets); + wl_list_init(&tablet_seat->tools); + tablet_seat->seat_info = seat; + + tablet_info->info->roundtrip_needed = true; +} + +static void +add_tablet_v2_info(struct weston_info *info, uint32_t id, uint32_t version) +{ + struct seat_info *seat; + struct tablet_v2_info *tablet = xzalloc(sizeof *tablet); + + wl_list_init(&tablet->seats); + tablet->info = info; + + init_global_info(info, &tablet->global, id, + zwp_tablet_manager_v2_interface.name, version); + tablet->global.print = print_tablet_v2_info; + tablet->global.destroy = destroy_tablet_v2_info; + + tablet->manager = wl_registry_bind(info->registry, + id, &zwp_tablet_manager_v2_interface, 1); + + wl_list_for_each(seat, &info->seats, global_link) { + add_tablet_seat_info(tablet, seat); + } + + info->tablet_info = tablet; } static void @@ -477,6 +1313,11 @@ add_seat_info(struct weston_info *info, uint32_t id, uint32_t version) seat->repeat_rate = seat->repeat_delay = -1; info->roundtrip_needed = true; + wl_list_insert(&info->seats, &seat->global_link); + + if (info->tablet_info) { + add_tablet_seat_info(info->tablet_info, seat); + } } static void @@ -784,6 +1625,8 @@ global_handler(void *data, struct wl_registry *registry, uint32_t id, add_output_info(info, id, version); else if (!strcmp(interface, wp_presentation_interface.name)) add_presentation_info(info, id, version); + else if (!strcmp(interface, zwp_tablet_manager_v2_interface.name)) + add_tablet_v2_info(info, id, version); else add_global_info(info, id, interface, version); } @@ -837,7 +1680,9 @@ main(int argc, char **argv) return -1; } + info.tablet_info = NULL; wl_list_init(&info.infos); + wl_list_init(&info.seats); info.registry = wl_display_get_registry(info.display); wl_registry_add_listener(info.registry, ®istry_listener, &info); From 995bf51b2cc622762b7d9079f03ab46d4055cd37 Mon Sep 17 00:00:00 2001 From: Markus Ongyerth Date: Mon, 30 Apr 2018 11:35:50 +0200 Subject: [PATCH 0508/1642] weston-info: destroy wl_keyboard Fixes a memory leak by calling wl_keyboard_destroy on any keyboard that was used to listen for events. Signed-off-by: Markus Ongyerth Acked-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- clients/weston-info.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clients/weston-info.c b/clients/weston-info.c index 6606117a9..609e270a4 100644 --- a/clients/weston-info.c +++ b/clients/weston-info.c @@ -118,6 +118,7 @@ struct seat_info { struct wl_seat *seat; struct weston_info *info; + struct wl_keyboard *keyboard; uint32_t capabilities; char *name; @@ -498,10 +499,8 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, return; if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { - struct wl_keyboard *keyboard; - - keyboard = wl_seat_get_keyboard(seat->seat); - wl_keyboard_add_listener(keyboard, &keyboard_listener, + seat->keyboard = wl_seat_get_keyboard(seat->seat); + wl_keyboard_add_listener(seat->keyboard, &keyboard_listener, seat); seat->info->roundtrip_needed = true; @@ -531,6 +530,9 @@ destroy_seat_info(void *data) if (seat->name != NULL) free(seat->name); + if (seat->keyboard) + wl_keyboard_destroy(seat->keyboard); + wl_list_remove(&seat->global_link); } From 6ef59c98431cc2cf25d7cdbfb7e37fbfdea1ba99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= Date: Tue, 28 Nov 2017 20:42:47 -0500 Subject: [PATCH 0509/1642] input: introduce weston_touch_device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce weston_touch_device for libweston core to track individual touchscreen input devices. A weston_seat/weston_touch may be an aggregation of several physical touchscreen input devices. Separating the physical devices will be required for implementing touchscreen calibration. One can only calibrate one device at a time, and we want to make sure to handle the right one. Both backends that support touch devices are updated to create weston_touch_devices. Wayland-backend provides touch devices that cannot be calibrated, because we have no access to raw touch coordinates from the device - calibration is the responsibility of the parent display server. Libinput backend provides touch devices that can be calibrated, hence implementing the set and get calibration hooks. Backends need to maintain an output pointer in any case, so we have a get_output() hook instead of having to maintain an identical field in weston_touch_device. The same justification applies to get_calibration_head_name. Also update the test plugin to manage weston_touch_device objects. Co-developed by Louis-Francis and Pekka. v2: - Consistently use 'cal' instead of 'calb' or 'matrix'. - change devpath into syspath - update copyrights Signed-off-by: Louis-Francis Ratté-Boulianne Signed-off-by: Pekka Paalanen v1 Tested-by: Matt Hoosier Reviewed-by: Peter Hutterer --- libweston/compositor-wayland.c | 21 ++++++ libweston/compositor.h | 56 ++++++++++++++++ libweston/input.c | 67 +++++++++++++++++++ libweston/libinput-device.c | 118 ++++++++++++++++++++++++++++++++- libweston/libinput-device.h | 3 + tests/weston-test.c | 38 +++++++++++ 6 files changed, 302 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 56ed474a5..5ed90c0f0 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -198,6 +198,8 @@ struct wayland_input { } cursor; } parent; + struct weston_touch_device *touch_device; + enum weston_key_state_update keyboard_state_update; uint32_t key_serial; uint32_t enter_serial; @@ -2248,6 +2250,22 @@ static const struct wl_touch_listener touch_listener = { }; +static struct weston_touch_device * +create_touch_device(struct wayland_input *input) +{ + struct weston_touch_device *touch_device; + char str[128]; + + /* manufacture a unique'ish name */ + snprintf(str, sizeof str, "wayland-touch[%u]", + wl_proxy_get_id((struct wl_proxy *)input->parent.seat)); + + touch_device = weston_touch_create_touch_device(input->base.touch_state, + str, NULL, NULL); + + return touch_device; +} + static void input_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) @@ -2289,7 +2307,10 @@ input_handle_capabilities(void *data, struct wl_seat *seat, wl_touch_add_listener(input->parent.touch, &touch_listener, input); weston_seat_init_touch(&input->base); + input->touch_device = create_touch_device(input); } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && input->parent.touch) { + weston_touch_device_destroy(input->touch_device); + input->touch_device = NULL; if (input->seat_version >= WL_TOUCH_RELEASE_SINCE_VERSION) wl_touch_release(input->parent.touch); else diff --git a/libweston/compositor.h b/libweston/compositor.h index 47337d8a8..7f3fdb951 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -454,10 +454,54 @@ struct weston_pointer { struct wl_list timestamps_list; }; +/** libinput style calibration matrix + * + * See https://wayland.freedesktop.org/libinput/doc/latest/absolute_axes.html + * and libinput_device_config_calibration_set_matrix(). + */ +struct weston_touch_device_matrix { + float m[6]; +}; + +struct weston_touch_device; + +/** Operations for a calibratable touchscreen */ +struct weston_touch_device_ops { + /** Get the associated output if existing. */ + struct weston_output *(*get_output)(struct weston_touch_device *device); + + /** Get the name of the associated head if existing. */ + const char * + (*get_calibration_head_name)(struct weston_touch_device *device); + + /** Retrieve the current calibration matrix. */ + void (*get_calibration)(struct weston_touch_device *device, + struct weston_touch_device_matrix *cal); + + /** Set a new calibration matrix. */ + void (*set_calibration)(struct weston_touch_device *device, + const struct weston_touch_device_matrix *cal); +}; + +/** Represents a physical touchscreen input device */ +struct weston_touch_device { + char *syspath; /**< unique name */ + struct weston_touch *aggregate; /**< weston_touch this is part of */ + struct wl_list link; /**< in weston_touch::device_list */ + struct wl_signal destroy_signal; /**< destroy notifier */ + + void *backend_data; /**< backend-specific private */ + + const struct weston_touch_device_ops *ops; +}; + +/** Represents a set of touchscreen devices aggregated under a seat */ struct weston_touch { struct weston_seat *seat; + struct wl_list device_list; /* struct weston_touch_device::link */ + struct wl_list resource_list; struct wl_list focus_resource_list; struct weston_view *focus; @@ -579,6 +623,18 @@ weston_touch_send_motion(struct weston_touch *touch, void weston_touch_send_frame(struct weston_touch *touch); +struct weston_touch_device * +weston_touch_create_touch_device(struct weston_touch *touch, + const char *syspath, + void *backend_data, + const struct weston_touch_device_ops *ops); + +void +weston_touch_device_destroy(struct weston_touch_device *device); + +bool +weston_touch_device_can_calibrate(struct weston_touch_device *device); + void wl_data_device_set_keyboard_focus(struct weston_seat *seat); diff --git a/libweston/input.c b/libweston/input.c index 833ce22e9..acb564e0c 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -1,5 +1,7 @@ /* * Copyright © 2013 Intel Corporation + * Copyright 2017-2018 Collabora, Ltd. + * Copyright 2017-2018 General Electric Company * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -123,6 +125,68 @@ remove_input_resource_from_timestamps(struct wl_resource *input_resource, } } +/** Register a touchscreen input device + * + * \param touch The parent weston_touch that identifies the seat. + * \param syspath Unique device name. + * \param backend_data Backend private data if necessary. + * \param ops Calibration operations, or NULL for not able to run calibration. + * \return New touch device, or NULL on failure. + */ +WL_EXPORT struct weston_touch_device * +weston_touch_create_touch_device(struct weston_touch *touch, + const char *syspath, + void *backend_data, + const struct weston_touch_device_ops *ops) +{ + struct weston_touch_device *device; + + assert(syspath); + if (ops) { + assert(ops->get_output); + assert(ops->get_calibration_head_name); + assert(ops->get_calibration); + assert(ops->set_calibration); + } + + device = zalloc(sizeof *device); + if (!device) + return NULL; + + wl_signal_init(&device->destroy_signal); + + device->syspath = strdup(syspath); + if (!device->syspath) { + free(device); + return NULL; + } + + device->backend_data = backend_data; + device->ops = ops; + + device->aggregate = touch; + wl_list_insert(touch->device_list.prev, &device->link); + + return device; +} + +/** Destroy the touch device. */ +WL_EXPORT void +weston_touch_device_destroy(struct weston_touch_device *device) +{ + wl_list_remove(&device->link); + wl_signal_emit(&device->destroy_signal, device); + free(device->syspath); + free(device); +} + +/** Is it possible to run calibration on this touch device? */ +WL_EXPORT bool +weston_touch_device_can_calibrate(struct weston_touch_device *device) +{ + return !!device->ops; +} + static struct weston_pointer_client * weston_pointer_client_create(struct wl_client *client) { @@ -1277,6 +1341,7 @@ weston_touch_create(void) if (touch == NULL) return NULL; + wl_list_init(&touch->device_list); wl_list_init(&touch->resource_list); wl_list_init(&touch->focus_resource_list); wl_list_init(&touch->focus_view_listener.link); @@ -1297,6 +1362,8 @@ weston_touch_destroy(struct weston_touch *touch) { struct wl_resource *resource; + assert(wl_list_empty(&touch->device_list)); + wl_resource_for_each(resource, &touch->resource_list) { wl_resource_set_user_data(resource, NULL); } diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index e1738613d..63f503190 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -1,6 +1,8 @@ /* * Copyright © 2010 Intel Corporation * Copyright © 2013 Jonas Ådahl + * Copyright 2017-2018 Collabora, Ltd. + * Copyright 2017-2018 General Electric Company * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -296,6 +298,111 @@ handle_pointer_axis(struct libinput_device *libinput_device, return true; } +static struct weston_output * +touch_get_output(struct weston_touch_device *device) +{ + struct evdev_device *evdev_device = device->backend_data; + + return evdev_device->output; +} + +static const char * +touch_get_calibration_head_name(struct weston_touch_device *device) +{ + struct evdev_device *evdev_device = device->backend_data; + struct weston_output *output = evdev_device->output; + struct weston_head *head; + + if (!output) + return NULL; + + assert(output->enabled); + if (evdev_device->output_name) + return evdev_device->output_name; + + /* No specific head was configured, so the association was made by + * the default rule. Just grab whatever head's name. + */ + wl_list_for_each(head, &output->head_list, output_link) + return head->name; + + assert(0); + return NULL; +} + +static void +touch_get_calibration(struct weston_touch_device *device, + struct weston_touch_device_matrix *cal) +{ + struct evdev_device *evdev_device = device->backend_data; + + libinput_device_config_calibration_get_matrix(evdev_device->device, + cal->m); +} + +static void +do_set_calibration(struct evdev_device *evdev_device, + const struct weston_touch_device_matrix *cal) +{ + enum libinput_config_status status; + + status = libinput_device_config_calibration_set_matrix(evdev_device->device, + cal->m); + if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) + weston_log("Failed to apply calibration.\n"); +} + +static void +touch_set_calibration(struct weston_touch_device *device, + const struct weston_touch_device_matrix *cal) +{ + struct evdev_device *evdev_device = device->backend_data; + + /* Stop output hotplug from reloading the WL_CALIBRATION values. + * libinput will maintain the latest calibration for us. + */ + evdev_device->override_wl_calibration = true; + + do_set_calibration(evdev_device, cal); +} + +static const struct weston_touch_device_ops touch_calibration_ops = { + .get_output = touch_get_output, + .get_calibration_head_name = touch_get_calibration_head_name, + .get_calibration = touch_get_calibration, + .set_calibration = touch_set_calibration +}; + +static struct weston_touch_device * +create_touch_device(struct evdev_device *device) +{ + const struct weston_touch_device_ops *ops = NULL; + struct weston_touch_device *touch_device; + struct udev_device *udev_device; + + if (libinput_device_config_calibration_has_matrix(device->device)) + ops = &touch_calibration_ops; + + udev_device = libinput_device_get_udev_device(device->device); + if (!udev_device) + return NULL; + + touch_device = weston_touch_create_touch_device(device->seat->touch_state, + udev_device_get_syspath(udev_device), + device, ops); + + udev_device_unref(udev_device); + + if (!touch_device) + return NULL; + + weston_log("Touchscreen - %s - %s\n", + libinput_device_get_name(device->device), + touch_device->syspath); + + return touch_device; +} + static void handle_touch_with_coords(struct libinput_device *libinput_device, struct libinput_event_touch *touch_event, @@ -466,6 +573,12 @@ evdev_device_set_calibration(struct evdev_device *device) calibration) != 0) return; + /* touch_set_calibration() has updated the values, do not load old + * values from WL_CALIBRATION. + */ + if (device->override_wl_calibration) + return; + if (!device->output) { weston_log("input device %s has no enabled output associated " "(%s named), skipping calibration for now.\n", @@ -598,6 +711,7 @@ evdev_device_create(struct libinput_device *libinput_device, LIBINPUT_DEVICE_CAP_TOUCH)) { weston_seat_init_touch(seat); device->seat_caps |= EVDEV_SEAT_TOUCH; + device->touch_device = create_touch_device(device); } libinput_device_set_user_data(libinput_device, device); @@ -613,8 +727,10 @@ evdev_device_destroy(struct evdev_device *device) weston_seat_release_pointer(device->seat); if (device->seat_caps & EVDEV_SEAT_KEYBOARD) weston_seat_release_keyboard(device->seat); - if (device->seat_caps & EVDEV_SEAT_TOUCH) + if (device->seat_caps & EVDEV_SEAT_TOUCH) { + weston_touch_device_destroy(device->touch_device); weston_seat_release_touch(device->seat); + } if (device->output) wl_list_remove(&device->output_destroy_listener.link); diff --git a/libweston/libinput-device.h b/libweston/libinput-device.h index 8e9f56085..6147a5137 100644 --- a/libweston/libinput-device.h +++ b/libweston/libinput-device.h @@ -31,6 +31,7 @@ #include #include #include +#include #include "compositor.h" @@ -44,11 +45,13 @@ struct evdev_device { struct weston_seat *seat; enum evdev_device_seat_capability seat_caps; struct libinput_device *device; + struct weston_touch_device *touch_device; struct wl_list link; struct weston_output *output; struct wl_listener output_destroy_listener; char *output_name; int fd; + bool override_wl_calibration; }; void diff --git a/tests/weston-test.c b/tests/weston-test.c index ae08b02f5..9662b67ce 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -45,11 +45,15 @@ #include "shared/helpers.h" #include "shared/timespec-util.h" +#define MAX_TOUCH_DEVICES 32 + struct weston_test { struct weston_compositor *compositor; struct weston_layer layer; struct weston_process process; struct weston_seat seat; + struct weston_touch_device *touch_device[MAX_TOUCH_DEVICES]; + int nr_touch_devices; bool is_seat_initialized; }; @@ -77,6 +81,34 @@ test_client_sigchld(struct weston_process *process, int status) wl_display_terminate(test->compositor->wl_display); } +static void +touch_device_add(struct weston_test *test) +{ + char buf[128]; + int i = test->nr_touch_devices; + + assert(i < MAX_TOUCH_DEVICES); + assert(!test->touch_device[i]); + + snprintf(buf, sizeof buf, "test-touch-device-%d", i); + + test->touch_device[i] = weston_touch_create_touch_device( + test->seat.touch_state, buf, NULL, NULL); + test->nr_touch_devices++; +} + +static void +touch_device_remove(struct weston_test *test) +{ + int i = test->nr_touch_devices - 1; + + assert(i >= 0); + assert(test->touch_device[i]); + weston_touch_device_destroy(test->touch_device[i]); + test->touch_device[i] = NULL; + --test->nr_touch_devices; +} + static int test_seat_init(struct weston_test *test) { @@ -92,6 +124,7 @@ test_seat_init(struct weston_test *test) if (weston_seat_init_keyboard(&test->seat, NULL) < 0) return -1; weston_seat_init_touch(&test->seat); + touch_device_add(test); return 0; } @@ -99,6 +132,9 @@ test_seat_init(struct weston_test *test) static void test_seat_release(struct weston_test *test) { + while (test->nr_touch_devices > 0) + touch_device_remove(test); + assert(test->is_seat_initialized && "Trying to release already released test seat"); test->is_seat_initialized = false; @@ -281,6 +317,7 @@ device_release(struct wl_client *client, } else if (strcmp(device, "keyboard") == 0) { weston_seat_release_keyboard(seat); } else if (strcmp(device, "touch") == 0) { + touch_device_remove(test); weston_seat_release_touch(seat); } else if (strcmp(device, "seat") == 0) { test_seat_release(test); @@ -302,6 +339,7 @@ device_add(struct wl_client *client, weston_seat_init_keyboard(seat, NULL); } else if (strcmp(device, "touch") == 0) { weston_seat_init_touch(seat); + touch_device_add(test); } else if (strcmp(device, "seat") == 0) { test_seat_init(test); } else { From 09fbe14e8f396f70d9132df50f759003c67b3e90 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 18 Apr 2017 12:11:53 +0300 Subject: [PATCH 0510/1642] libinput: move calibration printing into do_set_calibration() Move calibration printing here and call do_set_calibration() from evdev_device_set_calibration() so that all matrix setting paths print the same way. Print the matrix values in a matrix style to help readability, and mention the input device. v2: - use 'cal' instead of 'calb' as variable name Signed-off-by: Pekka Paalanen v1 Tested-by: Matt Hoosier Reviewed-by: Peter Hutterer --- libweston/libinput-device.c | 51 +++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index 63f503190..1fc013350 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -346,10 +346,17 @@ do_set_calibration(struct evdev_device *evdev_device, { enum libinput_config_status status; + weston_log("input device %s: applying calibration:\n", + libinput_device_get_sysname(evdev_device->device)); + weston_log_continue(STAMP_SPACE " %f %f %f\n", + cal->m[0], cal->m[1], cal->m[2]); + weston_log_continue(STAMP_SPACE " %f %f %f\n", + cal->m[3], cal->m[4], cal->m[5]); + status = libinput_device_config_calibration_set_matrix(evdev_device->device, cal->m); if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) - weston_log("Failed to apply calibration.\n"); + weston_log("Error: Failed to apply calibration.\n"); } static void @@ -559,8 +566,7 @@ evdev_device_set_calibration(struct evdev_device *device) const char *sysname = libinput_device_get_sysname(device->device); const char *calibration_values; uint32_t width, height; - float calibration[6]; - enum libinput_config_status status; + struct weston_touch_device_matrix calibration; if (!libinput_device_config_calibration_has_matrix(device->device)) return; @@ -570,7 +576,7 @@ evdev_device_set_calibration(struct evdev_device *device) * output to load a calibration. */ if (libinput_device_config_calibration_get_default_matrix( device->device, - calibration) != 0) + calibration.m) != 0) return; /* touch_set_calibration() has updated the values, do not load old @@ -614,35 +620,26 @@ evdev_device_set_calibration(struct evdev_device *device) if (!calibration_values || sscanf(calibration_values, "%f %f %f %f %f %f", - &calibration[0], - &calibration[1], - &calibration[2], - &calibration[3], - &calibration[4], - &calibration[5]) != 6) + &calibration.m[0], + &calibration.m[1], + &calibration.m[2], + &calibration.m[3], + &calibration.m[4], + &calibration.m[5]) != 6) goto out; - weston_log("Applying calibration: %f %f %f %f %f %f " - "(normalized %f %f)\n", - calibration[0], - calibration[1], - calibration[2], - calibration[3], - calibration[4], - calibration[5], - calibration[2] / width, - calibration[5] / height); - /* normalize to a format libinput can use. There is a chance of this being wrong if the width/height don't match the device width/height but I'm not sure how to fix that */ - calibration[2] /= width; - calibration[5] /= height; + calibration.m[2] /= width; + calibration.m[5] /= height; - status = libinput_device_config_calibration_set_matrix(device->device, - calibration); - if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) - weston_log("Failed to apply calibration.\n"); + do_set_calibration(device, &calibration); + + weston_log_continue(STAMP_SPACE " raw translation %f %f for output %s\n", + calibration.m[2] * width, + calibration.m[5] * height, + device->output->name); out: if (udev_device) From bcbce33000a72fd8c466b0748d17c2ff173c49aa Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 26 Feb 2018 14:43:00 +0200 Subject: [PATCH 0511/1642] libweston: notify_touch API to use weston_touch_device Relay touch input events into libweston core through the weston_touch_device, so that the core can tell which individual physical device they come from. This is necessary for supporting touchscreen calibration, where one needs to process a single physical device at a time instead of the aggregate of all touch devices on the weston_seat. Signed-off-by: Pekka Paalanen v1 Tested-by: Matt Hoosier Reviewed-by: Peter Hutterer --- libweston/compositor-wayland.c | 10 +++++----- libweston/compositor.h | 6 +++--- libweston/input.c | 18 ++++++++---------- libweston/libinput-device.c | 7 +++---- tests/weston-test.c | 6 ++++-- 5 files changed, 23 insertions(+), 24 deletions(-) diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 5ed90c0f0..e80ecc16b 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -2146,7 +2146,7 @@ input_handle_touch_down(void *data, struct wl_touch *wl_touch, weston_output_transform_coordinate(&output->base, x, y, &x, &y); - notify_touch(&input->base, &ts, id, x, y, WL_TOUCH_DOWN); + notify_touch(input->touch_device, &ts, id, x, y, WL_TOUCH_DOWN); input->touch_active = true; } @@ -2187,7 +2187,7 @@ input_handle_touch_up(void *data, struct wl_touch *wl_touch, } if (active) - notify_touch(&input->base, &ts, id, 0, 0, WL_TOUCH_UP); + notify_touch(input->touch_device, &ts, id, 0, 0, WL_TOUCH_UP); } static void @@ -2216,7 +2216,7 @@ input_handle_touch_motion(void *data, struct wl_touch *wl_touch, weston_output_transform_coordinate(&output->base, x, y, &x, &y); - notify_touch(&input->base, &ts, id, x, y, WL_TOUCH_MOTION); + notify_touch(input->touch_device, &ts, id, x, y, WL_TOUCH_MOTION); } static void @@ -2227,7 +2227,7 @@ input_handle_touch_frame(void *data, struct wl_touch *wl_touch) if (!input->touch_focus || !input->touch_active) return; - notify_touch_frame(&input->base); + notify_touch_frame(input->touch_device); } static void @@ -2238,7 +2238,7 @@ input_handle_touch_cancel(void *data, struct wl_touch *wl_touch) if (!input->touch_focus || !input->touch_active) return; - notify_touch_cancel(&input->base); + notify_touch_cancel(input->touch_device); } static const struct wl_touch_listener touch_listener = { diff --git a/libweston/compositor.h b/libweston/compositor.h index 7f3fdb951..09a9dab87 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1543,13 +1543,13 @@ void notify_keyboard_focus_out(struct weston_seat *seat); void -notify_touch(struct weston_seat *seat, const struct timespec *time, +notify_touch(struct weston_touch_device *device, const struct timespec *time, int touch_id, double x, double y, int touch_type); void -notify_touch_frame(struct weston_seat *seat); +notify_touch_frame(struct weston_touch_device *device); void -notify_touch_cancel(struct weston_seat *seat); +notify_touch_cancel(struct weston_touch_device *device); void weston_layer_entry_insert(struct weston_layer_entry *list, diff --git a/libweston/input.c b/libweston/input.c index acb564e0c..e02b3060b 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -2347,12 +2347,12 @@ weston_touch_set_focus(struct weston_touch *touch, struct weston_view *view) * */ WL_EXPORT void -notify_touch(struct weston_seat *seat, const struct timespec *time, +notify_touch(struct weston_touch_device *device, const struct timespec *time, int touch_id, double double_x, double double_y, int touch_type) { - struct weston_compositor *ec = seat->compositor; - struct weston_touch *touch = weston_seat_get_touch(seat); - struct weston_touch_grab *grab = touch->grab; + struct weston_touch *touch = device->aggregate; + struct weston_touch_grab *grab = device->aggregate->grab; + struct weston_compositor *ec = device->aggregate->seat->compositor; struct weston_view *ev; wl_fixed_t sx, sy; wl_fixed_t x = wl_fixed_from_double(double_x); @@ -2426,19 +2426,17 @@ notify_touch(struct weston_seat *seat, const struct timespec *time, } WL_EXPORT void -notify_touch_frame(struct weston_seat *seat) +notify_touch_frame(struct weston_touch_device *device) { - struct weston_touch *touch = weston_seat_get_touch(seat); - struct weston_touch_grab *grab = touch->grab; + struct weston_touch_grab *grab = device->aggregate->grab; grab->interface->frame(grab); } WL_EXPORT void -notify_touch_cancel(struct weston_seat *seat) +notify_touch_cancel(struct weston_touch_device *device) { - struct weston_touch *touch = weston_seat_get_touch(seat); - struct weston_touch_grab *grab = touch->grab; + struct weston_touch_grab *grab = device->aggregate->grab; grab->interface->cancel(grab); } diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index 1fc013350..dda28fdc5 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -438,7 +438,7 @@ handle_touch_with_coords(struct libinput_device *libinput_device, weston_output_transform_coordinate(device->output, x, y, &x, &y); - notify_touch(device->seat, &time, slot, x, y, touch_type); + notify_touch(device->touch_device, &time, slot, x, y, touch_type); } static void @@ -467,7 +467,7 @@ handle_touch_up(struct libinput_device *libinput_device, timespec_from_usec(&time, libinput_event_touch_get_time_usec(touch_event)); - notify_touch(device->seat, &time, slot, 0, 0, WL_TOUCH_UP); + notify_touch(device->touch_device, &time, slot, 0, 0, WL_TOUCH_UP); } static void @@ -476,9 +476,8 @@ handle_touch_frame(struct libinput_device *libinput_device, { struct evdev_device *device = libinput_device_get_user_data(libinput_device); - struct weston_seat *seat = device->seat; - notify_touch_frame(seat); + notify_touch_frame(device->touch_device); } int diff --git a/tests/weston-test.c b/tests/weston-test.c index 9662b67ce..412eb243c 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -595,12 +595,14 @@ send_touch(struct wl_client *client, struct wl_resource *resource, int32_t touch_id, wl_fixed_t x, wl_fixed_t y, uint32_t touch_type) { struct weston_test *test = wl_resource_get_user_data(resource); - struct weston_seat *seat = get_seat(test); + struct weston_touch_device *device = test->touch_device[0]; struct timespec time; + assert(device); + timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); - notify_touch(seat, &time, touch_id, wl_fixed_to_double(x), + notify_touch(device, &time, touch_id, wl_fixed_to_double(x), wl_fixed_to_double(y), touch_type); } From f406253d3ce8378b7bbeb6cb17d4a887e16d0450 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 26 Feb 2018 16:18:29 +0200 Subject: [PATCH 0512/1642] libweston: introduce notify_touch_normalized() and doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit notify_touch_normalized() is an extended form of notify_touch(), adding normalized touch coordinates which are necessary for calibrating a touchscreen. It would be possible to invert the transformation and convert from global coordinates to normalized device coordinates in input.c without adding this API, but this way it is more robust against code changes. Recovering normalized device coordinates is necessary because libinput calibration matrix must be given in normalized units, and it would be difficult to compute otherwise. Libinput API does not offer normalized coordinates directly either, but those can be fetched by pretending the output resolution is 1x1. Anticipating touch calibration mode, the old notify_touch() is renamed into a private process_touch_normal(), and the new notify_touch_normalized() delegates to it. Co-developed by Louis-Francis and Pekka. v2: - introduce struct weston_point2d_device_normalized - rename notify_touch_cal() to notify_touch_normalized() - remove WESTON_INVALID_TOUCH_COORDINATE Cc: Louis-Francis Ratté-Boulianne Signed-off-by: Pekka Paalanen v1 Tested-by: Matt Hoosier Reviewed-by: Peter Hutterer --- libweston/compositor.h | 24 +++++++++++++++- libweston/input.c | 57 ++++++++++++++++++++++++++++++------- libweston/libinput-device.c | 10 ++++++- 3 files changed, 78 insertions(+), 13 deletions(-) diff --git a/libweston/compositor.h b/libweston/compositor.h index 09a9dab87..083c154d3 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -64,6 +64,12 @@ struct weston_transform { struct wl_list link; }; +/** 2D device coordinates normalized to [0, 1] range */ +struct weston_point2d_device_normalized { + double x; + double y; +}; + struct weston_surface; struct weston_buffer; struct shell_surface; @@ -1543,8 +1549,24 @@ void notify_keyboard_focus_out(struct weston_seat *seat); void +notify_touch_normalized(struct weston_touch_device *device, + const struct timespec *time, + int touch_id, + double x, double y, + const struct weston_point2d_device_normalized *norm, + int touch_type); + +/** Feed in touch down, motion, and up events, non-calibratable device. + * + * @sa notify_touch_cal + */ +static inline void notify_touch(struct weston_touch_device *device, const struct timespec *time, - int touch_id, double x, double y, int touch_type); + int touch_id, double x, double y, int touch_type) +{ + notify_touch_normalized(device, time, touch_id, x, y, NULL, touch_type); +} + void notify_touch_frame(struct weston_touch_device *device); diff --git a/libweston/input.c b/libweston/input.c index e02b3060b..a58382256 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -2338,17 +2338,10 @@ weston_touch_set_focus(struct weston_touch *touch, struct weston_view *view) touch->focus = view; } -/** - * notify_touch - emulates button touches and notifies surfaces accordingly. - * - * It assumes always the correct cycle sequence until it gets here: touch_down - * → touch_update → ... → touch_update → touch_end. The driver is responsible - * for sending along such order. - * - */ -WL_EXPORT void -notify_touch(struct weston_touch_device *device, const struct timespec *time, - int touch_id, double double_x, double double_y, int touch_type) +static void +process_touch_normal(struct weston_touch_device *device, + const struct timespec *time, int touch_id, + double double_x, double double_y, int touch_type) { struct weston_touch *touch = device->aggregate; struct weston_touch_grab *grab = device->aggregate->grab; @@ -2425,6 +2418,48 @@ notify_touch(struct weston_touch_device *device, const struct timespec *time, } } +/** Feed in touch down, motion, and up events, calibratable device. + * + * It assumes always the correct cycle sequence until it gets here: touch_down + * → touch_update → ... → touch_update → touch_end. The driver is responsible + * for sending along such order. + * + * \param device The physical device that generated the event. + * \param time The event timestamp. + * \param touch_id ID for the touch point of this event (multi-touch). + * \param double_x X coordinate in compositor global space. + * \param double_y Y coordinate in compositor global space. + * \param norm Normalized device X, Y coordinates in calibration space, or NULL. + * \param touch_type Either WL_TOUCH_DOWN, WL_TOUCH_UP, or WL_TOUCH_MOTION. + * + * Coordinates double_x and double_y are used for normal operation. + * + * Coordinates norm are only used for touch device calibration. If and only if + * the weston_touch_device does not support calibrating, norm must be NULL. + * + * The calibration space is the normalized coordinate space + * [0.0, 1.0]×[0.0, 1.0] of the weston_touch_device. This is assumed to + * map to the similar normalized coordinate space of the associated + * weston_output. + */ +WL_EXPORT void +notify_touch_normalized(struct weston_touch_device *device, + const struct timespec *time, + int touch_id, + double x, double y, + const struct weston_point2d_device_normalized *norm, + int touch_type) +{ + if (touch_type != WL_TOUCH_UP) { + if (weston_touch_device_can_calibrate(device)) + assert(norm != NULL); + else + assert(norm == NULL); + } + + process_touch_normal(device, time, touch_id, x, y, touch_type); +} + WL_EXPORT void notify_touch_frame(struct weston_touch_device *device) { diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index dda28fdc5..e25df1440 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -419,6 +419,7 @@ handle_touch_with_coords(struct libinput_device *libinput_device, libinput_device_get_user_data(libinput_device); double x; double y; + struct weston_point2d_device_normalized norm; uint32_t width, height; struct timespec time; int32_t slot; @@ -438,7 +439,14 @@ handle_touch_with_coords(struct libinput_device *libinput_device, weston_output_transform_coordinate(device->output, x, y, &x, &y); - notify_touch(device->touch_device, &time, slot, x, y, touch_type); + if (weston_touch_device_can_calibrate(device->touch_device)) { + norm.x = libinput_event_touch_get_x_transformed(touch_event, 1); + norm.y = libinput_event_touch_get_y_transformed(touch_event, 1); + notify_touch_normalized(device->touch_device, &time, slot, + x, y, &norm, touch_type); + } else { + notify_touch(device->touch_device, &time, slot, x, y, touch_type); + } } static void From 813a06e4558f397aec11e457fe7a1d683a8b3bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= Date: Tue, 28 Nov 2017 20:42:47 -0500 Subject: [PATCH 0513/1642] input: move touchpoint counting up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The touchpoint counting is needed regardless of what we do with the touch events, so move it out of process_touch_normal() into the caller notify_touch_normalized(). This is pure refactoring. Signed-off-by: Louis-Francis Ratté-Boulianne Signed-off-by: Pekka Paalanen v1 Tested-by: Matt Hoosier Reviewed-by: Peter Hutterer --- libweston/input.c | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/libweston/input.c b/libweston/input.c index a58382256..6816cc32d 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -2359,10 +2359,6 @@ process_touch_normal(struct weston_touch_device *device, switch (touch_type) { case WL_TOUCH_DOWN: - weston_compositor_idle_inhibit(ec); - - touch->num_tp++; - /* the first finger down picks the view, and all further go * to that view for the remainder of the touch session i.e. * until all touch points are up again. */ @@ -2400,17 +2396,6 @@ process_touch_normal(struct weston_touch_device *device, grab->interface->motion(grab, time, touch_id, x, y); break; case WL_TOUCH_UP: - if (touch->num_tp == 0) { - /* This can happen if we start out with one or - * more fingers on the touch screen, in which - * case we didn't get the corresponding down - * event. */ - weston_log("unmatched touch up event\n"); - break; - } - weston_compositor_idle_release(ec); - touch->num_tp--; - grab->interface->up(grab, time, touch_id); if (touch->num_tp == 0) weston_touch_set_focus(touch, NULL); @@ -2450,6 +2435,9 @@ notify_touch_normalized(struct weston_touch_device *device, const struct weston_point2d_device_normalized *norm, int touch_type) { + struct weston_seat *seat = device->aggregate->seat; + struct weston_touch *touch = device->aggregate; + if (touch_type != WL_TOUCH_UP) { if (weston_touch_device_can_calibrate(device)) assert(norm != NULL); @@ -2457,6 +2445,30 @@ notify_touch_normalized(struct weston_touch_device *device, assert(norm == NULL); } + /* Update touchpoints count regardless of the current mode. */ + switch (touch_type) { + case WL_TOUCH_DOWN: + weston_compositor_idle_inhibit(seat->compositor); + + touch->num_tp++; + break; + case WL_TOUCH_UP: + if (touch->num_tp == 0) { + /* This can happen if we start out with one or + * more fingers on the touch screen, in which + * case we didn't get the corresponding down + * event. */ + weston_log("unmatched touch up event\n"); + break; + } + weston_compositor_idle_release(seat->compositor); + + touch->num_tp--; + break; + default: + break; + } + process_touch_normal(device, time, touch_id, x, y, touch_type); } From 332a45e0966b2dae9bfadc136345590fc1d4ab35 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 2 Mar 2018 14:20:59 +0200 Subject: [PATCH 0514/1642] input: do not forward unmatched touch-ups Commit a30e29af2e4d0ad6fc476ae7cc13c4cad5119217 introduced the code to deal with a touchscreen with touches already down when Weston starts using it. It fixed the touchpoint counting problem. However, Weston still should not forward or process the unmatched touch-ups either. Code inspection says it would confuse the idle-inhibit counting, and it could probably confuse clients as well. Hence, just drop unmatched touch-ups. Enhance the warning message to allow identifying where the event came from. v2: - use syspath instead of devpath Signed-off-by: Pekka Paalanen v1 Reviewed-by: Peter Hutterer v1 Tested-by: Matt Hoosier Reviewed-by: Peter Hutterer --- libweston/input.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libweston/input.c b/libweston/input.c index 6816cc32d..5bea691a5 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -2458,8 +2458,9 @@ notify_touch_normalized(struct weston_touch_device *device, * more fingers on the touch screen, in which * case we didn't get the corresponding down * event. */ - weston_log("unmatched touch up event\n"); - break; + weston_log("Unmatched touch up event on seat %s, device %s\n", + seat->seat_name, device->syspath); + return; } weston_compositor_idle_release(seat->compositor); From c4689ff1d06792b56a004a3b3fcb056813294ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= Date: Tue, 28 Nov 2017 20:42:47 -0500 Subject: [PATCH 0515/1642] input: introduce touch event mode for calibrator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In addition to the normal touch event processing mode, introduce a new mode for calibrating a touchscreen input device. In the calibration mode, normal touch event processing is skipped, and the raw events are forwarded to the calibrator instead. The calibrator is not yet implemented, so the calls will be added in a following patch. To switch between modes, two functions are added, one for entering each mode. The mode switch happens only when no touches are down on any touch device, to avoid confusing touch grabs and clients. To realise this, the state machine has four states: prepare and actual state for both normal and calibrator modes. At this point nothing will attempt to change the touch event mode. The new calibrator mode is necessary, because when calibrating a touchscreen, the touch events must be routed to the calibration client directly. The touch coordinates are expected to be wrong, so they cannot go through the normal focus surface picking. The calibrator code also cannot use the normal touch grab interface, because it needs to be able to distinguish between different physical touch input devices, even if they are part of the same weston_seat. This requirement makes calibration special enough to warrant the new mode, a sort of "super grab". Co-developed by Louis-Francis and Pekka. Signed-off-by: Louis-Francis Ratté-Boulianne Signed-off-by: Pekka Paalanen v1 Tested-by: Matt Hoosier Reviewed-by: Peter Hutterer --- libweston/compositor.c | 2 + libweston/compositor.h | 39 +++++++++++ libweston/input.c | 146 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 182 insertions(+), 5 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 619e88b3a..101096cdb 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6334,6 +6334,8 @@ weston_compositor_create(struct wl_display *display, void *user_data) ec->activate_serial = 1; + ec->touch_mode = WESTON_TOUCH_MODE_NORMAL; + if (!wl_global_create(ec->wl_display, &wl_compositor_interface, 4, ec, compositor_bind)) goto fail; diff --git a/libweston/compositor.h b/libweston/compositor.h index 083c154d3..67d4db7cc 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -489,6 +489,34 @@ struct weston_touch_device_ops { const struct weston_touch_device_matrix *cal); }; +enum weston_touch_mode { + /** Normal touch event handling */ + WESTON_TOUCH_MODE_NORMAL, + + /** Prepare moving to WESTON_TOUCH_MODE_CALIB. + * + * Move to WESTON_TOUCH_MODE_CALIB as soon as no touches are down on + * any seat. Until then, all touch events are routed normally. + */ + WESTON_TOUCH_MODE_PREP_CALIB, + + /** Calibration mode + * + * Only a single weston_touch_device forwards events to the calibrator + * all other touch device cause a calibrator "wrong device" event to + * be sent. + */ + WESTON_TOUCH_MODE_CALIB, + + /** Prepare moving to WESTON_TOUCH_MODE_NORMAL. + * + * Move to WESTON_TOUCH_MODE_NORMAL as soon as no touches are down on + * any seat. Until then, touch events are routed as in + * WESTON_TOUCH_MODE_CALIB except "wrong device" events are not sent. + */ + WESTON_TOUCH_MODE_PREP_NORMAL +}; + /** Represents a physical touchscreen input device */ struct weston_touch_device { char *syspath; /**< unique name */ @@ -1092,6 +1120,8 @@ struct weston_compositor { */ struct wl_signal heads_changed_signal; struct wl_event_source *heads_changed_source; + + enum weston_touch_mode touch_mode; }; struct weston_buffer { @@ -1573,6 +1603,15 @@ notify_touch_frame(struct weston_touch_device *device); void notify_touch_cancel(struct weston_touch_device *device); +void +weston_compositor_set_touch_mode_normal(struct weston_compositor *compositor); + +void +weston_compositor_set_touch_mode_calib(struct weston_compositor *compositor); + +static inline void +touch_calibrator_mode_changed(struct weston_compositor *compositor) { /* stub */ } + void weston_layer_entry_insert(struct weston_layer_entry *list, struct weston_layer_entry *entry); diff --git a/libweston/input.c b/libweston/input.c index 5bea691a5..ba211f383 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -187,6 +187,12 @@ weston_touch_device_can_calibrate(struct weston_touch_device *device) return !!device->ops; } +static enum weston_touch_mode +weston_touch_device_get_mode(struct weston_touch_device *device) +{ + return device->aggregate->seat->compositor->touch_mode; +} + static struct weston_pointer_client * weston_pointer_client_create(struct wl_client *client) { @@ -2403,6 +2409,105 @@ process_touch_normal(struct weston_touch_device *device, } } +static enum weston_touch_mode +get_next_touch_mode(enum weston_touch_mode from) +{ + switch (from) { + case WESTON_TOUCH_MODE_PREP_NORMAL: + return WESTON_TOUCH_MODE_NORMAL; + + case WESTON_TOUCH_MODE_PREP_CALIB: + return WESTON_TOUCH_MODE_CALIB; + + case WESTON_TOUCH_MODE_NORMAL: + case WESTON_TOUCH_MODE_CALIB: + return from; + } + + return WESTON_TOUCH_MODE_NORMAL; +} + +/** Global touch mode update + * + * If no seat has a touch down and the compositor is in a PREP touch mode, + * set the compositor to the goal touch mode. + * + * Calls calibrator if touch mode changed. + */ +static void +weston_compositor_update_touch_mode(struct weston_compositor *compositor) +{ + struct weston_seat *seat; + struct weston_touch *touch; + enum weston_touch_mode goal; + + wl_list_for_each(seat, &compositor->seat_list, link) { + touch = weston_seat_get_touch(seat); + if (!touch) + continue; + + if (touch->num_tp > 0) + return; + } + + goal = get_next_touch_mode(compositor->touch_mode); + if (compositor->touch_mode != goal) { + compositor->touch_mode = goal; + touch_calibrator_mode_changed(compositor); + } +} + +/** Start transition to normal touch event handling + * + * The touch event mode changes when all touches on all touch devices have + * been lifted. If no touches are currently down, the transition is immediate. + * + * \sa weston_touch_mode + */ +void +weston_compositor_set_touch_mode_normal(struct weston_compositor *compositor) +{ + switch (compositor->touch_mode) { + case WESTON_TOUCH_MODE_PREP_NORMAL: + case WESTON_TOUCH_MODE_NORMAL: + return; + case WESTON_TOUCH_MODE_PREP_CALIB: + compositor->touch_mode = WESTON_TOUCH_MODE_NORMAL; + touch_calibrator_mode_changed(compositor); + return; + case WESTON_TOUCH_MODE_CALIB: + compositor->touch_mode = WESTON_TOUCH_MODE_PREP_NORMAL; + } + + weston_compositor_update_touch_mode(compositor); +} + +/** Start transition to calibrator touch event handling + * + * The touch event mode changes when all touches on all touch devices have + * been lifted. If no touches are currently down, the transition is immediate. + * + * \sa weston_touch_mode + */ +void +weston_compositor_set_touch_mode_calib(struct weston_compositor *compositor) +{ + switch (compositor->touch_mode) { + case WESTON_TOUCH_MODE_PREP_CALIB: + case WESTON_TOUCH_MODE_CALIB: + assert(0); + return; + case WESTON_TOUCH_MODE_PREP_NORMAL: + compositor->touch_mode = WESTON_TOUCH_MODE_CALIB; + touch_calibrator_mode_changed(compositor); + return; + case WESTON_TOUCH_MODE_NORMAL: + compositor->touch_mode = WESTON_TOUCH_MODE_PREP_CALIB; + } + + weston_compositor_update_touch_mode(compositor); +} + /** Feed in touch down, motion, and up events, calibratable device. * * It assumes always the correct cycle sequence until it gets here: touch_down @@ -2470,23 +2575,54 @@ notify_touch_normalized(struct weston_touch_device *device, break; } - process_touch_normal(device, time, touch_id, x, y, touch_type); + /* Properly forward the touch event */ + switch (weston_touch_device_get_mode(device)) { + case WESTON_TOUCH_MODE_NORMAL: + case WESTON_TOUCH_MODE_PREP_CALIB: + process_touch_normal(device, time, touch_id, x, y, touch_type); + break; + case WESTON_TOUCH_MODE_CALIB: + case WESTON_TOUCH_MODE_PREP_NORMAL: + break; + } } WL_EXPORT void notify_touch_frame(struct weston_touch_device *device) { - struct weston_touch_grab *grab = device->aggregate->grab; + struct weston_touch_grab *grab; - grab->interface->frame(grab); + switch (weston_touch_device_get_mode(device)) { + case WESTON_TOUCH_MODE_NORMAL: + case WESTON_TOUCH_MODE_PREP_CALIB: + grab = device->aggregate->grab; + grab->interface->frame(grab); + break; + case WESTON_TOUCH_MODE_CALIB: + case WESTON_TOUCH_MODE_PREP_NORMAL: + break; + } + + weston_compositor_update_touch_mode(device->aggregate->seat->compositor); } WL_EXPORT void notify_touch_cancel(struct weston_touch_device *device) { - struct weston_touch_grab *grab = device->aggregate->grab; + struct weston_touch_grab *grab; + + switch (weston_touch_device_get_mode(device)) { + case WESTON_TOUCH_MODE_NORMAL: + case WESTON_TOUCH_MODE_PREP_CALIB: + grab = device->aggregate->grab; + grab->interface->cancel(grab); + break; + case WESTON_TOUCH_MODE_CALIB: + case WESTON_TOUCH_MODE_PREP_NORMAL: + break; + } - grab->interface->cancel(grab); + weston_compositor_update_touch_mode(device->aggregate->seat->compositor); } static int From 999876a8f98791f92badd89b5b62481bf6dfcb4a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 22 Nov 2017 17:25:12 +0200 Subject: [PATCH 0516/1642] protocol: add weston_touch_calibration This is a Wayland protocol extension to allow the calibration of touchscreens in Weston. See: https://phabricator.freedesktop.org/T7868 v2: - replace "server" with "compositor" - rephrase error conditions to be simpler - reword the matrix description in 'save' request - rephrase when touch_device events are sent - change device id to DEVPATH with "/sys" prefix - qualify calibration units better - replace wrong_touch event with a more generic invalid_touch - fix error enum and add bad_coordinates - convert while cancelled will not raise any errors Signed-off-by: Pekka Paalanen v1 Tested-by: Matt Hoosier Reviewed-by: Peter Hutterer --- Makefile.am | 5 +- protocol/weston-touch-calibration.xml | 342 ++++++++++++++++++++++++++ 2 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 protocol/weston-touch-calibration.xml diff --git a/Makefile.am b/Makefile.am index d9c03fef1..30bced631 100644 --- a/Makefile.am +++ b/Makefile.am @@ -168,7 +168,9 @@ nodist_libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ protocol/pointer-constraints-unstable-v1-protocol.c \ protocol/pointer-constraints-unstable-v1-server-protocol.h \ protocol/input-timestamps-unstable-v1-protocol.c \ - protocol/input-timestamps-unstable-v1-server-protocol.h + protocol/input-timestamps-unstable-v1-server-protocol.h \ + protocol/weston-touch-calibration-protocol.c \ + protocol/weston-touch-calibration-server-protocol.h BUILT_SOURCES += $(nodist_libweston_@LIBWESTON_MAJOR@_la_SOURCES) @@ -1545,6 +1547,7 @@ EXTRA_DIST += \ protocol/weston-screenshooter.xml \ protocol/text-cursor-position.xml \ protocol/weston-test.xml \ + protocol/weston-touch-calibration.xml \ protocol/ivi-application.xml \ protocol/ivi-hmi-controller.xml diff --git a/protocol/weston-touch-calibration.xml b/protocol/weston-touch-calibration.xml new file mode 100644 index 000000000..7e898c07f --- /dev/null +++ b/protocol/weston-touch-calibration.xml @@ -0,0 +1,342 @@ + + + + + Copyright 2017-2018 Collabora, Ltd. + Copyright 2017-2018 General Electric Company + + 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. + + + + + This is the global interface for calibrating a touchscreen input + coordinate transformation. It is recommended to make this interface + privileged. + + This interface can be used by a client to show a calibration pattern and + receive uncalibrated touch coordinates, facilitating the computation of + a calibration transformation that will align actual touch positions + on screen with their expected coordinates. + + Immediately after being bound by a client, the compositor sends the + touch_device events. + + The client chooses a touch device from the touch_device events, creates a + wl_surface and then a weston_touch_calibrator for the wl_surface and the + chosen touch device. The client waits for the compositor to send a + configure event before it starts drawing the first calibration pattern. + After receiving the configure event, the client will iterate drawing a + pattern, getting touch input via weston_touch_calibrator, and converting + pixel coordinates to expected touch coordinates with + weston_touch_calibrator.convert until it has enough correspondences to + compute the calibration transformation or the compositor cancels the + calibration. + + Once the client has successfully computed a new calibration, it can use + weston_touch_calibration.save request to load the new calibration into + the compositor. The compositor may take this new calibration into use and + may write it into persistent storage. + + + + + + + + + + + + Destroy the binding to the global interface, does not affect any + objects already created through this interface. + + + + + + This gives the calibrator role to the surface and ties it with the + given touch input device. + + If the surface already has a role, then invalid_surface error is raised. + + If the device string is not one advertised with touch_device event's + device argument, then invalid_device error is raised. + + If a weston_touch_calibrator protocol object exists in the compositor + already, then already_exists error is raised. This limitation is + compositor-wide and not specific to any particular client. + + + + + + + + + This request asks the compositor to save the calibration data for the + given touch input device. The compositor may ignore this request. + + If the device string is not one advertised with touch_device event's + device argument, then invalid_device error is raised. + + The array must contain exactly six 'float' (the 32-bit floating + point format used by the C language on the system) numbers. For a 3x3 + calibration matrix in the form + @code + ( a b c ) + ( d e f ) + ( 0 0 1 ) + @endcode + the array must contain the values { a, b, c, d, e, f }. For the + definition of the coordinate spaces, see + libinput_device_config_calibration_set_matrix(). + + + + + + + + When a client binds to weston_touch_calibration, one touch_device event + is sent for each touchscreen that is available to be calibrated. This + is the only time the event is sent. Touch devices added in the + compositor will not generate events for existing + weston_touch_calibration objects. + + An event carries the touch device identification and the associated + output or head (display connector) name. + + On platforms using udev, the device identification is the udev sys + path. It is an absolute path and starts with the sys mount point. + + + + + + + + + On creation, this object is tied to a specific touch device. The + compositor sends a configure event which the client must obey with the + associated wl_surface. + + Once the client has committed content to the surface, the compositor can + grab the touch input device, prevent it from emitting normal touch + events, show the surface on the correct output, and relay input events + from the touch device via this protocol object. + + Touch events from other touch devices than the one tied to this object + must generate wrong_touch events on at least touch-down and must not + generate normal or calibration touch events. + + At any time, the compositor can choose to cancel the calibration + procedure by sending the cancel_calibration event. This should also be + used if the touch device disappears or anything else prevents the + calibration from continuing on the compositor side. + + If the wl_surface is destroyed, the compositor must cancel the + calibration. + + The touch event coordinates and conversion results are delivered in + calibration units. The calibration units cover the device coordinate + range exactly. Calibration units are in the closed interval [0.0, 1.0] + mapped into 32-bit unsigned integers. An integer can be converted into a + real value by dividing by 2^32-1. A calibration matrix must be computed + from the [0.0, 1.0] real values, but the matrix elements do not need to + fall into that range. + + + + + + + + + + + + This unmaps the surface if it was mapped. The input device grab + is dropped, if it was present. The surface loses its role as a + calibrator. + + + + + + This request asks the compositor to convert the surface-local + coordinates into the expected touch input coordinates appropriate for + the associated touch device. The intention is that a client uses this + request to convert marker positions that the user is supposed to touch + during calibration. + + If the compositor has cancelled the calibration, the conversion result + shall be zeroes and no errors will be raised. + + The coordinates given as arguments to this request are relative to + the associated wl_surface. + + If a client asks for conversion before it has committed valid + content to the wl_surface, the not_mapped error is raised. + + If the coordinates x, y are outside of the wl_surface content, the + bad_coordinates error is raised. + + + + + + + + + This event tells the client what size to make the surface. The client + must obey the size exactly on the next commit with a wl_buffer. + + This event shall be sent once as a response to creating a + weston_touch_calibrator object. + + + + + + + + This is sent when the compositor wants to cancel the calibration and + drop the touch device grab. The compositor unmaps the surface, if it + was mapped. + + The weston_touch_calibrator object will not send any more events. The + client should destroy it. + + + + + + For whatever reason, a touch event resulting from a user action cannot + be used for calibration. The client should show feedback to the user + that the touch was rejected. + + Possible causes for this event include the user touching a wrong + touchscreen when there are multiple ones present. This is particularly + useful when the touchscreens are cloned and there is no other way to + identify which screen the user should be touching. + + Another cause could be a touch device that sends coordinates beyond its + declared range. If motion takes a touch point outside the range, the + compositor should also send 'cancel' event to undo the touch-down. + + + + + + + A new touch point has appeared on the surface. This touch point is + assigned a unique ID. Future events from this touch point reference + this ID. The ID ceases to be valid after a touch up event and may be + reused in the future. + + For the coordinate units, see weston_touch_calibrator. + + + + + + + + + + The touch point has disappeared. No further events will be sent for + this touch point and the touch point's ID is released and may be + reused in a future touch down event. + + + + + + + + A touch point has changed coordinates. + + For the coordinate units, see weston_touch_calibrator. + + + + + + + + + + Indicates the end of a set of events that logically belong together. + A client is expected to accumulate the data in all events within the + frame before proceeding. + + A wl_touch.frame terminates at least one event but otherwise no + guarantee is provided about the set of events within a frame. A client + must assume that any state not updated in a frame is unchanged from the + previously known state. + + + + + + Sent if the compositor decides the touch stream is a global + gesture. No further events are sent to the clients from that + particular gesture. Touch cancellation applies to all touch points + currently active on this client's surface. The client is + responsible for finalizing the touch points, future touch points on + this surface may reuse the touch point ID. + + + + + + + + + + + + + + This event returns the conversion result from surface coordinates to + the expected touch device coordinates. + + For details, see weston_touch_calibrator.convert. For the coordinate + units, see weston_touch_calibrator. + + This event destroys the weston_touch_coordinate object. + + + + + + From 83630983ad5335d8a28a9a86ee9b0e9a682d6643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= Date: Tue, 28 Nov 2017 20:42:47 -0500 Subject: [PATCH 0517/1642] libweston: implement touch calibration protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This implements a new global interface weston_touch_calibration, which allows one client at a time to perform touchscreen calibration. This also implements the calibrator window management. A client asks to calibrate a specific physical touch device (not a wl_seat which may have several physical touch devices aggregated). Libweston grabs all touch devices and prevents normal touch event handling during the calibation sequence. API is added to enable this new global interface, but it not yet called by anything. Since the implementation allows clients to grab touch devices arbitrarily, it is not enabled by default. The compositor should take measures to prevent unexpected access to the interface. A client may upload a new calibration to the compositor. There is a vfunc to allow the compositor to reject/accept it and save it to persistent storage. The persistent storage could be a udev rule setting LIBINPUT_CALIBRATION_MATRIX, so that all display server would load the new calibration automatically. Co-developed by Louis-Francis and Pekka. v2: - use struct weston_point2d_device_normalized - use syspath instead of devpath - wrong_touch was renamed to invalid_touch - rename weston_touch_calibrator::cancelled to calibration_cancelled - send invalid_touch on out-of-bounds touch-down - cancel touch sequence and send invalid_touch on motion going out-of-bounds - rename calcoord_from_double() to wire_uint_from_double() - send bad_coordinates error in touch_calibrator_convert() - conversion results in 0,0 if cancelled Signed-off-by: Louis-Francis Ratté-Boulianne Signed-off-by: Pekka Paalanen v1 Tested-by: Matt Hoosier Reviewed-by: Peter Hutterer --- Makefile.am | 1 + libweston/compositor.h | 41 +- libweston/input.c | 4 + libweston/touch-calibration.c | 716 ++++++++++++++++++++++++++++++++++ 4 files changed, 760 insertions(+), 2 deletions(-) create mode 100644 libweston/touch-calibration.c diff --git a/Makefile.am b/Makefile.am index 30bced631..5260bd8a4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -81,6 +81,7 @@ libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ libweston/input.c \ libweston/data-device.c \ libweston/screenshooter.c \ + libweston/touch-calibration.c \ libweston/clipboard.c \ libweston/zoom.c \ libweston/bindings.c \ diff --git a/libweston/compositor.h b/libweston/compositor.h index 67d4db7cc..c2c40eeb1 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -528,6 +528,7 @@ struct weston_touch_device { void *backend_data; /**< backend-specific private */ const struct weston_touch_device_ops *ops; + struct weston_touch_device_matrix saved_calibration; }; /** Represents a set of touchscreen devices aggregated under a seat */ @@ -1013,6 +1014,21 @@ struct weston_backend { const char *name); }; +/** Callback for saving calibration + * + * \param compositor The compositor. + * \param device The physical touch device to save for. + * \param calibration The new calibration from a client. + * \return -1 on failure, 0 on success. + * + * Failure will prevent taking the new calibration into use. + */ +typedef int (*weston_touch_calibration_save_func)( + struct weston_compositor *compositor, + struct weston_touch_device *device, + const struct weston_touch_device_matrix *calibration); +struct weston_touch_calibrator; + struct weston_desktop_xwayland; struct weston_desktop_xwayland_interface; @@ -1121,7 +1137,12 @@ struct weston_compositor { struct wl_signal heads_changed_signal; struct wl_event_source *heads_changed_source; + /* Touchscreen calibrator support: */ enum weston_touch_mode touch_mode; + struct wl_global *touch_calibration; + weston_touch_calibration_save_func touch_calibration_save; + struct weston_layer calibrator_layer; + struct weston_touch_calibrator *touch_calibrator; }; struct weston_buffer { @@ -1609,8 +1630,20 @@ weston_compositor_set_touch_mode_normal(struct weston_compositor *compositor); void weston_compositor_set_touch_mode_calib(struct weston_compositor *compositor); -static inline void -touch_calibrator_mode_changed(struct weston_compositor *compositor) { /* stub */ } +void +touch_calibrator_mode_changed(struct weston_compositor *compositor); + +void +notify_touch_calibrator(struct weston_touch_device *device, + const struct timespec *time, int32_t slot, + const struct weston_point2d_device_normalized *norm, + int touch_type); + +void +notify_touch_calibrator_frame(struct weston_touch_device *device); + +void +notify_touch_calibrator_cancel(struct weston_touch_device *device); void weston_layer_entry_insert(struct weston_layer_entry *list, @@ -2257,6 +2290,10 @@ weston_head_from_resource(struct wl_resource *resource); struct weston_head * weston_output_get_first_head(struct weston_output *output); +int +weston_compositor_enable_touch_calibrator(struct weston_compositor *compositor, + weston_touch_calibration_save_func save); + #ifdef __cplusplus } #endif diff --git a/libweston/input.c b/libweston/input.c index ba211f383..04c114199 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -2583,6 +2583,8 @@ notify_touch_normalized(struct weston_touch_device *device, break; case WESTON_TOUCH_MODE_CALIB: case WESTON_TOUCH_MODE_PREP_NORMAL: + notify_touch_calibrator(device, time, touch_id, + norm, touch_type); break; } } @@ -2600,6 +2602,7 @@ notify_touch_frame(struct weston_touch_device *device) break; case WESTON_TOUCH_MODE_CALIB: case WESTON_TOUCH_MODE_PREP_NORMAL: + notify_touch_calibrator_frame(device); break; } @@ -2619,6 +2622,7 @@ notify_touch_cancel(struct weston_touch_device *device) break; case WESTON_TOUCH_MODE_CALIB: case WESTON_TOUCH_MODE_PREP_NORMAL: + notify_touch_calibrator_cancel(device); break; } diff --git a/libweston/touch-calibration.c b/libweston/touch-calibration.c new file mode 100644 index 000000000..6e09fe022 --- /dev/null +++ b/libweston/touch-calibration.c @@ -0,0 +1,716 @@ +/* + * Copyright 2017-2018 Collabora, Ltd. + * Copyright 2017-2018 General Electric Company + * + * 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 "shared/helpers.h" +#include "shared/string-helpers.h" +#include "shared/zalloc.h" +#include "shared/timespec-util.h" +#include "compositor.h" + +#include "weston-touch-calibration-server-protocol.h" + +struct weston_touch_calibrator { + struct wl_resource *resource; + + struct weston_compositor *compositor; + + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; + struct wl_listener surface_commit_listener; + + struct weston_touch_device *device; + struct wl_listener device_destroy_listener; + + struct weston_output *output; + struct wl_listener output_destroy_listener; + + struct weston_view *view; + + /** The calibration procedure has been cancelled. */ + bool calibration_cancelled; + + /** The current touch sequence has been cancelled. */ + bool touch_cancelled; +}; + +static struct weston_touch_calibrator * +calibrator_from_device(struct weston_touch_device *device) +{ + return device->aggregate->seat->compositor->touch_calibrator; +} + +static uint32_t +wire_uint_from_double(double c) +{ + assert(c >= 0.0); + assert(c <= 1.0); + + return round(c * 0xffffffff); +} + +static bool +normalized_is_valid(const struct weston_point2d_device_normalized *p) +{ + return p->x >= 0.0 && p->x <= 1.0 && + p->y >= 0.0 && p->y <= 1.0; +} + +WL_EXPORT void +notify_touch_calibrator(struct weston_touch_device *device, + const struct timespec *time, int32_t slot, + const struct weston_point2d_device_normalized *norm, + int touch_type) +{ + struct weston_touch_calibrator *calibrator; + struct wl_resource *res; + uint32_t msecs; + uint32_t x = 0; + uint32_t y = 0; + + calibrator = calibrator_from_device(device); + if (!calibrator) + return; + + res = calibrator->resource; + + /* Ignore any touch events coming from another device */ + if (device != calibrator->device) { + if (touch_type == WL_TOUCH_DOWN) + weston_touch_calibrator_send_invalid_touch(res); + return; + } + + /* Ignore all events if we have sent 'cancel' event until all + * touches (on the seat) are up. + */ + if (calibrator->touch_cancelled) { + if (calibrator->device->aggregate->num_tp == 0) { + assert(touch_type == WL_TOUCH_UP); + calibrator->touch_cancelled = false; + } + return; + } + + msecs = timespec_to_msec(time); + if (touch_type != WL_TOUCH_UP) { + if (normalized_is_valid(norm)) { + x = wire_uint_from_double(norm->x); + y = wire_uint_from_double(norm->y); + } else { + /* Coordinates are out of bounds */ + if (touch_type == WL_TOUCH_MOTION) { + weston_touch_calibrator_send_cancel(res); + calibrator->touch_cancelled = true; + } + weston_touch_calibrator_send_invalid_touch(res); + return; + } + } + + switch (touch_type) { + case WL_TOUCH_UP: + weston_touch_calibrator_send_up(res, msecs, slot); + break; + case WL_TOUCH_DOWN: + weston_touch_calibrator_send_down(res, msecs, slot, x, y); + break; + case WL_TOUCH_MOTION: + weston_touch_calibrator_send_motion(res, msecs, slot, x, y); + break; + default: + return; + } +} + +WL_EXPORT void +notify_touch_calibrator_frame(struct weston_touch_device *device) +{ + struct weston_touch_calibrator *calibrator; + + calibrator = calibrator_from_device(device); + if (!calibrator) + return; + + weston_touch_calibrator_send_frame(calibrator->resource); +} + +WL_EXPORT void +notify_touch_calibrator_cancel(struct weston_touch_device *device) +{ + struct weston_touch_calibrator *calibrator; + + calibrator = calibrator_from_device(device); + if (!calibrator) + return; + + weston_touch_calibrator_send_cancel(calibrator->resource); +} + +static void +map_calibrator(struct weston_touch_calibrator *calibrator) +{ + struct weston_compositor *c = calibrator->compositor; + struct weston_touch_device *device = calibrator->device; + static const struct weston_touch_device_matrix identity = { + .m = { 1, 0, 0, 0, 1, 0} + }; + + assert(!calibrator->view); + assert(calibrator->output); + assert(calibrator->surface); + assert(calibrator->surface->resource); + + calibrator->view = weston_view_create(calibrator->surface); + if (!calibrator->view) { + wl_resource_post_no_memory(calibrator->surface->resource); + return; + } + + weston_layer_entry_insert(&c->calibrator_layer.view_list, + &calibrator->view->layer_link); + + weston_view_set_position(calibrator->view, + calibrator->output->x, + calibrator->output->y); + calibrator->view->output = calibrator->surface->output; + calibrator->view->is_mapped = true; + + calibrator->surface->output = calibrator->output; + calibrator->surface->is_mapped = true; + + weston_output_schedule_repaint(calibrator->output); + + device->ops->get_calibration(device, &device->saved_calibration); + device->ops->set_calibration(device, &identity); +} + +static void +unmap_calibrator(struct weston_touch_calibrator *calibrator) +{ + struct weston_touch_device *device = calibrator->device; + + wl_list_remove(&calibrator->surface_commit_listener.link); + wl_list_init(&calibrator->surface_commit_listener.link); + + if (!calibrator->view) + return; + + weston_view_destroy(calibrator->view); + calibrator->view = NULL; + weston_surface_unmap(calibrator->surface); + + /* Reload saved calibration */ + if (device) + device->ops->set_calibration(device, + &device->saved_calibration); +} + +void +touch_calibrator_mode_changed(struct weston_compositor *compositor) +{ + struct weston_touch_calibrator *calibrator; + + calibrator = compositor->touch_calibrator; + if (!calibrator) + return; + + if (calibrator->calibration_cancelled) + return; + + if (compositor->touch_mode == WESTON_TOUCH_MODE_CALIB) + map_calibrator(calibrator); +} + +static void +touch_calibrator_surface_committed(struct wl_listener *listener, void *data) +{ + struct weston_touch_calibrator *calibrator = + container_of(listener, struct weston_touch_calibrator, + surface_commit_listener); + struct weston_surface *surface = calibrator->surface; + + wl_list_remove(&calibrator->surface_commit_listener.link); + wl_list_init(&calibrator->surface_commit_listener.link); + + if (surface->width != calibrator->output->width || + surface->height != calibrator->output->height) { + wl_resource_post_error(calibrator->resource, + WESTON_TOUCH_CALIBRATOR_ERROR_BAD_SIZE, + "calibrator surface size does not match"); + return; + } + + weston_compositor_set_touch_mode_calib(calibrator->compositor); + /* results in call to touch_calibrator_mode_changed() */ +} + +static void +touch_calibrator_convert(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y, + uint32_t coordinate_id) +{ + struct weston_touch_calibrator *calibrator; + struct wl_resource *coordinate_resource; + struct weston_output *output; + struct weston_surface *surface; + uint32_t version; + struct weston_vector p = { { 0.0, 0.0, 0.0, 1.0 } }; + struct weston_point2d_device_normalized norm; + + version = wl_resource_get_version(resource); + calibrator = wl_resource_get_user_data(resource); + surface = calibrator->surface; + output = calibrator->output; + + coordinate_resource = + wl_resource_create(client, &weston_touch_coordinate_interface, + version, coordinate_id); + if (!coordinate_resource) { + wl_client_post_no_memory(client); + return; + } + + if (calibrator->calibration_cancelled) { + weston_touch_coordinate_send_result(coordinate_resource, 0, 0); + wl_resource_destroy(coordinate_resource); + return; + } + + if (!surface || !surface->is_mapped) { + wl_resource_post_error(resource, + WESTON_TOUCH_CALIBRATOR_ERROR_NOT_MAPPED, + "calibrator surface is not mapped"); + return; + } + assert(calibrator->view); + assert(output); + + if (x < 0 || y < 0 || x >= surface->width || y >= surface->height) { + wl_resource_post_error(resource, + WESTON_TOUCH_CALIBRATOR_ERROR_BAD_COORDINATES, + "convert(%d, %d) input is out of bounds", + x, y); + return; + } + + /* Convert from surface-local coordinates into global, from global + * into output-raw, do perspective division and normalize. + */ + weston_view_to_global_float(calibrator->view, x, y, &p.f[0], &p.f[1]); + weston_matrix_transform(&output->matrix, &p); + norm.x = p.f[0] / (p.f[3] * output->current_mode->width); + norm.y = p.f[1] / (p.f[3] * output->current_mode->height); + + if (!normalized_is_valid(&norm)) { + wl_resource_post_error(resource, + WESTON_TOUCH_CALIBRATOR_ERROR_BAD_COORDINATES, + "convert(%d, %d) output is out of bounds", + x, y); + return; + } + + weston_touch_coordinate_send_result(coordinate_resource, + wire_uint_from_double(norm.x), + wire_uint_from_double(norm.y)); + wl_resource_destroy(coordinate_resource); +} + +static void +destroy_touch_calibrator(struct wl_resource *resource) +{ + struct weston_touch_calibrator *calibrator; + + calibrator = wl_resource_get_user_data(resource); + + calibrator->compositor->touch_calibrator = NULL; + + weston_compositor_set_touch_mode_normal(calibrator->compositor); + + if (calibrator->surface) { + unmap_calibrator(calibrator); + weston_surface_set_role(calibrator->surface, NULL, + calibrator->surface->resource, 0); + wl_list_remove(&calibrator->surface_destroy_listener.link); + wl_list_remove(&calibrator->surface_commit_listener.link); + } + + if (calibrator->device) + wl_list_remove(&calibrator->device_destroy_listener.link); + + if (calibrator->output) + wl_list_remove(&calibrator->output_destroy_listener.link); + + free(calibrator); +} + +static void +touch_calibrator_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct weston_touch_calibrator_interface +touch_calibrator_implementation = { + touch_calibrator_destroy, + touch_calibrator_convert +}; + +static void +touch_calibrator_cancel_calibration(struct weston_touch_calibrator *calibrator) +{ + weston_touch_calibrator_send_cancel_calibration(calibrator->resource); + calibrator->calibration_cancelled = true; + + if (calibrator->surface) + unmap_calibrator(calibrator); +} + +static void +touch_calibrator_output_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_touch_calibrator *calibrator = + container_of(listener, struct weston_touch_calibrator, + output_destroy_listener); + + assert(calibrator->output == data); + calibrator->output = NULL; + + touch_calibrator_cancel_calibration(calibrator); +} + +static void +touch_calibrator_device_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_touch_calibrator *calibrator = + container_of(listener, struct weston_touch_calibrator, + device_destroy_listener); + + assert(calibrator->device == data); + calibrator->device = NULL; + + touch_calibrator_cancel_calibration(calibrator); +} + +static void +touch_calibrator_surface_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_touch_calibrator *calibrator = + container_of(listener, struct weston_touch_calibrator, + surface_destroy_listener); + + assert(calibrator->surface->resource == data); + + unmap_calibrator(calibrator); + calibrator->surface = NULL; +} + +static void +touch_calibration_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static struct weston_touch_device * +weston_compositor_find_touch_device_by_syspath( + struct weston_compositor *compositor, + const char *syspath) +{ + struct weston_seat *seat; + struct weston_touch *touch; + struct weston_touch_device *device; + + if (!syspath) + return NULL; + + wl_list_for_each(seat, &compositor->seat_list, link) { + touch = weston_seat_get_touch(seat); + if (!touch) + continue; + + wl_list_for_each(device, &touch->device_list, link) { + if (strcmp(device->syspath, syspath) == 0) + return device; + } + } + + return NULL; +} + +static void +touch_calibration_create_calibrator( + struct wl_client *client, + struct wl_resource *touch_calibration_resource, + struct wl_resource *surface_resource, + const char *syspath, + uint32_t calibrator_id) +{ + struct weston_compositor *compositor; + struct weston_touch_calibrator *calibrator; + struct weston_touch_device *device; + struct weston_output *output = NULL; + struct weston_surface *surface; + uint32_t version; + int ret; + + version = wl_resource_get_version(touch_calibration_resource); + compositor = wl_resource_get_user_data(touch_calibration_resource); + + if (compositor->touch_calibrator != NULL) { + wl_resource_post_error(touch_calibration_resource, + WESTON_TOUCH_CALIBRATION_ERROR_ALREADY_EXISTS, + "a calibrator has already been created"); + return; + } + + calibrator = zalloc(sizeof *calibrator); + if (!calibrator) { + wl_client_post_no_memory(client); + return; + } + + calibrator->compositor = compositor; + calibrator->resource = wl_resource_create(client, + &weston_touch_calibrator_interface, + version, calibrator_id); + if (!calibrator->resource) { + wl_client_post_no_memory(client); + goto err_dealloc; + } + + surface = wl_resource_get_user_data(surface_resource); + assert(surface); + ret = weston_surface_set_role(surface, "weston_touch_calibrator", + touch_calibration_resource, + WESTON_TOUCH_CALIBRATION_ERROR_INVALID_SURFACE); + if (ret < 0) + goto err_destroy_resource; + + calibrator->surface_destroy_listener.notify = + touch_calibrator_surface_destroyed; + wl_resource_add_destroy_listener(surface->resource, + &calibrator->surface_destroy_listener); + calibrator->surface = surface; + + calibrator->surface_commit_listener.notify = + touch_calibrator_surface_committed; + wl_signal_add(&surface->commit_signal, + &calibrator->surface_commit_listener); + + device = weston_compositor_find_touch_device_by_syspath(compositor, + syspath); + if (device) { + output = device->ops->get_output(device); + if (weston_touch_device_can_calibrate(device) && output) + calibrator->device = device; + } + + if (!calibrator->device) { + wl_resource_post_error(touch_calibration_resource, + WESTON_TOUCH_CALIBRATION_ERROR_INVALID_DEVICE, + "the given touch device '%s' is not valid", + syspath ?: ""); + goto err_unlink_surface; + } + + calibrator->device_destroy_listener.notify = + touch_calibrator_device_destroyed; + wl_signal_add(&calibrator->device->destroy_signal, + &calibrator->device_destroy_listener); + + wl_resource_set_implementation(calibrator->resource, + &touch_calibrator_implementation, + calibrator, destroy_touch_calibrator); + + assert(output); + calibrator->output_destroy_listener.notify = + touch_calibrator_output_destroyed; + wl_signal_add(&output->destroy_signal, + &calibrator->output_destroy_listener); + calibrator->output = output; + + weston_touch_calibrator_send_configure(calibrator->resource, + output->width, + output->height); + + compositor->touch_calibrator = calibrator; + + return; + +err_unlink_surface: + wl_list_remove(&calibrator->surface_commit_listener.link); + wl_list_remove(&calibrator->surface_destroy_listener.link); + +err_destroy_resource: + wl_resource_destroy(calibrator->resource); + +err_dealloc: + free(calibrator); +} + +static void +touch_calibration_save(struct wl_client *client, + struct wl_resource *touch_calibration_resource, + const char *device_name, + struct wl_array *matrix_data) +{ + struct weston_touch_device *device; + struct weston_compositor *compositor; + struct weston_touch_device_matrix calibration; + struct weston_touch_calibrator *calibrator; + int i = 0; + float *c; + + compositor = wl_resource_get_user_data(touch_calibration_resource); + + device = weston_compositor_find_touch_device_by_syspath(compositor, + device_name); + if (!device || !weston_touch_device_can_calibrate(device)) { + wl_resource_post_error(touch_calibration_resource, + WESTON_TOUCH_CALIBRATION_ERROR_INVALID_DEVICE, + "the given device is not valid"); + return; + } + + wl_array_for_each(c, matrix_data) { + calibration.m[i++] = *c; + } + + /* If calibration can't be saved, don't set it as current */ + if (compositor->touch_calibration_save && + compositor->touch_calibration_save(compositor, device, + &calibration) < 0) + return; + + /* If calibrator is still mapped, the compositor will use + * saved_calibration when going back to normal touch handling. + * Continuing calibrating after save request is undefined. */ + calibrator = compositor->touch_calibrator; + if (calibrator && + calibrator->surface && + weston_surface_is_mapped(calibrator->surface)) + device->saved_calibration = calibration; + else + device->ops->set_calibration(device, &calibration); +} + +static const struct weston_touch_calibration_interface +touch_calibration_implementation = { + touch_calibration_destroy, + touch_calibration_create_calibrator, + touch_calibration_save +}; + +static void +bind_touch_calibration(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct weston_compositor *compositor = data; + struct wl_resource *resource; + struct weston_touch_device *device; + struct weston_seat *seat; + struct weston_touch *touch; + const char *name; + + resource = wl_resource_create(client, + &weston_touch_calibration_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, + &touch_calibration_implementation, + compositor, NULL); + + wl_list_for_each(seat, &compositor->seat_list, link) { + touch = weston_seat_get_touch(seat); + if (!touch) + continue; + + wl_list_for_each(device, &touch->device_list, link) { + if (!weston_touch_device_can_calibrate(device)) + continue; + + name = device->ops->get_calibration_head_name(device); + if (!name) + continue; + + weston_touch_calibration_send_touch_device(resource, + device->syspath, name); + } + } +} + +/** Advertise touch_calibration support + * + * \param compositor The compositor to init for. + * \param save The callback function for saving a new calibration, or NULL. + * \return Zero on success, -1 on failure or if already enabled. + * + * Calling this initializes the weston_touch_calibration protocol support, + * so that the interface will be advertised to clients. It is recommended + * to use some mechanism, e.g. wl_display_set_global_filter(), to restrict + * access to the interface. + * + * There is no way to disable this once enabled. + * + * If the save callback is NULL, a new calibration provided by a client will + * always be accepted. If the save callback is not NULL, it must return + * success for the new calibration to be accepted. + */ +WL_EXPORT int +weston_compositor_enable_touch_calibrator(struct weston_compositor *compositor, + weston_touch_calibration_save_func save) +{ + if (compositor->touch_calibration) + return -1; + + compositor->touch_calibration = wl_global_create(compositor->wl_display, + &weston_touch_calibration_interface, 1, + compositor, bind_touch_calibration); + if (!compositor->touch_calibration) + return -1; + + compositor->touch_calibration_save = save; + weston_layer_init(&compositor->calibrator_layer, compositor); + + /* needs to be stacked above everything except lock screen and cursor, + * otherwise the position value is arbitrary */ + weston_layer_set_position(&compositor->calibrator_layer, + WESTON_LAYER_POSITION_TOP_UI + 120); + + return 0; +} From 5a1b0cf0e76d01478dbfb76009d8e986fbe83e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= Date: Fri, 15 Dec 2017 02:02:56 -0500 Subject: [PATCH 0518/1642] weston: add touchscreen_calibrator option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an option to enable the touchscreen calibrator interface. This is a global on/off toggle, in lack of more fine-grained access restrictions. As Weston should not hardcode system specifics, the actual permanent saving of a new calibration is left for a user supplied script or a program. Usually this script would write an appropriate udev rule to set LIBINPUT_CALIBRATION_MATRIX for the touch device. Co-developed by Louis-Francis and Pekka. v2: - use syspath instead of devpath Signed-off-by: Louis-Francis Ratté-Boulianne Signed-off-by: Pekka Paalanen v1 Tested-by: Matt Hoosier Reviewed-by: Peter Hutterer --- compositor/main.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++ man/weston.ini.man | 39 ++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index 1092204f8..e84857f7c 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -764,6 +764,64 @@ load_modules(struct weston_compositor *ec, const char *modules, return 0; } +static int +save_touch_device_calibration(struct weston_compositor *compositor, + struct weston_touch_device *device, + const struct weston_touch_device_matrix *calibration) +{ + struct weston_config_section *s; + struct weston_config *config = wet_get_config(compositor); + char *helper = NULL; + char *helper_cmd = NULL; + int ret = -1; + int status; + const float *m = calibration->m; + + s = weston_config_get_section(config, + "libinput", NULL, NULL); + + weston_config_section_get_string(s, "calibration_helper", + &helper, NULL); + + if (!helper || strlen(helper) == 0) { + ret = 0; + goto out; + } + + if (asprintf(&helper_cmd, "\"%s\" '%s' %f %f %f %f %f %f", + helper, device->syspath, + m[0], m[1], m[2], + m[3], m[4], m[5]) < 0) + goto out; + + status = system(helper_cmd); + free(helper_cmd); + + if (status < 0) { + weston_log("Error: failed to run calibration helper '%s'.\n", + helper); + goto out; + } + + if (!WIFEXITED(status)) { + weston_log("Error: calibration helper '%s' possibly killed.\n", + helper); + goto out; + } + + if (WEXITSTATUS(status) == 0) { + ret = 0; + } else { + weston_log("Calibration helper '%s' exited with status %d.\n", + helper, WEXITSTATUS(status)); + } + +out: + free(helper); + + return ret; +} + static int weston_compositor_init_config(struct weston_compositor *ec, struct weston_config *config) @@ -772,7 +830,9 @@ weston_compositor_init_config(struct weston_compositor *ec, struct weston_config_section *s; int repaint_msec; int vt_switching; + int cal; + /* weston.ini [keyboard] */ s = weston_config_get_section(config, "keyboard", NULL, NULL); weston_config_section_get_string(s, "keymap_rules", (char **) &xkb_names.rules, NULL); @@ -797,6 +857,7 @@ weston_compositor_init_config(struct weston_compositor *ec, &vt_switching, true); ec->vt_switching = vt_switching; + /* weston.ini [core] */ s = weston_config_get_section(config, "core", NULL, NULL); weston_config_section_get_int(s, "repaint-window", &repaint_msec, ec->repaint_msec); @@ -809,6 +870,13 @@ weston_compositor_init_config(struct weston_compositor *ec, weston_log("Output repaint window is %d ms maximum.\n", ec->repaint_msec); + /* weston.ini [libinput] */ + s = weston_config_get_section(config, "libinput", NULL, NULL); + weston_config_section_get_bool(s, "touchscreen_calibrator", &cal, 0); + if (cal) + weston_compositor_enable_touch_calibrator(ec, + save_touch_device_calibration); + return 0; } diff --git a/man/weston.ini.man b/man/weston.ini.man index f237fd609..b5668b5a3 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -200,8 +200,47 @@ Available configuration are: .TP 7 .BI "enable_tap=" true enables tap to click on touchpad devices +.TP 7 +.BI "touchscreen_calibrator=" true +Advertise the touchscreen calibrator interface to all clients. This is a +potential denial-of-service attack vector, so it should only be enabled on +trusted userspace. Boolean, defaults to +.BR false . + +The interface is required for running touchscreen calibrator applications. It +provides the application raw touch events, bypassing the normal touch handling. +It also allows the application to upload a new calibration into the compositor. + +Even though this option is listed in the libinput section, it does affect all +Weston configurations regardless of the used backend. If the backend does not +use libinput, the interface can still be advertised, but it will not list any +devices. +.TP 7 +.BI "calibration_helper=" /bin/echo +An optional calibration helper program to permanently save a new touchscreen +calibration. String, defaults to unset. + +The given program will be executed with seven arguments when a calibrator +application requests the server to take a new calibration matrix into use. +The program is executed synchronously and will therefore block Weston for its +duration. If the program exit status is non-zero, Weston will not apply the +new calibration. If the helper is unset or the program exit status is zero, +Weston will use the new calibration immediately. + +The program is invoked as: +.PP +.RS 10 +.I calibration_helper syspath m1 m2 m3 m4 m5 m6 +.RE .RS .PP +.RI "where " syspath +is the udev sys path for the device and +.IR m1 " through " m6 +are the calibration matrix elements in libinput's +.BR LIBINPUT_CALIBRATION_MATRIX " udev property format." +The sys path is an absolute path and starts with the sys mount point. +.RE .SH "SHELL SECTION" The From b79dead1dded6744d251da5357e14c83b91cef32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= Date: Wed, 29 Nov 2017 16:38:44 -0500 Subject: [PATCH 0519/1642] clients: add a new touchscreen calibrator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new calibrator uses weston_touch_calibration protocol extension and provides the following features: - chooses the physical touch device to be calibrated by DEVPATH or by the output/head name; device enumeration provided - the compositor ensures the calibrator window is shown in the correct position and size - no matter how wrong the old calibration is, the touch events will always arrive in the application - the calibration is complete, not incremental; the received touch events are guaranteed to be unmodified - computes a libinput style calibration matrix directly, not the WL_CALIBRATION format - supports multiple touch devices: calibrate one device at a time, and show user feedback on touching a wrong device instead of recording bad data - uses four touch point samples: three to compute the calibration, and one to verify the calibration is roughly correct - consistent exit codes - upload the new calibration into the server after successful and verified calibration Due to using special touchscreen calibration protocol extension, this application cannot be tested without touch input from the compositor. Practically all of the above mentioned are unlike how the old calibrator client worked. Co-developed by Louis-Francis and Pekka. v2: - improve help() text - rename wrong_touch_handler() to invalid_touch_handler() - improve debug prints by adding sample number - reorganize code into sample funcs vs. touch funcs - add a state machine to properly process touch and related events Signed-off-by: Louis-Francis Ratté-Boulianne Signed-off-by: Pekka Paalanen v1 Tested-by: Matt Hoosier Reviewed-by: Peter Hutterer --- .gitignore | 1 + Makefile.am | 14 + clients/touch-calibrator.c | 970 +++++++++++++++++++++++++++++++++++++ clients/window.c | 4 +- clients/window.h | 4 + 5 files changed, 991 insertions(+), 2 deletions(-) create mode 100644 clients/touch-calibrator.c diff --git a/.gitignore b/.gitignore index 661f48824..69c5d61ba 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ weston-simple-damage weston-smoke weston-stacking weston-subsurfaces +weston-touch-calibrator weston-transformed weston-view diff --git a/Makefile.am b/Makefile.am index 5260bd8a4..664eb2eb7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -546,6 +546,7 @@ demo_clients = \ weston-fullscreen \ weston-stacking \ weston-calibrator \ + weston-touch-calibrator \ weston-scaler if INSTALL_DEMO_CLIENTS @@ -785,6 +786,17 @@ weston_calibrator_SOURCES = \ weston_calibrator_LDADD = libtoytoolkit.la weston_calibrator_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) +weston_touch_calibrator_SOURCES = \ + clients/touch-calibrator.c \ + shared/helpers.h \ + shared/matrix.c \ + shared/matrix.h +nodist_weston_touch_calibrator_SOURCES = \ + protocol/weston-touch-calibration-protocol.c \ + protocol/weston-touch-calibration-client-protocol.h +weston_touch_calibrator_LDADD = libtoytoolkit.la +weston_touch_calibrator_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) + if BUILD_SUBSURFACES_CLIENT demo_clients += weston-subsurfaces weston_subsurfaces_SOURCES = \ @@ -872,6 +884,8 @@ endif BUILT_SOURCES += \ protocol/weston-screenshooter-protocol.c \ protocol/weston-screenshooter-client-protocol.h \ + protocol/weston-touch-calibration-protocol.c \ + protocol/weston-touch-calibration-client-protocol.h \ protocol/text-cursor-position-client-protocol.h \ protocol/text-cursor-position-protocol.c \ protocol/text-input-unstable-v1-protocol.c \ diff --git a/clients/touch-calibrator.c b/clients/touch-calibrator.c new file mode 100644 index 000000000..3c7e6ece6 --- /dev/null +++ b/clients/touch-calibrator.c @@ -0,0 +1,970 @@ +/* + * Copyright 2012 Intel Corporation + * Copyright 2017-2018 Collabora, Ltd. + * Copyright 2017-2018 General Electric Company + * + * 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 + +#include "clients/window.h" +#include "shared/helpers.h" +#include "shared/matrix.h" + +#include "weston-touch-calibration-client-protocol.h" + +enum exit_code { + CAL_EXIT_SUCCESS = 0, + CAL_EXIT_ERROR = 1, + CAL_EXIT_CANCELLED = 2, +}; + +static int debug_; +static int verbose_; + +#define pr_ver(...) do { \ + if (verbose_) \ + printf(__VA_ARGS__); \ +} while (0) + +#define pr_dbg(...) do { \ + if (debug_) \ + fprintf(stderr, __VA_ARGS__); \ +} while (0) + +static void +pr_err(const char *fmt, ...) WL_PRINTF(1, 2); + +/* Our points for the calibration must be not be on a line */ +static const struct { + float x_ratio, y_ratio; +} test_ratios[] = { + { 0.15, 0.10 }, /* three points for calibration */ + { 0.85, 0.13 }, + { 0.20, 0.80 }, + { 0.70, 0.75 } /* and one for verification */ +}; + +#define NR_SAMPLES ((int)ARRAY_LENGTH(test_ratios)) + +struct point { + double x; + double y; +}; + +struct sample { + int ind; + struct point drawn; /**< drawn point, pixels */ + struct weston_touch_coordinate *pending; + struct point drawn_cal; /**< drawn point, converted */ + bool conv_done; + struct point touched; /**< touch point, normalized */ + bool touch_done; +}; + +struct poly { + struct color { + double r, g, b, a; + } color; + int n_verts; + const struct point *verts; +}; + +/** Touch event handling state machine + * + * Only a complete down->up->frame sequence should be accepted with user + * feedback "right", and anything that deviates from that (invalid_touch, + * cancel, multiple touch-downs) needs to undo the current sample and + * possibly show user feedback "wrong". + * + * + * - : + * + * IDLE + * - touch down: sample, -> DOWN + * - touch up: no-op + * - frame: no-op + * - invalid_touch: (undo), wrong, -> WAIT + * - cancel: no-op + * DOWN (first touch down) + * - touch down: undo, wrong, -> WAIT + * - touch up: -> UP + * - frame: no-op + * - invalid_touch: undo, wrong, -> WAIT + * - cancel: undo, -> IDLE + * UP (first touch was down and up) + * - touch down: undo, wrong, -> WAIT + * - touch up: no-op + * - frame: right, touch finish, -> WAIT + * - invalid_touch: undo, wrong, -> WAIT + * - cancel: undo, -> IDLE + * WAIT (show user feedback) + * - touch down: no-op + * - touch up: no-op + * - frame, cancel, timer: if num_tp == 0 && timer_done -> IDLE + * - invalid_touch: no-op + */ +enum touch_state { + STATE_IDLE, + STATE_DOWN, + STATE_UP, + STATE_WAIT +}; + +struct calibrator { + struct sample samples[NR_SAMPLES]; + int current_sample; + + struct display *display; + struct weston_touch_calibration *calibration; + struct weston_touch_calibrator *calibrator; + struct window *window; + struct widget *widget; + + int n_devices_listed; + char *match_name; + char *device_name; + + int width; + int height; + + bool cancelled; + + const struct poly *current_poly; + bool exiting; + + struct toytimer wait_timer; + bool timer_pending; + enum touch_state state; + + int num_tp; /* touch points down count */ +}; + +static struct sample * +current_sample(struct calibrator *cal) +{ + return &cal->samples[cal->current_sample]; +} + +static void +sample_start(struct calibrator *cal, int i) +{ + struct sample *s = &cal->samples[i]; + + assert(i >= 0 && i < NR_SAMPLES); + + s->ind = i; + s->drawn.x = round(test_ratios[i].x_ratio * cal->width); + s->drawn.y = round(test_ratios[i].y_ratio * cal->height); + s->pending = NULL; + s->conv_done = false; + s->touch_done = false; + + cal->current_sample = i; +} + +static struct point +wire_to_point(uint32_t xu, uint32_t yu) +{ + struct point p = { + .x = (double)xu / 0xffffffff, + .y = (double)yu / 0xffffffff + }; + + return p; +} + +static void +sample_touch_down(struct calibrator *cal, uint32_t xu, uint32_t yu) +{ + struct sample *s = current_sample(cal); + + s->touched = wire_to_point(xu, yu); + s->touch_done = true; + + pr_dbg("Down[%d] (%f, %f)\n", s->ind, s->touched.x, s->touched.y); +} + +static void +coordinate_result_handler(void *data, struct weston_touch_coordinate *interface, + uint32_t xu, uint32_t yu) +{ + struct sample *s = data; + + weston_touch_coordinate_destroy(s->pending); + s->pending = NULL; + + s->drawn_cal = wire_to_point(xu, yu); + s->conv_done = true; + + pr_dbg("Conv[%d] (%f, %f)\n", s->ind, s->drawn_cal.x, s->drawn_cal.y); +} + +struct weston_touch_coordinate_listener coordinate_listener = { + coordinate_result_handler +}; + +static void +sample_undo(struct calibrator *cal) +{ + struct sample *s = current_sample(cal); + + pr_dbg("Undo[%d]\n", s->ind); + + s->touch_done = false; + s->conv_done = false; + if (s->pending) { + weston_touch_coordinate_destroy(s->pending); + s->pending = NULL; + } +} + +static void +sample_finish(struct calibrator *cal) +{ + struct sample *s = current_sample(cal); + + pr_dbg("Finish[%d]\n", s->ind); + + assert(!s->pending && !s->conv_done); + + s->pending = weston_touch_calibrator_convert(cal->calibrator, + (int32_t)s->drawn.x, + (int32_t)s->drawn.y); + weston_touch_coordinate_add_listener(s->pending, + &coordinate_listener, s); + + if (cal->current_sample + 1 < NR_SAMPLES) { + sample_start(cal, cal->current_sample + 1); + } else { + pr_dbg("got all touches\n"); + cal->exiting = true; + } +} + +/* + * Calibration algorithm: + * + * The equation we want to apply at event time where x' and y' are the + * calibrated co-ordinates. + * + * x' = Ax + By + C + * y' = Dx + Ey + F + * + * For example "zero calibration" would be A=1.0 B=0.0 C=0.0, D=0.0, E=1.0, + * and F=0.0. + * + * With 6 unknowns we need 6 equations to find the constants: + * + * x1' = Ax1 + By1 + C + * y1' = Dx1 + Ey1 + F + * ... + * x3' = Ax3 + By3 + C + * y3' = Dx3 + Ey3 + F + * + * In matrix form: + * + * x1' x1 y1 1 A + * x2' = x2 y2 1 x B + * x3' x3 y3 1 C + * + * So making the matrix M we can find the constants with: + * + * A x1' + * B = M^-1 x x2' + * C x3' + * + * (and similarly for D, E and F) + * + * For the calibration the desired values x, y are the same values at which + * we've drawn at. + * + */ +static int +compute_calibration(struct calibrator *cal, float *result) +{ + struct weston_matrix m; + struct weston_matrix inverse; + struct weston_vector x_calib; + struct weston_vector y_calib; + int i; + + assert(NR_SAMPLES >= 3); + + /* + * x1 y1 1 0 + * x2 y2 1 0 + * x3 y3 1 0 + * 0 0 0 1 + */ + weston_matrix_init(&m); + for (i = 0; i < 3; i++) { + m.d[i + 0] = cal->samples[i].touched.x; + m.d[i + 4] = cal->samples[i].touched.y; + m.d[i + 8] = 1.0f; + } + m.type = WESTON_MATRIX_TRANSFORM_OTHER; + + if (weston_matrix_invert(&inverse, &m) < 0) { + pr_err("non-invertible matrix during computation\n"); + return -1; + } + + for (i = 0; i < 3; i++) { + x_calib.f[i] = cal->samples[i].drawn_cal.x; + y_calib.f[i] = cal->samples[i].drawn_cal.y; + } + x_calib.f[3] = 0.0f; + y_calib.f[3] = 0.0f; + + /* Multiples into the vector */ + weston_matrix_transform(&inverse, &x_calib); + weston_matrix_transform(&inverse, &y_calib); + + for (i = 0; i < 3; i++) + result[i] = x_calib.f[i]; + for (i = 0; i < 3; i++) + result[i + 3] = y_calib.f[i]; + + return 0; +} + +static int +verify_calibration(struct calibrator *cal, const float *r) +{ + double thr = 0.1; /* accepted error radius */ + struct point e; /* expected value; error */ + const struct sample *s = &cal->samples[3]; + + /* transform raw touches through the matrix */ + e.x = r[0] * s->touched.x + r[1] * s->touched.y + r[2]; + e.y = r[3] * s->touched.x + r[4] * s->touched.y + r[5]; + + /* compute error */ + e.x -= s->drawn_cal.x; + e.y -= s->drawn_cal.y; + + pr_dbg("calibration test error: %f, %f\n", e.x, e.y); + + if (e.x * e.x + e.y * e.y < thr * thr) + return 0; + + pr_err("Calibration verification failed, too large error.\n"); + return -1; +} + +static void +send_calibration(struct calibrator *cal, float *values) +{ + struct wl_array matrix; + float *f; + int i; + + wl_array_init(&matrix); + for (i = 0; i < 6; i++) { + f = wl_array_add(&matrix, sizeof *f); + *f = values[i]; + } + weston_touch_calibration_save(cal->calibration, + cal->device_name, &matrix); + wl_array_release(&matrix); +} + +static const struct point cross_verts[] = { + { 0.1, 0.2 }, + { 0.2, 0.1 }, + { 0.5, 0.4 }, + { 0.8, 0.1 }, + { 0.9, 0.2 }, + { 0.6, 0.5 }, + { 0.9, 0.8 }, + { 0.8, 0.9 }, + { 0.5, 0.6 }, + { 0.2, 0.9 }, + { 0.1, 0.8 }, + { 0.4, 0.5 }, +}; + +/* a red cross, for "wrong" */ +static const struct poly cross = { + .color = { 0.7, 0.0, 0.0, 1.0 }, + .n_verts = ARRAY_LENGTH(cross_verts), + .verts = cross_verts +}; + +static const struct point check_verts[] = { + { 0.5, 0.7 }, + { 0.8, 0.1 }, + { 0.9, 0.1 }, + { 0.55, 0.8 }, + { 0.45, 0.8 }, + { 0.3, 0.5 }, + { 0.4, 0.5 } +}; + +/* a green check mark, for "right" */ +static const struct poly check = { + .color = { 0.0, 0.7, 0.0, 1.0 }, + .n_verts = ARRAY_LENGTH(check_verts), + .verts = check_verts +}; + +static void +draw_poly(cairo_t *cr, const struct poly *poly) +{ + int i; + + cairo_set_source_rgba(cr, poly->color.r, poly->color.g, + poly->color.b, poly->color.a); + cairo_move_to(cr, poly->verts[0].x, poly->verts[0].y); + for (i = 1; i < poly->n_verts; i++) + cairo_line_to(cr, poly->verts[i].x, poly->verts[i].y); + cairo_close_path(cr); + cairo_fill(cr); +} + +static void +feedback_show(struct calibrator *cal, const struct poly *what) +{ + cal->current_poly = what; + widget_schedule_redraw(cal->widget); + + toytimer_arm_once_usec(&cal->wait_timer, 1000 * 1000); + cal->timer_pending = true; +} + +static void +feedback_hide(struct calibrator *cal) +{ + cal->current_poly = NULL; + widget_schedule_redraw(cal->widget); +} + +static void +try_enter_state_idle(struct calibrator *cal) +{ + if (cal->num_tp != 0) + return; + + if (cal->timer_pending) + return; + + cal->state = STATE_IDLE; + + feedback_hide(cal); + + if (cal->exiting) + display_exit(cal->display); +} + +static void +enter_state_wait(struct calibrator *cal) +{ + assert(cal->timer_pending); + cal->state = STATE_WAIT; +} + +static void +wait_timer_done(struct toytimer *tt) +{ + struct calibrator *cal = container_of(tt, struct calibrator, wait_timer); + + assert(cal->state == STATE_WAIT); + cal->timer_pending = false; + try_enter_state_idle(cal); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct calibrator *cal = data; + struct sample *s = current_sample(cal); + struct rectangle allocation; + cairo_surface_t *surface; + cairo_t *cr; + + widget_get_allocation(cal->widget, &allocation); + assert(allocation.width == cal->width); + assert(allocation.height == cal->height); + + surface = window_get_surface(cal->window); + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); + cairo_paint(cr); + + if (!cal->current_poly) { + cairo_translate(cr, s->drawn.x, s->drawn.y); + cairo_set_line_width(cr, 2.0); + cairo_set_source_rgb(cr, 0.7, 0.0, 0.0); + cairo_move_to(cr, 0, -10.0); + cairo_line_to(cr, 0, 10.0); + cairo_stroke(cr); + cairo_move_to(cr, -10.0, 0); + cairo_line_to(cr, 10.0, 0.0); + cairo_stroke(cr); + } else { + cairo_scale(cr, allocation.width, allocation.height); + draw_poly(cr, cal->current_poly); + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static struct calibrator * +calibrator_create(struct display *display, const char *match_name) +{ + struct calibrator *cal; + + cal = zalloc(sizeof *cal); + if (!cal) + abort(); + + cal->match_name = match_name ? strdup(match_name) : NULL; + cal->window = window_create_custom(display); + cal->widget = window_add_widget(cal->window, cal); + window_inhibit_redraw(cal->window); + window_set_title(cal->window, "Touchscreen calibrator"); + cal->display = display; + + widget_set_redraw_handler(cal->widget, redraw_handler); + + toytimer_init(&cal->wait_timer, CLOCK_MONOTONIC, + display, wait_timer_done); + + cal->state = STATE_IDLE; + cal->num_tp = 0; + + return cal; +} + +static void +configure_handler(void *data, struct weston_touch_calibrator *interface, + int32_t width, int32_t height) +{ + struct calibrator *cal = data; + + pr_dbg("Configure calibrator window to size %ix%i\n", width, height); + cal->width = width; + cal->height = height; + window_schedule_resize(cal->window, width, height); + window_uninhibit_redraw(cal->window); + + sample_start(cal, 0); + widget_schedule_redraw(cal->widget); +} + +static void +cancel_calibration_handler(void *data, struct weston_touch_calibrator *interface) +{ + struct calibrator *cal = data; + + pr_dbg("calibration cancelled by the display server, quitting.\n"); + cal->cancelled = true; + display_exit(cal->display); +} + +static void +invalid_touch_handler(void *data, struct weston_touch_calibrator *interface) +{ + struct calibrator *cal = data; + + pr_dbg("invalid touch\n"); + + switch (cal->state) { + case STATE_IDLE: + case STATE_DOWN: + case STATE_UP: + sample_undo(cal); + feedback_show(cal, &cross); + enter_state_wait(cal); + break; + case STATE_WAIT: + /* no-op */ + break; + } +} + +static void +down_handler(void *data, struct weston_touch_calibrator *interface, + uint32_t time, int32_t id, uint32_t xu, uint32_t yu) +{ + struct calibrator *cal = data; + + cal->num_tp++; + + switch (cal->state) { + case STATE_IDLE: + sample_touch_down(cal, xu, yu); + cal->state = STATE_DOWN; + break; + case STATE_DOWN: + case STATE_UP: + sample_undo(cal); + feedback_show(cal, &cross); + enter_state_wait(cal); + break; + case STATE_WAIT: + /* no-op */ + break; + } + + if (cal->current_poly) + return; +} + +static void +up_handler(void *data, struct weston_touch_calibrator *interface, + uint32_t time, int32_t id) +{ + struct calibrator *cal = data; + + cal->num_tp--; + if (cal->num_tp < 0) { + pr_dbg("Unmatched touch up.\n"); + cal->num_tp = 0; + } + + switch (cal->state) { + case STATE_DOWN: + cal->state = STATE_UP; + break; + case STATE_IDLE: + case STATE_UP: + case STATE_WAIT: + /* no-op */ + break; + } +} + +static void +motion_handler(void *data, struct weston_touch_calibrator *interface, + uint32_t time, int32_t id, uint32_t xu, uint32_t yu) +{ + /* motion is ignored */ +} + +static void +frame_handler(void *data, struct weston_touch_calibrator *interface) +{ + struct calibrator *cal = data; + + switch (cal->state) { + case STATE_IDLE: + case STATE_DOWN: + /* no-op */ + break; + case STATE_UP: + feedback_show(cal, &check); + sample_finish(cal); + enter_state_wait(cal); + break; + case STATE_WAIT: + try_enter_state_idle(cal); + break; + } +} + +static void +cancel_handler(void *data, struct weston_touch_calibrator *interface) +{ + struct calibrator *cal = data; + + cal->num_tp = 0; + + switch (cal->state) { + case STATE_IDLE: + /* no-op */ + break; + case STATE_DOWN: + case STATE_UP: + sample_undo(cal); + try_enter_state_idle(cal); + break; + case STATE_WAIT: + try_enter_state_idle(cal); + break; + } +} + +struct weston_touch_calibrator_listener calibrator_listener = { + configure_handler, + cancel_calibration_handler, + invalid_touch_handler, + down_handler, + up_handler, + motion_handler, + frame_handler, + cancel_handler +}; + +static void +calibrator_show(struct calibrator *cal) +{ + struct wl_surface *surface = window_get_wl_surface(cal->window); + + cal->calibrator = + weston_touch_calibration_create_calibrator(cal->calibration, + surface, + cal->device_name); + weston_touch_calibrator_add_listener(cal->calibrator, + &calibrator_listener, cal); +} + +static void +calibrator_destroy(struct calibrator *cal) +{ + toytimer_fini(&cal->wait_timer); + if (cal->calibrator) + weston_touch_calibrator_destroy(cal->calibrator); + if (cal->calibration) + weston_touch_calibration_destroy(cal->calibration); + if (cal->widget) + widget_destroy(cal->widget); + if (cal->window) + window_destroy(cal->window); + free(cal->match_name); + free(cal->device_name); + free(cal); +} + +static void +touch_device_handler(void *data, struct weston_touch_calibration *c, + const char *device, const char *head) +{ + struct calibrator *cal = data; + + cal->n_devices_listed++; + + if (!cal->match_name) { + printf("device \"%s\" - head \"%s\"\n", device, head); + return; + } + + if (cal->device_name) + return; + + if (strcmp(cal->match_name, device) == 0 || + strcmp(cal->match_name, head) == 0) + cal->device_name = strdup(device); +} + +struct weston_touch_calibration_listener touch_calibration_listener = { + touch_device_handler +}; + +static void +global_handler(struct display *display, uint32_t name, + const char *interface, uint32_t version, void *data) +{ + struct calibrator *cal = data; + + if (strcmp(interface, "weston_touch_calibration") == 0) { + cal->calibration = display_bind(display, name, + &weston_touch_calibration_interface, 1); + weston_touch_calibration_add_listener(cal->calibration, + &touch_calibration_listener, + cal); + } +} + +static int +calibrator_run(struct calibrator *cal) +{ + struct wl_display *dpy; + struct sample *s; + bool wait; + int i; + int ret; + float result[6]; + + calibrator_show(cal); + display_run(cal->display); + + if (cal->cancelled) + return CAL_EXIT_CANCELLED; + + /* remove the window, no more input events */ + widget_destroy(cal->widget); + cal->widget = NULL; + window_destroy(cal->window); + cal->window = NULL; + + /* wait for all conversions to return */ + dpy = display_get_display(cal->display); + do { + wait = false; + + for (i = 0; i < NR_SAMPLES; i++) + if (cal->samples[i].pending) + wait = true; + + if (wait) { + ret = wl_display_roundtrip(dpy); + if (ret < 0) + return CAL_EXIT_ERROR; + } + } while (wait); + + for (i = 0; i < NR_SAMPLES; i++) { + s = &cal->samples[i]; + if (!s->conv_done || !s->touch_done) + return CAL_EXIT_ERROR; + } + + if (compute_calibration(cal, result) < 0) + return CAL_EXIT_ERROR; + + if (verify_calibration(cal, result) < 0) + return CAL_EXIT_ERROR; + + pr_ver("Calibration values:"); + for (i = 0; i < 6; i++) + pr_ver(" %f", result[i]); + pr_ver("\n"); + + send_calibration(cal, result); + ret = wl_display_roundtrip(dpy); + if (ret < 0) + return CAL_EXIT_ERROR; + + return CAL_EXIT_SUCCESS; +} + +static void +pr_err(const char *fmt, ...) +{ + va_list argp; + + va_start(argp, fmt); + fprintf(stderr, "%s error: ", program_invocation_short_name); + vfprintf(stderr, fmt, argp); + va_end(argp); +} + +static void +help(void) +{ + fprintf(stderr, "Compute a touchscreen calibration matrix for " + "a Wayland compositor by\n" + "having the user touch points on the screen.\n\n"); + fprintf(stderr, "Usage: %s [options...] name\n\n", + program_invocation_short_name); + fprintf(stderr, + "Where 'name' can be a touch device sys path or a head name.\n" + "If 'name' is not given, all devices available for " + "calibration will be listed.\n" + "If 'name' is given, it must be exactly as listed.\n" + "Options:\n" + " --debug Print messages to help debugging.\n" + " -h, --help Display this help message\n" + " -v, --verbose Print list header and calibration result.\n"); +} + +int +main(int argc, char *argv[]) +{ + struct display *display; + struct calibrator *cal; + int c; + char *match_name = NULL; + int exit_code = CAL_EXIT_SUCCESS; + static const struct option opts[] = { + { "help", no_argument, NULL, 'h' }, + { "debug", no_argument, &debug_, 1 }, + { "verbose", no_argument, &verbose_, 1 }, + { 0, 0, NULL, 0 } + }; + + while ((c = getopt_long(argc, argv, "hv", opts, NULL)) != -1) { + switch (c) { + case 'h': + help(); + return CAL_EXIT_SUCCESS; + case 'v': + verbose_ = 1; + break; + case 0: + break; + default: + return CAL_EXIT_ERROR; + } + } + + if (optind < argc) + match_name = argv[optind++]; + + if (optind < argc) { + pr_err("extra arguments given.\n\n"); + help(); + return CAL_EXIT_ERROR; + } + + display = display_create(&argc, argv); + if (!display) + return CAL_EXIT_ERROR; + + cal = calibrator_create(display, match_name); + if (!cal) + return CAL_EXIT_ERROR; + + display_set_user_data(display, cal); + display_set_global_handler(display, global_handler); + + if (!match_name) + pr_ver("Available touch devices:\n"); + + /* Roundtrip to get list of available touch devices, + * first globals, then touch_device events */ + wl_display_roundtrip(display_get_display(display)); + wl_display_roundtrip(display_get_display(display)); + + if (!cal->calibration) { + exit_code = CAL_EXIT_ERROR; + pr_err("the Wayland server does not expose the calibration interface.\n"); + } else if (cal->device_name) { + exit_code = calibrator_run(cal); + } else if (match_name) { + exit_code = CAL_EXIT_ERROR; + pr_err("\"%s\" was not found.\n", match_name); + } else if (cal->n_devices_listed == 0) { + fprintf(stderr, "No devices listed.\n"); + } + + calibrator_destroy(cal); + display_destroy(display); + + return exit_code; +} diff --git a/clients/window.c b/clients/window.c index dee4455f1..b64e96cf5 100644 --- a/clients/window.c +++ b/clients/window.c @@ -4220,7 +4220,7 @@ window_get_shadow_margin(struct window *window) return 0; } -static void +void window_inhibit_redraw(struct window *window) { window->redraw_inhibited = 1; @@ -4229,7 +4229,7 @@ window_inhibit_redraw(struct window *window) window->redraw_task_scheduled = 0; } -static void +void window_uninhibit_redraw(struct window *window) { window->redraw_inhibited = 0; diff --git a/clients/window.h b/clients/window.h index 3366ab15f..fde5c2f05 100644 --- a/clients/window.h +++ b/clients/window.h @@ -604,6 +604,10 @@ widget_set_axis_handlers(struct widget *widget, widget_axis_stop_handler_t axis_stop_handler, widget_axis_discrete_handler_t axis_discrete_handler); +void +window_inhibit_redraw(struct window *window); +void +window_uninhibit_redraw(struct window *window); void widget_schedule_redraw(struct widget *widget); void From bfaaedc9145acfe626c0173495d49f63392f3956 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 13 Mar 2018 14:51:00 +0200 Subject: [PATCH 0520/1642] doc: add example calibration-helper script This is not to be installed, except maybe as a doc. It is just an example of what one might do. It also has not been tested, it's just for giving an idea of what it should do. It also contains untested speculation. v2: - use syspath instead of devpath - add license blurb Signed-off-by: Pekka Paalanen Reviewed-by: Peter Hutterer --- Makefile.am | 1 + doc/calibration-helper.bash | 66 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100755 doc/calibration-helper.bash diff --git a/Makefile.am b/Makefile.am index 664eb2eb7..9d23b50d2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1605,6 +1605,7 @@ SUFFIXES = .1 .5 .7 .man $(AM_V_GEN)$(SED) $(MAN_SUBSTS) < $< > $@ EXTRA_DIST += \ + doc/calibration-helper.bash \ man/weston.man \ man/weston-drm.man \ man/weston-rdp.man \ diff --git a/doc/calibration-helper.bash b/doc/calibration-helper.bash new file mode 100755 index 000000000..72effe3a8 --- /dev/null +++ b/doc/calibration-helper.bash @@ -0,0 +1,66 @@ +#!/bin/bash + +# Copyright 2018 Collabora, Ltd. +# Copyright 2018 General Electric Company +# +# 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. + +# This is an example script working as Weston's calibration helper. +# Its purpose is to permanently store the calibration matrix for the given +# touchscreen input device into a udev property. Since this script naturally +# runs as the user that runs Weston, it presumably cannot write directly into +# /etc. It is left for the administrator to set up appropriate files and +# permissions. + +# To use this script, one needs to edit weston.ini, in section [libinput], add: +# calibration_helper=/path/to/bin/calibration-helper.bash + +# exit immediately if any command fails +set -e + +# The arguments Weston gives us: +SYSPATH="$1" +MATRIX="$2 $3 $4 $5 $6 $7" + +# Pick something to recognize the right touch device with. +# Usually one would use something like a serial. +SERIAL=$(udevadm info "$SYSPATH" --query=property | \ + awk -- 'BEGIN { FS="=" } { if ($1 == "ID_SERIAL") { print $2; exit } }') + +# If cannot find a serial, tell the server to not use the new calibration. +[ -z "$SERIAL" ] && exit 1 + +# You'd have this write a file instead. +echo "ACTION==\"add|change\",SUBSYSTEM==\"input\",ENV{ID_SERIAL}==\"$SERIAL\",ENV{LIBINPUT_CALIBRATION_MATRIX}=\"$MATRIX\"" + +# Then you'd tell udev to reload the rules: +#udevadm control --reload +# This lets Weston get the new calibration if you unplug and replug the input +# device. Instead of writing a udev rule directly, you could have a udev rule +# with IMPORT{file}="/path/to/calibration", write +# "LIBINPUT_CALIBRATION_MATRIX=\"$MATRIX\"" into /path/to/calibration instead, +# and skip this reload step. + +# Make udev process the new rule by triggering a "change" event: +#udevadm trigger "$SYSPATH" +# If you were to restart Weston without rebooting, this lets it pick up the new +# calibration. From fa6069adca19e83c2f5e8fb38e55dcee9c26cfeb Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 5 Jun 2018 22:20:40 +0100 Subject: [PATCH 0521/1642] Add .gitlab-ci.yml Add a GitLab CI configuration which tests building, 'make check', and 'make distcheck' of the tree inside a Debian Stretch container. The choice of distribution base was arbitrary and may easily be changed. As the version of wayland-protocols available is not sufficiently new, we clone and build our own local version first. libwayland is new enough, however we could potentially reuse the artifacts generated by the Wayland CI job. When commits are pushed to upstream, the commits will run this CI pipeline to run these tests, and capture the result as an artifact bundle, including the compiled binaries and full test suite logs. Results can be seen at: https://gitlab.freedesktop.org/wayland/weston/pipelines/ Signed-off-by: Daniel Stone --- .gitlab-ci.yml | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..80b99278e --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,49 @@ +image: debian:stretch + +stages: + - build + +before_script: + - echo 'path-exclude=/usr/share/doc/*' > /etc/dpkg/dpkg.cfg.d/99-exclude-cruft + - echo 'path-exclude=/usr/share/man/*' >> /etc/dpkg/dpkg.cfg.d/99-exclude-cruft + - echo '#!/bin/sh' > /usr/sbin/policy-rc.d + - echo 'exit 101' >> /usr/sbin/policy-rc.d + - chmod +x /usr/sbin/policy-rc.d + - apt-get update + - apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git + - mkdir -p /tmp/.X11-unix + - chmod 777 /tmp/.X11-unix + +build-native: + stage: build + script: + - git clone --depth=1 git://anongit.freedesktop.org/git/wayland/wayland-protocols + - export WAYLAND_PROTOCOLS_DIR="$(pwd)/prefix-wayland-protocols" + - export PKG_CONFIG_PATH="$WAYLAND_PROTOCOLS_DIR/share/pkgconfig:$PKG_CONFIG_PATH" + - export MAKEFLAGS="-j4" + - cd wayland-protocols + - git show -s HEAD + - mkdir build + - cd build + - ../autogen.sh --prefix="$WAYLAND_PROTOCOLS_DIR" + - make install + - cd ../../ + - export XDG_RUNTIME_DIR="$(mktemp -p $(pwd) -d xdg-runtime-XXXXXX)" + - export BUILD_ID="weston-$CI_JOB_NAME_$CI_COMMIT_SHA-$CI_JOB_ID" + - export PREFIX="$(pwd)/prefix-$BUILD_ID" + - export BUILDDIR="$(pwd)/build-$BUILD_ID" + - mkdir "$BUILDDIR" "$PREFIX" + - cd "$BUILDDIR" + - ../autogen.sh --prefix="$PREFIX" --disable-setuid-install --enable-xwayland --enable-x11-compositor --enable-drm-compositor --enable-wayland-compositor --enable-headless-compositor --enable-fbdev-compositor --disable-rdp-compositor --enable-screen-sharing --enable-vaapi-recorder --enable-simple-clients --enable-simple-egl-clients --enable-simple-dmabuf-drm-client --enable-simple-dmabuf-v4l-client --enable-clients --enable-resize-optimization --enable-weston-launch --enable-fullscreen-shell --enable-colord --enable-dbus --enable-systemd-login --enable-junit-xml --enable-ivi-shell --enable-wcap-tools --disable-libunwind --enable-demo-clients-install --enable-lcms --with-cairo=image + - make all + - make check + - make install + - make distcheck + artifacts: + name: weston-$CI_COMMIT_SHA-$CI_JOB_ID + when: always + paths: + - build-*/weston-*.tar.xz + - build-*/*.log + - build-*/logs + - prefix-* From 3a80ca0629c5082b3dd7daaa221c015ee737cbfe Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 5 Jun 2018 23:01:51 +0100 Subject: [PATCH 0522/1642] simple-dmabuf-drm: Fallback DRM_FORMAT_MOD_LINEAR definition Just in case we're running on something quite old. Signed-off-by: Daniel Stone --- clients/simple-dmabuf-drm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index df96758c9..fcab30e5c 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -62,6 +62,10 @@ #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" +#ifndef DRM_FORMAT_MOD_LINEAR +#define DRM_FORMAT_MOD_LINEAR 0 +#endif + struct buffer; /* Possible options that affect the displayed image */ From 09bfcd6e1e57fc4bef46ec6833dd44b075ab93e0 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Tue, 5 Jun 2018 10:37:05 +0900 Subject: [PATCH 0523/1642] libweston: fix indentation Signed-off-by: Tomohito Esaki Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 101096cdb..d11a6552c 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2546,13 +2546,13 @@ output_repaint_timer_handler(void *data) } if (ret == 0) { - if (compositor->backend->repaint_flush) - compositor->backend->repaint_flush(compositor, - repaint_data); + if (compositor->backend->repaint_flush) + compositor->backend->repaint_flush(compositor, + repaint_data); } else { - if (compositor->backend->repaint_cancel) - compositor->backend->repaint_cancel(compositor, - repaint_data); + if (compositor->backend->repaint_cancel) + compositor->backend->repaint_cancel(compositor, + repaint_data); } output_repaint_timer_arm(compositor); From 7f4d9ffefa7162c0d78a8071c4a4f5c050ff8885 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Tue, 5 Jun 2018 10:37:06 +0900 Subject: [PATCH 0524/1642] libweston: Reset repaint schedule for all repainted outputs when repaint cancel All outputs is canceled repaint when a output repaint is failed. At that time, the output whose repaint is success is not scheduled because the repaint status of that is still REPAINT_AWAITING_COMPLETION. Therefore, we need to reset repaint schedule for all repainted outputs. Signed-off-by: Tomohito Esaki Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 8 ++++++++ libweston/compositor.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index d11a6552c..91f311dff 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2450,6 +2450,8 @@ weston_output_maybe_repaint(struct weston_output *output, struct timespec *now, int ret = 0; int64_t msec_to_repaint; + output->repainted = false; + /* We're not ready yet; come back to make a decision later. */ if (output->repaint_status != REPAINT_SCHEDULED) return ret; @@ -2479,6 +2481,7 @@ weston_output_maybe_repaint(struct weston_output *output, struct timespec *now, if (ret != 0) goto err; + output->repainted = true; return ret; err: @@ -2550,6 +2553,11 @@ output_repaint_timer_handler(void *data) compositor->backend->repaint_flush(compositor, repaint_data); } else { + wl_list_for_each(output, &compositor->output_list, link) { + if (output->repainted) + weston_output_schedule_repaint_reset(output); + } + if (compositor->backend->repaint_cancel) compositor->backend->repaint_cancel(compositor, repaint_data); diff --git a/libweston/compositor.h b/libweston/compositor.h index c2c40eeb1..8942ab9a2 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -212,6 +212,9 @@ struct weston_output { * if set, a repaint will eventually occur. */ bool repaint_needed; + /** Used only between repaint_begin and repaint_cancel. */ + bool repainted; + /** State of the repaint loop */ enum { REPAINT_NOT_SCHEDULED = 0, /**< idle; no repaint will occur */ From bc04d70336bc8749b7ca7696e5236b80e7ad59b5 Mon Sep 17 00:00:00 2001 From: Yong Gan Date: Tue, 5 Sep 2017 08:00:30 +0800 Subject: [PATCH 0525/1642] client: Fix segmentation fault in the case weston-nested eglTerminate should be called before the display was disconnected. Signed-off-by: Yong Gan Reviewed-by: Daniel Stone --- clients/nested-client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/nested-client.c b/clients/nested-client.c index 5027075c8..70bb4d1b4 100644 --- a/clients/nested-client.c +++ b/clients/nested-client.c @@ -343,10 +343,10 @@ nested_client_destroy(struct nested_client *client) wl_compositor_destroy(client->compositor); wl_registry_destroy(client->registry); - wl_display_flush(client->display); - wl_display_disconnect(client->display); eglTerminate(client->egl_display); eglReleaseThread(); + wl_display_flush(client->display); + wl_display_disconnect(client->display); } int From dcfb19585e216bae1f70222454a04caf55d224ff Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Sat, 9 Jun 2018 00:51:06 +0100 Subject: [PATCH 0526/1642] doc: Update for GitLab migration Update issue report and build instruction URLs for moving to GitLab, and for everything having been HTTPS-only for quite some time. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- README | 4 ++-- configure.ac | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README b/README index 84c30a4ca..a0a078c46 100644 --- a/README +++ b/README @@ -12,11 +12,11 @@ shell. Finally, weston also provides integration with the Xorg server and can pull X clients into the Wayland desktop and act as an X window manager. -Refer to http://wayland.freedesktop.org/building.html for building +Refer to https://wayland.freedesktop.org/building.html for building weston and its dependencies. The test suite can be invoked via `make check`; see -http://wayland.freedesktop.org/testing.html for additional details. +https://wayland.freedesktop.org/testing.html for additional details. Developer documentation can be built via `make doc`. Output will be in the build root under diff --git a/configure.ac b/configure.ac index ba11b6c8b..2a62b62ae 100644 --- a/configure.ac +++ b/configure.ac @@ -10,9 +10,9 @@ m4_define([libweston_patch_version], [0]) AC_PREREQ([2.64]) AC_INIT([weston], [weston_version], - [https://bugs.freedesktop.org/enter_bug.cgi?product=Wayland&component=weston&version=weston_version], + [https://gitlab.freedesktop.org/wayland/weston/issues/], [weston], - [http://wayland.freedesktop.org]) + [https://wayland.freedesktop.org]) WAYLAND_PREREQ_VERSION="1.12.0" From 84bc4035b824f69604415e41b5a1b9c87b90db05 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 11 Jun 2018 09:57:04 +1000 Subject: [PATCH 0527/1642] weston-launch: always run through all groups If the user is in group 0, we'd exit the loop early with a failure. Make sure we run through all groups. https://gitlab.freedesktop.org/wayland/weston/issues/86 Signed-off-by: Peter Hutterer [Pekka: fix one whitespace] Reviewed-by: Pekka Paalanen --- libweston/weston-launch.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index 1adcf21a9..bf73e0d61 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -121,7 +121,7 @@ struct weston_launch { union cmsg_data { unsigned char b[4]; int fd; }; static gid_t * -read_groups(void) +read_groups(int *ngroups) { int n; gid_t *groups; @@ -142,6 +142,8 @@ read_groups(void) free(groups); return NULL; } + + *ngroups = n; return groups; } @@ -150,7 +152,7 @@ weston_launch_allowed(struct weston_launch *wl) { struct group *gr; gid_t *groups; - int i; + int ngroups; #ifdef HAVE_SYSTEMD_LOGIN char *session, *seat; int err; @@ -161,10 +163,10 @@ weston_launch_allowed(struct weston_launch *wl) gr = getgrnam("weston-launch"); if (gr) { - groups = read_groups(); - if (groups) { - for (i = 0; groups[i]; ++i) { - if (groups[i] == gr->gr_gid) { + groups = read_groups(&ngroups); + if (groups && ngroups > 0) { + while (ngroups--) { + if (groups[ngroups] == gr->gr_gid) { free(groups); return true; } From 70e8a32fb7096ed524296b6b9805179a420e0fae Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 11 Jun 2018 09:57:06 +1000 Subject: [PATCH 0528/1642] compositor: print usage to stdout on success (not stderr) Triggered by weston --help, the usage() output should not look like an error. Note that there is only one caller of usage() at the moment, but let's handle this here based on the status in case we add other cases. https://gitlab.freedesktop.org/wayland/weston/issues/112 Signed-off-by: Peter Hutterer Reviewed-by: Pekka Paalanen --- compositor/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compositor/main.c b/compositor/main.c index e84857f7c..93f92fdcc 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -438,7 +438,7 @@ verify_xdg_runtime_dir(void) static int usage(int error_code) { - fprintf(stderr, + fprintf(error_code == EXIT_SUCCESS ? stdout : stderr, "Usage: weston [OPTIONS]\n\n" "This is weston version " VERSION ", the Wayland reference compositor.\n" "Weston supports multiple backends, and depending on which backend is in use\n" From a1fd4302bcbe9a2f8f863e1d9a710c9cab1a401d Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 12 Jun 2018 10:42:18 +1000 Subject: [PATCH 0529/1642] compositor: print usage to stdout on success (not stderr) - this time really Fix all the other printfs too. https://gitlab.freedesktop.org/wayland/weston/issues/112 Signed-off-by: Peter Hutterer Signed-off-by: Pekka Paalanen --- compositor/main.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 93f92fdcc..86e3782f2 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -438,7 +438,9 @@ verify_xdg_runtime_dir(void) static int usage(int error_code) { - fprintf(error_code == EXIT_SUCCESS ? stdout : stderr, + FILE *out = error_code == EXIT_SUCCESS ? stdout : stderr; + + fprintf(out, "Usage: weston [OPTIONS]\n\n" "This is weston version " VERSION ", the Wayland reference compositor.\n" "Weston supports multiple backends, and depending on which backend is in use\n" @@ -478,7 +480,7 @@ usage(int error_code) " -h, --help\t\tThis help message\n\n"); #if defined(BUILD_DRM_COMPOSITOR) - fprintf(stderr, + fprintf(out, "Options for drm-backend.so:\n\n" " --seat=SEAT\t\tThe seat that weston should run on\n" " --tty=TTY\t\tThe tty to use\n" @@ -488,7 +490,7 @@ usage(int error_code) #endif #if defined(BUILD_FBDEV_COMPOSITOR) - fprintf(stderr, + fprintf(out, "Options for fbdev-backend.so:\n\n" " --tty=TTY\t\tThe tty to use\n" " --device=DEVICE\tThe framebuffer device to use\n" @@ -496,7 +498,7 @@ usage(int error_code) #endif #if defined(BUILD_HEADLESS_COMPOSITOR) - fprintf(stderr, + fprintf(out, "Options for headless-backend.so:\n\n" " --width=WIDTH\t\tWidth of memory surface\n" " --height=HEIGHT\tHeight of memory surface\n" @@ -508,7 +510,7 @@ usage(int error_code) #endif #if defined(BUILD_RDP_COMPOSITOR) - fprintf(stderr, + fprintf(out, "Options for rdp-backend.so:\n\n" " --width=WIDTH\t\tWidth of desktop\n" " --height=HEIGHT\tHeight of desktop\n" @@ -523,7 +525,7 @@ usage(int error_code) #endif #if defined(BUILD_WAYLAND_COMPOSITOR) - fprintf(stderr, + fprintf(out, "Options for wayland-backend.so:\n\n" " --width=WIDTH\t\tWidth of Wayland surface\n" " --height=HEIGHT\tHeight of Wayland surface\n" @@ -536,7 +538,7 @@ usage(int error_code) #endif #if defined(BUILD_X11_COMPOSITOR) - fprintf(stderr, + fprintf(out, "Options for x11-backend.so:\n\n" " --width=WIDTH\t\tWidth of X window\n" " --height=HEIGHT\tHeight of X window\n" From 002f0c56e0c9ab62ea008f110de46ed9c1dd29b9 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 13 Jun 2018 09:11:48 +1000 Subject: [PATCH 0530/1642] man: fix prefixes for weston.ini(5) Replace a few hardcoded paths with the substitutes https://gitlab.freedesktop.org/wayland/weston/issues/105 Signed-off-by: Peter Hutterer Reviewed-by: Pekka Paalanen --- Makefile.am | 6 +++++- man/weston.ini.man | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index 9d23b50d2..d2025bc55 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1595,8 +1595,12 @@ endif MAN_SUBSTS = \ -e 's|__weston_native_backend__|$(WESTON_NATIVE_BACKEND)|g' \ - -e 's|__weston_modules_dir__|$(pkglibdir)|g' \ + -e 's|__weston_modules_dir__|$(moduledir)|g' \ + -e 's|__libweston_modules_dir__|$(libweston_moduledir)|g' \ -e 's|__weston_shell_client__|$(WESTON_SHELL_CLIENT)|g' \ + -e 's|__weston_libexecdir__|$(libexecdir)|g' \ + -e 's|__weston_bindir__|$(bindir)|g' \ + -e 's|__xserver_path__|$(XSERVER_PATH)|g' \ -e 's|__version__|$(PACKAGE_VERSION)|g' SUFFIXES = .1 .5 .7 .man diff --git a/man/weston.ini.man b/man/weston.ini.man index b5668b5a3..027eae0ed 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -124,7 +124,7 @@ directory are: .TP 7 .BI "backend=" headless-backend.so overrides defaults backend. Available backend modules in the -.IR "__weston_modules_dir__" +.IR "__libweston_modules_dir__" directory are: .PP .RS 10 @@ -477,7 +477,7 @@ present. This seat can be constrained like any other. .RE .SH "INPUT-METHOD SECTION" .TP 7 -.BI "path=" "/usr/libexec/weston-keyboard" +.BI "path=" "__weston_libexecdir__/weston-keyboard" sets the path of the on screen keyboard input method (string). .RE .RE @@ -560,13 +560,13 @@ The terminal shell (string). Sets the $TERM variable. .RE .SH "XWAYLAND SECTION" .TP 7 -.BI "path=" "/usr/bin/Xwayland" +.BI "path=" "__xserver_path__" sets the path to the xserver to run (string). .RE .RE .SH "SCREEN-SHARE SECTION" .TP 7 -.BI "command=" "/usr/bin/weston --backend=rdp-backend.so \ +.BI "command=" "__weston_bindir__/weston --backend=rdp-backend.so \ --shell=fullscreen-shell.so --no-clients-resize" sets the command to start a fullscreen-shell server for screen sharing (string). .RE From e03c111e4e631817e82c5172396859968a736b2d Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 24 Nov 2016 20:45:45 +0000 Subject: [PATCH 0531/1642] tests: Don't rely on build directory layout Rather than having a hardcoded dependency on the build-directory layout, use an explicit module-map environment variable, which rewrites requests for modules and helper/libexec binaries to specific paths. Pekka: This will help with migration to Meson where setting up the paths according to autotools would be painful and unnecessary. Emre: This should also help setting up the test suite after a cross-compile. Pekka: A caveat here is that this patch makes it slightly easier to load external backends by abusing the module map. External backends are specifically not supported in libweston. Signed-off-by: Daniel Stone v2: Fixed ivi_layout-test-plugin.c:wet_module_init(). Do not change the lookup name of ivi-layout.ivi. Improved documentation of weston_module_path_from_env() and made it cope with map strings that a) do not end with a semicolon, and b) have multiple consecutive semicolons. Let WESTON_MODULE_MAP be printed into the test log so that it is easier to run tests manually. Reviewed-by: Daniel Stone Reviewed-by: Emre Ucan Suggested by Emil: Use a variable for strlen(name). Signed-off-by: Pekka Paalanen Reviewed-by: Emil Velikov --- compositor/main.c | 26 ++++++++++---- compositor/text-backend.c | 6 +--- compositor/weston-screenshooter.c | 8 ++--- compositor/weston.h | 3 ++ desktop-shell/shell.c | 9 ++--- libweston/compositor.c | 58 ++++++++++++++++++++++++++++--- libweston/compositor.h | 3 ++ tests/ivi_layout-test-plugin.c | 16 ++++----- tests/weston-tests-env | 17 +++++++++ 9 files changed, 109 insertions(+), 37 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 86e3782f2..a801be872 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -652,7 +652,6 @@ weston_create_listening_socket(struct wl_display *display, const char *socket_na WL_EXPORT void * wet_load_module_entrypoint(const char *name, const char *entrypoint) { - const char *builddir = getenv("WESTON_BUILD_DIR"); char path[PATH_MAX]; void *module, *init; size_t len; @@ -661,10 +660,8 @@ wet_load_module_entrypoint(const char *name, const char *entrypoint) return NULL; if (name[0] != '/') { - if (builddir) - len = snprintf(path, sizeof path, "%s/.libs/%s", builddir, - name); - else + len = weston_module_path_from_env(name, path, sizeof path); + if (len == 0) len = snprintf(path, sizeof path, "%s/%s", MODULEDIR, name); } else { @@ -701,7 +698,6 @@ wet_load_module_entrypoint(const char *name, const char *entrypoint) return init; } - WL_EXPORT int wet_load_module(struct weston_compositor *compositor, const char *name, int *argc, char *argv[]) @@ -732,6 +728,24 @@ wet_load_shell(struct weston_compositor *compositor, return 0; } +WL_EXPORT char * +wet_get_binary_path(const char *name) +{ + char path[PATH_MAX]; + size_t len; + + len = weston_module_path_from_env(name, path, sizeof path); + if (len > 0) + return strdup(path); + + len = snprintf(path, sizeof path, "%s/%s", + weston_config_get_libexec_dir(), name); + if (len >= sizeof path) + return NULL; + + return strdup(path); +} + static int load_modules(struct weston_compositor *ec, const char *modules, int *argc, char *argv[], int32_t *xwayland) diff --git a/compositor/text-backend.c b/compositor/text-backend.c index 4d8c085bb..542424278 100644 --- a/compositor/text-backend.c +++ b/compositor/text-backend.c @@ -1047,14 +1047,10 @@ text_backend_configuration(struct text_backend *text_backend) struct weston_config *config = wet_get_config(text_backend->compositor); struct weston_config_section *section; char *client; - int ret; section = weston_config_get_section(config, "input-method", NULL, NULL); - ret = asprintf(&client, "%s/weston-keyboard", - weston_config_get_libexec_dir()); - if (ret < 0) - client = NULL; + client = wet_get_binary_path("weston-keyboard"); weston_config_section_get_string(section, "path", &text_backend->input_method.path, client); diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c index 70afed4a5..0994cb4f2 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -117,12 +117,10 @@ screenshooter_binding(struct weston_keyboard *keyboard, { struct screenshooter *shooter = data; char *screenshooter_exe; - int ret; - ret = asprintf(&screenshooter_exe, "%s/%s", - weston_config_get_libexec_dir(), - "/weston-screenshooter"); - if (ret < 0) { + + screenshooter_exe = wet_get_binary_path("weston-screenshooter"); + if (!screenshooter_exe) { weston_log("Could not construct screenshooter path.\n"); return; } diff --git a/compositor/weston.h b/compositor/weston.h index 5708aca40..8e1d2602c 100644 --- a/compositor/weston.h +++ b/compositor/weston.h @@ -77,6 +77,9 @@ int module_init(struct weston_compositor *compositor, int *argc, char *argv[]); +char * +wet_get_binary_path(const char *name); + int wet_load_xwayland(struct weston_compositor *comp); diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 42fc27c6a..8b7a23ade 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -468,17 +468,12 @@ shell_configuration(struct desktop_shell *shell) { struct weston_config_section *section; char *s, *client; - int ret; int allow_zap; section = weston_config_get_section(wet_get_config(shell->compositor), "shell", NULL, NULL); - ret = asprintf(&client, "%s/%s", weston_config_get_libexec_dir(), - WESTON_SHELL_CLIENT); - if (ret < 0) - client = NULL; - weston_config_section_get_string(section, - "client", &s, client); + client = wet_get_binary_path(WESTON_SHELL_CLIENT); + weston_config_section_get_string(section, "client", &s, client); free(client); shell->client = s; diff --git a/libweston/compositor.c b/libweston/compositor.c index 91f311dff..8e01eacca 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6573,10 +6573,60 @@ weston_version(int *major, int *minor, int *micro) *micro = WESTON_VERSION_MICRO; } +/** + * Attempts to find a module path from the module map specified in the + * environment. If found, writes the full path into the path variable. + * + * The module map is a string in environment variable WESTON_MODULE_MAP, where + * each entry is of the form "name=path" and entries are separated by + * semicolons. Whitespace is significant. + * + * \param name The name to search for. + * \param path Where the path is written to if found. + * \param path_len Allocated bytes at \c path . + * \returns The length of the string written to path on success, or 0 if the + * module was not specified in the environment map or path_len was too small. + */ +WL_EXPORT size_t +weston_module_path_from_env(const char *name, char *path, size_t path_len) +{ + const char *mapping = getenv("WESTON_MODULE_MAP"); + const char *end; + const int name_len = strlen(name); + + if (!mapping) + return 0; + + end = mapping + strlen(mapping); + while (mapping < end && *mapping) { + const char *filename, *next; + + /* early out: impossibly short string */ + if (end - mapping < name_len + 1) + return 0; + + filename = &mapping[name_len + 1]; + next = strchrnul(mapping, ';'); + + if (strncmp(mapping, name, name_len) == 0 && + mapping[name_len] == '=') { + size_t file_len = next - filename; /* no trailing NUL */ + if (file_len >= path_len) + return 0; + strncpy(path, filename, file_len); + path[file_len] = '\0'; + return file_len; + } + + mapping = next + 1; + } + + return 0; +} + WL_EXPORT void * weston_load_module(const char *name, const char *entrypoint) { - const char *builddir = getenv("WESTON_BUILD_DIR"); char path[PATH_MAX]; void *module, *init; size_t len; @@ -6585,10 +6635,8 @@ weston_load_module(const char *name, const char *entrypoint) return NULL; if (name[0] != '/') { - if (builddir) - len = snprintf(path, sizeof path, "%s/.libs/%s", - builddir, name); - else + len = weston_module_path_from_env(name, path, sizeof path); + if (len == 0) len = snprintf(path, sizeof path, "%s/%s", LIBWESTON_MODULEDIR, name); } else { diff --git a/libweston/compositor.h b/libweston/compositor.h index 8942ab9a2..fbf8a73db 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -2149,6 +2149,9 @@ weston_transformed_region(int width, int height, void * weston_load_module(const char *name, const char *entrypoint); +size_t +weston_module_path_from_env(const char *name, char *path, size_t path_len); + int weston_parse_transform(const char *transform, uint32_t *out); diff --git a/tests/ivi_layout-test-plugin.c b/tests/ivi_layout-test-plugin.c index 1f19c55f0..0fe9212dc 100644 --- a/tests/ivi_layout-test-plugin.c +++ b/tests/ivi_layout-test-plugin.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "compositor.h" #include "compositor/weston.h" @@ -223,7 +224,6 @@ wet_module_init(struct weston_compositor *compositor, { struct wl_event_loop *loop; struct test_launcher *launcher; - const char *path; const struct ivi_layout_interface *iface; iface = ivi_layout_get_api(compositor); @@ -233,20 +233,18 @@ wet_module_init(struct weston_compositor *compositor, return -1; } - path = getenv("WESTON_BUILD_DIR"); - if (!path) { - weston_log("test setup failure: WESTON_BUILD_DIR not set\n"); - return -1; - } - launcher = zalloc(sizeof *launcher); if (!launcher) return -1; + if (weston_module_path_from_env("ivi-layout.ivi", launcher->exe, + sizeof launcher->exe) == 0) { + weston_log("test setup failure: WESTON_MODULE_MAP not set\n"); + return -1; + } + launcher->compositor = compositor; launcher->layout_interface = iface; - snprintf(launcher->exe, sizeof launcher->exe, - "%s/ivi-layout.ivi", path); if (wl_global_create(compositor->wl_display, &weston_test_runner_interface, 1, diff --git a/tests/weston-tests-env b/tests/weston-tests-env index ac2473f77..04648bf1b 100755 --- a/tests/weston-tests-env +++ b/tests/weston-tests-env @@ -25,6 +25,19 @@ MODDIR=$abs_builddir/.libs SHELL_PLUGIN=$MODDIR/desktop-shell.so TEST_PLUGIN=$MODDIR/weston-test.so +WESTON_MODULE_MAP= +for mod in cms-colord cms-static desktop-shell drm-backend fbdev-backend \ + fullscreen-shell gl-renderer headless-backend hmi-controller \ + ivi-shell rdp-compositor screen-share wayland-backend \ + weston-test-desktop-shell x11-backend xwayland; do + WESTON_MODULE_MAP="${WESTON_MODULE_MAP}${mod}.so=${abs_builddir}/.libs/${mod}.so;" +done + +for exe in weston-desktop-shell weston-keyboard weston-screenshooter \ + weston-simple-im ivi-layout.ivi; do \ + WESTON_MODULE_MAP="${WESTON_MODULE_MAP}${exe}=${abs_builddir}/${exe};" +done + CONFIG_FILE="${TEST_NAME}.ini" if [ -e "${abs_builddir}/${CONFIG_FILE}" ]; then @@ -40,6 +53,7 @@ case $TEST_FILE in SHELL_PLUGIN=$MODDIR/ivi-shell.so set -x + WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ WESTON_DATA_DIR=$abs_top_srcdir/data \ WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ @@ -53,6 +67,7 @@ case $TEST_FILE in ;; *.la|*.so) set -x + WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ WESTON_DATA_DIR=$abs_top_srcdir/data \ WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ @@ -69,6 +84,7 @@ case $TEST_FILE in SHELL_PLUGIN=$MODDIR/ivi-shell.so set -x + WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ WESTON_DATA_DIR=$abs_top_srcdir/data \ WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ @@ -84,6 +100,7 @@ case $TEST_FILE in ;; *) set -x + WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ WESTON_DATA_DIR=$abs_top_srcdir/data \ WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ From c5aaaa7b39b3e0fd11202b84442752e2ada3ee00 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 14 Jun 2018 16:20:32 +0300 Subject: [PATCH 0532/1642] shared: remove weston_config_get_libexec_dir() Now that WESTON_MODULE_MAP supersedes WESTON_BUILD_DIR for libexec binaries, we don't need to check in WESTON_BUILD_DIR anymore. There was only one user of weston_config_get_libexec_dir(), so remove the whole function. There is no reason to export it. Due to libshared.la being pulled into libweston, this probably was libweston ABI unintended. Regardless, libweston major has already been bumped. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Reviewed-by: Emre Ucan Reviewed-by: Emil Velikov --- compositor/main.c | 3 +-- shared/config-parser.c | 12 ------------ shared/config-parser.h | 2 -- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index a801be872..2cb50c197 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -738,8 +738,7 @@ wet_get_binary_path(const char *name) if (len > 0) return strdup(path); - len = snprintf(path, sizeof path, "%s/%s", - weston_config_get_libexec_dir(), name); + len = snprintf(path, sizeof path, "%s/%s", LIBEXECDIR, name); if (len >= sizeof path) return NULL; diff --git a/shared/config-parser.c b/shared/config-parser.c index 2a595b1f6..ae5f80350 100644 --- a/shared/config-parser.c +++ b/shared/config-parser.c @@ -330,18 +330,6 @@ weston_config_section_get_bool(struct weston_config_section *section, return 0; } -WL_EXPORT -const char * -weston_config_get_libexec_dir(void) -{ - const char *path = getenv("WESTON_BUILD_DIR"); - - if (path) - return path; - - return LIBEXECDIR; -} - const char * weston_config_get_name_from_env(void) { diff --git a/shared/config-parser.h b/shared/config-parser.h index af3f66a24..5cb0bca1b 100644 --- a/shared/config-parser.h +++ b/shared/config-parser.h @@ -103,8 +103,6 @@ int weston_config_section_get_bool(struct weston_config_section *section, const char *key, int *value, int default_value); -const char * -weston_config_get_libexec_dir(void); const char * weston_config_get_name_from_env(void); From f5c96467e31624df842e93cc72f27069b7cb237d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 14 Jun 2018 16:25:39 +0300 Subject: [PATCH 0533/1642] tests: remove WESTON_BUILD_DIR from env There are no users left. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Reviewed-by: Emre Ucan --- tests/weston-tests-env | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/weston-tests-env b/tests/weston-tests-env index 04648bf1b..63aad4af2 100755 --- a/tests/weston-tests-env +++ b/tests/weston-tests-env @@ -55,7 +55,6 @@ case $TEST_FILE in set -x WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ WESTON_DATA_DIR=$abs_top_srcdir/data \ - WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ $WESTON --backend=$MODDIR/$BACKEND \ --no-config \ @@ -69,7 +68,6 @@ case $TEST_FILE in set -x WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ WESTON_DATA_DIR=$abs_top_srcdir/data \ - WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ $WESTON --backend=$MODDIR/$BACKEND \ ${CONFIG} \ @@ -86,7 +84,6 @@ case $TEST_FILE in set -x WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ WESTON_DATA_DIR=$abs_top_srcdir/data \ - WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ WESTON_TEST_CLIENT_PATH=$abs_builddir/$TEST_FILE \ $WESTON --backend=$MODDIR/$BACKEND \ @@ -102,7 +99,6 @@ case $TEST_FILE in set -x WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ WESTON_DATA_DIR=$abs_top_srcdir/data \ - WESTON_BUILD_DIR=$abs_builddir \ WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ WESTON_TEST_CLIENT_PATH=$abs_builddir/$TEST_FILE \ $WESTON --backend=$MODDIR/$BACKEND \ From 78a42116ae92f93a01539a785ce95cc478189608 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 28 Nov 2016 15:54:06 +0000 Subject: [PATCH 0534/1642] tests: Reshuffle IVI layout tests Rename the IVI tests to be more consistent with the others, and invert the naming of plugin/client to make it slightly more clear what's going to happen. Handle the renaming by using wet_get_binary_path to rewrite the local binaries. As a side-effect, weston.ini ivi-shell-user-interface no longer needs to be given as an absolute path. Signed-off-by: Daniel Stone v2: Call ivi-layout.ivi as ivi-layout-test-client.ivi to keep the same name in both the file and the lookup, so that the module map does not need to change the name. Update code comments to reflect the new names. Rename ivi_layout-test-plugin.c to ivi-layout-test-plugin.c. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Reviewed-by: Emre Ucan Reviewed-by: Emil Velikov --- Makefile.am | 16 ++++++++-------- ivi-shell/hmi-controller.c | 12 +++++++++++- ivi-shell/weston.ini.in | 8 ++++---- ...nternal-test.c => ivi-layout-internal-test.c} | 0 ...vi_layout-test.c => ivi-layout-test-client.c} | 8 ++++---- ...ut-test-plugin.c => ivi-layout-test-plugin.c} | 12 +++++++----- tests/weston-tests-env | 2 +- 7 files changed, 35 insertions(+), 23 deletions(-) rename tests/{ivi_layout-internal-test.c => ivi-layout-internal-test.c} (100%) rename tests/{ivi_layout-test.c => ivi-layout-test-client.c} (97%) rename tests/{ivi_layout-test-plugin.c => ivi-layout-test-plugin.c} (98%) diff --git a/Makefile.am b/Makefile.am index d2025bc55..3bce47a11 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1477,13 +1477,13 @@ ivi_layout_internal_test_la_LIBADD = $(test_module_libadd) ivi_layout_internal_test_la_LDFLAGS = $(test_module_ldflags) ivi_layout_internal_test_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) ivi_layout_internal_test_la_SOURCES = \ - tests/ivi_layout-internal-test.c + tests/ivi-layout-internal-test.c ivi_layout_test_la_LIBADD = $(test_module_libadd) ivi_layout_test_la_LDFLAGS = $(test_module_ldflags) ivi_layout_test_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) ivi_layout_test_la_SOURCES = \ - tests/ivi_layout-test-plugin.c \ + tests/ivi-layout-test-plugin.c \ tests/ivi-test.h \ shared/helpers.h nodist_ivi_layout_test_la_SOURCES = \ @@ -1500,17 +1500,17 @@ nodist_ivi_shell_app_weston_SOURCES = \ ivi_shell_app_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) ivi_shell_app_weston_LDADD = libtest-client.la -noinst_PROGRAMS += ivi-layout.ivi +noinst_PROGRAMS += ivi-layout-test-client.ivi -ivi_layout_ivi_SOURCES = \ - tests/ivi_layout-test.c \ +ivi_layout_test_client_ivi_SOURCES = \ + tests/ivi-layout-test-client.c \ tests/ivi-test.h \ shared/helpers.h -nodist_ivi_layout_ivi_SOURCES = \ +nodist_ivi_layout_test_client_ivi_SOURCES = \ protocol/ivi-application-protocol.c \ protocol/ivi-application-client-protocol.h -ivi_layout_ivi_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -ivi_layout_ivi_LDADD = libtest-client.la +ivi_layout_test_client_ivi_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) +ivi_layout_test_client_ivi_LDADD = libtest-client.la endif if BUILD_SETBACKLIGHT diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index 0e44df88b..a0e49ba0d 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -676,6 +676,7 @@ hmi_server_setting_create(struct weston_compositor *ec) struct hmi_server_setting *setting = MEM_ALLOC(sizeof(*setting)); struct weston_config *config = wet_get_config(ec); struct weston_config_section *shell_section = NULL; + char *ivi_ui_config; shell_section = weston_config_get_section(config, "ivi-shell", NULL, NULL); @@ -704,7 +705,16 @@ hmi_server_setting_create(struct weston_compositor *ec) weston_config_section_get_string(shell_section, "ivi-shell-user-interface", - &setting->ivi_homescreen, NULL); + &ivi_ui_config, NULL); + if (ivi_ui_config && ivi_ui_config[0] != '/') { + setting->ivi_homescreen = wet_get_binary_path(ivi_ui_config); + if (setting->ivi_homescreen) + free(ivi_ui_config); + else + setting->ivi_homescreen = ivi_ui_config; + } else { + setting->ivi_homescreen = ivi_ui_config; + } return setting; } diff --git a/ivi-shell/weston.ini.in b/ivi-shell/weston.ini.in index 3f11e1c02..a06d76ef2 100644 --- a/ivi-shell/weston.ini.in +++ b/ivi-shell/weston.ini.in @@ -1,9 +1,9 @@ [core] -shell=@plugin_prefix@ivi-shell.so -modules=@plugin_prefix@hmi-controller.so +shell=ivi-shell.so +modules=hmi-controller.so [ivi-shell] -ivi-shell-user-interface=@abs_top_builddir@/weston-ivi-shell-user-interface +ivi-shell-user-interface=weston-ivi-shell-user-interface #developermode=true @@ -38,7 +38,7 @@ workspace-background-color=0x99000000 workspace-background-id=2001 [input-method] -path=@libexecdir@/weston-keyboard +path=weston-keyboard [ivi-launcher] workspace-id=0 diff --git a/tests/ivi_layout-internal-test.c b/tests/ivi-layout-internal-test.c similarity index 100% rename from tests/ivi_layout-internal-test.c rename to tests/ivi-layout-internal-test.c diff --git a/tests/ivi_layout-test.c b/tests/ivi-layout-test-client.c similarity index 97% rename from tests/ivi_layout-test.c rename to tests/ivi-layout-test-client.c index d2f4c9353..192245311 100644 --- a/tests/ivi_layout-test.c +++ b/tests/ivi-layout-test-client.c @@ -175,18 +175,18 @@ ivi_window_destroy(struct ivi_window *wnd) /******************************** tests ********************************/ /* - * This is a test program, launched by ivi_layout-test-plugin.c. Each TEST() + * This is a test program, launched by ivi-layout-test-plugin.c. Each TEST() * is forked and exec'd as usual with the weston-test-runner framework. * * These tests make use of weston_test_runner global interface exposed by - * ivi_layout-test-plugin.c. This allows these tests to trigger compositor-side + * ivi-layout-test-plugin.c. This allows these tests to trigger compositor-side * checks. * - * See ivi_layout-test-plugin.c for further details. + * See ivi-layout-test-plugin.c for further details. */ /** - * RUNNER_TEST() names are defined in ivi_layout-test-plugin.c. + * RUNNER_TEST() names are defined in ivi-layout-test-plugin.c. * Each RUNNER_TEST name listed here uses the same simple initial client setup. */ const char * const basic_test_names[] = { diff --git a/tests/ivi_layout-test-plugin.c b/tests/ivi-layout-test-plugin.c similarity index 98% rename from tests/ivi_layout-test-plugin.c rename to tests/ivi-layout-test-plugin.c index 0fe9212dc..aa88a712f 100644 --- a/tests/ivi_layout-test-plugin.c +++ b/tests/ivi-layout-test-plugin.c @@ -237,7 +237,8 @@ wet_module_init(struct weston_compositor *compositor, if (!launcher) return -1; - if (weston_module_path_from_env("ivi-layout.ivi", launcher->exe, + if (weston_module_path_from_env("ivi-layout-test-client.ivi", + launcher->exe, sizeof launcher->exe) == 0) { weston_log("test setup failure: WESTON_MODULE_MAP not set\n"); return -1; @@ -300,11 +301,12 @@ runner_assert_fail(const char *cond, const char *file, int line, * this module specially by loading it in ivi-shell. * * Once Weston init completes, this module launches one test program: - * ivi-layout.ivi (ivi_layout-test.c). That program uses the weston-test-runner - * framework to fork and exec each TEST() in ivi_layout-test.c with a fresh + * ivi-layout-test-client.ivi (ivi-layout-test-client.c). + * That program uses the weston-test-runner + * framework to fork and exec each TEST() in ivi-layout-test-client.c with a fresh * connection to the single compositor instance. * - * Each TEST() in ivi_layout-test.c will bind to weston_test_runner global + * Each TEST() in ivi-layout-test-client.c will bind to weston_test_runner global * interface. A TEST() will set up the client state, and issue * weston_test_runner.run request to execute the compositor-side of the test. * @@ -317,7 +319,7 @@ runner_assert_fail(const char *cond, const char *file, int line, * runner_assert_or_return(). This module catches the test program exit * code and passes it out of Weston to the test harness. * - * A single TEST() in ivi_layout-test.c may use multiple RUNNER_TEST()s to + * A single TEST() in ivi-layout-test-client.c may use multiple RUNNER_TEST()s to * achieve multiple test points over a client action sequence. */ diff --git a/tests/weston-tests-env b/tests/weston-tests-env index 63aad4af2..4a352ff0d 100755 --- a/tests/weston-tests-env +++ b/tests/weston-tests-env @@ -34,7 +34,7 @@ for mod in cms-colord cms-static desktop-shell drm-backend fbdev-backend \ done for exe in weston-desktop-shell weston-keyboard weston-screenshooter \ - weston-simple-im ivi-layout.ivi; do \ + weston-simple-im ivi-layout-test-client.ivi; do \ WESTON_MODULE_MAP="${WESTON_MODULE_MAP}${exe}=${abs_builddir}/${exe};" done From 6a699b1a35c6b57c9044bf607933a77184b4e872 Mon Sep 17 00:00:00 2001 From: Matheus Santana Date: Fri, 22 Jun 2018 18:00:44 -0300 Subject: [PATCH 0535/1642] man: remove redundant word in weston.ini(5) Signed-off-by: Matheus Santana Reviewed-by: Yong Bakos Reviewed-by: Emil Velikov --- man/weston.ini.man | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/weston.ini.man b/man/weston.ini.man index 027eae0ed..02c9b0304 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -468,7 +468,7 @@ denoting the scaling multiplier for the output. .RE .TP 7 .BI "seat=" name -The logical seat name that that this output should be associated with. If this +The logical seat name that this output should be associated with. If this is set then the seat's input will be confined to the output that has the seat set on it. The expectation is that this functionality will be used in a multiheaded environment with a single compositor for multiple output and input From 944dd235b4ff6e5ef64e48f168bb217933c30acb Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 3 Nov 2017 09:56:05 +0200 Subject: [PATCH 0536/1642] weston: support clone mode on DRM-frontend Add a new output section key "same-as" for configuring clone mode. An output marked "same-as" another output will be configured identically to the other output. The current implementation supports only CRTC sharing for clone mode. Independent CRTC clone mode cannot be supported until output layout logic is moved from libweston into the frontend and libweston's damage tracking issues stemming from overlapping outputs are solved. Quite a lot of infrastructure is needed to properly configure clone mode. The implemented logic allows easy addition of independent CRTC clone mode once libweston supports it. The idea is that wet_layoutput is the item to be laid out and all weston_outputs a wet_layoutput contains show exactly the same area of the desktop. The configuration logic attempts to automatically fall back to creating more weston_outputs when all heads do not work under the same weston_output. For now, the fallback path ends with an error message. Enabling a weston_output is bit complicated, because one needs to first collect all relevant heads, try to attach them all to the weston_output, and then back up head by head until enabling the weston_output succeeds. A new weston_output is created for the left-over heads and the process is repeated. CRTC-sharing clone mode is the most efficient clone mode, offering synchronized scanout timings, but it is not always supported by hardware. v10: - rebased trivial conflicts in man page - switch to gitlab issue URL v9: - replace weston_compositor_set_heads_changed_cb() with weston_compositor_add_heads_changed_listener() - remove workaround in simple_head_enable() v6: - Add man-page note about cms-colord. - Don't create an output just to turn it off. Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/22 Signed-off-by: Pekka Paalanen Acked-by: Derek Foreman Acked-by: Daniel Stone Reviewed-by: Ian Ray --- compositor/main.c | 492 +++++++++++++++++++++++++++++++++++++++++++-- man/weston-drm.man | 12 ++ 2 files changed, 484 insertions(+), 20 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 2cb50c197..389113410 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -71,11 +71,41 @@ struct wet_output_config { }; struct wet_compositor; +struct wet_layoutput; struct wet_head_tracker { struct wl_listener head_destroy_listener; }; +/** User data for each weston_output */ +struct wet_output { + struct weston_output *output; + struct wl_listener output_destroy_listener; + struct wet_layoutput *layoutput; + struct wl_list link; /**< in wet_layoutput::output_list */ +}; + +#define MAX_CLONE_HEADS 16 + +struct wet_head_array { + struct weston_head *heads[MAX_CLONE_HEADS]; /**< heads to add */ + unsigned n; /**< the number of heads */ +}; + +/** A layout output + * + * Contains wet_outputs that are all clones (independent CRTCs). + * Stores output layout information in the future. + */ +struct wet_layoutput { + struct wet_compositor *compositor; + struct wl_list compositor_link; /**< in wet_compositor::layoutput_list */ + struct wl_list output_list; /**< wet_output::link */ + char *name; + struct weston_config_section *section; + struct wet_head_array add; /**< tmp: heads to add as clones */ +}; + struct wet_compositor { struct weston_compositor *compositor; struct weston_config *config; @@ -84,6 +114,7 @@ struct wet_compositor { struct wl_listener heads_changed_listener; int (*simple_output_configure)(struct weston_output *output); bool init_failed; + struct wl_list layoutput_list; /**< wet_layoutput::compositor_link */ }; static FILE *weston_logfile = NULL; @@ -1178,12 +1209,6 @@ simple_head_enable(struct wet_compositor *wet, struct weston_head *head) struct weston_output *output; int ret = 0; - /* Workaround for repeated DRM backend "off" setting. - * For any other case, we should not have an attached head that is not - * enabled. */ - if (weston_head_get_output(head)) - return; - output = weston_compositor_create_output_with_head(wet->compositor, head); if (!output) { @@ -1205,10 +1230,6 @@ simple_head_enable(struct wet_compositor *wet, struct weston_head *head) return; } - /* Escape hatch for DRM backend "off" setting. */ - if (ret > 0) - return; - if (weston_output_enable(output) < 0) { weston_log("Enabling output \"%s\" failed.\n", weston_head_get_name(head)); @@ -1304,32 +1325,29 @@ configure_input_device(struct weston_compositor *compositor, } static int -drm_backend_output_configure(struct weston_output *output) +drm_backend_output_configure(struct weston_output *output, + struct weston_config_section *section) { - struct weston_config *wc = wet_get_config(output->compositor); struct wet_compositor *wet = to_wet_compositor(output->compositor); - struct weston_config_section *section; - const struct weston_drm_output_api *api = weston_drm_output_get_api(output->compositor); + const struct weston_drm_output_api *api; enum weston_drm_backend_output_mode mode = WESTON_DRM_BACKEND_OUTPUT_PREFERRED; - char *s; char *modeline = NULL; char *gbm_format = NULL; char *seat = NULL; + api = weston_drm_output_get_api(output->compositor); if (!api) { weston_log("Cannot use weston_drm_output_api.\n"); return -1; } - section = weston_config_get_section(wc, "output", "name", output->name); weston_config_section_get_string(section, "mode", &s, "preferred"); if (strcmp(s, "off") == 0) { - weston_output_disable(output); - free(s); - return 1; + assert(0 && "off was supposed to be pruned"); + return -1; } else if (wet->drm_use_current_mode || strcmp(s, "current") == 0) { mode = WESTON_DRM_BACKEND_OUTPUT_CURRENT; } else if (strcmp(s, "preferred") != 0) { @@ -1362,6 +1380,434 @@ drm_backend_output_configure(struct weston_output *output) return 0; } +/* Find the output section to use for configuring the output with the + * named head. If an output section with the given name contains + * a "same-as" key, ignore all other settings in the output section and + * instead find an output section named by the "same-as". Do this + * recursively. + */ +static struct weston_config_section * +drm_config_find_controlling_output_section(struct weston_config *config, + const char *head_name) +{ + struct weston_config_section *section; + char *same_as; + int depth = 0; + + same_as = strdup(head_name); + do { + section = weston_config_get_section(config, "output", + "name", same_as); + if (!section && depth > 0) + weston_log("Configuration error: " + "output section referred to with " + "'same-as=%s' not found.\n", same_as); + + free(same_as); + + if (!section) + return NULL; + + if (++depth > 10) { + weston_log("Configuration error: " + "'same-as' nested too deep for output '%s'.\n", + head_name); + return NULL; + } + + weston_config_section_get_string(section, "same-as", + &same_as, NULL); + } while (same_as); + + return section; +} + +static struct wet_layoutput * +wet_compositor_create_layoutput(struct wet_compositor *compositor, + const char *name, + struct weston_config_section *section) +{ + struct wet_layoutput *lo; + + lo = zalloc(sizeof *lo); + if (!lo) + return NULL; + + lo->compositor = compositor; + wl_list_insert(compositor->layoutput_list.prev, &lo->compositor_link); + wl_list_init(&lo->output_list); + lo->name = strdup(name); + lo->section = section; + + return lo; +} + +static void +wet_layoutput_destroy(struct wet_layoutput *lo) +{ + wl_list_remove(&lo->compositor_link); + assert(wl_list_empty(&lo->output_list)); + free(lo->name); + free(lo); +} + +static void +wet_output_handle_destroy(struct wl_listener *listener, void *data) +{ + struct wet_output *output; + + output = wl_container_of(listener, output, output_destroy_listener); + assert(output->output == data); + + output->output = NULL; + wl_list_remove(&output->output_destroy_listener.link); +} + +static struct wet_output * +wet_layoutput_create_output(struct wet_layoutput *lo, const char *name) +{ + struct wet_output *output; + + output = zalloc(sizeof *output); + if (!output) + return NULL; + + output->output = + weston_compositor_create_output(lo->compositor->compositor, + name); + if (!output) { + free(output); + return NULL; + } + + output->layoutput = lo; + wl_list_insert(lo->output_list.prev, &output->link); + output->output_destroy_listener.notify = wet_output_handle_destroy; + weston_output_add_destroy_listener(output->output, + &output->output_destroy_listener); + + return output; +} + +static struct wet_output * +wet_output_from_weston_output(struct weston_output *base) +{ + struct wl_listener *lis; + + lis = weston_output_get_destroy_listener(base, + wet_output_handle_destroy); + if (!lis) + return NULL; + + return container_of(lis, struct wet_output, output_destroy_listener); +} + +static void +wet_output_destroy(struct wet_output *output) +{ + if (output->output) + weston_output_destroy(output->output); + + wl_list_remove(&output->link); + free(output); +} + +static struct wet_layoutput * +wet_compositor_find_layoutput(struct wet_compositor *wet, const char *name) +{ + struct wet_layoutput *lo; + + wl_list_for_each(lo, &wet->layoutput_list, compositor_link) + if (strcmp(lo->name, name) == 0) + return lo; + + return NULL; +} + +static void +wet_compositor_layoutput_add_head(struct wet_compositor *wet, + const char *output_name, + struct weston_config_section *section, + struct weston_head *head) +{ + struct wet_layoutput *lo; + + lo = wet_compositor_find_layoutput(wet, output_name); + if (!lo) { + lo = wet_compositor_create_layoutput(wet, output_name, section); + if (!lo) + return; + } + + if (lo->add.n + 1 >= ARRAY_LENGTH(lo->add.heads)) + return; + + lo->add.heads[lo->add.n++] = head; +} + +static void +wet_compositor_destroy_layout(struct wet_compositor *wet) +{ + struct wet_layoutput *lo, *lo_tmp; + struct wet_output *output, *output_tmp; + + wl_list_for_each_safe(lo, lo_tmp, + &wet->layoutput_list, compositor_link) { + wl_list_for_each_safe(output, output_tmp, + &lo->output_list, link) { + wet_output_destroy(output); + } + wet_layoutput_destroy(lo); + } +} + +static void +drm_head_prepare_enable(struct wet_compositor *wet, + struct weston_head *head) +{ + const char *name = weston_head_get_name(head); + struct weston_config_section *section; + char *output_name = NULL; + char *mode = NULL; + + section = drm_config_find_controlling_output_section(wet->config, name); + if (section) { + /* skip outputs that are explicitly off, the backend turns + * them off automatically. + */ + weston_config_section_get_string(section, "mode", &mode, NULL); + if (mode && strcmp(mode, "off") == 0) { + free(mode); + return; + } + free(mode); + + weston_config_section_get_string(section, "name", + &output_name, NULL); + assert(output_name); + + wet_compositor_layoutput_add_head(wet, output_name, + section, head); + free(output_name); + } else { + wet_compositor_layoutput_add_head(wet, name, NULL, head); + } +} + +static void +drm_try_attach(struct weston_output *output, + struct wet_head_array *add, + struct wet_head_array *failed) +{ + unsigned i; + + /* try to attach all heads, this probably succeeds */ + for (i = 0; i < add->n; i++) { + if (!add->heads[i]) + continue; + + if (weston_output_attach_head(output, add->heads[i]) < 0) { + assert(failed->n < ARRAY_LENGTH(failed->heads)); + + failed->heads[failed->n++] = add->heads[i]; + add->heads[i] = NULL; + } + } +} + +static int +drm_try_enable(struct weston_output *output, + struct wet_head_array *undo, + struct wet_head_array *failed) +{ + /* Try to enable, and detach heads one by one until it succeeds. */ + while (!output->enabled) { + if (weston_output_enable(output) == 0) + return 0; + + /* the next head to drop */ + while (undo->n > 0 && undo->heads[--undo->n] == NULL) + ; + + /* No heads left to undo and failed to enable. */ + if (undo->heads[undo->n] == NULL) + return -1; + + assert(failed->n < ARRAY_LENGTH(failed->heads)); + + /* undo one head */ + weston_head_detach(undo->heads[undo->n]); + failed->heads[failed->n++] = undo->heads[undo->n]; + undo->heads[undo->n] = NULL; + } + + return 0; +} + +static int +drm_try_attach_enable(struct weston_output *output, struct wet_layoutput *lo) +{ + struct wet_head_array failed = {}; + unsigned i; + + assert(!output->enabled); + + drm_try_attach(output, &lo->add, &failed); + if (drm_backend_output_configure(output, lo->section) < 0) + return -1; + + if (drm_try_enable(output, &lo->add, &failed) < 0) + return -1; + + /* For all successfully attached/enabled heads */ + for (i = 0; i < lo->add.n; i++) + if (lo->add.heads[i]) + wet_head_tracker_create(lo->compositor, + lo->add.heads[i]); + + /* Push failed heads to the next round. */ + lo->add = failed; + + return 0; +} + +static int +drm_process_layoutput(struct wet_compositor *wet, struct wet_layoutput *lo) +{ + struct wet_output *output, *tmp; + char *name = NULL; + int ret; + + /* + * For each existing wet_output: + * try attach + * While heads left to enable: + * Create output + * try attach, try enable + */ + + wl_list_for_each_safe(output, tmp, &lo->output_list, link) { + struct wet_head_array failed = {}; + + if (!output->output) { + /* Clean up left-overs from destroyed heads. */ + wet_output_destroy(output); + continue; + } + + assert(output->output->enabled); + + drm_try_attach(output->output, &lo->add, &failed); + lo->add = failed; + if (lo->add.n == 0) + return 0; + } + + if (!weston_compositor_find_output_by_name(wet->compositor, lo->name)) + name = strdup(lo->name); + + while (lo->add.n > 0) { + if (!wl_list_empty(&lo->output_list)) { + weston_log("Error: independent-CRTC clone mode is not implemented.\n"); + return -1; + } + + if (!name) { + ret = asprintf(&name, "%s:%s", lo->name, + weston_head_get_name(lo->add.heads[0])); + if (ret < 0) + return -1; + } + output = wet_layoutput_create_output(lo, name); + free(name); + name = NULL; + + if (!output) + return -1; + + if (drm_try_attach_enable(output->output, lo) < 0) { + wet_output_destroy(output); + return -1; + } + } + + return 0; +} + +static int +drm_process_layoutputs(struct wet_compositor *wet) +{ + struct wet_layoutput *lo; + int ret = 0; + + wl_list_for_each(lo, &wet->layoutput_list, compositor_link) { + if (lo->add.n == 0) + continue; + + if (drm_process_layoutput(wet, lo) < 0) { + lo->add = (struct wet_head_array){}; + ret = -1; + } + } + + return ret; +} + +static void +drm_head_disable(struct weston_head *head) +{ + struct weston_output *output_base; + struct wet_output *output; + struct wet_head_tracker *track; + + track = wet_head_tracker_from_head(head); + if (track) + wet_head_tracker_destroy(track); + + output_base = weston_head_get_output(head); + assert(output_base); + output = wet_output_from_weston_output(output_base); + assert(output && output->output == output_base); + + weston_head_detach(head); + if (count_remaining_heads(output->output, NULL) == 0) + wet_output_destroy(output); +} + +static void +drm_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; + bool connected; + bool enabled; + bool changed; + + /* We need to collect all cloned heads into outputs before enabling the + * output. + */ + while ((head = weston_compositor_iterate_heads(compositor, head))) { + connected = weston_head_is_connected(head); + enabled = weston_head_is_enabled(head); + changed = weston_head_is_device_changed(head); + + if (connected && !enabled) { + drm_head_prepare_enable(wet, head); + } else if (!connected && enabled) { + drm_head_disable(head); + } else if (enabled && changed) { + weston_log("Detected a monitor change on head '%s', " + "not bothering to do anything about it.\n", + weston_head_get_name(head)); + } + weston_head_reset_device_changed(head); + } + + if (drm_process_layoutputs(wet) < 0) + wet->init_failed = true; +} + static int load_drm_backend(struct weston_compositor *c, int *argc, char **argv, struct weston_config *wc) @@ -1397,7 +1843,9 @@ load_drm_backend(struct weston_compositor *c, config.base.struct_size = sizeof(struct weston_drm_backend_config); config.configure_device = configure_input_device; - wet_set_simple_head_configurator(c, drm_backend_output_configure); + wet->heads_changed_listener.notify = drm_heads_changed; + weston_compositor_add_heads_changed_listener(c, + &wet->heads_changed_listener); ret = weston_compositor_load_backend(c, WESTON_BACKEND_DRM, &config.base); @@ -1936,6 +2384,8 @@ int main(int argc, char *argv[]) { WESTON_OPTION_BOOLEAN, "wait-for-debugger", 0, &wait_for_debugger }, }; + wl_list_init(&wet.layoutput_list); + if (os_fd_set_cloexec(fileno(stdin))) { printf("Unable to set stdin as close on exec().\n"); return EXIT_FAILURE; @@ -2123,6 +2573,8 @@ int main(int argc, char *argv[]) ret = wet.compositor->exit_code; out: + wet_compositor_destroy_layout(&wet); + /* free(NULL) is valid, and it won't be NULL if it's used */ free(wet.parsed_options); diff --git a/man/weston-drm.man b/man/weston-drm.man index d4cb75a77..c9e691512 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -83,6 +83,18 @@ and possibly flipped. Possible values are \fBpixman-shadow\fR=\fIboolean\fR If using the Pixman-renderer, use shadow framebuffers. Defaults to .BR true . +.TP +\fBsame-as\fR=\fIname\fR +Make this output (connector) a clone of another. The argument +.IR name " is the " +.BR name " value of another output section. The +referred to output section must exist. When this key is present in an +output section, all other keys have no effect on the configuration. + +NOTE: cms-colord plugin does not work correctly with this option. The plugin +chooses an arbitrary monitor to load the color profile for, but the +profile is applied equally to all cloned monitors regardless of their +properties. . .\" *************************************************************** .SH OPTIONS From 9071817089a0b22acbddf183817201036bf04157 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 23 May 2018 14:34:36 +0300 Subject: [PATCH 0537/1642] weston: add force-on option for DRM Add a new boolean output section key "force-on". When set to true, the output will be enabled regardless of connector status. This is the opposite of the mode=off setting. Forcing connectors on is useful in special circumstances: avoid output configuration changes due to hotplug e.g. with KVM switches, or hardware with unreliable connector status readout for example. Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray --- compositor/main.c | 22 ++++++++++++++++++++-- man/weston-drm.man | 8 ++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 389113410..147485e56 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1594,6 +1594,22 @@ drm_head_prepare_enable(struct wet_compositor *wet, } } +static bool +drm_head_should_force_enable(struct wet_compositor *wet, + struct weston_head *head) +{ + const char *name = weston_head_get_name(head); + struct weston_config_section *section; + int force = 0; + + section = drm_config_find_controlling_output_section(wet->config, name); + if (!section) + return false; + + weston_config_section_get_bool(section, "force-on", &force, 0); + return !!force; +} + static void drm_try_attach(struct weston_output *output, struct wet_head_array *add, @@ -1783,6 +1799,7 @@ drm_heads_changed(struct wl_listener *listener, void *arg) bool connected; bool enabled; bool changed; + bool forced; /* We need to collect all cloned heads into outputs before enabling the * output. @@ -1791,10 +1808,11 @@ drm_heads_changed(struct wl_listener *listener, void *arg) connected = weston_head_is_connected(head); enabled = weston_head_is_enabled(head); changed = weston_head_is_device_changed(head); + forced = drm_head_should_force_enable(wet, head); - if (connected && !enabled) { + if ((connected || forced) && !enabled) { drm_head_prepare_enable(wet, head); - } else if (!connected && enabled) { + } else if (!(connected || forced) && enabled) { drm_head_disable(head); } else if (enabled && changed) { weston_log("Detected a monitor change on head '%s', " diff --git a/man/weston-drm.man b/man/weston-drm.man index c9e691512..6988eb6a0 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -95,6 +95,14 @@ NOTE: cms-colord plugin does not work correctly with this option. The plugin chooses an arbitrary monitor to load the color profile for, but the profile is applied equally to all cloned monitors regardless of their properties. +.TP +\fBforce-on\fR=\fItrue\fR +Force the output to be enabled even if the connector is disconnected. +Defaults to false. Note that +.BR mode=off " will override " force-on=true . +When a connector is disconnected, there is no EDID information to provide +a list of video modes. Therefore a forced output should also have a +detailed mode line specified. . .\" *************************************************************** .SH OPTIONS From efdebbc4e82b2b43318383c5959d82fdb0dd5840 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 11 Jun 2018 09:29:49 +1000 Subject: [PATCH 0538/1642] configure.ac: bump libdrm requirement to 2.4.68 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Had a stale libdrm sitting around which gave me errors, both fixed with the .68 version. libweston/pixel-formats.c:291:13: error: ‘DRM_FORMAT_NV24’ undeclared here (not in a function); did you mean ‘DRM_FORMAT_NV21’? .format = DRM_FORMAT_NV24, ^~~~~~~~~~~~~~~ DRM_FORMAT_NV21 libweston/pixel-formats.c:296:13: error: ‘DRM_FORMAT_NV42’ undeclared here (not in a function); did you mean ‘DRM_FORMAT_NV12’? .format = DRM_FORMAT_NV42, ^~~~~~~~~~~~~~~ DRM_FORMAT_NV12 Signed-off-by: Peter Hutterer Acked-by: Daniel Stone Reviewed-by: Emil Velikov Reviewed-by: Pekka Paalanen --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 2a62b62ae..39168e205 100644 --- a/configure.ac +++ b/configure.ac @@ -113,7 +113,7 @@ AC_CHECK_FUNCS([mkostemp strchrnul initgroups posix_fallocate]) # check for libdrm as a build-time dependency only # libdrm 2.4.30 introduced drm_fourcc.h. -PKG_CHECK_MODULES(LIBDRM, [libdrm >= 2.4.30], [], [AC_MSG_ERROR([ +PKG_CHECK_MODULES(LIBDRM, [libdrm >= 2.4.68], [], [AC_MSG_ERROR([ libdrm is a hard build-time dependency for libweston core, but a sufficient version was not found. However, libdrm is not a runtime dependency unless you have features From b16c4ac55bf795d65d42e907ab11219dd652307a Mon Sep 17 00:00:00 2001 From: nerdopolis Date: Fri, 29 Jun 2018 08:17:46 -0400 Subject: [PATCH 0539/1642] libweston: set the seat automatically based on the XDG_SEAT environment variable This will allow the seat to be set by the environment as pam_systemd typically sets the XDG_SEAT variable Reviewed-by: Pekka Paalanen Reviewed-by: Emil Velikov --- compositor/main.c | 2 +- libweston/compositor-drm.c | 11 ++++++++--- libweston/compositor-drm.h | 3 ++- man/weston-drm.man | 7 +++++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 147485e56..04d8ff016 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -513,7 +513,7 @@ usage(int error_code) #if defined(BUILD_DRM_COMPOSITOR) fprintf(out, "Options for drm-backend.so:\n\n" - " --seat=SEAT\t\tThe seat that weston should run on\n" + " --seat=SEAT\t\tThe seat that weston should run on, instead of the seat defined in XDG_SEAT\n" " --tty=TTY\t\tThe tty to use\n" " --drm-device=CARD\tThe DRM device to use, e.g. \"card0\".\n" " --use-pixman\t\tUse the pixman (CPU) renderer\n" diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 8b1ea66d9..4a352132c 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -6029,8 +6029,16 @@ drm_backend_create(struct weston_compositor *compositor, struct udev_device *drm_device; struct wl_event_loop *loop; const char *seat_id = default_seat; + const char *session_seat; int ret; + session_seat = getenv("XDG_SEAT"); + if (session_seat) + seat_id = session_seat; + + if (config->seat_id) + seat_id = config->seat_id; + weston_log("initializing drm backend\n"); b = zalloc(sizeof *b); @@ -6062,9 +6070,6 @@ drm_backend_create(struct weston_compositor *compositor, if (parse_gbm_format(config->gbm_format, GBM_FORMAT_XRGB8888, &b->gbm_format) < 0) goto err_compositor; - if (config->seat_id) - seat_id = config->seat_id; - /* Check if we run drm-backend using weston-launch */ compositor->launcher = weston_launcher_connect(compositor, config->tty, seat_id, true); diff --git a/libweston/compositor-drm.h b/libweston/compositor-drm.h index 532222938..9c37c1539 100644 --- a/libweston/compositor-drm.h +++ b/libweston/compositor-drm.h @@ -106,7 +106,8 @@ struct weston_drm_backend_config { /** The seat to be used for input and output. * - * If NULL the default "seat0" will be used. The backend will + * If seat_id is NULL, the seat is taken from XDG_SEAT environment + * variable. If neither is set, "seat0" is used. The backend will * take ownership of the seat_id pointer and will free it on * backend destruction. */ diff --git a/man/weston-drm.man b/man/weston-drm.man index 6988eb6a0..ae7944233 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -125,8 +125,8 @@ status. For example, use \fB\-\-seat\fR=\fIseatid\fR Use graphics and input devices designated for seat .I seatid -instead of the default seat -.BR seat0 . +instead of the seat defined in the environment variable +.BR XDG_SEAT ". If neither is specifed, seat0 will be assumed." .TP \fB\-\-tty\fR=\fIx\fR Launch Weston on tty @@ -153,6 +153,9 @@ The file descriptor (integer) where .B weston-launch is listening. Automatically set by .BR weston-launch . +.TP +.B XDG_SEAT +The seat Weston will start on, unless overridden on the command line. . .\" *************************************************************** .SH "SEE ALSO" From d68109b9606c610d22d73c3aa8a605220c7f451a Mon Sep 17 00:00:00 2001 From: nerdopolis Date: Fri, 29 Jun 2018 08:17:47 -0400 Subject: [PATCH 0540/1642] compositor-fbdev: support the --seat option, (and XDG_SEAT variable) This allows the fbdev backend to run on, and use devices from the specified seat, similar to the drm backend. Reviewed-by: Pekka Paalanen Reviewed-by: Emil Velikov --- compositor/main.c | 2 ++ libweston/compositor-fbdev.c | 10 +++++++++- libweston/compositor-fbdev.h | 9 +++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/compositor/main.c b/compositor/main.c index 04d8ff016..89e389b21 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -525,6 +525,7 @@ usage(int error_code) "Options for fbdev-backend.so:\n\n" " --tty=TTY\t\tThe tty to use\n" " --device=DEVICE\tThe framebuffer device to use\n" + " --seat=SEAT\t\tThe seat that weston should run on, instead of the seat defined in XDG_SEAT\n" "\n"); #endif @@ -2059,6 +2060,7 @@ load_fbdev_backend(struct weston_compositor *c, const struct weston_option fbdev_options[] = { { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, { WESTON_OPTION_STRING, "device", 0, &config.device }, + { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, }; parse_options(fbdev_options, ARRAY_LENGTH(fbdev_options), argc, argv); diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index a78f6fab6..09a2eb39f 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -776,6 +776,13 @@ fbdev_backend_create(struct weston_compositor *compositor, { struct fbdev_backend *backend; const char *seat_id = default_seat; + const char *session_seat; + + session_seat = getenv("XDG_SEAT"); + if (session_seat) + seat_id = session_seat; + if (param->seat_id) + seat_id = param->seat_id; weston_log("initializing fbdev backend\n"); @@ -800,7 +807,7 @@ fbdev_backend_create(struct weston_compositor *compositor, wl_signal_add(&compositor->session_signal, &backend->session_listener); compositor->launcher = - weston_launcher_connect(compositor, param->tty, "seat0", false); + weston_launcher_connect(compositor, param->tty, seat_id, false); if (!compositor->launcher) { weston_log("fatal: fbdev backend should be run using " "weston-launch binary, or your system should " @@ -846,6 +853,7 @@ config_init_to_defaults(struct weston_fbdev_backend_config *config) * udev, rather than passing a device node in as a parameter. */ config->tty = 0; /* default to current tty */ config->device = "/dev/fb0"; /* default frame buffer */ + config->seat_id = NULL; } WL_EXPORT int diff --git a/libweston/compositor-fbdev.h b/libweston/compositor-fbdev.h index 8b7d900e7..29c21828e 100644 --- a/libweston/compositor-fbdev.h +++ b/libweston/compositor-fbdev.h @@ -52,6 +52,15 @@ struct weston_fbdev_backend_config { */ void (*configure_device)(struct weston_compositor *compositor, struct libinput_device *device); + + /** The seat to be used for input and output. + * + * If seat_id is NULL, the seat is taken from XDG_SEAT environment + * variable. If neither is set, "seat0" is used. The backend will + * take ownership of the seat_id pointer and will free it on + * backend destruction. + */ + char *seat_id; }; #ifdef __cplusplus From 68220dbac6576678f81deb2667aace3b84d53519 Mon Sep 17 00:00:00 2001 From: nerdopolis Date: Fri, 29 Jun 2018 08:17:48 -0400 Subject: [PATCH 0541/1642] launcher-logind: only get a VT on seat0, as only seat0 supports VTs As only seat0 supports TTYs, this changes the logind launcher where it detects a TTY, only if the seat is seat0. This has only been tested for logind Reviewed-by: Pekka Paalanen Reviewed-by: Emil Velikov --- libweston/launcher-logind.c | 23 +++++++++++++---------- libweston/launcher-util.c | 4 ++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c index d0559c8fb..34e6e5ca4 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -762,17 +762,20 @@ launcher_logind_connect(struct weston_launcher **out, struct weston_compositor * free(t); goto err_session; } - free(t); - r = weston_sd_session_get_vt(wl->sid, &wl->vtnr); - if (r < 0) { - weston_log("logind: session not running on a VT\n"); - goto err_session; - } else if (tty > 0 && wl->vtnr != (unsigned int )tty) { - weston_log("logind: requested VT --tty=%d differs from real session VT %u\n", - tty, wl->vtnr); - r = -EINVAL; - goto err_session; + r = strcmp(t, "seat0"); + free(t); + if (r == 0) { + r = weston_sd_session_get_vt(wl->sid, &wl->vtnr); + if (r < 0) { + weston_log("logind: session not running on a VT\n"); + goto err_session; + } else if (tty > 0 && wl->vtnr != (unsigned int )tty) { + weston_log("logind: requested VT --tty=%d differs from real session VT %u\n", + tty, wl->vtnr); + r = -EINVAL; + goto err_session; + } } loop = wl_display_get_event_loop(compositor->wl_display); diff --git a/libweston/launcher-util.c b/libweston/launcher-util.c index 03f3219b4..41ac79507 100644 --- a/libweston/launcher-util.c +++ b/libweston/launcher-util.c @@ -104,6 +104,10 @@ WL_EXPORT void weston_setup_vt_switch_bindings(struct weston_compositor *compositor) { uint32_t key; + struct weston_launcher *launcher = compositor->launcher; + + if (launcher->iface->get_vt(launcher) <= 0) + return; if (compositor->vt_switching == false) return; From 92a06a96e4adff21bcf3b746def202d155ab5377 Mon Sep 17 00:00:00 2001 From: nerdopolis Date: Fri, 29 Jun 2018 08:17:49 -0400 Subject: [PATCH 0542/1642] compositor-fbdev: set fb device info upon the first run. This attempts to wake up secondary framebuffer devices (/dev/fb1 and up) as usually these devices start powered off, and the FBIOPUT_VSCREENINFO ioctl turns it on. This was tested on a qemu system with the options: -vga none -device VGA,id=video0 -device secondary-vga,id=video1 \ -device secondary-vga,id=video2 Reviewed-by: Pekka Paalanen Reviewed-by: Emil Velikov --- libweston/compositor-fbdev.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 09a2eb39f..74971c35d 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -384,6 +384,14 @@ fbdev_frame_buffer_open(const char *fb_dev, return -1; } + /* Attempt to wake up the framebuffer device, needed for secondary + * framebuffer devices */ + if (fbdev_set_screen_info(fd, screen_info) < 0) { + weston_log("Failed to set mode settings. " + "Attempting to open output anyway.\n"); + } + + return fd; } From 4381040dc644f2f82b4698646672ae2be2ec5688 Mon Sep 17 00:00:00 2001 From: nerdopolis Date: Fri, 29 Jun 2018 08:17:50 -0400 Subject: [PATCH 0543/1642] compositor-fbdev: detect the first fb device in the seat This adds a function to detect the first framebuffer device in the current seat. Instead of hardcoding /dev/fb0, detect the device with udev, favoring the boot_vga device, and falling back to the first framebuffer device in the seat if there is none. This is very similar to what compositor-drm does to find display devices Reviewed-by: Pekka Paalanen Reviewed-by: Emil Velikov --- libweston/compositor-fbdev.c | 85 ++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 74971c35d..a71b7bdcf 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -778,6 +778,77 @@ session_notify(struct wl_listener *listener, void *data) } } +static char * +find_framebuffer_device(struct fbdev_backend *b, const char *seat) +{ + struct udev_enumerate *e; + struct udev_list_entry *entry; + const char *path, *device_seat, *id; + char *fb_device_path = NULL; + struct udev_device *device, *fb_device, *pci; + + e = udev_enumerate_new(b->udev); + udev_enumerate_add_match_subsystem(e, "graphics"); + udev_enumerate_add_match_sysname(e, "fb[0-9]*"); + + udev_enumerate_scan_devices(e); + fb_device = NULL; + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { + bool is_boot_vga = false; + + path = udev_list_entry_get_name(entry); + device = udev_device_new_from_syspath(b->udev, path); + if (!device) + continue; + device_seat = udev_device_get_property_value(device, "ID_SEAT"); + if (!device_seat) + device_seat = default_seat; + if (strcmp(device_seat, seat)) { + udev_device_unref(device); + continue; + } + + pci = udev_device_get_parent_with_subsystem_devtype(device, + "pci", NULL); + if (pci) { + id = udev_device_get_sysattr_value(pci, "boot_vga"); + if (id && !strcmp(id, "1")) + is_boot_vga = true; + } + + /* If a framebuffer device was found, and this device isn't + * the boot-VGA device, don't use it. */ + if (!is_boot_vga && fb_device) { + udev_device_unref(device); + continue; + } + + /* There can only be one boot_vga device. Try to use it + * at all costs. */ + if (is_boot_vga) { + if (fb_device) + udev_device_unref(fb_device); + fb_device = device; + break; + } + + /* Per the (!is_boot_vga && fb_device) test above, only + * trump existing saved devices with boot-VGA devices, so if + * the test ends up here, this must be the first device seen. */ + assert(!fb_device); + fb_device = device; + } + + udev_enumerate_unref(e); + + if (fb_device) { + fb_device_path = strdup(udev_device_get_devnode(fb_device)); + udev_device_unref(fb_device); + } + + return fb_device_path; +} + static struct fbdev_backend * fbdev_backend_create(struct weston_compositor *compositor, struct weston_fbdev_backend_config *param) @@ -810,6 +881,13 @@ fbdev_backend_create(struct weston_compositor *compositor, goto out_compositor; } + if (!param->device) + param->device = find_framebuffer_device(backend, seat_id); + if (!param->device) { + weston_log("fatal: no framebuffer devices detected.\n"); + goto out_udev; + } + /* Set up the TTY. */ backend->session_listener.notify = session_notify; wl_signal_add(&compositor->session_signal, @@ -836,12 +914,15 @@ fbdev_backend_create(struct weston_compositor *compositor, if (!fbdev_head_create(backend, param->device)) goto out_launcher; + free(param->device); + udev_input_init(&backend->input, compositor, backend->udev, seat_id, param->configure_device); return backend; out_launcher: + free(param->device); weston_launcher_destroy(compositor->launcher); out_udev: @@ -857,10 +938,8 @@ fbdev_backend_create(struct weston_compositor *compositor, static void config_init_to_defaults(struct weston_fbdev_backend_config *config) { - /* TODO: Ideally, available frame buffers should be enumerated using - * udev, rather than passing a device node in as a parameter. */ config->tty = 0; /* default to current tty */ - config->device = "/dev/fb0"; /* default frame buffer */ + config->device = NULL; config->seat_id = NULL; } From 352804488aa88eef2feeb8a098ecd3c33ecc4dc4 Mon Sep 17 00:00:00 2001 From: nerdopolis Date: Fri, 29 Jun 2018 08:17:51 -0400 Subject: [PATCH 0544/1642] main: don't configure /dev/fb0 by default The framebuffer backend now detects the framebuffer device dynamically. Don't assume that the framebuffer device is /dev/fb0 Reviewed-by: Pekka Paalanen Reviewed-by: Emil Velikov --- compositor/main.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 89e389b21..b5b4fc594 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2065,9 +2065,6 @@ load_fbdev_backend(struct weston_compositor *c, parse_options(fbdev_options, ARRAY_LENGTH(fbdev_options), argc, argv); - if (!config.device) - config.device = strdup("/dev/fb0"); - config.base.struct_version = WESTON_FBDEV_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_fbdev_backend_config); config.configure_device = configure_input_device; From 92278e080689f8efd0c068fcdc0affd6ca449a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 26 Jun 2018 20:40:08 +0200 Subject: [PATCH 0545/1642] compositor-drm: ignore case of {h,v}sync flags in modeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some modeline generators put out e.g. +HSync instead of +hsync. Accept that too since it's not ambigous. Signed-off-by: Guido Günther Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 4a352132c..b96e29671 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4341,16 +4341,16 @@ parse_modeline(const char *s, drmModeModeInfo *mode) return -1; mode->clock = fclock * 1000; - if (strcmp(hsync, "+hsync") == 0) + if (strcasecmp(hsync, "+hsync") == 0) mode->flags |= DRM_MODE_FLAG_PHSYNC; - else if (strcmp(hsync, "-hsync") == 0) + else if (strcasecmp(hsync, "-hsync") == 0) mode->flags |= DRM_MODE_FLAG_NHSYNC; else return -1; - if (strcmp(vsync, "+vsync") == 0) + if (strcasecmp(vsync, "+vsync") == 0) mode->flags |= DRM_MODE_FLAG_PVSYNC; - else if (strcmp(vsync, "-vsync") == 0) + else if (strcasecmp(vsync, "-vsync") == 0) mode->flags |= DRM_MODE_FLAG_NVSYNC; else return -1; From 82dd54d943effa3c1b3d2a4979c16e760d7ae232 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 21 Jun 2018 15:38:56 +0300 Subject: [PATCH 0546/1642] desktop-shell: fix output removal for background/panel When the compositor has multiple outputs (not clones) and one of them is removed, the ones remaining to the right will be moved to close the gap. Because reflowing the remaining outputs happens before removing the wl_output global, we get the new output x,y before the removal. This causes us to consider the remaining output immediately to the right of the removed output to be a clone of the removed output whose x,y don't get updated. That will then hit the two assertions this patch removes. The reason the assertions were not actually hit is because of a compositor bug which moved the remaining outputs in the wrong direction. The next patch will fix the reflow, so we need this patch first to avoid the asserts. Remove the assertions and hand over the background and panel if the "clone" does not already have them. If the clone already has them, we destroy the unnecessary background and panel. Signed-off-by: Pekka Paalanen Reviewed-by: Marius Vlad --- clients/desktop-shell.c | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index 6d19d0292..fcc0b657b 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -1337,19 +1337,30 @@ output_remove(struct desktop *desktop, struct output *output) } if (rep) { - /* If found, hand over the background and panel so they don't - * get destroyed. */ - assert(!rep->background); - assert(!rep->panel); - - rep->background = output->background; - output->background = NULL; - rep->background->owner = rep; - - rep->panel = output->panel; - output->panel = NULL; - if (rep->panel) - rep->panel->owner = rep; + /* If found and it does not already have a background or panel, + * hand over the background and panel so they don't get + * destroyed. + * + * We never create multiple backgrounds or panels for clones, + * but if the compositor moves outputs, a pair of wl_outputs + * might become "clones". This may happen temporarily when + * an output is about to be removed and the rest are reflowed. + * In this case it is correct to let the background/panel be + * destroyed. + */ + + if (!rep->background) { + rep->background = output->background; + output->background = NULL; + rep->background->owner = rep; + } + + if (!rep->panel) { + rep->panel = output->panel; + output->panel = NULL; + if (rep->panel) + rep->panel->owner = rep; + } } output_destroy(output); From 9711fd9850739d3b7064e6b407ad37266bf47772 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 21 Jun 2018 14:26:18 +0300 Subject: [PATCH 0547/1642] libweston: fix output reflow on removal This is regression apparently introduced in 0de859ede4bad72b5d3b78e086632f02196d997f, which accidentally swapped the sign of 'delta_width' in the original call site. If one removes an output, the remaining outputs on the right are getting moved even further to the right. The outputs to the right should be moved to the left instead, to close the gap left by the removed output. Reported-by: Tomasz Olszak Signed-off-by: Pekka Paalanen Reviewed-by: Ian Ray --- libweston/compositor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 8e01eacca..516be96bd 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5409,7 +5409,7 @@ weston_compositor_remove_output(struct weston_output *output) weston_presentation_feedback_discard_list(&output->feedback_list); - weston_compositor_reflow_outputs(compositor, output, output->width); + weston_compositor_reflow_outputs(compositor, output, -output->width); wl_list_remove(&output->link); wl_list_insert(compositor->pending_output_list.prev, &output->link); From f82ff35e8e67de1d11f0ef2538864881b37993fe Mon Sep 17 00:00:00 2001 From: Ankit Nautiyal Date: Tue, 19 Mar 2097 00:24:56 +0530 Subject: [PATCH 0548/1642] man: add description for specifying modes with aspect-ratio in weston.ini This patch adds information about the new resolution-format that can be specified by a user in weston.ini to select a CEA mode. CEA defines timing of a video mode, which is considered as a standard for HDMI certification and compliance testing. It defines each and every parameter, of a video mode, like h/vactive,h/vfront h/vback etc., including aspect-ratio information. The drm layer, specifies the aspect-ratio information in user-mode flag bits 19-22. For the non-CEA modes a value of 0 is given in the aspect-ratio flag bits. Each CEA-mode is identified by a unique, Video Identification Code (VIC). For example, VIC=4 is 1280x720@60 aspect-ratio 16:9. This mode will be different than a non-CEA mode 1280x720@60 0:0. The new mode-format helps to differentiate between the CEA and non-CEA modes, by letting user specify aspect-ratio along with other paremeters: mode=widthxheight@rr ratio. This helps when certification testing is done, in tests like 7-27, the HDMI analyzer applies a particular CEA mode, and expects the applied mode to be with exactly same timings, including the aspect-ratio and VIC field. Signed-off-by: Ankit Nautiyal Reviewed-by: Pekka Paalanen --- man/weston-drm.man | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/man/weston-drm.man b/man/weston-drm.man index ae7944233..f9247b270 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -64,8 +64,43 @@ can be one of the words .BR off " to turn the output off, " .BR preferred " to use the monitor's preferred video mode, or " .BR current " to use the current video mode and avoid a mode switch." -It can also be a resolution as -\fIwidth\fBx\fIheight\fR, or a detailed mode line as below. +It can also be a resolution as: +.TP +\fBmode\fR=\fIwidth\fBx\fIheight\fR +.TP +\fBmode\fR=\fIwidth\fBx\fIheight\fB@\fIrefresh_rate\fR +Specify a mode with a given refresh-rate measured in Hz. +.TP +\fBmode\fR=\fIwidth\fBx\fIheight\fB@\fIrefresh_rate ratio\fR +Here \fIratio\fR is Picture Aspect-Ratio which can have values as 4:3, 16:9, +64:27, and 256:135. This resolution-format helps to select a CEA mode, if such a +video mode is present in the mode-list of the output. + +CEA defines the timing of a video mode, which is considered as a standard for +HDMI spcification and compliance testing. It defines each and every parameter of +a video mode, like hactive, vactive, vfront, vback etc., including aspect-ratio +information. For CEA modes, the drm layer, stores this aspect-ratio information +in user-mode (drmModeModeInfo) flag bits 19-22. For the non-CEA modes a value of +0 is stored in the aspect-ratio flag bits. + +Each CEA-mode is identified by a unique, Video Identification Code (VIC). +For example, VIC=4 is 1280x720@60 aspect-ratio 16:9. This mode will be +different than a non-CEA mode 1280x720@60 0:0. When the video mode +1280x720@60 0:0 is applied, since its timing doesnt exactly match with the CEA +information for VIC=4, it would be treated as a non-CEA mode. Also, while setting +the HDMI-AVI-Inforframe, VIC parameter will be given as '0'. If video mode +1280x720@60 16:9 is applied, its CEA timimgs matches with that of video mode with +VIC=4, so the VIC parameter in HDMI-AVI-Infoframe will be set to 4. + +Many a times, an output may have both CEA and non-CEA modes, which are similar +in all resepct, differing only in the aspect-ratio. A user can select a CEA mode +by giving the aspect-ratio, along with the other arguments for the mode. +By omitting the aspect-ratio, user can specify the non-CEA modes. +This helps when certification testing is done, in tests like 7-27, the +HDMI-analyzer applies a particular CEA mode, and expects the applied mode to be +with exactly same timings, including the aspect-ratio and VIC field. + +The resolution can also be a detailed mode line as below. .TP \fBmode\fR=\fIdotclock hdisp hsyncstart hsyncend htotal \ vdisp vsyncstart vsyncend vtotal hflag vflag\fR From a21c39310853f29f79dc1470987da7a9395eb5cd Mon Sep 17 00:00:00 2001 From: Ankit Nautiyal Date: Tue, 19 Mar 2097 00:24:57 +0530 Subject: [PATCH 0549/1642] compositor-drm: Add aspect-ratio parsing support The flag bits 19-22 of the connector modes, provide the aspect-ratio information. This information can be stored in flags bits of the weston mode structure, so that it can used for setting a mode with a particular aspect-ratio. Currently, DRM layer supports aspect-ratio with atomic-modesetting by default. For legacy modeset path, the user-space needs to set the drm client cap for aspect-ratio, if it wants aspect-ratio information in modes. This patch: - preserves aspect-ratio flags from kernel video modes and accommodates it in wayland mode. - uses aspect-ratio to pick the appropriate mode during modeset. - changes the mode format in configuration file weston.ini to accommodate aspect-ratio information as: WIDTHxHEIGHT@REFRESH-RATE ASPECT-RATIO The aspect-ratio can take the following values : 4:3, 16:9, 64:27, 256:135. v2: As per recommendation from Pekka Paalanen, Quentin Glidic, Daniel Stone, dropped the aspect-ratio info from wayland protocol, thereby avoiding exposure of aspect-ratio to the client. v3: As suggested by Pekka Paalanen, added aspect_ratio field to store aspect-ratio information from the drm. Also added drm client capability for aspect-ratio, as recommended by Daniel Vetter. v4: Minor modifications and fixes as suggested by Pekka Paalanen. v5: Rebased, fixed some styling issues, and added aspect-ratio information while printing weston_modes. v6: Moved the man pages changes to a different patch. Minor reorganization of code as suggested by Pekka Paalanen. Signed-off-by: Ankit Nautiyal [Pekka: replace ARRAY_SIZE with ARRAY_LENGTH] Signed-off-by: Pekka Paalanen --- libweston/compositor-drm.c | 122 +++++++++++++++++++++++++++++++++---- libweston/compositor.h | 15 +++++ 2 files changed, 126 insertions(+), 11 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index b96e29671..0b355f8b0 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -73,6 +73,10 @@ #define DRM_CLIENT_CAP_UNIVERSAL_PLANES 2 #endif +#ifndef DRM_CLIENT_CAP_ASPECT_RATIO +#define DRM_CLIENT_CAP_ASPECT_RATIO 4 +#endif + #ifndef DRM_CAP_CURSOR_WIDTH #define DRM_CAP_CURSOR_WIDTH 0x8 #endif @@ -87,6 +91,15 @@ #define MAX_CLONED_CONNECTORS 4 +/** + * aspect ratio info taken from the drmModeModeInfo flag bits 19-22, + * which should be used to fill the aspect ratio field in weston_mode. + */ +#define DRM_MODE_FLAG_PIC_AR_BITS_POS 19 +#ifndef DRM_MODE_FLAG_PIC_AR_MASK +#define DRM_MODE_FLAG_PIC_AR_MASK (0xF << DRM_MODE_FLAG_PIC_AR_BITS_POS) +#endif + /** * Represents the values of an enum-type KMS property */ @@ -273,6 +286,8 @@ struct drm_backend { uint32_t pageflip_timeout; bool shutting_down; + + bool aspect_ratio_supported; }; struct drm_mode { @@ -465,6 +480,14 @@ struct drm_output { struct wl_event_source *pageflip_timer; }; +static const char *const aspect_ratio_as_string[] = { + [WESTON_MODE_PIC_AR_NONE] = "", + [WESTON_MODE_PIC_AR_4_3] = " 4:3", + [WESTON_MODE_PIC_AR_16_9] = " 16:9", + [WESTON_MODE_PIC_AR_64_27] = " 64:27", + [WESTON_MODE_PIC_AR_256_135] = " 256:135", +}; + static struct gl_renderer_interface *gl_renderer; static const char default_seat[] = "seat0"; @@ -3042,6 +3065,29 @@ drm_output_set_cursor(struct drm_output_state *output_state) drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); } +/* + * Get the aspect-ratio from drmModeModeInfo mode flags. + * + * @param drm_mode_flags- flags from drmModeModeInfo structure. + * @returns aspect-ratio as encoded in enum 'weston_mode_aspect_ratio'. + */ +static enum weston_mode_aspect_ratio +drm_to_weston_mode_aspect_ratio(uint32_t drm_mode_flags) +{ + return (drm_mode_flags & DRM_MODE_FLAG_PIC_AR_MASK) >> + DRM_MODE_FLAG_PIC_AR_BITS_POS; +} + +static const char * +aspect_ratio_to_string(enum weston_mode_aspect_ratio ratio) +{ + if (ratio < 0 || ratio >= ARRAY_LENGTH(aspect_ratio_as_string) || + !aspect_ratio_as_string[ratio]) + return " (unknown aspect ratio)"; + + return aspect_ratio_as_string[ratio]; +} + static void drm_assign_planes(struct weston_output *output_base, void *repaint_data) { @@ -3172,25 +3218,43 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) static struct drm_mode * choose_mode (struct drm_output *output, struct weston_mode *target_mode) { - struct drm_mode *tmp_mode = NULL, *mode; + struct drm_mode *tmp_mode = NULL, *mode_fall_back = NULL, *mode; + enum weston_mode_aspect_ratio src_aspect = WESTON_MODE_PIC_AR_NONE; + enum weston_mode_aspect_ratio target_aspect = WESTON_MODE_PIC_AR_NONE; + struct drm_backend *b; + b = to_drm_backend(output->base.compositor); + target_aspect = target_mode->aspect_ratio; + src_aspect = output->base.current_mode->aspect_ratio; if (output->base.current_mode->width == target_mode->width && output->base.current_mode->height == target_mode->height && (output->base.current_mode->refresh == target_mode->refresh || - target_mode->refresh == 0)) - return to_drm_mode(output->base.current_mode); + target_mode->refresh == 0)) { + if (!b->aspect_ratio_supported || src_aspect == target_aspect) + return to_drm_mode(output->base.current_mode); + } wl_list_for_each(mode, &output->base.mode_list, base.link) { + + src_aspect = mode->base.aspect_ratio; if (mode->mode_info.hdisplay == target_mode->width && mode->mode_info.vdisplay == target_mode->height) { if (mode->base.refresh == target_mode->refresh || target_mode->refresh == 0) { - return mode; - } else if (!tmp_mode) + if (!b->aspect_ratio_supported || + src_aspect == target_aspect) + return mode; + else if (!mode_fall_back) + mode_fall_back = mode; + } else if (!tmp_mode) { tmp_mode = mode; + } } } + if (mode_fall_back) + return mode_fall_back; + return tmp_mode; } @@ -3328,6 +3392,11 @@ init_kms_caps(struct drm_backend *b) weston_log("DRM: %s atomic modesetting\n", b->atomic_modeset ? "supports" : "does not support"); + ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ASPECT_RATIO, 1); + b->aspect_ratio_supported = (ret == 0); + weston_log("DRM: %s picture aspect ratio\n", + b->aspect_ratio_supported ? "supports" : "does not support"); + return 0; } @@ -3732,6 +3801,8 @@ drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) if (info->type & DRM_MODE_TYPE_PREFERRED) mode->base.flags |= WL_OUTPUT_MODE_PREFERRED; + mode->base.aspect_ratio = drm_to_weston_mode_aspect_ratio(info->flags); + wl_list_insert(output->base.mode_list.prev, &mode->base.link); return mode; @@ -4116,7 +4187,7 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) if (pixman_renderer_output_create(&output->base, flags) < 0) goto err; - + weston_log("DRM: output %s %s shadow framebuffer.\n", output->base.name, b->use_pixman_shadow ? "uses" : "does not use"); @@ -4605,17 +4676,35 @@ drm_output_choose_initial_mode(struct drm_backend *backend, struct drm_mode *preferred = NULL; struct drm_mode *current = NULL; struct drm_mode *configured = NULL; + struct drm_mode *config_fall_back = NULL; struct drm_mode *best = NULL; struct drm_mode *drm_mode; drmModeModeInfo drm_modeline; int32_t width = 0; int32_t height = 0; uint32_t refresh = 0; + uint32_t aspect_width = 0; + uint32_t aspect_height = 0; + enum weston_mode_aspect_ratio aspect_ratio = WESTON_MODE_PIC_AR_NONE; int n; if (mode == WESTON_DRM_BACKEND_OUTPUT_PREFERRED && modeline) { - n = sscanf(modeline, "%dx%d@%d", &width, &height, &refresh); - if (n != 2 && n != 3) { + n = sscanf(modeline, "%dx%d@%d %u:%u", &width, &height, + &refresh, &aspect_width, &aspect_height); + if (backend->aspect_ratio_supported && n == 5) { + if (aspect_width == 4 && aspect_height == 3) + aspect_ratio = WESTON_MODE_PIC_AR_4_3; + else if (aspect_width == 16 && aspect_height == 9) + aspect_ratio = WESTON_MODE_PIC_AR_16_9; + else if (aspect_width == 64 && aspect_height == 27) + aspect_ratio = WESTON_MODE_PIC_AR_64_27; + else if (aspect_width == 256 && aspect_height == 135) + aspect_ratio = WESTON_MODE_PIC_AR_256_135; + else + weston_log("Invalid modeline \"%s\" for output %s\n", + modeline, output->base.name); + } + if (n != 2 && n != 3 && n != 5) { width = -1; if (parse_modeline(modeline, &drm_modeline) == 0) { @@ -4632,8 +4721,13 @@ drm_output_choose_initial_mode(struct drm_backend *backend, wl_list_for_each_reverse(drm_mode, &output->base.mode_list, base.link) { if (width == drm_mode->base.width && height == drm_mode->base.height && - (refresh == 0 || refresh == drm_mode->mode_info.vrefresh)) - configured = drm_mode; + (refresh == 0 || refresh == drm_mode->mode_info.vrefresh)) { + if (!backend->aspect_ratio_supported || + aspect_ratio == drm_mode->base.aspect_ratio) + configured = drm_mode; + else + config_fall_back = drm_mode; + } if (memcmp(current_mode, &drm_mode->mode_info, sizeof *current_mode) == 0) @@ -4657,6 +4751,9 @@ drm_output_choose_initial_mode(struct drm_backend *backend, if (configured) return configured; + if (config_fall_back) + return config_fall_back; + if (preferred) return preferred; @@ -5039,12 +5136,15 @@ drm_output_print_modes(struct drm_output *output) { struct weston_mode *m; struct drm_mode *dm; + const char *aspect_ratio; wl_list_for_each(m, &output->base.mode_list, link) { dm = to_drm_mode(m); - weston_log_continue(STAMP_SPACE "%dx%d@%.1f%s%s, %.1f MHz\n", + aspect_ratio = aspect_ratio_to_string(m->aspect_ratio); + weston_log_continue(STAMP_SPACE "%dx%d@%.1f%s%s%s, %.1f MHz\n", m->width, m->height, m->refresh / 1000.0, + aspect_ratio, m->flags & WL_OUTPUT_MODE_PREFERRED ? ", preferred" : "", m->flags & WL_OUTPUT_MODE_CURRENT ? diff --git a/libweston/compositor.h b/libweston/compositor.h index fbf8a73db..fd0ff7b5b 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -99,8 +99,23 @@ enum weston_led { LED_SCROLL_LOCK = (1 << 2), }; +enum weston_mode_aspect_ratio { + /** The picture aspect ratio values, for the aspect_ratio field of + * weston_mode. The values here, are taken from + * DRM_MODE_PICTURE_ASPECT_* from drm_mode.h. + */ + WESTON_MODE_PIC_AR_NONE = 0, /* DRM_MODE_PICTURE_ASPECT_NONE */ + WESTON_MODE_PIC_AR_4_3 = 1, /* DRM_MODE_PICTURE_ASPECT_4_3 */ + WESTON_MODE_PIC_AR_16_9 = 2, /* DRM_MODE_PICTURE_ASPECT_16_9 */ + WESTON_MODE_PIC_AR_64_27 = 3, /* DRM_MODE_PICTURE_ASPECT_64_27 */ + WESTON_MODE_PIC_AR_256_135 = 4, /* DRM_MODE_PICTURE_ASPECT_256_135*/ +}; + + + struct weston_mode { uint32_t flags; + enum weston_mode_aspect_ratio aspect_ratio; int32_t width, height; uint32_t refresh; struct wl_list link; From 85eebdf456b67df1386209b78d2d7f833f7b43a0 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 5 Jul 2018 17:55:43 +0100 Subject: [PATCH 0550/1642] compositor-drm: Property accessor can be const Since it doesn't write to the parameter, we can make it const. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 0b355f8b0..c5f971d29 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -591,7 +591,7 @@ to_drm_mode(struct weston_mode *base) */ static uint64_t drm_property_get_value(struct drm_property_info *info, - drmModeObjectPropertiesPtr props, + const drmModeObjectProperties *props, uint64_t def) { unsigned int i; From f9bd54682b3f5350367a8fb7ae5d2288201231ab Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 15 May 2018 17:09:53 +0100 Subject: [PATCH 0551/1642] compositor-drm: Remove unnecessary picked_scanout variable e2e80136334f fixed the same issue as df573031d0ba in a different way. The latter commit (applied earlier in the upstream tree) adds a variable to assign_planes to keep track of when we successfully assign a view to the scanout plane, and doesn't call prepare_scanout_view if we have. The former commit adds this checking inside prepare_scanout_view: if the pending output state already has a framebuffer assigned to the scanout plane, we drop out of prepare_scanout_view early. The picked_scanout variable inside assign_planes can thus be removed. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- libweston/compositor-drm.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c5f971d29..2e3cbd938 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3099,7 +3099,6 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) struct weston_view *ev; pixman_region32_t surface_overlap, renderer_region; struct weston_plane *primary, *next_plane; - bool picked_scanout = false; assert(!output->state_last); state = drm_output_state_duplicate(output->state_cur, @@ -3147,19 +3146,13 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) &ev->transform.boundingbox); next_plane = NULL; - if (pixman_region32_not_empty(&surface_overlap) || picked_scanout) + if (pixman_region32_not_empty(&surface_overlap)) next_plane = primary; if (next_plane == NULL) next_plane = drm_output_prepare_cursor_view(state, ev); - /* If a higher-stacked view already got assigned to scanout, it's incorrect to - * assign a subsequent (lower-stacked) view to scanout. - */ - if (next_plane == NULL) { + if (next_plane == NULL) next_plane = drm_output_prepare_scanout_view(state, ev); - if (next_plane) - picked_scanout = true; - } if (next_plane == NULL) next_plane = drm_output_prepare_overlay_view(state, ev); From db10df183a77dc58ec5ba910c0844f8c98f340f0 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 8 Dec 2016 13:15:58 +0000 Subject: [PATCH 0552/1642] compositor-drm: Make alpha-to-opaque handling common Rather than a hardcoded ARGB8888 -> XRGB8888 translation inside a GBM-specific helper, just determine whether or not the view is opaque, and use the generic helpers to implement the format translation. As a consequence of reordering the calls in drm_output_prepare_overlay_view(), we move the GBM BO dereference into a different failure path, before it gets captured by the plane state. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- libweston/compositor-drm.c | 117 ++++++++++++++----------------------- 1 file changed, 44 insertions(+), 73 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 2e3cbd938..88c65bc94 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -973,7 +973,7 @@ drm_fb_ref(struct drm_fb *fb) static struct drm_fb * drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, - uint32_t format, enum drm_fb_type type) + bool is_opaque, enum drm_fb_type type) { struct drm_fb *fb = gbm_bo_get_user_data(bo); uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 }; @@ -984,8 +984,6 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, return drm_fb_ref(fb); } - assert(format != 0); - fb = zalloc(sizeof *fb); if (fb == NULL) return NULL; @@ -998,16 +996,21 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, fb->height = gbm_bo_get_height(bo); fb->stride = gbm_bo_get_stride(bo); fb->handle = gbm_bo_get_handle(bo).u32; - fb->format = pixel_format_get_info(format); + fb->format = pixel_format_get_info(gbm_bo_get_format(bo)); fb->size = fb->stride * fb->height; fb->fd = backend->drm.fd; if (!fb->format) { weston_log("couldn't look up format 0x%lx\n", - (unsigned long) format); + (unsigned long) gbm_bo_get_format(bo)); goto err_free; } + /* We can scanout an ARGB buffer if the surface's opaque region covers + * the whole output, but we have to use XRGB as the KMS format code. */ + if (is_opaque) + fb->format = pixel_format_get_opaque_substitute(fb->format); + if (backend->min_width > fb->width || fb->width > backend->max_width || backend->min_height > fb->height || @@ -1538,34 +1541,23 @@ drm_view_transform_supported(struct weston_view *ev) (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); } -static uint32_t -drm_output_check_scanout_format(struct drm_output *output, - struct weston_surface *es, struct gbm_bo *bo) +static bool +drm_view_is_opaque(struct weston_view *ev) { - uint32_t format; pixman_region32_t r; + bool ret = false; - format = gbm_bo_get_format(bo); - - if (format == GBM_FORMAT_ARGB8888) { - /* We can scanout an ARGB buffer if the surface's - * opaque region covers the whole output, but we have - * to use XRGB as the KMS format code. */ - pixman_region32_init_rect(&r, 0, 0, - output->base.width, - output->base.height); - pixman_region32_subtract(&r, &r, &es->opaque); + pixman_region32_init_rect(&r, 0, 0, + ev->surface->width, + ev->surface->height); + pixman_region32_subtract(&r, &r, &ev->surface->opaque); - if (!pixman_region32_not_empty(&r)) - format = GBM_FORMAT_XRGB8888; + if (!pixman_region32_not_empty(&r)) + ret = true; - pixman_region32_fini(&r); - } + pixman_region32_fini(&r); - if (output->gbm_format == format) - return format; - - return 0; + return ret; } static struct weston_plane * @@ -1579,7 +1571,6 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; struct gbm_bo *bo; - uint32_t format; /* Don't import buffers which span multiple outputs. */ if (ev->output_mask != (1u << output->base.id)) @@ -1632,17 +1623,19 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, if (!bo) return NULL; - format = drm_output_check_scanout_format(output, ev->surface, bo); - if (format == 0) { + state->fb = drm_fb_get_from_bo(bo, b, drm_view_is_opaque(ev), + BUFFER_CLIENT); + if (!state->fb) { drm_plane_state_put_back(state); gbm_bo_destroy(bo); return NULL; } - state->fb = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); - if (!state->fb) { + /* Can't change formats with just a pageflip */ + if (state->fb->format->format != output->gbm_format) { + /* No need to destroy the GBM BO here, as it's now owned + * by the FB. */ drm_plane_state_put_back(state); - gbm_bo_destroy(bo); return NULL; } @@ -1680,7 +1673,8 @@ drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) return NULL; } - ret = drm_fb_get_from_bo(bo, b, output->gbm_format, BUFFER_GBM_SURFACE); + /* The renderer always produces an opaque image. */ + ret = drm_fb_get_from_bo(bo, b, true, BUFFER_GBM_SURFACE); if (!ret) { weston_log("failed to get drm_fb for bo\n"); gbm_surface_release_buffer(output->gbm_surface, bo); @@ -2653,35 +2647,6 @@ atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, } #endif -static uint32_t -drm_output_check_plane_format(struct drm_plane *p, - struct weston_view *ev, struct gbm_bo *bo) -{ - uint32_t i, format; - - format = gbm_bo_get_format(bo); - - if (format == GBM_FORMAT_ARGB8888) { - pixman_region32_t r; - - pixman_region32_init_rect(&r, 0, 0, - ev->surface->width, - ev->surface->height); - pixman_region32_subtract(&r, &r, &ev->surface->opaque); - - if (!pixman_region32_not_empty(&r)) - format = GBM_FORMAT_XRGB8888; - - pixman_region32_fini(&r); - } - - for (i = 0; i < p->count_formats; i++) - if (p->formats[i] == format) - return format; - - return 0; -} - static struct weston_plane * drm_output_prepare_overlay_view(struct drm_output_state *output_state, struct weston_view *ev) @@ -2694,11 +2659,11 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, struct drm_plane *p; struct drm_plane_state *state = NULL; struct linux_dmabuf_buffer *dmabuf; - struct gbm_bo *bo; + struct gbm_bo *bo = NULL; pixman_region32_t dest_rect, src_rect; pixman_box32_t *box, tbox; - uint32_t format; wl_fixed_t sx1, sy1, sx2, sy2; + unsigned int i; if (b->sprites_are_broken) return NULL; @@ -2776,7 +2741,7 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, if (dmabuf->attributes.n_planes != 1 || dmabuf->attributes.offset[0] != 0 || dmabuf->attributes.flags) - return NULL; + goto err; bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_FD, &gbm_dmabuf, GBM_BO_USE_SCANOUT); @@ -2790,12 +2755,17 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, if (!bo) goto err; - format = drm_output_check_plane_format(p, ev, bo); - if (format == 0) + state->fb = drm_fb_get_from_bo(bo, b, drm_view_is_opaque(ev), + BUFFER_CLIENT); + if (!state->fb) goto err; + bo = NULL; - state->fb = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); - if (!state->fb) + /* Check whether the format is supported */ + for (i = 0; i < p->count_formats; i++) + if (p->formats[i] == state->fb->format->format) + break; + if (i == p->count_formats) goto err; drm_fb_set_buffer(state->fb, ev->surface->buffer_ref.buffer); @@ -2870,9 +2840,11 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, return &p->base; err: - drm_plane_state_put_back(state); + /* Destroy the BO as we've allocated it, but it won't yet + * be deallocated by the state. */ if (bo) gbm_bo_destroy(bo); + drm_plane_state_put_back(state); return NULL; } @@ -4062,8 +4034,7 @@ drm_output_init_cursor_egl(struct drm_output *output, struct drm_backend *b) goto err; output->gbm_cursor_fb[i] = - drm_fb_get_from_bo(bo, b, GBM_FORMAT_ARGB8888, - BUFFER_CURSOR); + drm_fb_get_from_bo(bo, b, false, BUFFER_CURSOR); if (!output->gbm_cursor_fb[i]) { gbm_bo_destroy(bo); goto err; From d6e2a76a7c725042199e889afdcd251f6e0cbaf1 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 16 Nov 2016 19:33:20 +0000 Subject: [PATCH 0553/1642] compositor-drm: Extract buffer->plane co-ord translation Pull this into a helper function, so we can use it everywhere. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- libweston/compositor-drm.c | 162 +++++++++++++++++++++---------------- 1 file changed, 93 insertions(+), 69 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 88c65bc94..a9c68ca88 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1197,6 +1197,97 @@ drm_plane_state_put_back(struct drm_plane_state *state) (void) drm_plane_state_alloc(state_output, plane); } +/** + * Given a weston_view, fill the drm_plane_state's co-ordinates to display on + * a given plane. + */ +static void +drm_plane_state_coords_for_view(struct drm_plane_state *state, + struct weston_view *ev) +{ + struct drm_output *output = state->output; + struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; + pixman_region32_t dest_rect, src_rect; + pixman_box32_t *box, tbox; + wl_fixed_t sx1, sy1, sx2, sy2; + + /* Update the base weston_plane co-ordinates. */ + box = pixman_region32_extents(&ev->transform.boundingbox); + state->plane->base.x = box->x1; + state->plane->base.y = box->y1; + + /* First calculate the destination co-ordinates by taking the + * area of the view which is visible on this output, performing any + * transforms to account for output rotation and scale as necessary. */ + pixman_region32_init(&dest_rect); + pixman_region32_intersect(&dest_rect, &ev->transform.boundingbox, + &output->base.region); + pixman_region32_translate(&dest_rect, -output->base.x, -output->base.y); + box = pixman_region32_extents(&dest_rect); + tbox = weston_transformed_rect(output->base.width, + output->base.height, + output->base.transform, + output->base.current_scale, + *box); + state->dest_x = tbox.x1; + state->dest_y = tbox.y1; + state->dest_w = tbox.x2 - tbox.x1; + state->dest_h = tbox.y2 - tbox.y1; + pixman_region32_fini(&dest_rect); + + /* Now calculate the source rectangle, by finding the extents of the + * view, and working backwards to source co-ordinates. */ + pixman_region32_init(&src_rect); + pixman_region32_intersect(&src_rect, &ev->transform.boundingbox, + &output->base.region); + box = pixman_region32_extents(&src_rect); + + /* Accounting for any transformations made to this particular surface + * view, find the source rectangle to use. */ + weston_view_from_global_fixed(ev, + wl_fixed_from_int(box->x1), + wl_fixed_from_int(box->y1), + &sx1, &sy1); + weston_view_from_global_fixed(ev, + wl_fixed_from_int(box->x2), + wl_fixed_from_int(box->y2), + &sx2, &sy2); + + /* Clamp our source co-ordinates to surface bounds; it's possible + * for intermediate translations to give us slightly incorrect + * co-ordinates if we have, for example, multiple zooming + * transformations. View bounding boxes are also explicitly rounded + * greedily. */ + if (sx1 < 0) + sx1 = 0; + if (sy1 < 0) + sy1 = 0; + if (sx2 > wl_fixed_from_int(ev->surface->width)) + sx2 = wl_fixed_from_int(ev->surface->width); + if (sy2 > wl_fixed_from_int(ev->surface->height)) + sy2 = wl_fixed_from_int(ev->surface->height); + + tbox.x1 = sx1; + tbox.y1 = sy1; + tbox.x2 = sx2; + tbox.y2 = sy2; + pixman_region32_fini(&src_rect); + + /* Apply viewport transforms in reverse, to get the source co-ordinates + * in buffer space. */ + tbox = weston_transformed_rect(wl_fixed_from_int(ev->surface->width), + wl_fixed_from_int(ev->surface->height), + viewport->buffer.transform, + viewport->buffer.scale, + tbox); + + /* Shift from S23.8 wl_fixed to U16.16 KMS fixed-point encoding. */ + state->src_x = tbox.x1 << 8; + state->src_y = tbox.y1 << 8; + state->src_w = (tbox.x2 - tbox.x1) << 8; + state->src_h = (tbox.y2 - tbox.y1) << 8; +} + /** * Return a plane state from a drm_output_state. */ @@ -2653,16 +2744,13 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, { struct drm_output *output = output_state->output; struct weston_compositor *ec = output->base.compositor; - struct drm_backend *b = to_drm_backend(ec); struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; + struct drm_backend *b = to_drm_backend(ec); struct wl_resource *buffer_resource; struct drm_plane *p; struct drm_plane_state *state = NULL; struct linux_dmabuf_buffer *dmabuf; struct gbm_bo *bo = NULL; - pixman_region32_t dest_rect, src_rect; - pixman_box32_t *box, tbox; - wl_fixed_t sx1, sy1, sx2, sy2; unsigned int i; if (b->sprites_are_broken) @@ -2771,71 +2859,7 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, drm_fb_set_buffer(state->fb, ev->surface->buffer_ref.buffer); state->output = output; - - box = pixman_region32_extents(&ev->transform.boundingbox); - p->base.x = box->x1; - p->base.y = box->y1; - - /* - * Calculate the source & dest rects properly based on actual - * position (note the caller has called weston_view_update_transform() - * for us already). - */ - pixman_region32_init(&dest_rect); - pixman_region32_intersect(&dest_rect, &ev->transform.boundingbox, - &output->base.region); - pixman_region32_translate(&dest_rect, -output->base.x, -output->base.y); - box = pixman_region32_extents(&dest_rect); - tbox = weston_transformed_rect(output->base.width, - output->base.height, - output->base.transform, - output->base.current_scale, - *box); - state->dest_x = tbox.x1; - state->dest_y = tbox.y1; - state->dest_w = tbox.x2 - tbox.x1; - state->dest_h = tbox.y2 - tbox.y1; - pixman_region32_fini(&dest_rect); - - pixman_region32_init(&src_rect); - pixman_region32_intersect(&src_rect, &ev->transform.boundingbox, - &output->base.region); - box = pixman_region32_extents(&src_rect); - - weston_view_from_global_fixed(ev, - wl_fixed_from_int(box->x1), - wl_fixed_from_int(box->y1), - &sx1, &sy1); - weston_view_from_global_fixed(ev, - wl_fixed_from_int(box->x2), - wl_fixed_from_int(box->y2), - &sx2, &sy2); - - if (sx1 < 0) - sx1 = 0; - if (sy1 < 0) - sy1 = 0; - if (sx2 > wl_fixed_from_int(ev->surface->width)) - sx2 = wl_fixed_from_int(ev->surface->width); - if (sy2 > wl_fixed_from_int(ev->surface->height)) - sy2 = wl_fixed_from_int(ev->surface->height); - - tbox.x1 = sx1; - tbox.y1 = sy1; - tbox.x2 = sx2; - tbox.y2 = sy2; - - tbox = weston_transformed_rect(wl_fixed_from_int(ev->surface->width), - wl_fixed_from_int(ev->surface->height), - viewport->buffer.transform, - viewport->buffer.scale, - tbox); - - state->src_x = tbox.x1 << 8; - state->src_y = tbox.y1 << 8; - state->src_w = (tbox.x2 - tbox.x1) << 8; - state->src_h = (tbox.y2 - tbox.y1) << 8; - pixman_region32_fini(&src_rect); + drm_plane_state_coords_for_view(state, ev); return &p->base; From df2726a08913ab8bd00fc6be98d7ca5626b77569 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 7 Feb 2017 18:48:19 +0000 Subject: [PATCH 0554/1642] compositor-drm: Fully account for buffer transformation In our new and improved helper to determine the src/dest values for a buffer on a given plane, make sure we account for all buffer transformations, including viewport clipping. Rather than badly open-coding it ourselves, just use the helper which does exactly this. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Reported-by: Tiago Gomes Tested-by: Emre Ucan --- libweston/compositor-drm.c | 73 +++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index a9c68ca88..787ffad25 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1206,10 +1206,10 @@ drm_plane_state_coords_for_view(struct drm_plane_state *state, struct weston_view *ev) { struct drm_output *output = state->output; - struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; + struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; pixman_region32_t dest_rect, src_rect; pixman_box32_t *box, tbox; - wl_fixed_t sx1, sy1, sx2, sy2; + float sxf1, syf1, sxf2, syf2; /* Update the base weston_plane co-ordinates. */ box = pixman_region32_extents(&ev->transform.boundingbox); @@ -1241,51 +1241,44 @@ drm_plane_state_coords_for_view(struct drm_plane_state *state, pixman_region32_intersect(&src_rect, &ev->transform.boundingbox, &output->base.region); box = pixman_region32_extents(&src_rect); + weston_view_from_global_float(ev, box->x1, box->y1, &sxf1, &syf1); + weston_surface_to_buffer_float(ev->surface, sxf1, syf1, &sxf1, &syf1); + weston_view_from_global_float(ev, box->x2, box->y2, &sxf2, &syf2); + weston_surface_to_buffer_float(ev->surface, sxf2, syf2, &sxf2, &syf2); + pixman_region32_fini(&src_rect); - /* Accounting for any transformations made to this particular surface - * view, find the source rectangle to use. */ - weston_view_from_global_fixed(ev, - wl_fixed_from_int(box->x1), - wl_fixed_from_int(box->y1), - &sx1, &sy1); - weston_view_from_global_fixed(ev, - wl_fixed_from_int(box->x2), - wl_fixed_from_int(box->y2), - &sx2, &sy2); + /* Buffer transforms may mean that x2 is to the left of x1, and/or that + * y2 is above y1. */ + if (sxf2 < sxf1) { + double tmp = sxf1; + sxf1 = sxf2; + sxf2 = tmp; + } + if (syf2 < syf1) { + double tmp = syf1; + syf1 = syf2; + syf2 = tmp; + } + + /* Shift from S23.8 wl_fixed to U16.16 KMS fixed-point encoding. */ + state->src_x = wl_fixed_from_double(sxf1) << 8; + state->src_y = wl_fixed_from_double(syf1) << 8; + state->src_w = wl_fixed_from_double(sxf2 - sxf1) << 8; + state->src_h = wl_fixed_from_double(syf2 - syf1) << 8; /* Clamp our source co-ordinates to surface bounds; it's possible * for intermediate translations to give us slightly incorrect * co-ordinates if we have, for example, multiple zooming * transformations. View bounding boxes are also explicitly rounded * greedily. */ - if (sx1 < 0) - sx1 = 0; - if (sy1 < 0) - sy1 = 0; - if (sx2 > wl_fixed_from_int(ev->surface->width)) - sx2 = wl_fixed_from_int(ev->surface->width); - if (sy2 > wl_fixed_from_int(ev->surface->height)) - sy2 = wl_fixed_from_int(ev->surface->height); - - tbox.x1 = sx1; - tbox.y1 = sy1; - tbox.x2 = sx2; - tbox.y2 = sy2; - pixman_region32_fini(&src_rect); - - /* Apply viewport transforms in reverse, to get the source co-ordinates - * in buffer space. */ - tbox = weston_transformed_rect(wl_fixed_from_int(ev->surface->width), - wl_fixed_from_int(ev->surface->height), - viewport->buffer.transform, - viewport->buffer.scale, - tbox); - - /* Shift from S23.8 wl_fixed to U16.16 KMS fixed-point encoding. */ - state->src_x = tbox.x1 << 8; - state->src_y = tbox.y1 << 8; - state->src_w = (tbox.x2 - tbox.x1) << 8; - state->src_h = (tbox.y2 - tbox.y1) << 8; + if (state->src_x < 0) + state->src_x = 0; + if (state->src_y < 0) + state->src_y = 0; + if (state->src_w > (uint32_t) ((buffer->width << 16) - state->src_x)) + state->src_w = (buffer->width << 16) - state->src_x; + if (state->src_h > (uint32_t) ((buffer->height << 16) - state->src_y)) + state->src_h = (buffer->height << 16) - state->src_y; } /** From ce137472faab26f2c92d40efdfa64a5a937b57fb Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 16 Nov 2016 19:35:03 +0000 Subject: [PATCH 0555/1642] compositor-drm: Only check final co-ordinates for overlay scaling When considering a view for placement into an overlay plane, we previously checked that the buffer's transform and scale were identical to the output's, and that there were no transformations applied. We now use a more consistent set of checks through drm_plane_state_coords_for_view. This checks the complete transformation chain, allowing only translation and scaling; at the end, we check if the total buffer -> surface -> view -> output chain requires scaling or rotation, and disallow it if so. This allows scaling in the cases where the transformation chain cancels itself out to produce a 1:1 buffer -> output pixel scale. An erroneously disallowed case is where buffer -> view -> output rotations cancel each other out; we prevent a view from being on an overlay plane if rotation is involved at all. Fixing this would require a complete analysis of the overall transformation matrix. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- libweston/compositor-drm.c | 56 +++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 787ffad25..c35fccdde 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1197,11 +1197,30 @@ drm_plane_state_put_back(struct drm_plane_state *state) (void) drm_plane_state_alloc(state_output, plane); } +static bool +drm_view_transform_supported(struct weston_view *ev, struct weston_output *output) +{ + struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; + + /* This will incorrectly disallow cases where the combination of + * buffer and view transformations match the output transform. + * Fixing this requires a full analysis of the transformation + * chain. */ + if (ev->transform.enabled && + ev->transform.matrix.type >= WESTON_MATRIX_TRANSFORM_ROTATE) + return false; + + if (viewport->buffer.transform != output->transform) + return false; + + return true; +} + /** * Given a weston_view, fill the drm_plane_state's co-ordinates to display on * a given plane. */ -static void +static bool drm_plane_state_coords_for_view(struct drm_plane_state *state, struct weston_view *ev) { @@ -1211,6 +1230,9 @@ drm_plane_state_coords_for_view(struct drm_plane_state *state, pixman_box32_t *box, tbox; float sxf1, syf1, sxf2, syf2; + if (!drm_view_transform_supported(ev, &output->base)) + return false; + /* Update the base weston_plane co-ordinates. */ box = pixman_region32_extents(&ev->transform.boundingbox); state->plane->base.x = box->x1; @@ -1279,6 +1301,8 @@ drm_plane_state_coords_for_view(struct drm_plane_state *state, state->src_w = (buffer->width << 16) - state->src_x; if (state->src_h > (uint32_t) ((buffer->height << 16) - state->src_y)) state->src_h = (buffer->height << 16) - state->src_y; + + return true; } /** @@ -1618,13 +1642,6 @@ drm_output_assign_state(struct drm_output_state *state, } } -static int -drm_view_transform_supported(struct weston_view *ev) -{ - return !ev->transform.enabled || - (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); -} - static bool drm_view_is_opaque(struct weston_view *ev) { @@ -1681,11 +1698,9 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, return NULL; if (ev->geometry.scissor_enabled) return NULL; - if (viewport->buffer.transform != output->base.transform) - return NULL; if (viewport->buffer.scale != output->base.current_scale) return NULL; - if (!drm_view_transform_supported(ev)) + if (!drm_view_transform_supported(ev, &output->base)) return NULL; if (ev->alpha != 1.0f) @@ -2737,7 +2752,6 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, { struct drm_output *output = output_state->output; struct weston_compositor *ec = output->base.compositor; - struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; struct drm_backend *b = to_drm_backend(ec); struct wl_resource *buffer_resource; struct drm_plane *p; @@ -2763,13 +2777,6 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, if (wl_shm_buffer_get(buffer_resource)) return NULL; - if (viewport->buffer.transform != output->base.transform) - return NULL; - if (viewport->buffer.scale != output->base.current_scale) - return NULL; - if (!drm_view_transform_supported(ev)) - return NULL; - if (ev->alpha != 1.0f) return NULL; @@ -2793,6 +2800,14 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, if (!state) return NULL; + state->output = output; + if (!drm_plane_state_coords_for_view(state, ev)) + goto err; + + if (state->src_w != state->dest_w << 16 || + state->src_h != state->dest_h << 16) + goto err; + if ((dmabuf = linux_dmabuf_buffer_get(buffer_resource))) { #ifdef HAVE_GBM_FD_IMPORT /* XXX: TODO: @@ -2851,9 +2866,6 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, drm_fb_set_buffer(state->fb, ev->surface->buffer_ref.buffer); - state->output = output; - drm_plane_state_coords_for_view(state, ev); - return &p->base; err: From 7cdf231c762602b820a8ab03bae37297fb5df54f Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 16 Nov 2016 19:40:29 +0000 Subject: [PATCH 0556/1642] compositor-drm: Use plane_state_coords_for_view for scanout Now that we have a helper to fill the plane state co-ordinates from a view, use this for the scanout plane. We now explicitly check that the view fills exactly the fullscreen area and nothing else. We then use the new helper to fill out the plane state values, and do further checks against the filled-in co-ordinates, i.e. that we're not trying to show an offset into the buffer, or to scale the image. This now allows cases where the buffer -> surface -> view -> output transform chain cancels each other out for scaling: previously, we would never consider a buffer for scanout unless its scale matched the output's. We now only look at the final result of the buffer -> output transformation, to check that this does not result in translation or scaling. An audit of the error paths found some places where we would leave a plane state hanging; this makes them all consistent. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- libweston/compositor-drm.c | 62 ++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c35fccdde..71f27d1c1 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1670,8 +1670,8 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, struct drm_plane *scanout_plane = output->scanout_plane; struct drm_plane_state *state; struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; - struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; struct gbm_bo *bo; + pixman_box32_t *extents; /* Don't import buffers which span multiple outputs. */ if (ev->output_mask != (1u << output->base.id)) @@ -1686,21 +1686,13 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, if (wl_shm_buffer_get(buffer->resource)) return NULL; - /* Make sure our view is exactly compatible with the output. */ - if (ev->geometry.x != output->base.x || - ev->geometry.y != output->base.y) - return NULL; - if (buffer->width != output->base.current_mode->width || - buffer->height != output->base.current_mode->height) - return NULL; - - if (ev->transform.enabled) - return NULL; - if (ev->geometry.scissor_enabled) - return NULL; - if (viewport->buffer.scale != output->base.current_scale) - return NULL; - if (!drm_view_transform_supported(ev, &output->base)) + /* Check the view spans exactly the output size, calculated in the + * logical co-ordinate space. */ + extents = pixman_region32_extents(&ev->transform.boundingbox); + if (extents->x1 != output->base.x || + extents->y1 != output->base.y || + extents->x2 != output->base.x + output->base.width || + extents->y2 != output->base.y + output->base.height) return NULL; if (ev->alpha != 1.0f) @@ -1715,44 +1707,48 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, return NULL; } + state->output = output; + if (!drm_plane_state_coords_for_view(state, ev)) + goto err; + + /* The legacy API does not let us perform cropping or scaling. */ + if (state->src_x != 0 || state->src_y != 0 || + state->src_w != state->dest_w << 16 || + state->src_h != state->dest_h << 16 || + state->dest_x != 0 || state->dest_y != 0 || + state->dest_w != (unsigned) output->base.current_mode->width || + state->dest_h != (unsigned) output->base.current_mode->height) + goto err; + bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, buffer->resource, GBM_BO_USE_SCANOUT); /* Unable to use the buffer for scanout */ if (!bo) - return NULL; + goto err; state->fb = drm_fb_get_from_bo(bo, b, drm_view_is_opaque(ev), BUFFER_CLIENT); if (!state->fb) { - drm_plane_state_put_back(state); + /* We need to explicitly destroy the BO. */ gbm_bo_destroy(bo); - return NULL; + goto err; } /* Can't change formats with just a pageflip */ if (state->fb->format->format != output->gbm_format) { /* No need to destroy the GBM BO here, as it's now owned * by the FB. */ - drm_plane_state_put_back(state); - return NULL; + goto err; } drm_fb_set_buffer(state->fb, buffer); - state->output = output; - - state->src_x = 0; - state->src_y = 0; - state->src_w = state->fb->width << 16; - state->src_h = state->fb->height << 16; - - state->dest_x = 0; - state->dest_y = 0; - state->dest_w = output->base.current_mode->width; - state->dest_h = output->base.current_mode->height; - return &scanout_plane->base; + +err: + drm_plane_state_put_back(state); + return NULL; } static struct drm_fb * From 9b560384176b883e02b9d90f907cae3247f8b920 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 16 Nov 2016 19:46:35 +0000 Subject: [PATCH 0557/1642] compositor-drm: Use plane_state_coords_for_view for cursor Use the new helper to populate the cursor state as well, with some special-case handling to account for how we always upload a full-size BO. As this now fully takes care of buffer transformations, HiDPI client cursors work, and we also clip the cursor plane completely to CRTC bounds. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Reported-by: Derek Foreman Tested-by: Emre Ucan Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/118 --- libweston/compositor-drm.c | 70 ++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 71f27d1c1..c8ee84d34 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2876,14 +2876,14 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, /** * Update the image for the current cursor surface * - * @param b DRM backend structure - * @param bo GBM buffer object to write into - * @param ev View to use for cursor image + * @param plane_state DRM cursor plane state + * @param ev Source view for cursor */ static void -cursor_bo_update(struct drm_backend *b, struct gbm_bo *bo, - struct weston_view *ev) +cursor_bo_update(struct drm_plane_state *plane_state, struct weston_view *ev) { + struct drm_backend *b = plane_state->plane->backend; + struct gbm_bo *bo = plane_state->fb->bo; struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; uint32_t buf[b->cursor_width * b->cursor_height]; int32_t stride; @@ -2892,18 +2892,18 @@ cursor_bo_update(struct drm_backend *b, struct gbm_bo *bo, assert(buffer && buffer->shm_buffer); assert(buffer->shm_buffer == wl_shm_buffer_get(buffer->resource)); - assert(ev->surface->width <= b->cursor_width); - assert(ev->surface->height <= b->cursor_height); + assert(buffer->width <= b->cursor_width); + assert(buffer->height <= b->cursor_height); memset(buf, 0, sizeof buf); stride = wl_shm_buffer_get_stride(buffer->shm_buffer); s = wl_shm_buffer_get_data(buffer->shm_buffer); wl_shm_buffer_begin_access(buffer->shm_buffer); - for (i = 0; i < ev->surface->height; i++) + for (i = 0; i < buffer->height; i++) memcpy(buf + i * b->cursor_width, s + i * stride, - ev->surface->width * 4); + buffer->width * 4); wl_shm_buffer_end_access(buffer->shm_buffer); if (gbm_bo_write(bo, buf, sizeof buf) < 0) @@ -2918,10 +2918,8 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, struct drm_backend *b = to_drm_backend(output->base.compositor); struct drm_plane *plane = output->cursor_plane; struct drm_plane_state *plane_state; - struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; struct wl_shm_buffer *shmbuf; bool needs_update = false; - float x, y; if (!plane) return NULL; @@ -2951,26 +2949,25 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, if (wl_shm_buffer_get_format(shmbuf) != WL_SHM_FORMAT_ARGB8888) return NULL; - if (output->base.transform != WL_OUTPUT_TRANSFORM_NORMAL) - return NULL; - if (ev->transform.enabled && - (ev->transform.matrix.type > WESTON_MATRIX_TRANSFORM_TRANSLATE)) - return NULL; - if (viewport->buffer.scale != output->base.current_scale) - return NULL; - if (ev->geometry.scissor_enabled) - return NULL; - - if (ev->surface->width > b->cursor_width || - ev->surface->height > b->cursor_height) - return NULL; - plane_state = drm_output_state_get_plane(output_state, output->cursor_plane); if (plane_state && plane_state->fb) return NULL; + /* We can't scale with the legacy API, and we don't try to account for + * simple cropping/translation in cursor_bo_update. */ + plane_state->output = output; + if (!drm_plane_state_coords_for_view(plane_state, ev)) + goto err; + + if (plane_state->src_x != 0 || plane_state->src_y != 0 || + plane_state->src_w > (unsigned) b->cursor_width << 16 || + plane_state->src_h > (unsigned) b->cursor_height << 16 || + plane_state->src_w != plane_state->dest_w << 16 || + plane_state->src_h != plane_state->dest_h << 16) + goto err; + /* Since we're setting plane state up front, we need to work out * whether or not we need to upload a new cursor. We can't use the * plane damage, since the planes haven't actually been calculated @@ -2987,26 +2984,27 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, } output->cursor_view = ev; - weston_view_to_global_float(ev, 0, 0, &x, &y); - plane->base.x = x; - plane->base.y = y; plane_state->fb = drm_fb_ref(output->gbm_cursor_fb[output->current_cursor]); - plane_state->output = output; - plane_state->src_x = 0; - plane_state->src_y = 0; + + if (needs_update) + cursor_bo_update(plane_state, ev); + + /* The cursor API is somewhat special: in cursor_bo_update(), we upload + * a buffer which is always cursor_width x cursor_height, even if the + * surface we want to promote is actually smaller than this. Manually + * mangle the plane state to deal with this. */ plane_state->src_w = b->cursor_width << 16; plane_state->src_h = b->cursor_height << 16; - plane_state->dest_x = (x - output->base.x) * output->base.current_scale; - plane_state->dest_y = (y - output->base.y) * output->base.current_scale; plane_state->dest_w = b->cursor_width; plane_state->dest_h = b->cursor_height; - if (needs_update) - cursor_bo_update(b, plane_state->fb->bo, ev); - return &plane->base; + +err: + drm_plane_state_put_back(plane_state); + return NULL; } static void From f11ec02cad401781e030e7eeefe500a2b5b6b82c Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 17 Nov 2016 17:32:42 +0000 Subject: [PATCH 0558/1642] compositor-drm: Extract overlay FB import to helper ... in order to be able to use it from scanout as well. In doing this, the check for format compatibility is moved from after selecting a plane to before selecting a plane. If different planes have disjoint format support, this ensures that we don't reject the view from all overlay consideration, just because the first plane we found didn't support its format. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- libweston/compositor-drm.c | 219 ++++++++++++++++++++----------------- 1 file changed, 119 insertions(+), 100 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c8ee84d34..0ecfc9069 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1305,6 +1305,109 @@ drm_plane_state_coords_for_view(struct drm_plane_state *state, return true; } +static bool +drm_view_is_opaque(struct weston_view *ev) +{ + pixman_region32_t r; + bool ret = false; + + pixman_region32_init_rect(&r, 0, 0, + ev->surface->width, + ev->surface->height); + pixman_region32_subtract(&r, &r, &ev->surface->opaque); + + if (!pixman_region32_not_empty(&r)) + ret = true; + + pixman_region32_fini(&r); + + return ret; +} + +static struct drm_fb * +drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) +{ + struct drm_output *output = state->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); + struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; + struct linux_dmabuf_buffer *dmabuf; + struct drm_fb *fb; + struct gbm_bo *bo; + + /* Don't import buffers which span multiple outputs. */ + if (ev->output_mask != (1u << output->base.id)) + return NULL; + + if (ev->alpha != 1.0f) + return NULL; + + if (!drm_view_transform_supported(ev, &output->base)) + return NULL; + + if (!buffer) + return NULL; + + if (wl_shm_buffer_get(buffer->resource)) + return NULL; + + if (!b->gbm) + return NULL; + + dmabuf = linux_dmabuf_buffer_get(buffer->resource); + if (dmabuf) { +#ifdef HAVE_GBM_FD_IMPORT + /* XXX: TODO: + * + * Use AddFB2 directly, do not go via GBM. + * Add support for multiplanar formats. + * Both require refactoring in the DRM-backend to + * support a mix of gbm_bos and drmfbs. + */ + struct gbm_import_fd_data gbm_dmabuf = { + .fd = dmabuf->attributes.fd[0], + .width = dmabuf->attributes.width, + .height = dmabuf->attributes.height, + .stride = dmabuf->attributes.stride[0], + .format = dmabuf->attributes.format + }; + + /* XXX: TODO: + * + * Currently the buffer is rejected if any dmabuf attribute + * flag is set. This keeps us from passing an inverted / + * interlaced / bottom-first buffer (or any other type that may + * be added in the future) through to an overlay. Ultimately, + * these types of buffers should be handled through buffer + * transforms and not as spot-checks requiring specific + * knowledge. */ + if (dmabuf->attributes.n_planes != 1 || + dmabuf->attributes.offset[0] != 0 || + dmabuf->attributes.flags) + return NULL; + + bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_FD, &gbm_dmabuf, + GBM_BO_USE_SCANOUT); +#else + return NULL; +#endif + } else { + bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, + buffer->resource, GBM_BO_USE_SCANOUT); + } + + if (!bo) + return NULL; + + fb = drm_fb_get_from_bo(bo, b, drm_view_is_opaque(ev), BUFFER_CLIENT); + if (!fb) { + gbm_bo_destroy(bo); + return NULL; + } + + drm_fb_set_buffer(fb, buffer); + return fb; +} + /** * Return a plane state from a drm_output_state. */ @@ -1642,25 +1745,6 @@ drm_output_assign_state(struct drm_output_state *state, } } -static bool -drm_view_is_opaque(struct weston_view *ev) -{ - pixman_region32_t r; - bool ret = false; - - pixman_region32_init_rect(&r, 0, 0, - ev->surface->width, - ev->surface->height); - pixman_region32_subtract(&r, &r, &ev->surface->opaque); - - if (!pixman_region32_not_empty(&r)) - ret = true; - - pixman_region32_fini(&r); - - return ret; -} - static struct weston_plane * drm_output_prepare_scanout_view(struct drm_output_state *output_state, struct weston_view *ev) @@ -2749,31 +2833,16 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, struct drm_output *output = output_state->output; struct weston_compositor *ec = output->base.compositor; struct drm_backend *b = to_drm_backend(ec); - struct wl_resource *buffer_resource; struct drm_plane *p; struct drm_plane_state *state = NULL; - struct linux_dmabuf_buffer *dmabuf; - struct gbm_bo *bo = NULL; + struct drm_fb *fb; unsigned int i; if (b->sprites_are_broken) return NULL; - /* Don't import buffers which span multiple outputs. */ - if (ev->output_mask != (1u << output->base.id)) - return NULL; - - /* We can only import GBM buffers. */ - if (b->gbm == NULL) - return NULL; - - if (ev->surface->buffer_ref.buffer == NULL) - return NULL; - buffer_resource = ev->surface->buffer_ref.buffer->resource; - if (wl_shm_buffer_get(buffer_resource)) - return NULL; - - if (ev->alpha != 1.0f) + fb = drm_fb_get_from_view(output_state, ev); + if (!fb) return NULL; wl_list_for_each(p, &b->plane_list, link) { @@ -2783,6 +2852,14 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, if (!drm_plane_is_available(p, output)) continue; + /* Check whether the format is supported */ + for (i = 0; i < p->count_formats; i++) { + if (p->formats[i] == fb->format->format) + break; + } + if (i == p->count_formats) + continue; + state = drm_output_state_get_plane(output_state, p); if (state->fb) { state = NULL; @@ -2793,10 +2870,14 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, } /* No sprites available */ - if (!state) + if (!state) { + drm_fb_unref(fb); return NULL; + } + state->fb = fb; state->output = output; + if (!drm_plane_state_coords_for_view(state, ev)) goto err; @@ -2804,71 +2885,9 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, state->src_h != state->dest_h << 16) goto err; - if ((dmabuf = linux_dmabuf_buffer_get(buffer_resource))) { -#ifdef HAVE_GBM_FD_IMPORT - /* XXX: TODO: - * - * Use AddFB2 directly, do not go via GBM. - * Add support for multiplanar formats. - * Both require refactoring in the DRM-backend to - * support a mix of gbm_bos and drmfbs. - */ - struct gbm_import_fd_data gbm_dmabuf = { - .fd = dmabuf->attributes.fd[0], - .width = dmabuf->attributes.width, - .height = dmabuf->attributes.height, - .stride = dmabuf->attributes.stride[0], - .format = dmabuf->attributes.format - }; - - /* XXX: TODO: - * - * Currently the buffer is rejected if any dmabuf attribute - * flag is set. This keeps us from passing an inverted / - * interlaced / bottom-first buffer (or any other type that may - * be added in the future) through to an overlay. Ultimately, - * these types of buffers should be handled through buffer - * transforms and not as spot-checks requiring specific - * knowledge. */ - if (dmabuf->attributes.n_planes != 1 || - dmabuf->attributes.offset[0] != 0 || - dmabuf->attributes.flags) - goto err; - - bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_FD, &gbm_dmabuf, - GBM_BO_USE_SCANOUT); -#else - goto err; -#endif - } else { - bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, - buffer_resource, GBM_BO_USE_SCANOUT); - } - if (!bo) - goto err; - - state->fb = drm_fb_get_from_bo(bo, b, drm_view_is_opaque(ev), - BUFFER_CLIENT); - if (!state->fb) - goto err; - bo = NULL; - - /* Check whether the format is supported */ - for (i = 0; i < p->count_formats; i++) - if (p->formats[i] == state->fb->format->format) - break; - if (i == p->count_formats) - goto err; - - drm_fb_set_buffer(state->fb, ev->surface->buffer_ref.buffer); - return &p->base; err: - /* Destroy the BO as we've allocated it, but it won't yet - * be deallocated by the state. */ - if (bo) - gbm_bo_destroy(bo); drm_plane_state_put_back(state); return NULL; } From bdf3e7e356ba0e01c502e7f29f4e8a71d521e6f1 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 17 Nov 2016 17:33:08 +0000 Subject: [PATCH 0559/1642] compositor-drm: Use plane FB-import helper for scanout Use the same codepath, which has the added advantage of being able to import dmabufs. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- libweston/compositor-drm.c | 53 ++++++++++---------------------------- 1 file changed, 13 insertions(+), 40 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 0ecfc9069..628f7cbf2 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1750,26 +1750,11 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, struct weston_view *ev) { struct drm_output *output = output_state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); struct drm_plane *scanout_plane = output->scanout_plane; struct drm_plane_state *state; - struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; - struct gbm_bo *bo; + struct drm_fb *fb; pixman_box32_t *extents; - /* Don't import buffers which span multiple outputs. */ - if (ev->output_mask != (1u << output->base.id)) - return NULL; - - /* We use GBM to import buffers. */ - if (b->gbm == NULL) - return NULL; - - if (buffer == NULL) - return NULL; - if (wl_shm_buffer_get(buffer->resource)) - return NULL; - /* Check the view spans exactly the output size, calculated in the * logical co-ordinate space. */ extents = pixman_region32_extents(&ev->transform.boundingbox); @@ -1782,15 +1767,27 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, if (ev->alpha != 1.0f) return NULL; + fb = drm_fb_get_from_view(output_state, ev); + if (!fb) + return NULL; + + /* Can't change formats with just a pageflip */ + if (fb->format->format != output->gbm_format) { + drm_fb_unref(fb); + return NULL; + } + state = drm_output_state_get_plane(output_state, scanout_plane); if (state->fb) { /* If there is already a framebuffer on the scanout plane, * a client view has already been placed on the scanout * view. In that case, do not free or put back the state, * but just leave it in place and quietly exit. */ + drm_fb_unref(fb); return NULL; } + state->fb = fb; state->output = output; if (!drm_plane_state_coords_for_view(state, ev)) goto err; @@ -1804,30 +1801,6 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, state->dest_h != (unsigned) output->base.current_mode->height) goto err; - bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, - buffer->resource, GBM_BO_USE_SCANOUT); - - /* Unable to use the buffer for scanout */ - if (!bo) - goto err; - - state->fb = drm_fb_get_from_bo(bo, b, drm_view_is_opaque(ev), - BUFFER_CLIENT); - if (!state->fb) { - /* We need to explicitly destroy the BO. */ - gbm_bo_destroy(bo); - goto err; - } - - /* Can't change formats with just a pageflip */ - if (state->fb->format->format != output->gbm_format) { - /* No need to destroy the GBM BO here, as it's now owned - * by the FB. */ - goto err; - } - - drm_fb_set_buffer(state->fb, buffer); - return &scanout_plane->base; err: From 8eece0c2e7dd2bc44efd8a476062b20dbbe451e2 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 17 Nov 2016 17:54:00 +0000 Subject: [PATCH 0560/1642] compositor-drm: Extract drm_fb_addfb into a helper We currently do the same thing in two places, and will soon have a third. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- libweston/compositor-drm.c | 91 ++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 628f7cbf2..780c341a3 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -309,7 +309,10 @@ struct drm_fb { int refcnt; - uint32_t fb_id, stride, handle, size; + uint32_t fb_id, size; + uint32_t handles[4]; + uint32_t strides[4]; + uint32_t offsets[4]; const struct pixel_format_info *format; int width, height; int fd; @@ -861,7 +864,7 @@ drm_fb_destroy_dumb(struct drm_fb *fb) munmap(fb->map, fb->size); memset(&destroy_arg, 0, sizeof(destroy_arg)); - destroy_arg.handle = fb->handle; + destroy_arg.handle = fb->handles[0]; drmIoctl(fb->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); drm_fb_destroy(fb); @@ -877,6 +880,32 @@ drm_fb_destroy_gbm(struct gbm_bo *bo, void *data) drm_fb_destroy(fb); } +static int +drm_fb_addfb(struct drm_fb *fb) +{ + int ret; + + ret = drmModeAddFB2(fb->fd, fb->width, fb->height, fb->format->format, + fb->handles, fb->strides, fb->offsets, &fb->fb_id, + 0); + if (ret == 0) + return 0; + + /* Legacy AddFB can't always infer the format from depth/bpp alone, so + * check if our format is one of the lucky ones. */ + if (!fb->format->depth || !fb->format->bpp) + return ret; + + /* Cannot fall back to AddFB for multi-planar formats either. */ + if (fb->handles[1] || fb->handles[2] || fb->handles[3]) + return ret; + + ret = drmModeAddFB(fb->fd, fb->width, fb->height, + fb->format->depth, fb->format->bpp, + fb->strides[0], fb->handles[0], &fb->fb_id); + return ret; +} + static struct drm_fb * drm_fb_create_dumb(struct drm_backend *b, int width, int height, uint32_t format) @@ -887,12 +916,10 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, struct drm_mode_create_dumb create_arg; struct drm_mode_destroy_dumb destroy_arg; struct drm_mode_map_dumb map_arg; - uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 }; fb = zalloc(sizeof *fb); if (!fb) return NULL; - fb->refcnt = 1; fb->format = pixel_format_get_info(format); @@ -918,30 +945,20 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, goto err_fb; fb->type = BUFFER_PIXMAN_DUMB; - fb->handle = create_arg.handle; - fb->stride = create_arg.pitch; + fb->handles[0] = create_arg.handle; + fb->strides[0] = create_arg.pitch; fb->size = create_arg.size; fb->width = width; fb->height = height; fb->fd = b->drm.fd; - handles[0] = fb->handle; - pitches[0] = fb->stride; - offsets[0] = 0; - - ret = drmModeAddFB2(b->drm.fd, width, height, fb->format->format, - handles, pitches, offsets, &fb->fb_id, 0); - if (ret) { - ret = drmModeAddFB(b->drm.fd, width, height, - fb->format->depth, fb->format->bpp, - fb->stride, fb->handle, &fb->fb_id); - } - - if (ret) + if (drm_fb_addfb(fb) != 0) { + weston_log("failed to create kms fb: %m\n"); goto err_bo; + } memset(&map_arg, 0, sizeof map_arg); - map_arg.handle = fb->handle; + map_arg.handle = fb->handles[0]; ret = drmIoctl(fb->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_arg); if (ret) goto err_add_fb; @@ -976,8 +993,6 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, bool is_opaque, enum drm_fb_type type) { struct drm_fb *fb = gbm_bo_get_user_data(bo); - uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 }; - int ret; if (fb) { assert(fb->type == type); @@ -994,10 +1009,10 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, fb->width = gbm_bo_get_width(bo); fb->height = gbm_bo_get_height(bo); - fb->stride = gbm_bo_get_stride(bo); - fb->handle = gbm_bo_get_handle(bo).u32; + fb->strides[0] = gbm_bo_get_stride(bo); + fb->handles[0] = gbm_bo_get_handle(bo).u32; fb->format = pixel_format_get_info(gbm_bo_get_format(bo)); - fb->size = fb->stride * fb->height; + fb->size = fb->strides[0] * fb->height; fb->fd = backend->drm.fd; if (!fb->format) { @@ -1019,19 +1034,7 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, goto err_free; } - handles[0] = fb->handle; - pitches[0] = fb->stride; - offsets[0] = 0; - - ret = drmModeAddFB2(backend->drm.fd, fb->width, fb->height, - fb->format->format, handles, pitches, offsets, - &fb->fb_id, 0); - if (ret && fb->format->depth && fb->format->bpp) - ret = drmModeAddFB(backend->drm.fd, fb->width, fb->height, - fb->format->depth, fb->format->bpp, - fb->stride, fb->handle, &fb->fb_id); - - if (ret) { + if (drm_fb_addfb(fb) != 0) { weston_log("failed to create kms fb: %m\n"); goto err_free; } @@ -2045,8 +2048,10 @@ drm_output_apply_state_legacy(struct drm_output_state *state) assert(scanout_state->dest_h == scanout_state->src_h >> 16); mode = to_drm_mode(output->base.current_mode); - if (backend->state_invalid || !scanout_plane->state_cur->fb || - scanout_plane->state_cur->fb->stride != scanout_state->fb->stride) { + if (backend->state_invalid || + !scanout_plane->state_cur->fb || + scanout_plane->state_cur->fb->strides[0] != + scanout_state->fb->strides[0]) { ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, scanout_state->fb->fb_id, 0, 0, @@ -4156,7 +4161,7 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) output->image[i] = pixman_image_create_bits(pixman_format, w, h, output->dumb[i]->map, - output->dumb[i]->stride); + output->dumb[i]->strides[0]); if (!output->image[i]) goto err; } @@ -5957,7 +5962,7 @@ recorder_frame_notify(struct wl_listener *listener, void *data) return; ret = drmPrimeHandleToFD(b->drm.fd, - output->scanout_plane->state_cur->fb->handle, + output->scanout_plane->state_cur->fb->handles[0], DRM_CLOEXEC, &fd); if (ret) { weston_log("[libva recorder] " @@ -5966,7 +5971,7 @@ recorder_frame_notify(struct wl_listener *listener, void *data) } ret = vaapi_recorder_frame(output->recorder, fd, - output->scanout_plane->state_cur->fb->stride); + output->scanout_plane->state_cur->fb->strides[0]); if (ret < 0) { weston_log("[libva recorder] aborted: %m\n"); recorder_destroy(output); From 65a4dbcc14810c4c96437966dbcb2d9641b332a2 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 8 Dec 2016 16:36:18 +0000 Subject: [PATCH 0561/1642] compositor-drm: Support modifiers for drm_fb Use the new drmModeAddFB2WithModifiers interface to import buffers with modifiers. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- configure.ac | 3 +++ libweston/compositor-drm.c | 26 +++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 39168e205..adb998dda 100644 --- a/configure.ac +++ b/configure.ac @@ -206,6 +206,9 @@ AM_CONDITIONAL(ENABLE_DRM_COMPOSITOR, test x$enable_drm_compositor = xyes) if test x$enable_drm_compositor = xyes; then AC_DEFINE([BUILD_DRM_COMPOSITOR], [1], [Build the DRM compositor]) PKG_CHECK_MODULES(DRM_COMPOSITOR, [libudev >= 136 libdrm >= 2.4.30 gbm]) + PKG_CHECK_MODULES(DRM_COMPOSITOR_MODIFIERS, [libdrm >= 2.4.71], + [AC_DEFINE([HAVE_DRM_ADDFB2_MODIFIERS], 1, [libdrm supports modifiers])], + [AC_MSG_WARN([libdrm does not support AddFB2 with modifiers])]) PKG_CHECK_MODULES(DRM_COMPOSITOR_ATOMIC, [libdrm >= 2.4.78], [AC_DEFINE([HAVE_DRM_ATOMIC], 1, [libdrm supports atomic API])], [AC_MSG_WARN([libdrm does not support atomic modesetting, will omit that capability])]) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 780c341a3..80f88ed95 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -314,6 +314,7 @@ struct drm_fb { uint32_t strides[4]; uint32_t offsets[4]; const struct pixel_format_info *format; + uint64_t modifier; int width, height; int fd; struct weston_buffer_reference buffer_ref; @@ -883,7 +884,28 @@ drm_fb_destroy_gbm(struct gbm_bo *bo, void *data) static int drm_fb_addfb(struct drm_fb *fb) { - int ret; + int ret = -EINVAL; +#ifdef HAVE_DRM_ADDFB2_MODIFIERS + uint64_t mods[4] = { }; + int i; +#endif + + /* If we have a modifier set, we must only use the WithModifiers + * entrypoint; we cannot import it through legacy ioctls. */ + if (fb->modifier != DRM_FORMAT_MOD_INVALID) { + /* KMS demands that if a modifier is set, it must be the same + * for all planes. */ +#ifdef HAVE_DRM_ADDFB2_MODIFIERS + for (i = 0; i < (int) ARRAY_LENGTH(mods) && fb->handles[i]; i++) + mods[i] = fb->modifier; + ret = drmModeAddFB2WithModifiers(fb->fd, fb->width, fb->height, + fb->format->format, + fb->handles, fb->strides, + fb->offsets, mods, &fb->fb_id, + DRM_MODE_FB_MODIFIERS); +#endif + return ret; + } ret = drmModeAddFB2(fb->fd, fb->width, fb->height, fb->format->format, fb->handles, fb->strides, fb->offsets, &fb->fb_id, @@ -945,6 +967,7 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, goto err_fb; fb->type = BUFFER_PIXMAN_DUMB; + fb->modifier = DRM_FORMAT_MOD_INVALID; fb->handles[0] = create_arg.handle; fb->strides[0] = create_arg.pitch; fb->size = create_arg.size; @@ -1012,6 +1035,7 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, fb->strides[0] = gbm_bo_get_stride(bo); fb->handles[0] = gbm_bo_get_handle(bo).u32; fb->format = pixel_format_get_info(gbm_bo_get_format(bo)); + fb->modifier = DRM_FORMAT_MOD_INVALID; fb->size = fb->strides[0] * fb->height; fb->fd = backend->drm.fd; From 79e95cb9951c167d5a0a13a2e101cae752256a63 Mon Sep 17 00:00:00 2001 From: Ankit Nautiyal Date: Fri, 6 Jul 2018 16:18:39 +0530 Subject: [PATCH 0562/1642] man: Remove description of DRM specific mode-options from weston.ini.man The weston.ini.man describes the mode-formats that a user can specify for selecting a video mode. The DRM specific examples are already provided in weston-drm.man, so this inofrmation is redundant and can be removed. This patch removes the DRM specific mode option details from the description of mode configuration in weston.ini.man. A pointer to weston-drm.man is given, which has complete information about the mode-format options supported by DRM backend. Signed-off-by: Ankit Nautiyal Reviewed-by: Pekka Paalanen --- man/weston.ini.man | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/man/weston.ini.man b/man/weston.ini.man index 02c9b0304..199b465c5 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -406,31 +406,11 @@ for more details. sets the output mode (string). The mode parameter is handled differently depending on the backend. On the X11 backend, it just sets the WIDTHxHEIGHT of the weston window. -The DRM backend accepts different modes: -.PP -.RS 10 -.nf -.BR "WIDTHxHEIGHT " "Resolution size width and height in pixels" -.BR "preferred " "Uses the preferred mode" -.BR "current " "Uses the current crt controller mode" -.BR "off " "Disables the output" -.fi -.RE -.RS -.PP -Optionally, a user may specify a modeline, such as: -.PP -.nf -.in +4n -.nf -173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync -.fi -.in -.PP -It consists of the refresh rate in Hz, horizontal and vertical resolution, -options for horizontal and vertical synchronisation. The program -.B "cvt(1)" -can provide suitable modeline string. +The DRM backend accepts different modes, along with an option of a modeline string. + +See +.B "weston-drm(7)" +for examples of modes-formats supported by DRM backend. .RE .TP 7 .BI "transform=" normal From 7625577a4f8dd975a1ba3270d3b5eca1fdef7256 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 6 Jul 2018 11:36:49 +0100 Subject: [PATCH 0563/1642] compositor-drm: Define DPMS property as an enum The DPMS connector property is an enum property in KMS, which made our property handling complain at startup as we weren't defining its enums. Fix our definition so we parse the enum values. The only user of the property is the legacy path, which can continue using fixed values as those values are part of the KMS ABI. The atomic path does not need any changes, since atomic uses routing and CRTC active to determine the connector's power state, rather than a property. Signed-off-by: Daniel Stone Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/125 Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 41 +++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 80f88ed95..e09719ce4 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -197,9 +197,36 @@ enum wdrm_connector_property { WDRM_CONNECTOR__COUNT }; +enum wdrm_dpms_state { + WDRM_DPMS_STATE_OFF = 0, + WDRM_DPMS_STATE_ON, + WDRM_DPMS_STATE_STANDBY, /* unused */ + WDRM_DPMS_STATE_SUSPEND, /* unused */ + WDRM_DPMS_STATE__COUNT +}; + +static struct drm_property_enum_info dpms_state_enums[] = { + [WDRM_DPMS_STATE_OFF] = { + .name = "Off", + }, + [WDRM_DPMS_STATE_ON] = { + .name = "On", + }, + [WDRM_DPMS_STATE_STANDBY] = { + .name = "Standby", + }, + [WDRM_DPMS_STATE_SUSPEND] = { + .name = "Suspend", + }, +}; + static const struct drm_property_info connector_props[] = { [WDRM_CONNECTOR_EDID] = { .name = "EDID" }, - [WDRM_CONNECTOR_DPMS] = { .name = "DPMS" }, + [WDRM_CONNECTOR_DPMS] = { + .name = "DPMS", + .enum_values = dpms_state_enums, + .num_enum_values = WDRM_DPMS_STATE__COUNT, + }, [WDRM_CONNECTOR_CRTC_ID] = { .name = "CRTC_ID", }, }; @@ -2265,6 +2292,8 @@ drm_output_apply_state_atomic(struct drm_output_state *state, current_mode->blob_id); ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 1); + /* No need for the DPMS property, since it is implicit in + * routing and CRTC activity. */ wl_list_for_each(head, &output->base.head_list, base.output_link) { ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, output->crtc_id); @@ -2273,6 +2302,8 @@ drm_output_apply_state_atomic(struct drm_output_state *state, ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, 0); ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 0); + /* No need for the DPMS property, since it is implicit in + * routing and CRTC activity. */ wl_list_for_each(head, &output->base.head_list, base.output_link) ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, 0); } @@ -2356,14 +2387,6 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, info->prop_id, 0); if (err <= 0) ret = -1; - - info = &head->props_conn[WDRM_CONNECTOR_DPMS]; - if (info->prop_id > 0) - err = drmModeAtomicAddProperty(req, head->connector_id, - info->prop_id, - DRM_MODE_DPMS_OFF); - if (err <= 0) - ret = -1; } wl_array_for_each(unused, &b->unused_crtcs) { From dc082cb0718d2d07d259ffc02209cbb28714fbdc Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 9 Jul 2018 13:49:04 +0100 Subject: [PATCH 0564/1642] compositor-drm: Avoid cast by using unsigned loop index ARRAY_LENGTH returns a size_t; rather than casting its result to int so we can compare to our signed index variable, just declare the index as a compatible type in the first place. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index e09719ce4..7753dfba6 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -914,7 +914,7 @@ drm_fb_addfb(struct drm_fb *fb) int ret = -EINVAL; #ifdef HAVE_DRM_ADDFB2_MODIFIERS uint64_t mods[4] = { }; - int i; + size_t i; #endif /* If we have a modifier set, we must only use the WithModifiers @@ -923,7 +923,7 @@ drm_fb_addfb(struct drm_fb *fb) /* KMS demands that if a modifier is set, it must be the same * for all planes. */ #ifdef HAVE_DRM_ADDFB2_MODIFIERS - for (i = 0; i < (int) ARRAY_LENGTH(mods) && fb->handles[i]; i++) + for (i = 0; i < ARRAY_LENGTH(mods) && fb->handles[i]; i++) mods[i] = fb->modifier; ret = drmModeAddFB2WithModifiers(fb->fd, fb->width, fb->height, fb->format->format, From bdebc3170e5816d6e2d34b36d4643c53634a0e41 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 9 Jul 2018 13:51:51 +0100 Subject: [PATCH 0565/1642] compositor-drm: Don't set fb->size for non-dumb buffers When creating a drm_fb from client (wl_buffer/dmabuf), gbm_surface, or client buffers, set fb->size to 0. The size member is only used for dumb buffers, where we mmap the whole buffer, and need the size recorded to later pass to munmap. Determining the full size of multi-planar buffers is difficult, as auxiliary planes are not guaranteed to have a (height*stride) allocation, e.g. if they are subsampled or if they do not contain pixel data at all but, e.g., compression information. Single-plane tiled buffers also often pad the buffer allocation to a multiple of tile height, making our existing calculation incorrect. Though it does no harm to record incorrect information, it also does no good as we never use it; remove it in order to avoid any confusion. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 7753dfba6..29fa98da6 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1063,7 +1063,7 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, fb->handles[0] = gbm_bo_get_handle(bo).u32; fb->format = pixel_format_get_info(gbm_bo_get_format(bo)); fb->modifier = DRM_FORMAT_MOD_INVALID; - fb->size = fb->strides[0] * fb->height; + fb->size = 0; fb->fd = backend->drm.fd; if (!fb->format) { From 11f91bbd36e7ebdc01fb6c2b29bcab53aec7209e Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 9 Jul 2018 13:05:59 +0100 Subject: [PATCH 0566/1642] helpers: Move static_assert definition to shared Collect the fallback definitions of static_assert() from desktop-shell and the test shell, and move them to helpers.h. This allows code throughout the tree to use static_assert() for build-time assertions, where it is supported by the compiler. As GCC goes out of its way to only add static_assert() when C11 has been explicitly requested - which we don't do - make sure to use the more widely available _Static_assert() if that is provided. This will be used in future patches to ensure two array lengths don't go out of sync. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- desktop-shell/shell.c | 4 ---- shared/helpers.h | 34 +++++++++++++++++++++++++++++++ tests/weston-test-desktop-shell.c | 4 ---- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 8b7a23ade..ea3c45354 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -47,10 +47,6 @@ #define DEFAULT_NUM_WORKSPACES 1 #define DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH 200 -#ifndef static_assert -#define static_assert(cond, msg) -#endif - struct focus_state { struct desktop_shell *shell; struct weston_seat *seat; diff --git a/shared/helpers.h b/shared/helpers.h index 46f745d1b..0ebcc4a44 100644 --- a/shared/helpers.h +++ b/shared/helpers.h @@ -100,6 +100,40 @@ extern "C" { (type *)( (char *)__mptr - offsetof(type,member) );}) #endif +/** + * Build-time static assertion support + * + * A build-time equivalent to assert(), will generate a compilation error + * if the supplied condition does not evaluate true. + * + * The following example demonstrates use of static_assert to ensure that + * arrays which are supposed to mirror each other have a consistent + * size. + * + * This is only a fallback definition; support must be provided by the + * compiler itself. + * + * @code + * int small[4]; + * long expanded[4]; + * + * static_assert(ARRAY_LENGTH(small) == ARRAY_LENGTH(expanded), + * "size mismatch between small and expanded arrays"); + * for (i = 0; i < ARRAY_LENGTH(small); i++) + * expanded[i] = small[4]; + * @endcode + * + * @param condition Expression to check for truth + * @param msg Message to print on failure + */ +#ifndef static_assert +# ifdef _Static_assert +# define static_assert(cond, msg) _Static_assert(cond, msg) +# else +# define static_assert(cond, msg) +# endif +#endif + #ifdef __cplusplus } #endif diff --git a/tests/weston-test-desktop-shell.c b/tests/weston-test-desktop-shell.c index de8442512..c780316d9 100644 --- a/tests/weston-test-desktop-shell.c +++ b/tests/weston-test-desktop-shell.c @@ -41,10 +41,6 @@ #include "shared/helpers.h" #include "libweston-desktop/libweston-desktop.h" -#ifndef static_assert -#define static_assert(cond, msg) -#endif - struct desktest_shell { struct wl_listener compositor_destroy_listener; struct weston_desktop *desktop; From f522e2261131eb9ed9f83157420f114540cfcbf1 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 18 Nov 2016 12:31:26 +0000 Subject: [PATCH 0567/1642] compositor-drm: Add modifiers to GBM dmabuf import Add support for the GBM_BO_IMPORT_FD_MODIFIER path, which allows us to import multi-plane dmabufs, as well as format modifiers. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/113 --- configure.ac | 6 +- libweston/compositor-drm.c | 211 +++++++++++++++++++++++++++++-------- 2 files changed, 170 insertions(+), 47 deletions(-) diff --git a/configure.ac b/configure.ac index adb998dda..ef55ace36 100644 --- a/configure.ac +++ b/configure.ac @@ -212,9 +212,9 @@ if test x$enable_drm_compositor = xyes; then PKG_CHECK_MODULES(DRM_COMPOSITOR_ATOMIC, [libdrm >= 2.4.78], [AC_DEFINE([HAVE_DRM_ATOMIC], 1, [libdrm supports atomic API])], [AC_MSG_WARN([libdrm does not support atomic modesetting, will omit that capability])]) - PKG_CHECK_MODULES(DRM_COMPOSITOR_GBM, [gbm >= 10.2], - [AC_DEFINE([HAVE_GBM_FD_IMPORT], 1, [gbm supports dmabuf import])], - [AC_MSG_WARN([gbm does not support dmabuf import, will omit that capability])]) + PKG_CHECK_MODULES(DRM_COMPOSITOR_GBM, [gbm >= 17.2], + [AC_DEFINE([HAVE_GBM_FD_IMPORT], 1, [gbm supports import with modifiers])], + [AC_MSG_WARN([GBM does not support dmabuf import, will omit that capability])]) fi diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 29fa98da6..87e3e5d25 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -326,6 +326,7 @@ struct drm_mode { enum drm_fb_type { BUFFER_INVALID = 0, /**< never used */ BUFFER_CLIENT, /**< directly sourced from client */ + BUFFER_DMABUF, /**< imported from linux_dmabuf client */ BUFFER_PIXMAN_DUMB, /**< internal Pixman rendering */ BUFFER_GBM_SURFACE, /**< internal EGL rendering */ BUFFER_CURSOR, /**< internal cursor buffer */ @@ -1038,6 +1039,155 @@ drm_fb_ref(struct drm_fb *fb) return fb; } +static void +drm_fb_destroy_dmabuf(struct drm_fb *fb) +{ + /* We deliberately do not close the GEM handles here; GBM manages + * their lifetime through the BO. */ + if (fb->bo) + gbm_bo_destroy(fb->bo); + drm_fb_destroy(fb); +} + +static struct drm_fb * +drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, + struct drm_backend *backend, bool is_opaque) +{ +#ifdef HAVE_GBM_FD_IMPORT + struct drm_fb *fb; + struct gbm_import_fd_data import_legacy = { + .width = dmabuf->attributes.width, + .height = dmabuf->attributes.height, + .format = dmabuf->attributes.format, + .stride = dmabuf->attributes.stride[0], + .fd = dmabuf->attributes.fd[0], + }; + struct gbm_import_fd_modifier_data import_mod = { + .width = dmabuf->attributes.width, + .height = dmabuf->attributes.height, + .format = dmabuf->attributes.format, + .num_fds = dmabuf->attributes.n_planes, + .modifier = dmabuf->attributes.modifier[0], + }; + int i; + + /* XXX: TODO: + * + * Currently the buffer is rejected if any dmabuf attribute + * flag is set. This keeps us from passing an inverted / + * interlaced / bottom-first buffer (or any other type that may + * be added in the future) through to an overlay. Ultimately, + * these types of buffers should be handled through buffer + * transforms and not as spot-checks requiring specific + * knowledge. */ + if (dmabuf->attributes.flags) + return NULL; + + fb = zalloc(sizeof *fb); + if (fb == NULL) + return NULL; + + fb->refcnt = 1; + fb->type = BUFFER_DMABUF; + + static_assert(ARRAY_LENGTH(import_mod.fds) == + ARRAY_LENGTH(dmabuf->attributes.fd), + "GBM and linux_dmabuf FD size must match"); + static_assert(sizeof(import_mod.fds) == sizeof(dmabuf->attributes.fd), + "GBM and linux_dmabuf FD size must match"); + memcpy(import_mod.fds, dmabuf->attributes.fd, sizeof(import_mod.fds)); + + static_assert(ARRAY_LENGTH(import_mod.strides) == + ARRAY_LENGTH(dmabuf->attributes.stride), + "GBM and linux_dmabuf stride size must match"); + static_assert(sizeof(import_mod.strides) == + sizeof(dmabuf->attributes.stride), + "GBM and linux_dmabuf stride size must match"); + memcpy(import_mod.strides, dmabuf->attributes.stride, + sizeof(import_mod.strides)); + + static_assert(ARRAY_LENGTH(import_mod.offsets) == + ARRAY_LENGTH(dmabuf->attributes.offset), + "GBM and linux_dmabuf offset size must match"); + static_assert(sizeof(import_mod.offsets) == + sizeof(dmabuf->attributes.offset), + "GBM and linux_dmabuf offset size must match"); + memcpy(import_mod.offsets, dmabuf->attributes.offset, + sizeof(import_mod.offsets)); + + /* The legacy FD-import path does not allow us to supply modifiers, + * multiple planes, or buffer offsets. */ + if (dmabuf->attributes.modifier[0] != DRM_FORMAT_MOD_INVALID || + import_mod.num_fds > 1 || + import_mod.offsets[0] > 0) { + fb->bo = gbm_bo_import(backend->gbm, GBM_BO_IMPORT_FD_MODIFIER, + &import_mod, + GBM_BO_USE_SCANOUT); + } else { + fb->bo = gbm_bo_import(backend->gbm, GBM_BO_IMPORT_FD, + &import_legacy, + GBM_BO_USE_SCANOUT); + } + + if (!fb->bo) + goto err_free; + + fb->width = dmabuf->attributes.width; + fb->height = dmabuf->attributes.height; + fb->modifier = dmabuf->attributes.modifier[0]; + fb->size = 0; + fb->fd = backend->drm.fd; + + static_assert(ARRAY_LENGTH(fb->strides) == + ARRAY_LENGTH(dmabuf->attributes.stride), + "drm_fb and dmabuf stride size must match"); + static_assert(sizeof(fb->strides) == sizeof(dmabuf->attributes.stride), + "drm_fb and dmabuf stride size must match"); + memcpy(fb->strides, dmabuf->attributes.stride, sizeof(fb->strides)); + static_assert(ARRAY_LENGTH(fb->offsets) == + ARRAY_LENGTH(dmabuf->attributes.offset), + "drm_fb and dmabuf offset size must match"); + static_assert(sizeof(fb->offsets) == sizeof(dmabuf->attributes.offset), + "drm_fb and dmabuf offset size must match"); + memcpy(fb->offsets, dmabuf->attributes.offset, sizeof(fb->offsets)); + + fb->format = pixel_format_get_info(dmabuf->attributes.format); + if (!fb->format) { + weston_log("couldn't look up format info for 0x%lx\n", + (unsigned long) dmabuf->attributes.format); + goto err_free; + } + + if (is_opaque) + fb->format = pixel_format_get_opaque_substitute(fb->format); + + if (backend->min_width > fb->width || + fb->width > backend->max_width || + backend->min_height > fb->height || + fb->height > backend->max_height) { + weston_log("bo geometry out of bounds\n"); + goto err_free; + } + + for (i = 0; i < dmabuf->attributes.n_planes; i++) { + fb->handles[i] = gbm_bo_get_handle_for_plane(fb->bo, i).u32; + if (!fb->handles[i]) + goto err_free; + } + + if (drm_fb_addfb(fb) != 0) { + weston_log("failed to create kms fb: %m\n"); + goto err_free; + } + + return fb; + +err_free: + drm_fb_destroy_dmabuf(fb); +#endif + return NULL; +} + static struct drm_fb * drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, bool is_opaque, enum drm_fb_type type) @@ -1103,7 +1253,7 @@ static void drm_fb_set_buffer(struct drm_fb *fb, struct weston_buffer *buffer) { assert(fb->buffer_ref.buffer == NULL); - assert(fb->type == BUFFER_CLIENT); + assert(fb->type == BUFFER_CLIENT || fb->type == BUFFER_DMABUF); weston_buffer_reference(&fb->buffer_ref, buffer); } @@ -1128,6 +1278,9 @@ drm_fb_unref(struct drm_fb *fb) case BUFFER_GBM_SURFACE: gbm_surface_release_buffer(fb->gbm_surface, fb->bo); break; + case BUFFER_DMABUF: + drm_fb_destroy_dmabuf(fb); + break; default: assert(NULL); break; @@ -1384,9 +1537,9 @@ drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) struct drm_output *output = state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; + bool is_opaque = drm_view_is_opaque(ev); struct linux_dmabuf_buffer *dmabuf; struct drm_fb *fb; - struct gbm_bo *bo; /* Don't import buffers which span multiple outputs. */ if (ev->output_mask != (1u << output->base.id)) @@ -1404,58 +1557,28 @@ drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) if (wl_shm_buffer_get(buffer->resource)) return NULL; + /* GBM is used for dmabuf import as well as from client wl_buffer. */ if (!b->gbm) return NULL; dmabuf = linux_dmabuf_buffer_get(buffer->resource); if (dmabuf) { -#ifdef HAVE_GBM_FD_IMPORT - /* XXX: TODO: - * - * Use AddFB2 directly, do not go via GBM. - * Add support for multiplanar formats. - * Both require refactoring in the DRM-backend to - * support a mix of gbm_bos and drmfbs. - */ - struct gbm_import_fd_data gbm_dmabuf = { - .fd = dmabuf->attributes.fd[0], - .width = dmabuf->attributes.width, - .height = dmabuf->attributes.height, - .stride = dmabuf->attributes.stride[0], - .format = dmabuf->attributes.format - }; - - /* XXX: TODO: - * - * Currently the buffer is rejected if any dmabuf attribute - * flag is set. This keeps us from passing an inverted / - * interlaced / bottom-first buffer (or any other type that may - * be added in the future) through to an overlay. Ultimately, - * these types of buffers should be handled through buffer - * transforms and not as spot-checks requiring specific - * knowledge. */ - if (dmabuf->attributes.n_planes != 1 || - dmabuf->attributes.offset[0] != 0 || - dmabuf->attributes.flags) + fb = drm_fb_get_from_dmabuf(dmabuf, b, is_opaque); + if (!fb) return NULL; - - bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_FD, &gbm_dmabuf, - GBM_BO_USE_SCANOUT); -#else - return NULL; -#endif } else { + struct gbm_bo *bo; + bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, buffer->resource, GBM_BO_USE_SCANOUT); - } - - if (!bo) - return NULL; + if (!bo) + return NULL; - fb = drm_fb_get_from_bo(bo, b, drm_view_is_opaque(ev), BUFFER_CLIENT); - if (!fb) { - gbm_bo_destroy(bo); - return NULL; + fb = drm_fb_get_from_bo(bo, b, is_opaque, BUFFER_CLIENT); + if (!fb) { + gbm_bo_destroy(bo); + return NULL; + } } drm_fb_set_buffer(fb, buffer); From f4456221db312f326dc0f16a306686d8c2f3c999 Mon Sep 17 00:00:00 2001 From: Sergi Granell Date: Thu, 12 Jan 2017 17:17:32 +0000 Subject: [PATCH 0568/1642] compositor-drm: Support plane IN_FORMATS The per-plane IN_FORMATS KMS property describes the format/modifier combinations supported for display on this plane. Read and parse this format, storing the data in each plane, so we can know which combinations might work, and which combinations definitely will not work. Similarly to f11ec02cad40 ("compositor-drm: Extract overlay FB import to helper"), we now use this when considering promoting a view to overlay planes. If the framebuffer's modifier is definitely not supported by the plane, we do not attempt to use that plane for that view. This will also be used in a follow-patch, passing the list of modifiers to GBM surface allocation to allow it to allocate more optimal buffers. Signed-off-by: Sergi Granell Reviewed-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- configure.ac | 3 + libweston/compositor-drm.c | 134 ++++++++++++++++++++++++++++++++++--- 2 files changed, 128 insertions(+), 9 deletions(-) diff --git a/configure.ac b/configure.ac index ef55ace36..c550198ae 100644 --- a/configure.ac +++ b/configure.ac @@ -212,6 +212,9 @@ if test x$enable_drm_compositor = xyes; then PKG_CHECK_MODULES(DRM_COMPOSITOR_ATOMIC, [libdrm >= 2.4.78], [AC_DEFINE([HAVE_DRM_ATOMIC], 1, [libdrm supports atomic API])], [AC_MSG_WARN([libdrm does not support atomic modesetting, will omit that capability])]) + PKG_CHECK_MODULES(DRM_COMPOSITOR_FORMATS_BLOB, [libdrm >= 2.4.83], + [AC_DEFINE([HAVE_DRM_FORMATS_BLOB], 1, [libdrm supports modifier advertisement])], + [AC_MSG_WARN([libdrm does not support modifier advertisement])]) PKG_CHECK_MODULES(DRM_COMPOSITOR_GBM, [gbm >= 17.2], [AC_DEFINE([HAVE_GBM_FD_IMPORT], 1, [gbm supports import with modifiers])], [AC_MSG_WARN([GBM does not support dmabuf import, will omit that capability])]) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 87e3e5d25..907a62b38 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -144,6 +144,7 @@ enum wdrm_plane_property { WDRM_PLANE_CRTC_H, WDRM_PLANE_FB_ID, WDRM_PLANE_CRTC_ID, + WDRM_PLANE_IN_FORMATS, WDRM_PLANE__COUNT }; @@ -185,6 +186,7 @@ static const struct drm_property_info plane_props[] = { [WDRM_PLANE_CRTC_H] = { .name = "CRTC_H", }, [WDRM_PLANE_FB_ID] = { .name = "FB_ID", }, [WDRM_PLANE_CRTC_ID] = { .name = "CRTC_ID", }, + [WDRM_PLANE_IN_FORMATS] = { .name = "IN_FORMATS" }, }; /** @@ -448,7 +450,11 @@ struct drm_plane { struct wl_list link; - uint32_t formats[]; + struct { + uint32_t format; + uint32_t count_modifiers; + uint64_t *modifiers; + } formats[]; }; struct drm_head { @@ -3002,7 +3008,19 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, /* Check whether the format is supported */ for (i = 0; i < p->count_formats; i++) { - if (p->formats[i] == fb->format->format) + unsigned int j; + + if (p->formats[i].format != fb->format->format) + continue; + + if (fb->modifier == DRM_FORMAT_MOD_INVALID) + break; + + for (j = 0; j < p->formats[i].count_modifiers; j++) { + if (p->formats[i].modifiers[j] == fb->modifier) + break; + } + if (j != p->formats[i].count_modifiers) break; } if (i == p->count_formats) @@ -3647,6 +3665,100 @@ init_pixman(struct drm_backend *b) return pixman_renderer_init(b->compositor); } +#ifdef HAVE_DRM_FORMATS_BLOB +static inline uint32_t * +formats_ptr(struct drm_format_modifier_blob *blob) +{ + return (uint32_t *)(((char *)blob) + blob->formats_offset); +} + +static inline struct drm_format_modifier * +modifiers_ptr(struct drm_format_modifier_blob *blob) +{ + return (struct drm_format_modifier *) + (((char *)blob) + blob->modifiers_offset); +} +#endif + +/** + * Populates the plane's formats array, using either the IN_FORMATS blob + * property (if available), or the plane's format list if not. + */ +static int +drm_plane_populate_formats(struct drm_plane *plane, const drmModePlane *kplane, + const drmModeObjectProperties *props) +{ + unsigned i; +#ifdef HAVE_DRM_FORMATS_BLOB + drmModePropertyBlobRes *blob; + struct drm_format_modifier_blob *fmt_mod_blob; + struct drm_format_modifier *blob_modifiers; + uint32_t *blob_formats; + uint32_t blob_id; + + blob_id = drm_property_get_value(&plane->props[WDRM_PLANE_IN_FORMATS], + props, + 0); + if (blob_id == 0) + goto fallback; + + blob = drmModeGetPropertyBlob(plane->backend->drm.fd, blob_id); + if (!blob) + goto fallback; + + fmt_mod_blob = blob->data; + blob_formats = formats_ptr(fmt_mod_blob); + blob_modifiers = modifiers_ptr(fmt_mod_blob); + + if (plane->count_formats != fmt_mod_blob->count_formats) { + weston_log("DRM backend: format count differs between " + "plane (%d) and IN_FORMATS (%d)\n", + plane->count_formats, + fmt_mod_blob->count_formats); + weston_log("This represents a kernel bug; Weston is " + "unable to continue.\n"); + abort(); + } + + for (i = 0; i < fmt_mod_blob->count_formats; i++) { + uint32_t count_modifiers = 0; + uint64_t *modifiers = NULL; + unsigned j; + + for (j = 0; j < fmt_mod_blob->count_modifiers; j++) { + struct drm_format_modifier *mod = &blob_modifiers[j]; + + if ((i < mod->offset) || (i > mod->offset + 63)) + continue; + if (!(mod->formats & (1 << (i - mod->offset)))) + continue; + + modifiers = realloc(modifiers, + (count_modifiers + 1) * + sizeof(modifiers[0])); + assert(modifiers); + modifiers[count_modifiers++] = mod->modifier; + } + + plane->formats[i].format = blob_formats[i]; + plane->formats[i].modifiers = modifiers; + plane->formats[i].count_modifiers = count_modifiers; + } + + drmModeFreePropertyBlob(blob); + + return 0; + +fallback: +#endif + /* No IN_FORMATS blob available, so just use the old. */ + assert(plane->count_formats == kplane->count_formats); + for (i = 0; i < kplane->count_formats; i++) + plane->formats[i].format = kplane->formats[i]; + + return 0; +} + /** * Create a drm_plane for a hardware plane * @@ -3676,25 +3788,23 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane, { struct drm_plane *plane; drmModeObjectProperties *props; - int num_formats = (kplane) ? kplane->count_formats : 1; + uint32_t num_formats = (kplane) ? kplane->count_formats : 1; plane = zalloc(sizeof(*plane) + - (sizeof(uint32_t) * num_formats)); + (sizeof(plane->formats[0]) * num_formats)); if (!plane) { weston_log("%s: out of memory\n", __func__); return NULL; } plane->backend = b; + plane->count_formats = num_formats; plane->state_cur = drm_plane_state_alloc(NULL, plane); plane->state_cur->complete = true; if (kplane) { plane->possible_crtcs = kplane->possible_crtcs; plane->plane_id = kplane->plane_id; - plane->count_formats = kplane->count_formats; - memcpy(plane->formats, kplane->formats, - kplane->count_formats * sizeof(kplane->formats[0])); props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id, DRM_MODE_OBJECT_PLANE); @@ -3708,13 +3818,19 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane, drm_property_get_value(&plane->props[WDRM_PLANE_TYPE], props, WDRM_PLANE_TYPE__COUNT); + + if (drm_plane_populate_formats(plane, kplane, props) < 0) { + drmModeFreeObjectProperties(props); + goto err; + } + drmModeFreeObjectProperties(props); } else { plane->possible_crtcs = (1 << output->pipe); plane->plane_id = 0; plane->count_formats = 1; - plane->formats[0] = format; + plane->formats[0].format = format; plane->type = type; } @@ -4990,7 +5106,7 @@ drm_output_set_gbm_format(struct weston_output *base, * supported by the primary plane; we just hope that the GBM format * works. */ if (!b->universal_planes) - output->scanout_plane->formats[0] = output->gbm_format; + output->scanout_plane->formats[0].format = output->gbm_format; } static void From 244244d11b5a9168656dd613029a0feb4879aff7 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 18 Nov 2016 18:02:08 +0000 Subject: [PATCH 0569/1642] compositor-drm: Use GBM modifier API Now that we collect information about which modifiers are supported for KMS display, and are able to create KMS framebuffers with modifiers, begin using the modifier-aware GBM API. Client buffers from dmabuf already store multi-plane and modifier information into drm_fb. Extend this to drm_fb_get_from_bo(), used for wl_buffer, cursor, and gbm_surface buffers. wl_buffer buffers should by convention not require modifiers. Cursor buffers must not require modifiers, as they should be linear. Prior to this patch, GBM buffers must have been single-planar, and able to used without explicitly naming modifiers. Using gbm_surface_create_with_modifiers allows us to pass the list of modifiers acceptable to KMS for scanout to GBM, so it can allocate multi-planar buffers or those which are otherwise only addressible with modifiers. On platforms supporting and preferring modifiers for scanout, this means that the gbm_bos we get from our scanout surface need to use the extended API to query multiple planes, offsets, modifiers, etc. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- configure.ac | 3 ++ libweston/compositor-drm.c | 57 ++++++++++++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/configure.ac b/configure.ac index c550198ae..357b6471e 100644 --- a/configure.ac +++ b/configure.ac @@ -215,6 +215,9 @@ if test x$enable_drm_compositor = xyes; then PKG_CHECK_MODULES(DRM_COMPOSITOR_FORMATS_BLOB, [libdrm >= 2.4.83], [AC_DEFINE([HAVE_DRM_FORMATS_BLOB], 1, [libdrm supports modifier advertisement])], [AC_MSG_WARN([libdrm does not support modifier advertisement])]) + PKG_CHECK_MODULES(DRM_COMPOSITOR_GBM_MODIFIERS, [gbm >= 17.1], + [AC_DEFINE([HAVE_GBM_MODIFIERS], 1, [GBM supports modifiers])], + [AC_MSG_WARN([GBM does not support modifiers])]) PKG_CHECK_MODULES(DRM_COMPOSITOR_GBM, [gbm >= 17.2], [AC_DEFINE([HAVE_GBM_FD_IMPORT], 1, [gbm supports import with modifiers])], [AC_MSG_WARN([GBM does not support dmabuf import, will omit that capability])]) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 907a62b38..ec79b7250 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1199,6 +1199,9 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, bool is_opaque, enum drm_fb_type type) { struct drm_fb *fb = gbm_bo_get_user_data(bo); +#ifdef HAVE_GBM_MODIFIERS + int i; +#endif if (fb) { assert(fb->type == type); @@ -1212,15 +1215,25 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, fb->type = type; fb->refcnt = 1; fb->bo = bo; + fb->fd = backend->drm.fd; fb->width = gbm_bo_get_width(bo); fb->height = gbm_bo_get_height(bo); + fb->format = pixel_format_get_info(gbm_bo_get_format(bo)); + fb->size = 0; + +#ifdef HAVE_GBM_MODIFIERS + fb->modifier = gbm_bo_get_modifier(bo); + for (i = 0; i < gbm_bo_get_plane_count(bo); i++) { + fb->strides[i] = gbm_bo_get_stride_for_plane(bo, i); + fb->handles[i] = gbm_bo_get_handle_for_plane(bo, i).u32; + fb->offsets[i] = gbm_bo_get_offset(bo, i); + } +#else fb->strides[0] = gbm_bo_get_stride(bo); fb->handles[0] = gbm_bo_get_handle(bo).u32; - fb->format = pixel_format_get_info(gbm_bo_get_format(bo)); fb->modifier = DRM_FORMAT_MOD_INVALID; - fb->size = 0; - fb->fd = backend->drm.fd; +#endif if (!fb->format) { weston_log("couldn't look up format 0x%lx\n", @@ -4365,13 +4378,39 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) fallback_format_for(output->gbm_format), }; int n_formats = 1; + struct weston_mode *mode = output->base.current_mode; + struct drm_plane *plane = output->scanout_plane; + unsigned int i; + + for (i = 0; i < plane->count_formats; i++) { + if (plane->formats[i].format == output->gbm_format) + break; + } + + if (i == plane->count_formats) { + weston_log("format 0x%x not supported by output %s\n", + output->gbm_format, output->base.name); + return -1; + } + +#ifdef HAVE_GBM_MODIFIERS + if (plane->formats[i].count_modifiers > 0) { + output->gbm_surface = + gbm_surface_create_with_modifiers(b->gbm, + mode->width, + mode->height, + output->gbm_format, + plane->formats[i].modifiers, + plane->formats[i].count_modifiers); + } else +#endif + { + output->gbm_surface = + gbm_surface_create(b->gbm, mode->width, mode->height, + output->gbm_format, + GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT); + } - output->gbm_surface = gbm_surface_create(b->gbm, - output->base.current_mode->width, - output->base.current_mode->height, - format[0], - GBM_BO_USE_SCANOUT | - GBM_BO_USE_RENDERING); if (!output->gbm_surface) { weston_log("failed to create gbm surface\n"); return -1; From ee1aea7cd1b554ef4bd39e492186519371d020d8 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 18 Dec 2017 13:41:09 +0000 Subject: [PATCH 0570/1642] compositor-drm: Split drm_assign_planes in two Move drm_assign_planes into two functions: one which proposes a plane configuration, and another which applies that state to the Weston internal structures. This will be used to try multiple configurations and see which is supported. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- libweston/compositor-drm.c | 155 +++++++++++++++++++++++-------------- 1 file changed, 97 insertions(+), 58 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index ec79b7250..6ce7515be 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -407,6 +407,8 @@ struct drm_plane_state { struct drm_fb *fb; + struct weston_view *ev; /**< maintained for drm_assign_planes only */ + int32_t src_x, src_y; uint32_t src_w, src_h; int32_t dest_x, dest_y; @@ -1984,6 +1986,7 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, } state->fb = fb; + state->ev = ev; state->output = output; if (!drm_plane_state_coords_for_view(state, ev)) goto err; @@ -3055,6 +3058,7 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, } state->fb = fb; + state->ev = ev; state->output = output; if (!drm_plane_state_coords_for_view(state, ev)) @@ -3182,6 +3186,7 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, } output->cursor_view = ev; + plane_state->ev = ev; plane_state->fb = drm_fb_ref(output->gbm_cursor_fb[output->current_cursor]); @@ -3258,40 +3263,15 @@ drm_output_set_cursor(struct drm_output_state *output_state) drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); } -/* - * Get the aspect-ratio from drmModeModeInfo mode flags. - * - * @param drm_mode_flags- flags from drmModeModeInfo structure. - * @returns aspect-ratio as encoded in enum 'weston_mode_aspect_ratio'. - */ -static enum weston_mode_aspect_ratio -drm_to_weston_mode_aspect_ratio(uint32_t drm_mode_flags) -{ - return (drm_mode_flags & DRM_MODE_FLAG_PIC_AR_MASK) >> - DRM_MODE_FLAG_PIC_AR_BITS_POS; -} - -static const char * -aspect_ratio_to_string(enum weston_mode_aspect_ratio ratio) -{ - if (ratio < 0 || ratio >= ARRAY_LENGTH(aspect_ratio_as_string) || - !aspect_ratio_as_string[ratio]) - return " (unknown aspect ratio)"; - - return aspect_ratio_as_string[ratio]; -} - -static void -drm_assign_planes(struct weston_output *output_base, void *repaint_data) +static struct drm_output_state * +drm_output_propose_state(struct weston_output *output_base, + struct drm_pending_state *pending_state) { - struct drm_backend *b = to_drm_backend(output_base->compositor); - struct drm_pending_state *pending_state = repaint_data; struct drm_output *output = to_drm_output(output_base); struct drm_output_state *state; - struct drm_plane_state *plane_state; struct weston_view *ev; pixman_region32_t surface_overlap, renderer_region; - struct weston_plane *primary, *next_plane; + struct weston_plane *primary = &output_base->compositor->primary_plane; assert(!output->state_last); state = drm_output_state_duplicate(output->state_cur, @@ -3312,35 +3292,21 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) * as we do for flipping full screen surfaces. */ pixman_region32_init(&renderer_region); - primary = &output_base->compositor->primary_plane; wl_list_for_each(ev, &output_base->compositor->view_list, link) { - struct weston_surface *es = ev->surface; - - /* Test whether this buffer can ever go into a plane: - * non-shm, or small enough to be a cursor. - * - * Also, keep a reference when using the pixman renderer. - * That makes it possible to do a seamless switch to the GL - * renderer and since the pixman renderer keeps a reference - * to the buffer anyway, there is no side effects. - */ - if (b->use_pixman || - (es->buffer_ref.buffer && - (!wl_shm_buffer_get(es->buffer_ref.buffer->resource) || - (ev->surface->width <= b->cursor_width && - ev->surface->height <= b->cursor_height)))) - es->keep_buffer = true; - else - es->keep_buffer = false; + struct weston_plane *next_plane = NULL; + /* Since we process views from top to bottom, we know that if + * the view intersects the calculated renderer region, it must + * be part of, or occluded by, it, and cannot go on a plane. */ pixman_region32_init(&surface_overlap); pixman_region32_intersect(&surface_overlap, &renderer_region, &ev->transform.boundingbox); - next_plane = NULL; if (pixman_region32_not_empty(&surface_overlap)) next_plane = primary; + pixman_region32_fini(&surface_overlap); + if (next_plane == NULL) next_plane = drm_output_prepare_cursor_view(state, ev); @@ -3353,17 +3319,70 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) if (next_plane == NULL) next_plane = primary; - weston_view_move_to_plane(ev, next_plane); - if (next_plane == primary) pixman_region32_union(&renderer_region, &renderer_region, &ev->transform.boundingbox); + } + pixman_region32_fini(&renderer_region); + + return state; +} + +static void +drm_assign_planes(struct weston_output *output_base, void *repaint_data) +{ + struct drm_backend *b = to_drm_backend(output_base->compositor); + struct drm_pending_state *pending_state = repaint_data; + struct drm_output *output = to_drm_output(output_base); + struct drm_output_state *state; + struct drm_plane_state *plane_state; + struct weston_view *ev; + struct weston_plane *primary = &output_base->compositor->primary_plane; + + state = drm_output_propose_state(output_base, pending_state); + + wl_list_for_each(ev, &output_base->compositor->view_list, link) { + struct drm_plane *target_plane = NULL; + + /* Test whether this buffer can ever go into a plane: + * non-shm, or small enough to be a cursor. + * + * Also, keep a reference when using the pixman renderer. + * That makes it possible to do a seamless switch to the GL + * renderer and since the pixman renderer keeps a reference + * to the buffer anyway, there is no side effects. + */ + if (b->use_pixman || + (ev->surface->buffer_ref.buffer && + (!wl_shm_buffer_get(ev->surface->buffer_ref.buffer->resource) || + (ev->surface->width <= b->cursor_width && + ev->surface->height <= b->cursor_height)))) + ev->surface->keep_buffer = true; + else + ev->surface->keep_buffer = false; + + /* This is a bit unpleasant, but lacking a temporary place to + * hang a plane off the view, we have to do a nested walk. + * Our first-order iteration has to be planes rather than + * views, because otherwise we won't reset views which were + * previously on planes to being on the primary plane. */ + wl_list_for_each(plane_state, &state->plane_list, link) { + if (plane_state->ev == ev) { + plane_state->ev = NULL; + target_plane = plane_state->plane; + break; + } + } + + if (target_plane) + weston_view_move_to_plane(ev, &target_plane->base); + else + weston_view_move_to_plane(ev, primary); - if (next_plane == primary || - (output->cursor_plane && - next_plane == &output->cursor_plane->base)) { - /* cursor plane involves a copy */ + if (!target_plane || + target_plane->type == WDRM_PLANE_TYPE_CURSOR) { + /* cursor plane & renderer involve a copy */ ev->psf_flags = 0; } else { /* All other planes are a direct scanout of a @@ -3371,10 +3390,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) */ ev->psf_flags = WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; } - - pixman_region32_fini(&surface_overlap); } - pixman_region32_fini(&renderer_region); /* We rely on output->cursor_view being both an accurate reflection of * the cursor plane's state, but also being maintained across repaints @@ -3390,6 +3406,29 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) } } +/* + * Get the aspect-ratio from drmModeModeInfo mode flags. + * + * @param drm_mode_flags- flags from drmModeModeInfo structure. + * @returns aspect-ratio as encoded in enum 'weston_mode_aspect_ratio'. + */ +static enum weston_mode_aspect_ratio +drm_to_weston_mode_aspect_ratio(uint32_t drm_mode_flags) +{ + return (drm_mode_flags & DRM_MODE_FLAG_PIC_AR_MASK) >> + DRM_MODE_FLAG_PIC_AR_BITS_POS; +} + +static const char * +aspect_ratio_to_string(enum weston_mode_aspect_ratio ratio) +{ + if (ratio < 0 || ratio >= ARRAY_LENGTH(aspect_ratio_as_string) || + !aspect_ratio_as_string[ratio]) + return " (unknown aspect ratio)"; + + return aspect_ratio_as_string[ratio]; +} + /** * Find the closest-matching mode for a given target * From 231ae2f33bae357e9d7461a15df07618c3461720 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 29 Nov 2016 21:03:44 +0000 Subject: [PATCH 0571/1642] compositor-drm: Ignore views on other outputs When we come to assign_planes, try very hard to ignore views which are only visible on other outputs, rather than forcibly moving them to the primary plane, which causes damage all round and unnecessary repaints. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- libweston/compositor-drm.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 6ce7515be..a5c84dcb9 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1562,10 +1562,6 @@ drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) struct linux_dmabuf_buffer *dmabuf; struct drm_fb *fb; - /* Don't import buffers which span multiple outputs. */ - if (ev->output_mask != (1u << output->base.id)) - return NULL; - if (ev->alpha != 1.0f) return NULL; @@ -3135,10 +3131,6 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, if (plane->state_cur->output && plane->state_cur->output != output) return NULL; - /* Don't import buffers which span multiple outputs. */ - if (ev->output_mask != (1u << output->base.id)) - return NULL; - /* We use GBM to import SHM buffers. */ if (b->gbm == NULL) return NULL; @@ -3296,6 +3288,16 @@ drm_output_propose_state(struct weston_output *output_base, wl_list_for_each(ev, &output_base->compositor->view_list, link) { struct weston_plane *next_plane = NULL; + /* If this view doesn't touch our output at all, there's no + * reason to do anything with it. */ + if (!(ev->output_mask & (1u << output->base.id))) + continue; + + /* We only assign planes to views which are exclusively present + * on our output. */ + if (ev->output_mask != (1u << output->base.id)) + next_plane = primary; + /* Since we process views from top to bottom, we know that if * the view intersects the calculated renderer region, it must * be part of, or occluded by, it, and cannot go on a plane. */ @@ -3345,6 +3347,11 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) wl_list_for_each(ev, &output_base->compositor->view_list, link) { struct drm_plane *target_plane = NULL; + /* If this view doesn't touch our output at all, there's no + * reason to do anything with it. */ + if (!(ev->output_mask & (1u << output->base.id))) + continue; + /* Test whether this buffer can ever go into a plane: * non-shm, or small enough to be a cursor. * From 8108239c39e0e79f08827780d9f848dfaf4e50cf Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 9 Dec 2016 18:03:31 +0000 Subject: [PATCH 0572/1642] compositor-drm: Ignore occluded views When trying to assign planes, keep track of the areas which are already occluded, and ignore views which are completely occluded. This allows us to build a state using planes only, when there are occluded views which cannot go into a plane behind views which can. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emre Ucan --- libweston/compositor-drm.c | 43 +++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index a5c84dcb9..51b2482d9 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3262,7 +3262,7 @@ drm_output_propose_state(struct weston_output *output_base, struct drm_output *output = to_drm_output(output_base); struct drm_output_state *state; struct weston_view *ev; - pixman_region32_t surface_overlap, renderer_region; + pixman_region32_t surface_overlap, renderer_region, occluded_region; struct weston_plane *primary = &output_base->compositor->primary_plane; assert(!output->state_last); @@ -3284,9 +3284,12 @@ drm_output_propose_state(struct weston_output *output_base, * as we do for flipping full screen surfaces. */ pixman_region32_init(&renderer_region); + pixman_region32_init(&occluded_region); wl_list_for_each(ev, &output_base->compositor->view_list, link) { struct weston_plane *next_plane = NULL; + pixman_region32_t clipped_view; + bool occluded = false; /* If this view doesn't touch our output at all, there's no * reason to do anything with it. */ @@ -3298,13 +3301,27 @@ drm_output_propose_state(struct weston_output *output_base, if (ev->output_mask != (1u << output->base.id)) next_plane = primary; + /* Ignore views we know to be totally occluded. */ + pixman_region32_init(&clipped_view); + pixman_region32_intersect(&clipped_view, + &ev->transform.boundingbox, + &output->base.region); + + pixman_region32_init(&surface_overlap); + pixman_region32_subtract(&surface_overlap, &clipped_view, + &occluded_region); + occluded = !pixman_region32_not_empty(&surface_overlap); + if (occluded) { + pixman_region32_fini(&surface_overlap); + pixman_region32_fini(&clipped_view); + continue; + } + /* Since we process views from top to bottom, we know that if * the view intersects the calculated renderer region, it must * be part of, or occluded by, it, and cannot go on a plane. */ - pixman_region32_init(&surface_overlap); pixman_region32_intersect(&surface_overlap, &renderer_region, - &ev->transform.boundingbox); - + &clipped_view); if (pixman_region32_not_empty(&surface_overlap)) next_plane = primary; pixman_region32_fini(&surface_overlap); @@ -3312,6 +3329,9 @@ drm_output_propose_state(struct weston_output *output_base, if (next_plane == NULL) next_plane = drm_output_prepare_cursor_view(state, ev); + if (next_plane == NULL && !drm_view_is_opaque(ev)) + next_plane = primary; + if (next_plane == NULL) next_plane = drm_output_prepare_scanout_view(state, ev); @@ -3321,12 +3341,25 @@ drm_output_propose_state(struct weston_output *output_base, if (next_plane == NULL) next_plane = primary; + /* If we've been assigned to the 'primary' (renderer) plane, + * add this to our renderer region. If we have been assigned + * to the cursor plane, do nothing, as the cursor plane is + * blended with content underneath it. If neither, we have + * been assigned to an overlay plane, so add this view's + * area to the occluded region. */ if (next_plane == primary) pixman_region32_union(&renderer_region, &renderer_region, - &ev->transform.boundingbox); + &clipped_view); + else if (!output->cursor_plane || + next_plane != &output->cursor_plane->base) + pixman_region32_union(&occluded_region, + &occluded_region, + &clipped_view); + pixman_region32_fini(&clipped_view); } pixman_region32_fini(&renderer_region); + pixman_region32_fini(&occluded_region); return state; } From ddaf95c5a1f0caa88a7c9348ef7af9fd07d1e8d6 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Tue, 10 Jul 2018 11:47:15 +0900 Subject: [PATCH 0573/1642] libweston: Fix clear timing of output repainted flag Since the repaint status of the flushed output may be reset if a output repaint is failed, it is necessary to clear the repainted flag immediately after output repaint flush/cancel. Signed-off-by: Tomohito Esaki Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 516be96bd..9deb7817f 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2450,8 +2450,6 @@ weston_output_maybe_repaint(struct weston_output *output, struct timespec *now, int ret = 0; int64_t msec_to_repaint; - output->repainted = false; - /* We're not ready yet; come back to make a decision later. */ if (output->repaint_status != REPAINT_SCHEDULED) return ret; @@ -2563,6 +2561,9 @@ output_repaint_timer_handler(void *data) repaint_data); } + wl_list_for_each(output, &compositor->output_list, link) + output->repainted = false; + output_repaint_timer_arm(compositor); return 0; From a0f8276fe814957ea8ae46bd4f5fda3a9e1b8e69 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 10 Jul 2018 11:44:25 +0100 Subject: [PATCH 0574/1642] compositor-drm: Disallow overlapping overlay planes The scanout plane strictly stacks under all overlay planes, and the cursor plane above. However, the stacking of overlay planes with respect to each other is undefined. We can control the stacking order of overlay planes with the zpos property, though this significantly complicates plane assignment. In the meantime, simply disallow assigning a view to an overlay, when it overlaps another view which is already on an overlay. This ensures stacking order is irrelevant, since the planes never intersect each other. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 51b2482d9..2305f708f 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3324,6 +3324,16 @@ drm_output_propose_state(struct weston_output *output_base, &clipped_view); if (pixman_region32_not_empty(&surface_overlap)) next_plane = primary; + + /* We do not control the stacking order of overlay planes; + * the scanout plane is strictly stacked bottom and the cursor + * plane top, but the ordering of overlay planes with respect + * to each other is undefined. Make sure we do not have two + * planes overlapping each other. */ + pixman_region32_intersect(&surface_overlap, &occluded_region, + &clipped_view); + if (pixman_region32_not_empty(&surface_overlap)) + next_plane = primary; pixman_region32_fini(&surface_overlap); if (next_plane == NULL) From 44abfaaffde1c575e044dfd8df2db02fd8dde1b7 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 10 Jul 2018 14:31:06 +0100 Subject: [PATCH 0575/1642] compositor-drm: Use sprites_are_broken for scanout plane When the sprites_are_broken variable is set, do not attempt to promote client surfaces to the scanout plane. We are currently assuming that every client buffer will be compatible with the scanout plane, but that is not the case, particularly with more exotic tiled/compressed buffers. Once we promote the client buffer to scanout, there is no going back: if the repaint fails, we do not mark this as failed and go back to repaint through composition. This permanently removes the ability for scanout bypass when using the non-atomic path. Future patches lift the restriction when using atomic modesetting, as we can actually test and ensure that the view is compatible with scanout. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Reported-by: Pekka Paalanen --- libweston/compositor-drm.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 2305f708f..d045778aa 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1944,11 +1944,14 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, struct weston_view *ev) { struct drm_output *output = output_state->output; + struct drm_backend *b = to_drm_backend(output->base.compositor); struct drm_plane *scanout_plane = output->scanout_plane; struct drm_plane_state *state; struct drm_fb *fb; pixman_box32_t *extents; + assert(!b->sprites_are_broken); + /* Check the view spans exactly the output size, calculated in the * logical co-ordinate space. */ extents = pixman_region32_extents(&ev->transform.boundingbox); @@ -3004,8 +3007,7 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, struct drm_fb *fb; unsigned int i; - if (b->sprites_are_broken) - return NULL; + assert(!b->sprites_are_broken); fb = drm_fb_get_from_view(output_state, ev); if (!fb) @@ -3260,6 +3262,7 @@ drm_output_propose_state(struct weston_output *output_base, struct drm_pending_state *pending_state) { struct drm_output *output = to_drm_output(output_base); + struct drm_backend *b = to_drm_backend(output->base.compositor); struct drm_output_state *state; struct weston_view *ev; pixman_region32_t surface_overlap, renderer_region, occluded_region; @@ -3342,6 +3345,9 @@ drm_output_propose_state(struct weston_output *output_base, if (next_plane == NULL && !drm_view_is_opaque(ev)) next_plane = primary; + if (next_plane == NULL && b->sprites_are_broken) + next_plane = primary; + if (next_plane == NULL) next_plane = drm_output_prepare_scanout_view(state, ev); From f7a2f835ae83409dec1e0487424cec7718385fb6 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 8 Dec 2016 17:19:09 +0000 Subject: [PATCH 0576/1642] compositor-drm: Add modes to drm_output_propose_state Add support for multiple modes to drm_output_propose_state. Currently we intend to operate in three modes: planes-only (no renderer buffer, client buffers in planes only), mixed-mode (promote client buffers to planes where possible, falling back to the renderer where not), and renderer-only (no plane usage at all). We want to use the first (planes-only) mode where possible: it can avoid us having to allocate buffers for the renderer, and it also gives us the best chance of the optimal configuration, with no composition. In this mode, we walk the scene looking at all views, trying to put them in planes, and failing as soon as we find a view we cannot place in a plane. In the second mode, rather than failing, we assign those views which cannot be on a plane to the renderer, and allow the renderer to composite them. In the third mode, planes are not usable, so everything but the cursor goes to the renderer. We will use this when we cannot use the planes-only mode (because some views cannot be placed in planes), but also cannot use the 'mixed' mode because we have no renderer buffer yet. Since we walk the scene graph from top to bottom, using atomic modesetting we will determine if planes can be promoted in mixed mode by placing a renderer buffer at the bottom of the scene, placing a cursor buffer if applicable, then testing if we can add overlay planes to this mode. Without a buffer from the renderer, we cannot do these tests, so we push everything through the renderer and then switch to mixed mode on the next repaint. This patch implements the mixed and renderer-only modes (previously differentiated only by the sprites_are_broken flag), with the planes-only mode being left for a later patch. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 70 ++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index d045778aa..56e7e5d1f 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1939,6 +1939,11 @@ drm_output_assign_state(struct drm_output_state *state, } } +enum drm_output_propose_state_mode { + DRM_OUTPUT_PROPOSE_STATE_MIXED, /**< mix renderer & planes */ + DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY, /**< only assign to renderer & cursor */ +}; + static struct weston_plane * drm_output_prepare_scanout_view(struct drm_output_state *output_state, struct weston_view *ev) @@ -3121,10 +3126,9 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, struct wl_shm_buffer *shmbuf; bool needs_update = false; - if (!plane) - return NULL; + assert(!b->cursors_are_broken); - if (b->cursors_are_broken) + if (!plane) return NULL; if (!plane->state_cur->complete) @@ -3259,7 +3263,8 @@ drm_output_set_cursor(struct drm_output_state *output_state) static struct drm_output_state * drm_output_propose_state(struct weston_output *output_base, - struct drm_pending_state *pending_state) + struct drm_pending_state *pending_state, + enum drm_output_propose_state_mode mode) { struct drm_output *output = to_drm_output(output_base); struct drm_backend *b = to_drm_backend(output->base.compositor); @@ -3267,6 +3272,7 @@ drm_output_propose_state(struct weston_output *output_base, struct weston_view *ev; pixman_region32_t surface_overlap, renderer_region, occluded_region; struct weston_plane *primary = &output_base->compositor->primary_plane; + bool planes_ok = (mode != DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY); assert(!output->state_last); state = drm_output_state_duplicate(output->state_cur, @@ -3339,13 +3345,16 @@ drm_output_propose_state(struct weston_output *output_base, next_plane = primary; pixman_region32_fini(&surface_overlap); - if (next_plane == NULL) + /* The cursor plane is 'special' in the sense that we can still + * place it in the legacy API, and we gate that with a separate + * cursors_are_broken flag. */ + if (next_plane == NULL && !b->cursors_are_broken) next_plane = drm_output_prepare_cursor_view(state, ev); if (next_plane == NULL && !drm_view_is_opaque(ev)) next_plane = primary; - if (next_plane == NULL && b->sprites_are_broken) + if (next_plane == NULL && !planes_ok) next_plane = primary; if (next_plane == NULL) @@ -3354,24 +3363,27 @@ drm_output_propose_state(struct weston_output *output_base, if (next_plane == NULL) next_plane = drm_output_prepare_overlay_view(state, ev); - if (next_plane == NULL) - next_plane = primary; + if (next_plane && next_plane != primary) { + /* If we have been assigned to an overlay or scanout + * plane, add this area to the occluded region, so + * other views are known to be behind it. The cursor + * plane, however, is special, in that it blends with + * the content underneath it: the area should neither + * be added to the renderer region nor the occluded + * region. */ + if (!output->cursor_plane || + next_plane != &output->cursor_plane->base) { + pixman_region32_union(&occluded_region, + &occluded_region, + &clipped_view); + pixman_region32_fini(&clipped_view); + } + continue; + } - /* If we've been assigned to the 'primary' (renderer) plane, - * add this to our renderer region. If we have been assigned - * to the cursor plane, do nothing, as the cursor plane is - * blended with content underneath it. If neither, we have - * been assigned to an overlay plane, so add this view's - * area to the occluded region. */ - if (next_plane == primary) - pixman_region32_union(&renderer_region, - &renderer_region, - &clipped_view); - else if (!output->cursor_plane || - next_plane != &output->cursor_plane->base) - pixman_region32_union(&occluded_region, - &occluded_region, - &clipped_view); + pixman_region32_union(&renderer_region, + &renderer_region, + &clipped_view); pixman_region32_fini(&clipped_view); } pixman_region32_fini(&renderer_region); @@ -3386,12 +3398,20 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) struct drm_backend *b = to_drm_backend(output_base->compositor); struct drm_pending_state *pending_state = repaint_data; struct drm_output *output = to_drm_output(output_base); - struct drm_output_state *state; + struct drm_output_state *state = NULL; struct drm_plane_state *plane_state; struct weston_view *ev; struct weston_plane *primary = &output_base->compositor->primary_plane; - state = drm_output_propose_state(output_base, pending_state); + if (!b->sprites_are_broken) + state = drm_output_propose_state(output_base, pending_state, + DRM_OUTPUT_PROPOSE_STATE_MIXED); + + if (!state) + state = drm_output_propose_state(output_base, pending_state, + DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY); + + assert(state); wl_list_for_each(ev, &output_base->compositor->view_list, link) { struct drm_plane *target_plane = NULL; From f829062ef40cd12c457990c1d5e60b0c4b486d48 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 9 Dec 2016 17:32:10 +0000 Subject: [PATCH 0577/1642] compositor-drm: Return plane state from plane preparation Return a pointer to the plane state, rather than returning its underlying weston_plane. This eliminates any ambiguity between placing client buffers on planes, and placing them through the renderer. drm_output_propose_state is only concerned with preparing, testing, and returning DRM state objects. Assigning views to weston_planes only happens later, inside drm_assign_planes. This makes that split more clear. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 63 +++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 56e7e5d1f..4bf18581b 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1944,7 +1944,7 @@ enum drm_output_propose_state_mode { DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY, /**< only assign to renderer & cursor */ }; -static struct weston_plane * +static struct drm_plane_state * drm_output_prepare_scanout_view(struct drm_output_state *output_state, struct weston_view *ev) { @@ -2004,7 +2004,7 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, state->dest_h != (unsigned) output->base.current_mode->height) goto err; - return &scanout_plane->base; + return state; err: drm_plane_state_put_back(state); @@ -2170,7 +2170,6 @@ drm_output_apply_state_legacy(struct drm_output_state *state) struct drm_property_info *dpms_prop; struct drm_plane_state *scanout_state; struct drm_plane_state *ps; - struct drm_plane *p; struct drm_mode *mode; struct drm_head *head; uint32_t connectors[MAX_CLONED_CONNECTORS]; @@ -2197,7 +2196,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) if (state->dpms != WESTON_DPMS_ON) { wl_list_for_each(ps, &state->plane_list, link) { - p = ps->plane; + struct drm_plane *p = ps->plane; assert(ps->fb == NULL); assert(ps->output == NULL); @@ -2287,8 +2286,8 @@ drm_output_apply_state_legacy(struct drm_output_state *state) .request.type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT, .request.sequence = 1, }; + struct drm_plane *p = ps->plane; - p = ps->plane; if (p->type != WDRM_PLANE_TYPE_OVERLAY) continue; @@ -3000,7 +2999,7 @@ atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, } #endif -static struct weston_plane * +static struct drm_plane_state * drm_output_prepare_overlay_view(struct drm_output_state *output_state, struct weston_view *ev) { @@ -3071,7 +3070,7 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, state->src_h != state->dest_h << 16) goto err; - return &p->base; + return state; err: drm_plane_state_put_back(state); @@ -3115,7 +3114,7 @@ cursor_bo_update(struct drm_plane_state *plane_state, struct weston_view *ev) weston_log("failed update cursor: %m\n"); } -static struct weston_plane * +static struct drm_plane_state * drm_output_prepare_cursor_view(struct drm_output_state *output_state, struct weston_view *ev) { @@ -3201,7 +3200,7 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, plane_state->dest_w = b->cursor_width; plane_state->dest_h = b->cursor_height; - return &plane->base; + return plane_state; err: drm_plane_state_put_back(plane_state); @@ -3271,7 +3270,6 @@ drm_output_propose_state(struct weston_output *output_base, struct drm_output_state *state; struct weston_view *ev; pixman_region32_t surface_overlap, renderer_region, occluded_region; - struct weston_plane *primary = &output_base->compositor->primary_plane; bool planes_ok = (mode != DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY); assert(!output->state_last); @@ -3296,7 +3294,8 @@ drm_output_propose_state(struct weston_output *output_base, pixman_region32_init(&occluded_region); wl_list_for_each(ev, &output_base->compositor->view_list, link) { - struct weston_plane *next_plane = NULL; + struct drm_plane_state *ps = NULL; + bool force_renderer = false; pixman_region32_t clipped_view; bool occluded = false; @@ -3308,7 +3307,7 @@ drm_output_propose_state(struct weston_output *output_base, /* We only assign planes to views which are exclusively present * on our output. */ if (ev->output_mask != (1u << output->base.id)) - next_plane = primary; + force_renderer = true; /* Ignore views we know to be totally occluded. */ pixman_region32_init(&clipped_view); @@ -3332,7 +3331,7 @@ drm_output_propose_state(struct weston_output *output_base, pixman_region32_intersect(&surface_overlap, &renderer_region, &clipped_view); if (pixman_region32_not_empty(&surface_overlap)) - next_plane = primary; + force_renderer = true; /* We do not control the stacking order of overlay planes; * the scanout plane is strictly stacked bottom and the cursor @@ -3342,28 +3341,29 @@ drm_output_propose_state(struct weston_output *output_base, pixman_region32_intersect(&surface_overlap, &occluded_region, &clipped_view); if (pixman_region32_not_empty(&surface_overlap)) - next_plane = primary; + force_renderer = true; pixman_region32_fini(&surface_overlap); /* The cursor plane is 'special' in the sense that we can still * place it in the legacy API, and we gate that with a separate * cursors_are_broken flag. */ - if (next_plane == NULL && !b->cursors_are_broken) - next_plane = drm_output_prepare_cursor_view(state, ev); - - if (next_plane == NULL && !drm_view_is_opaque(ev)) - next_plane = primary; - - if (next_plane == NULL && !planes_ok) - next_plane = primary; - - if (next_plane == NULL) - next_plane = drm_output_prepare_scanout_view(state, ev); - - if (next_plane == NULL) - next_plane = drm_output_prepare_overlay_view(state, ev); - - if (next_plane && next_plane != primary) { + if (!force_renderer && !b->cursors_are_broken) + ps = drm_output_prepare_cursor_view(state, ev); + + /* If sprites are disabled or the view is not fully opaque, we + * must put the view into the renderer - unless it has already + * been placed in the cursor plane, which can handle alpha. */ + if (!ps && !planes_ok) + force_renderer = true; + if (!ps && !drm_view_is_opaque(ev)) + force_renderer = true; + + if (!ps && !force_renderer) + ps = drm_output_prepare_scanout_view(state, ev); + if (!ps && !force_renderer) + ps = drm_output_prepare_overlay_view(state, ev); + + if (ps) { /* If we have been assigned to an overlay or scanout * plane, add this area to the occluded region, so * other views are known to be behind it. The cursor @@ -3371,8 +3371,7 @@ drm_output_propose_state(struct weston_output *output_base, * the content underneath it: the area should neither * be added to the renderer region nor the occluded * region. */ - if (!output->cursor_plane || - next_plane != &output->cursor_plane->base) { + if (ps->plane->type != WDRM_PLANE_TYPE_CURSOR) { pixman_region32_union(&occluded_region, &occluded_region, &clipped_view); From bb6c19f7bb18cc400e11274184322ffe499e20ef Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 8 Dec 2016 17:27:17 +0000 Subject: [PATCH 0578/1642] compositor-drm: Add test-only mode to state application The atomic API can allow us to test state before we apply it, to see if it will be valid. Use this when we construct a plane configuration, to see if it has a chance of ever working. If not, we can fail assign_planes early. This will be used in later patches to incrementally build state by proposing and testing potential configurations one at a time. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 4bf18581b..d86584883 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -260,6 +260,7 @@ enum drm_output_state_duplicate_mode { enum drm_state_apply_mode { DRM_STATE_APPLY_SYNC, /**< state fully processed */ DRM_STATE_APPLY_ASYNC, /**< state pending event delivery */ + DRM_STATE_TEST_ONLY, /**< test if the state can be applied */ }; struct drm_backend { @@ -1822,6 +1823,7 @@ drm_pending_state_get_output(struct drm_pending_state *pending_state, } static int drm_pending_state_apply_sync(struct drm_pending_state *state); +static int drm_pending_state_test(struct drm_pending_state *state); /** * Mark a drm_output_state (the output's last state) as complete. This handles @@ -2610,9 +2612,20 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, case DRM_STATE_APPLY_ASYNC: flags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; break; + case DRM_STATE_TEST_ONLY: + flags |= DRM_MODE_ATOMIC_TEST_ONLY; + break; } ret = drmModeAtomicCommit(b->drm.fd, req, flags, b); + + /* Test commits do not take ownership of the state; return + * without freeing here. */ + if (mode == DRM_STATE_TEST_ONLY) { + drmModeAtomicFree(req); + return ret; + } + if (ret != 0) { weston_log("atomic: couldn't commit new state: %m\n"); goto out; @@ -2633,6 +2646,40 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, } #endif +/** + * Tests a pending state, to see if the kernel will accept the update as + * constructed. + * + * Using atomic modesetting, the kernel performs the same checks as it would + * on a real commit, returning success or failure without actually modifying + * the running state. It does not return -EBUSY if there are pending updates + * in flight, so states may be tested at any point, however this means a + * state which passed testing may fail on a real commit if the timing is not + * respected (e.g. committing before the previous commit has completed). + * + * Without atomic modesetting, we have no way to check, so we optimistically + * claim it will work. + * + * Unlike drm_pending_state_apply() and drm_pending_state_apply_sync(), this + * function does _not_ take ownership of pending_state, nor does it clear + * state_invalid. + */ +static int +drm_pending_state_test(struct drm_pending_state *pending_state) +{ +#ifdef HAVE_DRM_ATOMIC + struct drm_backend *b = pending_state->backend; + + if (b->atomic_modeset) + return drm_pending_state_apply_atomic(pending_state, + DRM_STATE_TEST_ONLY); +#endif + + /* We have no way to test state before application on the legacy + * modesetting API, so just claim it succeeded. */ + return 0; +} + /** * Applies all of a pending_state asynchronously: the primary entry point for * applying KMS state to a device. Updates the state for all outputs in the @@ -3271,6 +3318,7 @@ drm_output_propose_state(struct weston_output *output_base, struct weston_view *ev; pixman_region32_t surface_overlap, renderer_region, occluded_region; bool planes_ok = (mode != DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY); + int ret; assert(!output->state_last); state = drm_output_state_duplicate(output->state_cur, @@ -3388,7 +3436,16 @@ drm_output_propose_state(struct weston_output *output_base, pixman_region32_fini(&renderer_region); pixman_region32_fini(&occluded_region); + /* Check to see if this state will actually work. */ + ret = drm_pending_state_test(state->pending_state); + if (ret != 0) + goto err; + return state; + +err: + drm_output_state_free(state); + return NULL; } static void From ca6fbe3c93ff05bf006e19ac8e60f896a169b5a1 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 10 Jul 2018 18:08:12 +0100 Subject: [PATCH 0579/1642] compositor-drm: Never lift solid surfaces to planes This will never work, so don't even try to do it. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index d86584883..e629779f4 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3357,6 +3357,9 @@ drm_output_propose_state(struct weston_output *output_base, if (ev->output_mask != (1u << output->base.id)) force_renderer = true; + if (!ev->surface->buffer_ref.buffer) + force_renderer = true; + /* Ignore views we know to be totally occluded. */ pixman_region32_init(&clipped_view); pixman_region32_intersect(&clipped_view, From d12e51646aae32a689af5785d0f81f58c8fa901c Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 10 Jul 2018 18:19:37 +0100 Subject: [PATCH 0580/1642] compositor-drm: Add planes-only mode to state proposal Add a new mode, which attempts to construct a scene exclusively using planes. This is a building block for incrementally testing and constructing state: in the plane-only mode, we test the state exactly once, when we have constructed a full set of planes and want to know if it works or not. When using the renderer, we need to incrementally test views one by one to see if they will work on planes, falling back to the renderer if not. This test is different, since the scanout plane will be occupied by the renderer's buffer. Testing using the renderer or client buffers may have completely different characteristics, so we need two passes: first, constructing a state with only planes and testing if that succeeds, falling back later to a mixed renderer/plane mode which tests incrementally. This implements the first mode, and preferentially attempts to use it. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index e629779f4..486fb71d9 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1944,6 +1944,7 @@ drm_output_assign_state(struct drm_output_state *state, enum drm_output_propose_state_mode { DRM_OUTPUT_PROPOSE_STATE_MIXED, /**< mix renderer & planes */ DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY, /**< only assign to renderer & cursor */ + DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY, /**< no renderer use, only planes */ }; static struct drm_plane_state * @@ -3318,6 +3319,7 @@ drm_output_propose_state(struct weston_output *output_base, struct weston_view *ev; pixman_region32_t surface_overlap, renderer_region, occluded_region; bool planes_ok = (mode != DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY); + bool renderer_ok = (mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); int ret; assert(!output->state_last); @@ -3431,6 +3433,14 @@ drm_output_propose_state(struct weston_output *output_base, continue; } + /* We have been assigned to the primary (renderer) plane: + * check if this is OK, and add ourselves to the renderer + * region if so. */ + if (!renderer_ok) { + pixman_region32_fini(&clipped_view); + goto err_region; + } + pixman_region32_union(&renderer_region, &renderer_region, &clipped_view); @@ -3446,6 +3456,9 @@ drm_output_propose_state(struct weston_output *output_base, return state; +err_region: + pixman_region32_fini(&renderer_region); + pixman_region32_fini(&occluded_region); err: drm_output_state_free(state); return NULL; @@ -3462,9 +3475,13 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) struct weston_view *ev; struct weston_plane *primary = &output_base->compositor->primary_plane; - if (!b->sprites_are_broken) + if (!b->sprites_are_broken) { state = drm_output_propose_state(output_base, pending_state, - DRM_OUTPUT_PROPOSE_STATE_MIXED); + DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); + if (!state) + state = drm_output_propose_state(output_base, pending_state, + DRM_OUTPUT_PROPOSE_STATE_MIXED); + } if (!state) state = drm_output_propose_state(output_base, pending_state, From a284d271f134eb805f799a9861c662d895bf0b11 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 10 Jul 2018 18:40:12 +0100 Subject: [PATCH 0581/1642] compositor-drm: Incrementally test plane states in mixed mode In the plane-only mode, we try to place every view on a hardware plane, and fail if we can't do this. This requires a full walk of the scene graph to come up with a complete configuration in order to be able to test. In mixed mode, we know at least some visible views will fail to be promoted to planes and must be composited via the renderer. In order to still use some planes where possible, we use atomic modesetting's test-only mode to incrementally test configurations. We know that the renderer output will always be visible, and because it is the renderer, that it will be occupying the scanout plane underneath everything else. The actual renderer buffer doesn't materialise until after assign_planes, because it cannot know what to render until then. However, in order to test whether a configuration is valid, we need the renderer buffer in the scanout plane. For testing, we fake this by temporarily stealing the old buffer - if it seems sufficiently compatible - and placing it in the state we construct. This is used to test whether or not a renderer buffer will work with the addition of overlay planes. Doing this incremental testing will allow us to enable plane usage for atomic by default, since we know ahead of time that our chosen plane configuration will work. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 123 +++++++++++++++++++++++++++---------- 1 file changed, 91 insertions(+), 32 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 486fb71d9..3e70265c3 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1949,7 +1949,8 @@ enum drm_output_propose_state_mode { static struct drm_plane_state * drm_output_prepare_scanout_view(struct drm_output_state *output_state, - struct weston_view *ev) + struct weston_view *ev, + enum drm_output_propose_state_mode mode) { struct drm_output *output = output_state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); @@ -1959,6 +1960,7 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, pixman_box32_t *extents; assert(!b->sprites_are_broken); + assert(mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); /* Check the view spans exactly the output size, calculated in the * logical co-ordinate space. */ @@ -1983,15 +1985,13 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, } state = drm_output_state_get_plane(output_state, scanout_plane); - if (state->fb) { - /* If there is already a framebuffer on the scanout plane, - * a client view has already been placed on the scanout - * view. In that case, do not free or put back the state, - * but just leave it in place and quietly exit. */ - drm_fb_unref(fb); - return NULL; - } + /* The only way we can already have a buffer in the scanout plane is + * if we are in mixed mode, or if a client buffer has already been + * placed into scanout. The former case will never call into here, + * and in the latter case, the view must have been marked as occluded, + * meaning we should never have ended up here. */ + assert(!state->fb); state->fb = fb; state->ev = ev; state->output = output; @@ -2007,6 +2007,8 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, state->dest_h != (unsigned) output->base.current_mode->height) goto err; + /* In plane-only mode, we don't need to test the state now, as we + * will only test it once at the end. */ return state; err: @@ -3049,7 +3051,8 @@ atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, static struct drm_plane_state * drm_output_prepare_overlay_view(struct drm_output_state *output_state, - struct weston_view *ev) + struct weston_view *ev, + enum drm_output_propose_state_mode mode) { struct drm_output *output = output_state->output; struct weston_compositor *ec = output->base.compositor; @@ -3058,6 +3061,7 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, struct drm_plane_state *state = NULL; struct drm_fb *fb; unsigned int i; + int ret; assert(!b->sprites_are_broken); @@ -3098,31 +3102,42 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, continue; } - break; - } + state->ev = ev; + state->output = output; + if (!drm_plane_state_coords_for_view(state, ev)) { + drm_plane_state_put_back(state); + state = NULL; + continue; + } + if (state->src_w != state->dest_w << 16 || + state->src_h != state->dest_h << 16) { + drm_plane_state_put_back(state); + state = NULL; + continue; + } - /* No sprites available */ - if (!state) { - drm_fb_unref(fb); - return NULL; - } + /* We hold one reference for the lifetime of this function; + * from calling drm_fb_get_from_view, to the out label where + * we unconditionally drop the reference. So, we take another + * reference here to live within the state. */ + state->fb = drm_fb_ref(fb); - state->fb = fb; - state->ev = ev; - state->output = output; + /* In planes-only mode, we don't have an incremental state to + * test against, so we just hope it'll work. */ + if (mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) + goto out; - if (!drm_plane_state_coords_for_view(state, ev)) - goto err; + ret = drm_pending_state_test(output_state->pending_state); + if (ret == 0) + goto out; - if (state->src_w != state->dest_w << 16 || - state->src_h != state->dest_h << 16) - goto err; + drm_plane_state_put_back(state); + state = NULL; + } +out: + drm_fb_unref(fb); return state; - -err: - drm_plane_state_put_back(state); - return NULL; } /** @@ -3316,6 +3331,7 @@ drm_output_propose_state(struct weston_output *output_base, struct drm_output *output = to_drm_output(output_base); struct drm_backend *b = to_drm_backend(output->base.compositor); struct drm_output_state *state; + struct drm_plane_state *scanout_state = NULL; struct weston_view *ev; pixman_region32_t surface_overlap, renderer_region, occluded_region; bool planes_ok = (mode != DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY); @@ -3327,6 +3343,35 @@ drm_output_propose_state(struct weston_output *output_base, pending_state, DRM_OUTPUT_STATE_CLEAR_PLANES); + /* We implement mixed mode by progressively creating and testing + * incremental states, of scanout + overlay + cursor. Since we + * walk our views top to bottom, the scanout plane is last, however + * we always need it in our scene for the test modeset to be + * meaningful. To do this, we steal a reference to the last + * renderer framebuffer we have, if we think it's basically + * compatible. If we don't have that, then we conservatively fall + * back to only using the renderer for this repaint. */ + if (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED) { + struct drm_plane *plane = output->scanout_plane; + struct drm_fb *scanout_fb = plane->state_cur->fb; + + if (!scanout_fb || + (scanout_fb->type != BUFFER_GBM_SURFACE && + scanout_fb->type != BUFFER_PIXMAN_DUMB)) { + drm_output_state_free(state); + return NULL; + } + + if (scanout_fb->width != output_base->current_mode->width || + scanout_fb->height != output_base->current_mode->height) { + drm_output_state_free(state); + return NULL; + } + + scanout_state = drm_plane_state_duplicate(state, + plane->state_cur); + } + /* * Find a surface for each sprite in the output using some heuristics: * 1) size @@ -3411,10 +3456,14 @@ drm_output_propose_state(struct weston_output *output_base, if (!ps && !drm_view_is_opaque(ev)) force_renderer = true; + /* Only try to place scanout surfaces in planes-only mode; in + * mixed mode, we have already failed to place a view on the + * scanout surface, forcing usage of the renderer on the + * scanout plane. */ + if (!ps && !force_renderer && !renderer_ok) + ps = drm_output_prepare_scanout_view(state, ev, mode); if (!ps && !force_renderer) - ps = drm_output_prepare_scanout_view(state, ev); - if (!ps && !force_renderer) - ps = drm_output_prepare_overlay_view(state, ev); + ps = drm_output_prepare_overlay_view(state, ev, mode); if (ps) { /* If we have been assigned to an overlay or scanout @@ -3454,6 +3503,16 @@ drm_output_propose_state(struct weston_output *output_base, if (ret != 0) goto err; + /* Counterpart to duplicating scanout state at the top of this + * function: if we have taken a renderer framebuffer and placed it in + * the pending state in order to incrementally test overlay planes, + * remove it now. */ + if (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED) { + assert(scanout_state->fb->type == BUFFER_GBM_SURFACE || + scanout_state->fb->type == BUFFER_PIXMAN_DUMB); + drm_plane_state_put_back(scanout_state); + } + return state; err_region: From b41abf9c8422b9a6f4236cfc42f8367b297acf3b Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Wed, 11 Jul 2018 13:03:31 +0100 Subject: [PATCH 0582/1642] compositor-drm: Allow scanout plane to be occluded by overlay a0f8276fe814 ("compositor-drm: Disallow overlapping overlay planes") was a little too pessimistic in rejecting occluded views. Whilst it correctly prevented overlay planes from occluding each other, it also prevented overlay planes from occluding the scanout plane. This is undesirable: the primary/scanout plane is specified to stack strictly below all overlay planes, so there is no need to reject a plane from consideration for scanout due to being occluded by an overlay plane. Shift the check downwards so it only applies to overlay rather than scanout planes. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 3e70265c3..a99ac8eae 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3392,7 +3392,8 @@ drm_output_propose_state(struct weston_output *output_base, struct drm_plane_state *ps = NULL; bool force_renderer = false; pixman_region32_t clipped_view; - bool occluded = false; + bool totally_occluded = false; + bool overlay_occluded = false; /* If this view doesn't touch our output at all, there's no * reason to do anything with it. */ @@ -3416,8 +3417,8 @@ drm_output_propose_state(struct weston_output *output_base, pixman_region32_init(&surface_overlap); pixman_region32_subtract(&surface_overlap, &clipped_view, &occluded_region); - occluded = !pixman_region32_not_empty(&surface_overlap); - if (occluded) { + totally_occluded = !pixman_region32_not_empty(&surface_overlap); + if (totally_occluded) { pixman_region32_fini(&surface_overlap); pixman_region32_fini(&clipped_view); continue; @@ -3439,13 +3440,13 @@ drm_output_propose_state(struct weston_output *output_base, pixman_region32_intersect(&surface_overlap, &occluded_region, &clipped_view); if (pixman_region32_not_empty(&surface_overlap)) - force_renderer = true; + overlay_occluded = true; pixman_region32_fini(&surface_overlap); /* The cursor plane is 'special' in the sense that we can still * place it in the legacy API, and we gate that with a separate * cursors_are_broken flag. */ - if (!force_renderer && !b->cursors_are_broken) + if (!force_renderer && !overlay_occluded && !b->cursors_are_broken) ps = drm_output_prepare_cursor_view(state, ev); /* If sprites are disabled or the view is not fully opaque, we @@ -3462,7 +3463,8 @@ drm_output_propose_state(struct weston_output *output_base, * scanout plane. */ if (!ps && !force_renderer && !renderer_ok) ps = drm_output_prepare_scanout_view(state, ev, mode); - if (!ps && !force_renderer) + + if (!ps && !overlay_occluded && !force_renderer) ps = drm_output_prepare_overlay_view(state, ev, mode); if (ps) { From 9fe4bf88630090af8f4239418681ffb9a883e0e8 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 9 Dec 2016 18:23:22 +0000 Subject: [PATCH 0583/1642] compositor-drm: Relax plane restrictions for atomic Since we now incrementally test atomic state as we build it, we can loosen restrictions on what we can do with planes, and let the kernel tell us whether or not it's OK. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index a99ac8eae..363354f9c 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1979,7 +1979,7 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, return NULL; /* Can't change formats with just a pageflip */ - if (fb->format->format != output->gbm_format) { + if (!b->atomic_modeset && fb->format->format != output->gbm_format) { drm_fb_unref(fb); return NULL; } @@ -1998,15 +1998,18 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, if (!drm_plane_state_coords_for_view(state, ev)) goto err; - /* The legacy API does not let us perform cropping or scaling. */ - if (state->src_x != 0 || state->src_y != 0 || - state->src_w != state->dest_w << 16 || - state->src_h != state->dest_h << 16 || - state->dest_x != 0 || state->dest_y != 0 || + if (state->dest_x != 0 || state->dest_y != 0 || state->dest_w != (unsigned) output->base.current_mode->width || state->dest_h != (unsigned) output->base.current_mode->height) goto err; + /* The legacy API does not let us perform cropping or scaling. */ + if (!b->atomic_modeset && + (state->src_x != 0 || state->src_y != 0 || + state->src_w != state->dest_w << 16 || + state->src_h != state->dest_h << 16)) + goto err; + /* In plane-only mode, we don't need to test the state now, as we * will only test it once at the end. */ return state; @@ -3109,8 +3112,9 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, state = NULL; continue; } - if (state->src_w != state->dest_w << 16 || - state->src_h != state->dest_h << 16) { + if (!b->atomic_modeset && + (state->src_w != state->dest_w << 16 || + state->src_h != state->dest_h << 16)) { drm_plane_state_put_back(state); state = NULL; continue; From 678aabe829371d48af00e718f4a41185a3d50f1f Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 9 Dec 2016 16:00:12 +0000 Subject: [PATCH 0584/1642] compositor-drm: Enable planes for atomic Now that we can sensibly test proposed plane configurations with atomic, sprites are not broken. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 363354f9c..95b379740 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3834,6 +3834,17 @@ init_kms_caps(struct drm_backend *b) weston_log("DRM: %s atomic modesetting\n", b->atomic_modeset ? "supports" : "does not support"); + /* + * KMS support for hardware planes cannot properly synchronize + * without nuclear page flip. Without nuclear/atomic, hw plane + * and cursor plane updates would either tear or cause extra + * waits for vblanks which means dropping the compositor framerate + * to a fraction. For cursors, it's not so bad, so they are + * enabled. + */ + if (!b->atomic_modeset) + b->sprites_are_broken = 1; + ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ASPECT_RATIO, 1); b->aspect_ratio_supported = (ret == 0); weston_log("DRM: %s picture aspect ratio\n", @@ -6714,17 +6725,6 @@ drm_backend_create(struct weston_compositor *compositor, b->drm.fd = -1; wl_array_init(&b->unused_crtcs); - /* - * KMS support for hardware planes cannot properly synchronize - * without nuclear page flip. Without nuclear/atomic, hw plane - * and cursor plane updates would either tear or cause extra - * waits for vblanks which means dropping the compositor framerate - * to a fraction. For cursors, it's not so bad, so they are - * enabled. - * - * These can be enabled again when nuclear/atomic support lands. - */ - b->sprites_are_broken = 1; b->compositor = compositor; b->use_pixman = config->use_pixman; b->pageflip_timeout = config->pageflip_timeout; From 1166f8e9a13f151a1dcab3cae081013324f0538e Mon Sep 17 00:00:00 2001 From: Emilio Pozuelo Monfort Date: Thu, 12 Jul 2018 13:46:23 +0200 Subject: [PATCH 0585/1642] simple-dmabuf-drm: require zwp_linux_dmabuf_v1 v3 We effectively require it as we don't react to dmabuf_format, only to dmabuf_modifiers, so there's a chance we may not get the supported formats information at all. Signed-off-by: Emilio Pozuelo Monfort Reviewed-by: Daniel Stone --- clients/simple-dmabuf-drm.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index fcab30e5c..0536b5222 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -85,7 +85,6 @@ struct display { int nv12_format_found; uint64_t nv12_modifier; int req_dmabuf_immediate; - int req_dmabuf_modifiers; }; struct drm_device { @@ -821,16 +820,10 @@ registry_handle_global(void *data, struct wl_registry *registry, d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { - int ver; - if (d->req_dmabuf_modifiers) - ver = 3; - else if (d->req_dmabuf_immediate) - ver = 2; - else - ver = 1; + if (version < 3) + return; d->dmabuf = wl_registry_bind(registry, - id, &zwp_linux_dmabuf_v1_interface, - ver); + id, &zwp_linux_dmabuf_v1_interface, 3); zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); } } @@ -861,7 +854,6 @@ create_display(int opts, int format) assert(display->display); display->req_dmabuf_immediate = opts & OPT_IMMEDIATE; - display->req_dmabuf_modifiers = (format == DRM_FORMAT_NV12); /* * hard code format if the platform egl doesn't support format From cce820dcafb4f04e6c53904c3a643ef534b31915 Mon Sep 17 00:00:00 2001 From: Emilio Pozuelo Monfort Date: Thu, 12 Jul 2018 13:46:24 +0200 Subject: [PATCH 0586/1642] simple-dmabuf-drm: fix build with --disable-egl Just rely on getting the supported formats through the dmabuf extension. Signed-off-by: Emilio Pozuelo Monfort Reviewed-by: Daniel Stone --- clients/simple-dmabuf-drm.c | 11 ----------- configure.ac | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 0536b5222..bd0f92240 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -57,7 +57,6 @@ #include #include "shared/zalloc.h" -#include "shared/platform.h" #include "xdg-shell-unstable-v6-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" @@ -843,7 +842,6 @@ static struct display * create_display(int opts, int format) { struct display *display; - const char *extensions; display = malloc(sizeof *display); if (display == NULL) { @@ -855,15 +853,6 @@ create_display(int opts, int format) display->req_dmabuf_immediate = opts & OPT_IMMEDIATE; - /* - * hard code format if the platform egl doesn't support format - * querying / advertising. - */ - extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - if (extensions && !weston_check_egl_extension(extensions, - "EGL_EXT_image_dma_buf_import_modifiers")) - display->xrgb8888_format_found = 1; - display->registry = wl_display_get_registry(display->display); wl_registry_add_listener(display->registry, ®istry_listener, display); diff --git a/configure.ac b/configure.ac index 357b6471e..6f9ad4343 100644 --- a/configure.ac +++ b/configure.ac @@ -400,7 +400,7 @@ AC_ARG_ENABLE(simple-dmabuf-drm-client, [do not build the simple dmabuf drm client]),, enable_simple_dmabuf_drm_client="auto") if ! test "x$enable_simple_dmabuf_drm_client" = "xno"; then - PKG_CHECK_MODULES(SIMPLE_DMABUF_DRM_CLIENT, [wayland-client libdrm egl], [have_simple_dmabuf_libs=yes], + PKG_CHECK_MODULES(SIMPLE_DMABUF_DRM_CLIENT, [wayland-client libdrm], [have_simple_dmabuf_libs=yes], [have_simple_dmabuf_libs=no]) PKG_CHECK_MODULES(LIBDRM_PLATFORM_FREEDRENO, [libdrm_freedreno], From 280b730144cafc58dc78ac60b3a3dbe61b6b6b50 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 13 Jul 2018 11:32:29 -0500 Subject: [PATCH 0587/1642] configure.ac: bump to version 4.0.91 for the alpha release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 6f9ad4343..093d6b549 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [4]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [90]) +m4_define([weston_micro_version], [91]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [5]) From cf4113c629751adcbb9904087932e12b5621219b Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Tue, 5 Jun 2018 10:23:00 +0200 Subject: [PATCH 0588/1642] ivi-shell: listen compositor wake_signal If compositor wakes up from sleep state, we have to trigger repaint for all outputs. Signed-off-by: Emre Ucan Reviewed-by: Daniel Stone --- ivi-shell/ivi-shell.c | 16 ++++++++++++++++ ivi-shell/ivi-shell.h | 1 + 2 files changed, 17 insertions(+) diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 51e13a0f5..0235d266f 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -361,6 +361,8 @@ shell_destroy(struct wl_listener *listener, void *data) text_backend_destroy(shell->text_backend); input_panel_destroy(shell); + wl_list_remove(&shell->wake_listener.link); + wl_list_for_each_safe(ivisurf, next, &shell->ivi_surface_list, link) { wl_list_remove(&ivisurf->link); free(ivisurf); @@ -369,6 +371,17 @@ shell_destroy(struct wl_listener *listener, void *data) free(shell); } +/* + * Called through the compositor's wake signal. + */ +static void +wake_handler(struct wl_listener *listener, void *data) +{ + struct weston_compositor *compositor = data; + + weston_compositor_damage_all(compositor); +} + static void terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) @@ -480,6 +493,9 @@ wet_shell_init(struct weston_compositor *compositor, shell->destroy_listener.notify = shell_destroy; wl_signal_add(&compositor->destroy_signal, &shell->destroy_listener); + shell->wake_listener.notify = wake_handler; + wl_signal_add(&compositor->wake_signal, &shell->wake_listener); + if (input_panel_setup(shell) < 0) goto out; diff --git a/ivi-shell/ivi-shell.h b/ivi-shell/ivi-shell.h index e35f75f23..2c0064d16 100644 --- a/ivi-shell/ivi-shell.h +++ b/ivi-shell/ivi-shell.h @@ -34,6 +34,7 @@ struct ivi_shell { struct wl_listener destroy_listener; + struct wl_listener wake_listener; struct weston_compositor *compositor; From 67546bed04a464d18508265976f2a4b673db565c Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Thu, 24 May 2018 17:08:47 +0200 Subject: [PATCH 0589/1642] ivi-shell: use install paths in example config The example weston.ini file uses source and build directory paths. Therefore, it is only useful when used on the same system that is used to build Weston. We can use install paths instead of build/source paths to fix this problem. v2 changes: - use $(westondatadir) instead of $(datadir) Reported-by: Michael Tretter Signed-off-by: Emre Ucan Reviewed-by: Michael Tretter Reviewed-by: Emil Velikov Reviewed-by: Daniel Stone --- Makefile.am | 4 +-- ivi-shell/weston.ini.in | 54 ++++++++++++++++++++--------------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/Makefile.am b/Makefile.am index 3bce47a11..637dd239a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,10 +24,8 @@ weston.ini : $(srcdir)/weston.ini.in ivi-shell/weston.ini : $(srcdir)/ivi-shell/weston.ini.in $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(SED) \ -e 's|@bindir[@]|$(bindir)|g' \ - -e 's|@abs_top_builddir[@]|$(abs_top_builddir)|g' \ - -e 's|@abs_top_srcdir[@]|$(abs_top_srcdir)|g' \ -e 's|@libexecdir[@]|$(libexecdir)|g' \ - -e 's|@plugin_prefix[@]||g' \ + -e 's|@westondatadir[@]|$(westondatadir)|g' \ $< > $@ all-local : weston.ini ivi-shell/weston.ini diff --git a/ivi-shell/weston.ini.in b/ivi-shell/weston.ini.in index a06d76ef2..ae3ac5193 100644 --- a/ivi-shell/weston.ini.in +++ b/ivi-shell/weston.ini.in @@ -19,20 +19,20 @@ application-layer-id=4000 transition-duration=300 -background-image=@abs_top_srcdir@/data/background.png +background-image=@westondatadir@/background.png background-id=1001 -panel-image=@abs_top_srcdir@/data/panel.png +panel-image=@westondatadir@/panel.png panel-id=1002 surface-id-offset=10 -tiling-image=@abs_top_srcdir@/data/tiling.png +tiling-image=@westondatadir@/tiling.png tiling-id=1003 -sidebyside-image=@abs_top_srcdir@/data/sidebyside.png +sidebyside-image=@westondatadir@/sidebyside.png sidebyside-id=1004 -fullscreen-image=@abs_top_srcdir@/data/fullscreen.png +fullscreen-image=@westondatadir@/fullscreen.png fullscreen-id=1005 -random-image=@abs_top_srcdir@/data/random.png +random-image=@westondatadir@/random.png random-id=1006 -home-image=@abs_top_srcdir@/data/home.png +home-image=@westondatadir@/home.png home-id=1007 workspace-background-color=0x99000000 workspace-background-id=2001 @@ -43,59 +43,59 @@ path=weston-keyboard [ivi-launcher] workspace-id=0 icon-id=4001 -icon=@abs_top_srcdir@/data/icon_ivi_flower.png -path=@abs_top_builddir@/weston-flower +icon=@westondatadir@/icon_ivi_flower.png +path=@bindir@/weston-flower [ivi-launcher] workspace-id=0 icon-id=4002 -icon=@abs_top_srcdir@/data/icon_ivi_clickdot.png -path=@abs_top_builddir@/weston-clickdot +icon=@westondatadir@/icon_ivi_clickdot.png +path=@bindir@/weston-clickdot [ivi-launcher] workspace-id=1 icon-id=4003 -icon=@abs_top_srcdir@/data/icon_ivi_simple-egl.png -path=@abs_top_builddir@/weston-simple-egl +icon=@westondatadir@/icon_ivi_simple-egl.png +path=@bindir@/weston-simple-egl [ivi-launcher] workspace-id=1 icon-id=4004 -icon=@abs_top_srcdir@/data/icon_ivi_simple-shm.png -path=@abs_top_builddir@/weston-simple-shm +icon=@westondatadir@/icon_ivi_simple-shm.png +path=@bindir@/weston-simple-shm [ivi-launcher] workspace-id=2 icon-id=4005 -icon=@abs_top_srcdir@/data/icon_ivi_smoke.png -path=@abs_top_builddir@/weston-smoke +icon=@westondatadir@/icon_ivi_smoke.png +path=@bindir@/weston-smoke [ivi-launcher] workspace-id=3 icon-id=4006 -icon=@abs_top_srcdir@/data/icon_ivi_flower.png -path=@abs_top_builddir@/weston-flower +icon=@westondatadir@/icon_ivi_flower.png +path=@bindir@/weston-flower [ivi-launcher] workspace-id=3 icon-id=4007 -icon=@abs_top_srcdir@/data/icon_ivi_clickdot.png -path=@abs_top_builddir@/weston-clickdot +icon=@westondatadir@/icon_ivi_clickdot.png +path=@bindir@/weston-clickdot [ivi-launcher] workspace-id=3 icon-id=4008 -icon=@abs_top_srcdir@/data/icon_ivi_simple-egl.png -path=@abs_top_builddir@/weston-simple-egl +icon=@westondatadir@/icon_ivi_simple-egl.png +path=@bindir@/weston-simple-egl [ivi-launcher] workspace-id=3 icon-id=4009 -icon=@abs_top_srcdir@/data/icon_ivi_simple-shm.png -path=@abs_top_builddir@/weston-simple-shm +icon=@westondatadir@/icon_ivi_simple-shm.png +path=@bindir@/weston-simple-shm [ivi-launcher] workspace-id=3 icon-id=4010 -icon=@abs_top_srcdir@/data/icon_ivi_smoke.png -path=@abs_top_builddir@/weston-smoke +icon=@westondatadir@/icon_ivi_smoke.png +path=@bindir@/weston-smoke From 3ea5437dbd07d9a94aebbb651d8f8eeacc8765bd Mon Sep 17 00:00:00 2001 From: Greg V Date: Sun, 22 Jul 2018 11:36:21 +0100 Subject: [PATCH 0590/1642] xwayland/selection: do not remove NULL property_source Happened mostly with neovim's xclip usage. [daniels: Added more cases.] Reviewed-by: Daniel Stone --- xwayland/selection.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/xwayland/selection.c b/xwayland/selection.c index a75557044..59702246d 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -370,7 +370,8 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) if (len == -1) { weston_log("read error from data source: %m\n"); weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); - wl_event_source_remove(wm->property_source); + if (wm->property_source) + wl_event_source_remove(wm->property_source); wm->property_source = NULL; close(fd); wl_array_release(&wm->source_data); @@ -394,7 +395,8 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) 1, &incr_chunk_size); wm->selection_property_set = 1; wm->flush_property_on_delete = 1; - wl_event_source_remove(wm->property_source); + if (wm->property_source) + wl_event_source_remove(wm->property_source); wm->property_source = NULL; weston_wm_send_selection_notify(wm, wm->selection_request.property); } else if (wm->selection_property_set) { @@ -402,7 +404,8 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) "property delete\n", wm->source_data.size); wm->flush_property_on_delete = 1; - wl_event_source_remove(wm->property_source); + if (wm->property_source) + wl_event_source_remove(wm->property_source); wm->property_source = NULL; } else { weston_log("got %zu bytes, " @@ -416,7 +419,8 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) weston_wm_flush_source_data(wm); weston_wm_send_selection_notify(wm, wm->selection_request.property); xcb_flush(wm->conn); - wl_event_source_remove(wm->property_source); + if (wm->property_source) + wl_event_source_remove(wm->property_source); wm->property_source = NULL; close(fd); wl_array_release(&wm->source_data); @@ -435,7 +439,8 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) weston_wm_flush_source_data(wm); } xcb_flush(wm->conn); - wl_event_source_remove(wm->property_source); + if (wm->property_source) + wl_event_source_remove(wm->property_source); wm->property_source = NULL; close(wm->data_source_fd); wm->data_source_fd = -1; From 8c9556c57d0f907e461e2c4ccf01155f0a7b1244 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 20 Jul 2018 11:06:19 +0100 Subject: [PATCH 0591/1642] compositor-drm: Remove unnecessary libdrm defines The backend begins with a series of #defines of libdrm tokens, in case the libdrm we build against is too old. Commit efdebbc4e82b ("configure.ac: bump libdrm requirement to 2.4.68") did what it said on the box; since we now depend on a relatively modern libdrm, we can get rid of most of our compatibility defines. DRM_CAP_TIMESTAMP_MONOTONIC was added in libdrm 2.4.47 (f8f1f6e37ae2). DRM_CLIENT_CAP_UNIVERSAL_PLANES was added in libdrm 2.4.55 (8fc62ca8ac01). DRM_CAP_CURSOR_WIDTH and HEIGHT were added in libdrm 2.4.68 (cc9a53f076d4). Remove these four fallback definitions. Signed-off-by: Daniel Stone Reviewed-by: Derek Foreman --- libweston/compositor-drm.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 95b379740..98e6ff839 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -65,26 +65,10 @@ #include "linux-dmabuf.h" #include "linux-dmabuf-unstable-v1-server-protocol.h" -#ifndef DRM_CAP_TIMESTAMP_MONOTONIC -#define DRM_CAP_TIMESTAMP_MONOTONIC 0x6 -#endif - -#ifndef DRM_CLIENT_CAP_UNIVERSAL_PLANES -#define DRM_CLIENT_CAP_UNIVERSAL_PLANES 2 -#endif - #ifndef DRM_CLIENT_CAP_ASPECT_RATIO #define DRM_CLIENT_CAP_ASPECT_RATIO 4 #endif -#ifndef DRM_CAP_CURSOR_WIDTH -#define DRM_CAP_CURSOR_WIDTH 0x8 -#endif - -#ifndef DRM_CAP_CURSOR_HEIGHT -#define DRM_CAP_CURSOR_HEIGHT 0x9 -#endif - #ifndef GBM_BO_USE_CURSOR #define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64 #endif From 117892259014d02e3ed41d241c6068d026f4c440 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 20 Jul 2018 19:55:37 +0100 Subject: [PATCH 0592/1642] compositor-drm: Don't test render-only atomic configuration In the RENDERER_ONLY state proposal mode, we don't actually have a viable configuration to test, because we won't get a renderer buffer until after assign_planes - where we're called from - has completed. This can result in us trying to test a configuration with the CRTC and connectors active, but no planes active, which the kernel can legitimately fail. If we're working in renderer-only mode, just return the state we have without trying to test it first, and let the kernel fill it in later. Signed-off-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 98e6ff839..704ac32c7 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3488,6 +3488,11 @@ drm_output_propose_state(struct weston_output *output_base, pixman_region32_fini(&renderer_region); pixman_region32_fini(&occluded_region); + /* In renderer-only mode, we can't test the state as we don't have a + * renderer buffer yet. */ + if (mode == DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY) + return state; + /* Check to see if this state will actually work. */ ret = drm_pending_state_test(state->pending_state); if (ret != 0) From 48687982b55771b19b958e6a8d62794c61b2d797 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 12 Jul 2018 12:16:47 +0100 Subject: [PATCH 0593/1642] compositor-drm: Remove addfb warning for user buffers THe KMS AddFB call can fail for any reason at all: format/modifier not suitable, stride not aligned, allocation not contiguous, etc. If this happens with Weston's own buffers, the result is bad - no composition output. Failing AddFB from user-supplied buffers though, is not an error. The user can't necessarily allocate suitable buffers, nor does it have to. Don't spam the log with warnings when we fail on user buffers. Signed-off-by: Daniel Stone Reported-by: Pekka Paalanen Reviewed-by: Derek Foreman --- libweston/compositor-drm.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 704ac32c7..70e966c9f 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1168,10 +1168,8 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, goto err_free; } - if (drm_fb_addfb(fb) != 0) { - weston_log("failed to create kms fb: %m\n"); + if (drm_fb_addfb(fb) != 0) goto err_free; - } return fb; @@ -1242,7 +1240,8 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, } if (drm_fb_addfb(fb) != 0) { - weston_log("failed to create kms fb: %m\n"); + if (type == BUFFER_GBM_SURFACE) + weston_log("failed to create kms fb: %m\n"); goto err_free; } From 0335e447314f7f2beadbe87e6914ae0f9e9bcabc Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 27 Jul 2018 11:45:59 -0500 Subject: [PATCH 0594/1642] configure.ac: bump to version 4.0.92 for the beta release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 093d6b549..8a1da996c 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [4]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [91]) +m4_define([weston_micro_version], [92]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [5]) From 2e3915f1bfd1cbefa7a417242600c3019751793e Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Sat, 14 Jul 2018 11:55:24 +0100 Subject: [PATCH 0595/1642] Add CONTRIBUTING.md document Taken from Pekka's wayland/wayland@630c25f4c160 and follow-ups, use Wayland's CONTRIBUTING document as a basis for Weston. Signed-off-by: Daniel Stone Reviewed-by: Quentin Glidic Reviewed-by: Pekka Paalanen --- CONTRIBUTING.md | 343 ++++++++++++++++++++++++++++++++++++++++++++++++ Makefile.am | 1 + 2 files changed, 344 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..4273d99d4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,343 @@ +Contributing to Wayland +======================= + +Sending patches +--------------- + +Patches should be sent to **wayland-devel@lists.freedesktop.org**, using +`git send-email`. See [git documentation] for help. + +The first line of a commit message should contain a prefix indicating +what part is affected by the patch followed by one sentence that +describes the change. For examples: + + protocol: Support scaled outputs and surfaces + +and + + doc: generate server documentation from XML too + +If in doubt what prefix to use, look at other commits that change the +same file(s) as the patch being sent. + +The body of the commit message should describe what the patch changes +and why, and also note any particular side effects. This shouldn't be +empty on most of the cases. It shouldn't take a lot of effort to write +a commit message for an obvious change, so an empty commit message +body is only acceptable if the questions "What?" and "Why?" are already +answered on the one-line summary. + +The lines of the commit message should have at most 76 characters, to +cope with the way git log presents them. + +See [notes on commit messages] for a recommended reading on writing commit +messages. + +Your patches should also include a Signed-off-by line with your name and +email address. If you're not the patch's original author, you should +also gather S-o-b's by them (and/or whomever gave the patch to you.) The +significance of this is that it certifies that you created the patch, +that it was created under an appropriate open source license, or +provided to you under those terms. This lets us indicate a chain of +responsibility for the copyright status of the code. + +We won't reject patches that lack S-o-b, but it is strongly recommended. + +When you re-send patches, revised or not, it would be very good to document the +changes compared to the previous revision in the commit message and/or the +cover letter. If you have already received Reviewed-by or Acked-by tags, you +should evaluate whether they still apply and include them in the respective +commit messages. Otherwise the tags may be lost, reviewers miss the credit they +deserve, and the patches may cause redundant review effort. + + +Tracking patches and following up +--------------------------------- + +[Wayland Patchwork](http://patchwork.freedesktop.org/project/wayland/list/) is +used for tracking patches to Wayland and Weston. Xwayland patches are tracked +with the [Xorg project](https://patchwork.freedesktop.org/project/Xorg/list/) +instead. Libinput patches, even though they use the same mailing list as +Wayland, are not tracked in the Wayland Patchwork. + +The following applies only to Wayland and Weston. + +If a patch is not found in Patchwork, there is a high possibility for it to be +forgotten. Patches attached to bug reports or not arriving to the mailing list +because of e.g. subscription issues will not be in Patchwork because Patchwork +only collects patches sent to the list. + +When you send a revised version of a patch, it would be very nice to mark your +old patch as superseded (or rejected, if that is applicable). You can change +the status of your own patches by registering to Patchwork - ownership is +identified by email address you use to register. Updating your patch status +appropriately will help maintainer work. + +The following patch states are found in Patchwork: + +- **New**: + Patches under discussion or not yet processed. + +- **Under review**: + Mostly unused state. + +- **Accepted**: + The patch is merged in the master branch upstream, as is or slightly + modified. + +- **Rejected**: + The idea or approach is rejected and cannot be fixed by revising + the patch. + +- **RFC**: + Request for comments, not meant to be merged as is. + +- **Not applicable**: + The email was not actually a patch, or the patch is not for Wayland or + Weston. Libinput patches are usually automatically ignored by Wayland + Patchwork, but if they get through, they will be marked as Not + applicable. + +- **Changes requested**: + Reviewers determined that changes to the patch are needed. The + submitter is expected to send a revised version. (You should + not wait for your patch to be set to this state before revising, + though.) + +- **Awaiting upstream**: + Mostly unused as the patch is waiting for upstream actions but + is not shown in the default list, which means it is easy to + overlook. + +- **Superseded**: + A revised version of the patch has been submitted. + +- **Deferred**: + Used mostly during freeze periods before releases, to temporarily + hide patches that cannot be merged during a freeze. + +Note, that in the default listing, only patches in *New* or *Under review* are +shown. + +There is also a command line interface to Patchwork called `pwclient`, see +http://patchwork.freedesktop.org/project/wayland/ +for links where to get it and the sample `.pwclientrc` for Wayland/Weston. + + +Coding style +------------ + +You should follow the style of the file you're editing. In general, we +try to follow the rules below. + +**Note: this file uses spaces due to markdown rendering issues for tabs. + Code must be implemented using tabs.** + +- indent with tabs, and a tab is always 8 characters wide +- opening braces are on the same line as the if statement; +- no braces in an if-body with just one statement; +- if one of the branches of an if-else condition has braces, then the + other branch should also have braces; +- there is always an empty line between variable declarations and the + code; + +```c +static int +my_function(void) +{ + int a = 0; + + if (a) + b(); + else + c(); + + if (a) { + b(); + c(); + } else { + d(); + } +} +``` + +- lines should be less than 80 characters wide; +- when breaking lines with functions calls, the parameters are aligned + with the opening parentheses; +- when assigning a variable with the result of a function call, if the + line would be longer we break it around the equal '=' sign if it makes + sense; + +```c + long_variable_name = + function_with_a_really_long_name(parameter1, parameter2, + parameter3, parameter4); + + x = function_with_a_really_long_name(parameter1, parameter2, + parameter3, parameter4); +``` + +Conduct +======= + +As a freedesktop.org project, Wayland follows the Contributor Covenant, +found at: +https://www.freedesktop.org/wiki/CodeOfConduct + +Please conduct yourself in a respectful and civilised manner when +interacting with community members on mailing lists, IRC, or bug +trackers. The community represents the project as a whole, and abusive +or bullying behaviour is not tolerated by the project. + + +Licensing +========= + +Wayland is licensed with the intention to be usable anywhere X.org is. +Originally, X.org was covered under the MIT X11 license, but changed to +the MIT Expat license. Similarly, Wayland was covered initially as MIT +X11 licensed, but changed to the MIT Expat license, following in X.org's +footsteps. Other than wording, the two licenses are substantially the +same, with the exception of a no-advertising clause in X11 not included +in Expat. + +New source code files should specify the MIT Expat license in their +boilerplate, as part of the copyright statement. + + +Review +====== + +All patches, even trivial ones, require at least one positive review +(Reviewed-by). Additionally, if no Reviewed-by's have been given by +people with commit access, there needs to be at least one Acked-by from +someone with commit access. A person with commit access is expected to be +able to evaluate the patch with respect to the project scope and architecture. + +The below review guidelines are intended to be interpreted in spirit, not by +the letter. There may be circumstances where some guidelines are better +ignored. We rely very much on the judgement of reviewers and commit rights +holders. + +During review, the following matters should be checked: + +- The commit message explains why the change is being made. + +- The code fits the project's scope. + +- The code license is the same MIT licence the project generally uses. + +- Stable ABI or API is not broken. + +- Stable ABI or API additions must be justified by actual use cases, not only +by speculation. They must also be documented, and it is strongly recommended to +include tests excercising the additions in the test suite. + +- The code fits the existing software architecture, e.g. no layering +violations. + +- The code is correct and does not introduce new failures for existing users, +does not add new corner-case bugs, and does not introduce new compiler +warnings. + +- The patch does what it says in the commit message and changes nothing else. + +- The patch is a single logical change. If the commit message addresses +multiple points, it is a hint that the commit might need splitting up. + +- A bug fix should target the underlying root cause instead of hiding symptoms. +If a complete fix is not practical, partial fixes are acceptable if they come +with code comments and filed Gitlab issues for the remaining bugs. + +- The bug root cause rule applies to external software components as well, e.g. +do not work around kernel driver issues in userspace. + +- The test suite passes. + +- The code does not depend on API or ABI which has no working free open source +implementation. + +- The code is not dead or untestable. E.g. if there are no free open source +software users for it then it is effectively dead code. + +- The code is written to be easy to understand, or if code cannot be clear +enough on its own there are code comments to explain it. + +- The code is minimal, i.e. prefer refactor and re-use when possible unless +clarity suffers. + +- The code adheres to the style guidelines. + +- In a patch series, every intermediate step adheres to the above guidelines. + + +Commit rights +============= + +Commit rights will be granted to anyone who requests them and fulfills the +below criteria: + +- Submitted some (10 as a rule of thumb) non-trivial (not just simple + spelling fixes and whitespace adjustment) patches that have been merged + already. + +- Are actively participating in public discussions about their work (on the + mailing list or IRC). This should not be interpreted as a requirement to + review other peoples patches but just make sure that patch submission isn't + one-way communication. Cross-review is still highly encouraged. + +- Will be regularly contributing further patches. This includes regular + contributors to other parts of the open source graphics stack who only + do the occasional development in this project. + +- Agrees to use their commit rights in accordance with the documented merge + criteria, tools, and processes. + +To apply for commit rights, create a new issue in gitlab for the respective +project and give it the "accounts" label. + +Committers are encouraged to request their commit rights get removed when they +no longer contribute to the project. Commit rights will be reinstated when they +come back to the project. + +Maintainers and committers should encourage contributors to request commit +rights, especially junior contributors tend to underestimate their skills. + + +Stabilising for releases +======================== + +A release cycle ends with a stable release which also starts a new cycle and +lifts any code freezes. Gradual code freezing towards a stable release starts +with an alpha release. The release stages of a cycle are: + +- **Alpha release**: + Signified by version number #.#.91. + Major features must have landed before this. Major features include + invasive code motion and refactoring, high risk changes, and new stable + library ABI. + +- **Beta release**: + Signified by version number #.#.92. + Minor features must have landed before this. Minor features include all + new features that are not major, low risk changes, clean-ups, and + documentation. Stable ABI that was new in the alpha release can be removed + before a beta release if necessary. + +- **Release candidates (RC)**: + Signified by version number #.#.93 and up to #.#.99. + Bug fixes that are not release critical must have landed before this. + Release critical bug fixes can still be landed after this, but they may + call for another RC. + +- **Stable release**: + Signified by version number #.#.0. + Ideally no changes since the last RC. + +Mind that version #.#.90 is never released. It is used during development when +no code freeze is in effect. Stable branches and point releases are not covered +by the above. + + +[git documentation]: http://git-scm.com/documentation +[notes on commit messages]: http://who-t.blogspot.de/2009/12/on-commit-messages.html diff --git a/Makefile.am b/Makefile.am index 637dd239a..67670d3ad 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1607,6 +1607,7 @@ SUFFIXES = .1 .5 .7 .man $(AM_V_GEN)$(SED) $(MAN_SUBSTS) < $< > $@ EXTRA_DIST += \ + CONTRIBUTING.md \ doc/calibration-helper.bash \ man/weston.man \ man/weston-drm.man \ From 3a6fa200b7dcfa830daa7412d1be06b7e5563f3d Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Sat, 14 Jul 2018 13:09:58 +0100 Subject: [PATCH 0596/1642] doc: Update CONTRIBUTING for Weston Change some Wayland-specific references to instead refer to Weston. Signed-off-by: Daniel Stone Reviewed-by: Quentin Glidic Reviewed-by: Pekka Paalanen --- CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4273d99d4..91b3fffe9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -Contributing to Wayland +Contributing to Weston ======================= Sending patches @@ -11,11 +11,11 @@ The first line of a commit message should contain a prefix indicating what part is affected by the patch followed by one sentence that describes the change. For examples: - protocol: Support scaled outputs and surfaces + compositor-drm: Support modifiers for drm_fb and - doc: generate server documentation from XML too + input: do not forward unmatched touch-ups If in doubt what prefix to use, look at other commits that change the same file(s) as the patch being sent. @@ -193,9 +193,9 @@ or bullying behaviour is not tolerated by the project. Licensing ========= -Wayland is licensed with the intention to be usable anywhere X.org is. +Weston is licensed with the intention to be usable anywhere X.org is. Originally, X.org was covered under the MIT X11 license, but changed to -the MIT Expat license. Similarly, Wayland was covered initially as MIT +the MIT Expat license. Similarly, Weston was covered initially as MIT X11 licensed, but changed to the MIT Expat license, following in X.org's footsteps. Other than wording, the two licenses are substantially the same, with the exception of a no-advertising clause in X11 not included From f987cb98ef3bf7758b20b8ca3d17f6fc318d0b21 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Sat, 14 Jul 2018 13:33:30 +0100 Subject: [PATCH 0597/1642] README: Move to Markdown, rewrite introduction Move the README file to Markdown, and update it to attempt to explain the current status and use of Weston. The first sections are user-facing, so they can quickly understand what Weston is, what it does, what it doesn't do, and how to go about using it. The following sections on libweston and for distribution packagers are left intact, but should probably be moved to separate documents. This includes a screenshot of Weston running weston-terminal, Chrome and simple-egl, which was taken by myself and subject to the same licensing terms as the rest of the tree. Signed-off-by: Daniel Stone Reviewed-by: Quentin Glidic Reviewed-by: Pekka Paalanen --- Makefile.am | 2 + README => README.md | 108 ++++++++++++++++++++++++++----------- doc/wayland-screenshot.jpg | Bin 0 -> 143832 bytes 3 files changed, 80 insertions(+), 30 deletions(-) rename README => README.md (69%) create mode 100644 doc/wayland-screenshot.jpg diff --git a/Makefile.am b/Makefile.am index 67670d3ad..83bb25339 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1608,6 +1608,8 @@ SUFFIXES = .1 .5 .7 .man EXTRA_DIST += \ CONTRIBUTING.md \ + README.md \ + doc/wayland-screenshot.jpg \ doc/calibration-helper.bash \ man/weston.man \ man/weston-drm.man \ diff --git a/README b/README.md similarity index 69% rename from README rename to README.md index a0a078c46..d61b9322a 100644 --- a/README +++ b/README.md @@ -1,33 +1,81 @@ - Weston - ====== - -Weston is the reference implementation of a Wayland compositor, and a -useful compositor in its own right. Weston has various backends that -lets it run on Linux kernel modesetting and evdev input as well as -under X11. Weston ships with a few example clients, from simple -clients that demonstrate certain aspects of the protocol to more -complete clients and a simplistic toolkit. There is also a quite -capable terminal emulator (weston-terminal) and an toy/example desktop -shell. Finally, weston also provides integration with the Xorg server -and can pull X clients into the Wayland desktop and act as an X window -manager. - -Refer to https://wayland.freedesktop.org/building.html for building -weston and its dependencies. - -The test suite can be invoked via `make check`; see -https://wayland.freedesktop.org/testing.html for additional details. - -Developer documentation can be built via `make doc`. Output will be in -the build root under - -docs/developer/html/index.html -docs/tools/html/index.html - - - - Libweston - ========= +Weston +====== + +![screenshot of skeletal Weston desktop](doc/wayland-screenshot.jpg) + +Weston is the reference implementation of a Wayland compositor, as well as a +useful environment in and of itself. + +Out of the box, Weston provides a very basic desktop, or a full-featured +environment for non-desktop uses such as automotive, embedded, in-flight, +industrial, kiosks, set-top boxes and TVs. It also provides a library allowing +other projects to build their own full-featured environments on top of Weston's +core. + +The core focus of Weston is correctness and reliability. Weston aims to be lean +and fast, but more importantly, to be predictable. Whilst Weston does have known +bugs and shortcomings, we avoid unknown or variable behaviour as much as +possible, including variable performance such as occasional spikes in frame +display time. + +A small suite of example or demo clients are also provided: though they can be +useful in themselves, their main purpose is to be an example or test case for +others building compositors or clients. + +If you are after a more mainline desktop experience, the +[GNOME](https://www.gnome.org) and [KDE](https://www.kde.org) projects provide +full-featured desktop environments built on the Wayland protocol. Many other +projects also exist providing Wayland clients and desktop environments: you are +not limited to just what you can find in Weston. + +Reporting issues and contributing +================================= + +Weston's development is +[hosted on freedesktop.org GitLab](https://gitlab.freedesktop.org/wayland/weston/). +Please also see [the contributing document](CONTRIBUTING.md), which details how +to make code or non-technical contributions to Weston. + +Building Weston +=============== + +Weston is built using autotools, with `autogen.sh` and `make`. It often depends +on the current release versions of +[Wayland](https://gitlab.freedesktop.org/wayland/wayland) and +[wayland-protocols](https://cgit.freedesktop.org/wayland/wayland-protocols). + +Every push to the Weston master repository and its forks is built using GitLab +CI. [Reading the configuration](.gitlab-ci.yml) may provide a useful example of +how to build and install Weston. + +More [detailed documentation on building Weston](https://wayland.freedesktop.org/building.html) +is available on the Wayland site. There are also more details on +[how to run and write tests](https://wayland.freedesktop.org/testing.html). + +Running Weston +============== + +Once Weston is installed, most users can simply run it by typing `weston`. This +will launch Weston inside whatever environment you launch it from: when launched +from a text console, it will take over that console. When launched from inside +an existing Wayland or X11 session, it will start a 'nested' instance of Weston +inside a window in that session. + +Help is available by running `weston --help`, or `man weston`, which will list +the available configuration options and display backends. It can also be +configured through a file on disk; more information on this can be found through +`man weston.ini`. + +In some special cases, such as when running remotely or without logind's session +control, Weston may not be able to run directly from a text console. In these +situations, you can instead execute the `weston-launch` helper, which will gain +privileged access to input and output devices by running as root, then granting +access to the main Weston binary running as your user. Running Weston this way +is not recommended unless necessary. + + +Libweston +========= Libweston is an effort to separate the re-usable parts of Weston into a library. Libweston provides most of the boring and tedious bits of diff --git a/doc/wayland-screenshot.jpg b/doc/wayland-screenshot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4d8f35c1f06ba45fb1cca83bdadf7c25862ba37b GIT binary patch literal 143832 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

B{ID)0=_E(+a7O8Pc?P@(CePraNrRS8~36&9qs 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

!+x^QFxqC=C?Ap9 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 E0q%_0$^ZZW literal 0 HcmV?d00001 From 8798fa7fdfcb219d85e5f76b24f1de2dc75f688f Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Sat, 14 Jul 2018 14:04:07 +0100 Subject: [PATCH 0598/1642] doc: Use GitLab MRs for patches, not the list Though Wayland and the protocols still use mail-based patch review, Weston can now move to GitLab MRs with review through that system. Add some documentation on how to submit patches through GitLab, specifically targeted at people who may be familiar with GitLab review, but not familiar with our rebasing microcommit workflow. Signed-off-by: Daniel Stone Reviewed-by: Quentin Glidic Reviewed-by: Pekka Paalanen --- CONTRIBUTING.md | 143 ++++++++++++++++++++++++------------------------ 1 file changed, 73 insertions(+), 70 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91b3fffe9..f2e4750df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,8 +4,48 @@ Contributing to Weston Sending patches --------------- -Patches should be sent to **wayland-devel@lists.freedesktop.org**, using -`git send-email`. See [git documentation] for help. +Patches should be sent via +[GitLab merge requests](https://docs.gitlab.com/ce/gitlab-basics/add-merge-request.html). +Weston is +[hosted on freedesktop.org's GitLab](https://gitlab.freedesktop.org/wayland/weston/): +in order to submit code, you should create an account on this GitLab instance, +fork the core Weston repository, push your changes to a branch in your new +repository, and then submit these patches for review through a merge request. + +Weston formerly accepted patches via `git-send-email`, sent to +**wayland-devel@lists.freedesktop.org**; these were +[tracked using Patchwork](https://patchwork.freedesktop.org/projects/wayland/). +Some old patches continue to be sent this way, and we may accept small new +patches sent to the list, but please send all new patches through GitLab merge +requests. + +Formatting and separating commits +--------------------------------- + +Unlike many projects using GitHub and GitLab, Weston has a +[linear, 'recipe' style history](http://www.bitsnbites.eu/git-history-work-log-vs-recipe/). +This means that every commit should be small, digestible, stand-alone, and +functional. Rather than a purely chronological commit history like this: + + doc: final docs for view transforms + fix tests when disabled, redo broken doc formatting + better transformed-view iteration (thanks Hannah!) + try to catch more cases in tests + tests: add new spline test + fix compilation on splines + doc: notes on reticulating splines + compositor: add spline reticulation for view transforms + +we aim to have a clean history which only reflects the final state, broken up +into functional groupings: + + compositor: add spline reticulation for view transforms + compositor: new iterator for view transforms + tests: add view-transform correctness tests + doc: fix Doxygen formatting for view transforms + +This ensures that the final patch series only contains the final state, +without the changes and missteps taken along the development process. The first line of a commit message should contain a prefix indicating what part is affected by the patch followed by one sentence that @@ -54,74 +94,37 @@ deserve, and the patches may cause redundant review effort. Tracking patches and following up --------------------------------- -[Wayland Patchwork](http://patchwork.freedesktop.org/project/wayland/list/) is -used for tracking patches to Wayland and Weston. Xwayland patches are tracked -with the [Xorg project](https://patchwork.freedesktop.org/project/Xorg/list/) -instead. Libinput patches, even though they use the same mailing list as -Wayland, are not tracked in the Wayland Patchwork. - -The following applies only to Wayland and Weston. - -If a patch is not found in Patchwork, there is a high possibility for it to be -forgotten. Patches attached to bug reports or not arriving to the mailing list -because of e.g. subscription issues will not be in Patchwork because Patchwork -only collects patches sent to the list. - -When you send a revised version of a patch, it would be very nice to mark your -old patch as superseded (or rejected, if that is applicable). You can change -the status of your own patches by registering to Patchwork - ownership is -identified by email address you use to register. Updating your patch status -appropriately will help maintainer work. - -The following patch states are found in Patchwork: - -- **New**: - Patches under discussion or not yet processed. - -- **Under review**: - Mostly unused state. - -- **Accepted**: - The patch is merged in the master branch upstream, as is or slightly - modified. - -- **Rejected**: - The idea or approach is rejected and cannot be fixed by revising - the patch. - -- **RFC**: - Request for comments, not meant to be merged as is. - -- **Not applicable**: - The email was not actually a patch, or the patch is not for Wayland or - Weston. Libinput patches are usually automatically ignored by Wayland - Patchwork, but if they get through, they will be marked as Not - applicable. - -- **Changes requested**: - Reviewers determined that changes to the patch are needed. The - submitter is expected to send a revised version. (You should - not wait for your patch to be set to this state before revising, - though.) - -- **Awaiting upstream**: - Mostly unused as the patch is waiting for upstream actions but - is not shown in the default list, which means it is easy to - overlook. - -- **Superseded**: - A revised version of the patch has been submitted. - -- **Deferred**: - Used mostly during freeze periods before releases, to temporarily - hide patches that cannot be merged during a freeze. - -Note, that in the default listing, only patches in *New* or *Under review* are -shown. - -There is also a command line interface to Patchwork called `pwclient`, see -http://patchwork.freedesktop.org/project/wayland/ -for links where to get it and the sample `.pwclientrc` for Wayland/Weston. +Once submitted to GitLab, your patches will be reviewed by the Weston +development team on GitLab. Review may be entirely positive and result in your +code landing instantly, in which case, great! You're done. However, we may ask +you to make some revisions: fixing some bugs we've noticed, working to a +slightly different design, or adding documentation and tests. + +If you do get asked to revise the patches, please bear in mind the notes above. +You should use `git rebase -i` to make revisions, so that your patches follow +the clear linear split documented above. Following that split makes it easier +for reviewers to understand your work, and to verify that the code you're +submitting is correct. + +A common request is to split single large patch into multiple patches. This can +happen, for example, if when adding a new feature you notice a bug in Weston's +core which you need to fix to progress. Separating these changes into separate +commits will allow us to verify and land the bugfix quickly, pushing part of +your work for the good of everyone, whilst revision and discussion continues on +the larger feature part. It also allows us to direct you towards reviewers who +best understand the different areas you are working on. + +When you have made any requested changes, please rebase the commits, verify +that they still individually look good, then force-push your new branch to +GitLab. This will update the merge request and notify everyone subscribed to +your merge request, so they can review it again. + +There are also +[many GitLab CLI clients](https://about.gitlab.com/applications/#cli-clients), +if you prefer to avoid the web interface. It may be difficult to follow review +comments without using the web interface though, so we do recommend using this +to go through the review process, even if you use other clients to track the +list of available patches. Coding style From 103dc42b5ec134362db9b6dfb32edf10344e6769 Mon Sep 17 00:00:00 2001 From: Will Thompson Date: Tue, 7 Aug 2018 14:16:46 +0000 Subject: [PATCH 0599/1642] doc: fix typos in CONTRIBUTING.md * Cover letters are no more; presumably the changes since the previous revision should be summarised in the MR * Code should be indented with tabs, not implemented with tabs Signed-off-by: Will Thompson --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f2e4750df..7e22afabe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,7 +85,7 @@ We won't reject patches that lack S-o-b, but it is strongly recommended. When you re-send patches, revised or not, it would be very good to document the changes compared to the previous revision in the commit message and/or the -cover letter. If you have already received Reviewed-by or Acked-by tags, you +merge request. If you have already received Reviewed-by or Acked-by tags, you should evaluate whether they still apply and include them in the respective commit messages. Otherwise the tags may be lost, reviewers miss the credit they deserve, and the patches may cause redundant review effort. @@ -134,7 +134,7 @@ You should follow the style of the file you're editing. In general, we try to follow the rules below. **Note: this file uses spaces due to markdown rendering issues for tabs. - Code must be implemented using tabs.** + Code must be indented using tabs.** - indent with tabs, and a tab is always 8 characters wide - opening braces are on the same line as the if statement; From b8b2c72709a16382fe7a75a3476236bd31c0e54c Mon Sep 17 00:00:00 2001 From: Harsha M M Date: Tue, 7 Aug 2018 19:05:02 +0530 Subject: [PATCH 0600/1642] libweston: Remove signals from the list during de-init During de-init ensure removal of added signals from list. Otherwise a dongling pointer is left behind which will affect other plugins. Signed-off-by: Harsha M M Reviewed-by: Pekka Paalanen --- compositor/text-backend.c | 3 +++ compositor/weston-screenshooter.c | 2 ++ desktop-shell/shell.c | 1 + 3 files changed, 6 insertions(+) diff --git a/compositor/text-backend.c b/compositor/text-backend.c index 542424278..4b2e8543f 100644 --- a/compositor/text-backend.c +++ b/compositor/text-backend.c @@ -452,6 +452,7 @@ text_input_manager_notifier_destroy(struct wl_listener *listener, void *data) struct text_input_manager, destroy_listener); + wl_list_remove(&text_input_manager->destroy_listener.link); wl_global_destroy(text_input_manager->text_input_manager_global); free(text_input_manager); @@ -1060,6 +1061,8 @@ text_backend_configuration(struct text_backend *text_backend) WL_EXPORT void text_backend_destroy(struct text_backend *text_backend) { + wl_list_remove(&text_backend->seat_created_listener.link); + if (text_backend->input_method.client) { /* disable respawn */ wl_list_remove(&text_backend->client_listener.link); diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c index 0994cb4f2..981aff86a 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -162,6 +162,8 @@ screenshooter_destroy(struct wl_listener *listener, void *data) struct screenshooter *shooter = container_of(listener, struct screenshooter, destroy_listener); + wl_list_remove(&shooter->destroy_listener.link); + wl_global_destroy(shooter->global); free(shooter); } diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index ea3c45354..9a447159a 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -4911,6 +4911,7 @@ shell_destroy(struct wl_listener *listener, void *data) wl_client_destroy(shell->child.client); } + wl_list_remove(&shell->destroy_listener.link); wl_list_remove(&shell->idle_listener.link); wl_list_remove(&shell->wake_listener.link); wl_list_remove(&shell->transform_listener.link); From 46cbd0a7f5271cd0832e6d8687829844f40f89cd Mon Sep 17 00:00:00 2001 From: Harsha M M Date: Tue, 7 Aug 2018 19:05:03 +0530 Subject: [PATCH 0601/1642] ivi-shell: Remove the compositor destory listener from list during de-init During de-init ensure removal of compositor destroy notification from list. Otherwise a dongling pointer is left behind which will affect other plugins. Signed-off-by: Harsha M M Reviewed-by: Pekka Paalanen --- ivi-shell/ivi-shell.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 0235d266f..58f53bc55 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -358,6 +358,8 @@ shell_destroy(struct wl_listener *listener, void *data) container_of(listener, struct ivi_shell, destroy_listener); struct ivi_shell_surface *ivisurf, *next; + wl_list_remove(&shell->destroy_listener.link); + text_backend_destroy(shell->text_backend); input_panel_destroy(shell); From dc1418ae8ebdb3916625f501a4d8ca4a9125b1b1 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 10 Aug 2018 13:03:57 -0500 Subject: [PATCH 0602/1642] configure.ac: bump to version 4.0.93 for the RC1 release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8a1da996c..9febf6275 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [4]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [92]) +m4_define([weston_micro_version], [93]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [5]) From f8f7fd69df5bf318b2da9ec6ce964ac676eb2e29 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 28 Jun 2017 11:41:43 -0500 Subject: [PATCH 0603/1642] input: add weston_keyboard_send_keymap helper function We've always had "send_keymap" internally, but some places failed to use it. Since we also use this in the text backend, export it. Reviewed-by: Daniel Stone --- compositor/text-backend.c | 4 +--- libweston/compositor.h | 4 ++++ libweston/input.c | 14 +++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/compositor/text-backend.c b/compositor/text-backend.c index 4b2e8543f..664c36f7a 100644 --- a/compositor/text-backend.c +++ b/compositor/text-backend.c @@ -680,9 +680,7 @@ input_method_context_grab_keyboard(struct wl_client *client, context->keyboard = cr; - wl_keyboard_send_keymap(cr, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, - keyboard->xkb_info->keymap_fd, - keyboard->xkb_info->keymap_size); + weston_keyboard_send_keymap(keyboard, cr); if (keyboard->grab != &keyboard->default_grab) { weston_keyboard_end_grab(keyboard); diff --git a/libweston/compositor.h b/libweston/compositor.h index fd0ff7b5b..82592eb2e 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -2186,6 +2186,10 @@ void weston_seat_set_keyboard_focus(struct weston_seat *seat, struct weston_surface *surface); +void +weston_keyboard_send_keymap(struct weston_keyboard *kbd, + struct wl_resource *resource); + int weston_compositor_load_xwayland(struct weston_compositor *compositor); diff --git a/libweston/input.c b/libweston/input.c index 04c114199..ad1dfeb33 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -2080,9 +2080,11 @@ update_modifier_state(struct weston_seat *seat, uint32_t serial, uint32_t key, notify_modifiers(seat, serial); } -static void -send_keymap(struct wl_resource *resource, struct weston_xkb_info *xkb_info) +WL_EXPORT void +weston_keyboard_send_keymap(struct weston_keyboard *kbd, struct wl_resource *resource) { + struct weston_xkb_info *xkb_info = kbd->xkb_info; + wl_keyboard_send_keymap(resource, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, xkb_info->keymap_fd, @@ -2146,9 +2148,9 @@ update_keymap(struct weston_seat *seat) keyboard->xkb_state.state = state; wl_resource_for_each(resource, &keyboard->resource_list) - send_keymap(resource, xkb_info); + weston_keyboard_send_keymap(keyboard, resource); wl_resource_for_each(resource, &keyboard->focus_resource_list) - send_keymap(resource, xkb_info); + weston_keyboard_send_keymap(keyboard, resource); notify_modifiers(seat, wl_display_next_serial(seat->compositor->wl_display)); @@ -2890,9 +2892,7 @@ seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, seat->compositor->kb_repeat_delay); } - wl_keyboard_send_keymap(cr, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, - keyboard->xkb_info->keymap_fd, - keyboard->xkb_info->keymap_size); + weston_keyboard_send_keymap(keyboard, cr); if (should_send_modifiers_to_client(seat, client)) { send_modifiers_to_resource(keyboard, From 76829fc4eaea329d2a525c3978271e13bd76c078 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 28 Jun 2017 12:17:46 -0500 Subject: [PATCH 0604/1642] input: Send unique keymap file descriptors to clients Client may map any file descriptor opened for writing with PROT_WRITE themselves. On linux, even a read-only file descriptor to an unlinked file can be re-opened with write permission through /proc/self/fd. The only way to prevent this is to create a memfd which is subsequently write-sealed. Unfortunately this prevents clients from mapping with MAP_SHARED, which is already in widespread usage. To isolate and protect the keymap, whilst allowing MAP_SHARED clients to continue to work, use a unique file descriptor for each wl_keyboard resource. Reviewed-by: Daniel Stone --- libweston/compositor.h | 3 +-- libweston/input.c | 60 +++++++++++++++++++----------------------- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/libweston/compositor.h b/libweston/compositor.h index 82592eb2e..8b7a1020b 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -714,9 +714,8 @@ weston_touch_start_drag(struct weston_touch *touch, struct weston_xkb_info { struct xkb_keymap *keymap; - int keymap_fd; size_t keymap_size; - char *keymap_area; + char *keymap_string; int32_t ref_count; xkb_mod_index_t shift_mod; xkb_mod_index_t caps_mod; diff --git a/libweston/input.c b/libweston/input.c index ad1dfeb33..6579592ba 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -2084,11 +2084,31 @@ WL_EXPORT void weston_keyboard_send_keymap(struct weston_keyboard *kbd, struct wl_resource *resource) { struct weston_xkb_info *xkb_info = kbd->xkb_info; + void *area; + int fd; + fd = os_create_anonymous_file(xkb_info->keymap_size); + if (fd < 0) { + weston_log("creating a keymap file for %lu bytes failed: %m\n", + (unsigned long) xkb_info->keymap_size); + return; + } + + area = mmap(NULL, xkb_info->keymap_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (area == MAP_FAILED) { + weston_log("failed to mmap() %lu bytes\n", + (unsigned long) xkb_info->keymap_size); + goto err_mmap; + } + strcpy(area, xkb_info->keymap_string); + munmap(area, xkb_info->keymap_size); wl_keyboard_send_keymap(resource, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, - xkb_info->keymap_fd, + fd, xkb_info->keymap_size); +err_mmap: + close(fd); } static void @@ -3126,10 +3146,8 @@ weston_xkb_info_destroy(struct weston_xkb_info *xkb_info) xkb_keymap_unref(xkb_info->keymap); - if (xkb_info->keymap_area) - munmap(xkb_info->keymap_area, xkb_info->keymap_size); - if (xkb_info->keymap_fd >= 0) - close(xkb_info->keymap_fd); + if (xkb_info->keymap_string) + free(xkb_info->keymap_string); free(xkb_info); } @@ -3157,8 +3175,6 @@ weston_xkb_info_create(struct xkb_keymap *keymap) xkb_info->keymap = xkb_keymap_ref(keymap); xkb_info->ref_count = 1; - char *keymap_str; - xkb_info->shift_mod = xkb_keymap_mod_get_index(xkb_info->keymap, XKB_MOD_NAME_SHIFT); xkb_info->caps_mod = xkb_keymap_mod_get_index(xkb_info->keymap, @@ -3183,38 +3199,16 @@ weston_xkb_info_create(struct xkb_keymap *keymap) xkb_info->scroll_led = xkb_keymap_led_get_index(xkb_info->keymap, XKB_LED_NAME_SCROLL); - keymap_str = xkb_keymap_get_as_string(xkb_info->keymap, - XKB_KEYMAP_FORMAT_TEXT_V1); - if (keymap_str == NULL) { + xkb_info->keymap_string = xkb_keymap_get_as_string(xkb_info->keymap, + XKB_KEYMAP_FORMAT_TEXT_V1); + if (xkb_info->keymap_string == NULL) { weston_log("failed to get string version of keymap\n"); goto err_keymap; } - xkb_info->keymap_size = strlen(keymap_str) + 1; - - xkb_info->keymap_fd = os_create_anonymous_file(xkb_info->keymap_size); - if (xkb_info->keymap_fd < 0) { - weston_log("creating a keymap file for %lu bytes failed: %m\n", - (unsigned long) xkb_info->keymap_size); - goto err_keymap_str; - } - - xkb_info->keymap_area = mmap(NULL, xkb_info->keymap_size, - PROT_READ | PROT_WRITE, - MAP_SHARED, xkb_info->keymap_fd, 0); - if (xkb_info->keymap_area == MAP_FAILED) { - weston_log("failed to mmap() %lu bytes\n", - (unsigned long) xkb_info->keymap_size); - goto err_dev_zero; - } - strcpy(xkb_info->keymap_area, keymap_str); - free(keymap_str); + xkb_info->keymap_size = strlen(xkb_info->keymap_string) + 1; return xkb_info; -err_dev_zero: - close(xkb_info->keymap_fd); -err_keymap_str: - free(keymap_str); err_keymap: xkb_keymap_unref(xkb_info->keymap); free(xkb_info); From 453345102818448c3078c0ce8f02e580c744a2a6 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 17 Aug 2018 11:20:27 -0500 Subject: [PATCH 0605/1642] configure.ac: bump to version 4.0.94 for the RC2 release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 9febf6275..5658cdb09 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [4]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [93]) +m4_define([weston_micro_version], [94]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [5]) From 30e283de702421a6c92c12dd3c7adec2bf252c4c Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 20 Aug 2018 17:11:38 +0200 Subject: [PATCH 0606/1642] compositor-drm: add DPI connector type Linux v4.7 introduced a new connector type for display parallel interface (DPI). Add DPI to the list of connectors in the DRM backend of Weston as well. This avoid DPI connectors showing up as UNNAMED. Signed-off-by: Stefan Agner --- libweston/compositor-drm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 70e966c9f..38911763c 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4549,6 +4549,9 @@ static const char * const connector_type_names[] = { [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual", [DRM_MODE_CONNECTOR_DSI] = "DSI", #endif +#ifdef DRM_MODE_CONNECTOR_DPI + [DRM_MODE_CONNECTOR_DPI] = "DPI", +#endif }; /** Create a name given a DRM connector From 030e7d40fe18869880fc72ce9ae3cbeca6f49600 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 24 Aug 2018 12:39:33 -0500 Subject: [PATCH 0607/1642] configure.ac: bump to version 5.0.0 for the official release --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 5658cdb09..50f8e013c 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ -m4_define([weston_major_version], [4]) +m4_define([weston_major_version], [5]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [94]) +m4_define([weston_micro_version], [0]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [5]) From 836b0530914afe4da9a804c945f76c73d1fa1c3f Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 24 Aug 2018 13:43:34 -0500 Subject: [PATCH 0608/1642] configure.ac: Reopen master for regular development --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 50f8e013c..ab9ff7c72 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,11 @@ m4_define([weston_major_version], [5]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [0]) +m4_define([weston_micro_version], [90]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [5]) m4_define([libweston_minor_version], [0]) -m4_define([libweston_patch_version], [0]) +m4_define([libweston_patch_version], [90]) AC_PREREQ([2.64]) AC_INIT([weston], From d117f33d9b7674e1cb62095e5b252deea5232977 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 16 Aug 2018 17:22:14 +0300 Subject: [PATCH 0609/1642] man: make substitutions meson-friendly Change format of substituted variables to follow the pattern used by configure_file() in Meson. This helps the migration to Meson, making man/meson.build much cleaner. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone --- Makefile.am | 16 ++++++++-------- man/weston-drm.man | 2 +- man/weston-rdp.man | 2 +- man/weston.ini.man | 16 ++++++++-------- man/weston.man | 8 ++++---- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Makefile.am b/Makefile.am index 83bb25339..83546b7ce 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1592,14 +1592,14 @@ man_MANS += weston-rdp.7 endif MAN_SUBSTS = \ - -e 's|__weston_native_backend__|$(WESTON_NATIVE_BACKEND)|g' \ - -e 's|__weston_modules_dir__|$(moduledir)|g' \ - -e 's|__libweston_modules_dir__|$(libweston_moduledir)|g' \ - -e 's|__weston_shell_client__|$(WESTON_SHELL_CLIENT)|g' \ - -e 's|__weston_libexecdir__|$(libexecdir)|g' \ - -e 's|__weston_bindir__|$(bindir)|g' \ - -e 's|__xserver_path__|$(XSERVER_PATH)|g' \ - -e 's|__version__|$(PACKAGE_VERSION)|g' + -e 's|@weston_native_backend@|$(WESTON_NATIVE_BACKEND)|g' \ + -e 's|@weston_modules_dir@|$(moduledir)|g' \ + -e 's|@libweston_modules_dir@|$(libweston_moduledir)|g' \ + -e 's|@weston_shell_client@|$(WESTON_SHELL_CLIENT)|g' \ + -e 's|@weston_libexecdir@|$(libexecdir)|g' \ + -e 's|@weston_bindir@|$(bindir)|g' \ + -e 's|@xserver_path@|$(XSERVER_PATH)|g' \ + -e 's|@version@|$(PACKAGE_VERSION)|g' SUFFIXES = .1 .5 .7 .man diff --git a/man/weston-drm.man b/man/weston-drm.man index f9247b270..6244a8237 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -1,4 +1,4 @@ -.TH WESTON-DRM 7 "2012-11-27" "Weston __version__" +.TH WESTON-DRM 7 "2012-11-27" "Weston @version@" .SH NAME weston-drm \- the DRM backend for Weston .SH SYNOPSIS diff --git a/man/weston-rdp.man b/man/weston-rdp.man index 0916c8d74..f6cdd1de4 100644 --- a/man/weston-rdp.man +++ b/man/weston-rdp.man @@ -1,4 +1,4 @@ -.TH WESTON-RDP 7 "2017-12-14" "Weston __version__" +.TH WESTON-RDP 7 "2017-12-14" "Weston @version@" .SH NAME weston-rdp \- the RDP backend for Weston .SH SYNOPSIS diff --git a/man/weston.ini.man b/man/weston.ini.man index 199b465c5..d4e367a6e 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -1,6 +1,6 @@ .\" shorthand for double quote that works everywhere. .ds q \N'34' -.TH weston.ini 5 "2013-01-17" "Weston __version__" +.TH weston.ini 5 "2013-01-17" "Weston @version@" .SH NAME weston.ini \- configuration file for .B Weston @@ -97,7 +97,7 @@ section is used to select the startup compositor modules and general options. specifies a shell to load (string). This can be used to load your own implemented shell or one with Weston as default. Available shells in the -.IR "__weston_modules_dir__" +.IR "@weston_modules_dir@" directory are: .PP .RS 10 @@ -112,7 +112,7 @@ ask Weston to load the XWayland module (boolean). .TP 7 .BI "modules=" cms-colord.so,screen-share.so specifies the modules to load (string). Available modules in the -.IR "__weston_modules_dir__" +.IR "@weston_modules_dir@" directory are: .PP .RS 10 @@ -124,7 +124,7 @@ directory are: .TP 7 .BI "backend=" headless-backend.so overrides defaults backend. Available backend modules in the -.IR "__libweston_modules_dir__" +.IR "@libweston_modules_dir@" directory are: .PP .RS 10 @@ -252,7 +252,7 @@ The entries that can appear in this section are: .TP 7 .BI "client=" file sets the path for the shell client to run. If not specified -.I __weston_shell_client__ +.I @weston_shell_client@ is launched (string). .TP 7 .BI "background-image=" file @@ -457,7 +457,7 @@ present. This seat can be constrained like any other. .RE .SH "INPUT-METHOD SECTION" .TP 7 -.BI "path=" "__weston_libexecdir__/weston-keyboard" +.BI "path=" "@weston_libexecdir@/weston-keyboard" sets the path of the on screen keyboard input method (string). .RE .RE @@ -540,13 +540,13 @@ The terminal shell (string). Sets the $TERM variable. .RE .SH "XWAYLAND SECTION" .TP 7 -.BI "path=" "__xserver_path__" +.BI "path=" "@xserver_path@" sets the path to the xserver to run (string). .RE .RE .SH "SCREEN-SHARE SECTION" .TP 7 -.BI "command=" "__weston_bindir__/weston --backend=rdp-backend.so \ +.BI "command=" "@weston_bindir@/weston --backend=rdp-backend.so \ --shell=fullscreen-shell.so --no-clients-resize" sets the command to start a fullscreen-shell server for screen sharing (string). .RE diff --git a/man/weston.man b/man/weston.man index 596041dff..44a73fa02 100644 --- a/man/weston.man +++ b/man/weston.man @@ -1,4 +1,4 @@ -.TH WESTON 1 "2012-11-27" "Weston __version__" +.TH WESTON 1 "2012-11-27" "Weston @version@" .SH NAME weston \- the reference Wayland server .SH SYNOPSIS @@ -115,9 +115,9 @@ and Load .I backend.so instead of the default backend. The file is searched for in -.IR "__weston_modules_dir__" , +.IR "@weston_modules_dir@" , or you can pass an absolute path. The default backend is -.I __weston_native_backend__ +.I @weston_native_backend@ unless the environment suggests otherwise, see .IR DISPLAY " and " WAYLAND_DISPLAY . .TP @@ -159,7 +159,7 @@ Ask Weston to load the XWayland module. \fB\-\-modules\fR=\fImodule1.so,module2.so\fR Load the comma-separated list of modules. Only used by the test suite. The file is searched for in -.IR "__weston_modules_dir__" , +.IR "@weston_modules_dir@" , or you can pass an absolute path. .TP .BR \-\-no-config From 7d0170cf8ab07b1d6f69106e86dbe80d87a6bcad Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 28 Aug 2018 17:55:02 +0100 Subject: [PATCH 0610/1642] CONTRIBUTING: How do I get started? Attempt to answer the question on everyone's lips. Signed-off-by: Daniel Stone --- CONTRIBUTING.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7e22afabe..4c1f9aa39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,30 @@ Contributing to Weston ======================= +Finding something to work on +---------------------------- + +Weston's development is [tracked on GitLab](https://gitlab.freedesktop.org/wayland/weston). +In addition to reviewing code submissions (see below), we use the issue tracker +to discuss both bugfixes and development of new features. + +The '[good for new contributors](https://gitlab.freedesktop.org/wayland/weston/issues?label_name%5B%5D=Good+for+new+contributors)' +label is used for issues the development team thinks are a good place to begin +working on Weston. These issues cover features or bugfixes which are small, +self-contained, don't require much specific background knowledge, and aren't +blocked by more complex work. + +If you have picked an issue you would like to work on, you may want to mention +in the issue tracker that you would like to pick it up. You can also discuss +it with the developers in the issue tracker, or on the +[mailing list](https://lists.freedesktop.org/mailman/listinfo/wayland-devel). +Many developers also use IRC through [Freenode](https://freenode.net)'s +`#wayland` channel; however you may need to wait some time for a response on +IRC, which requires keeping your client connected. If you cannot stay for a +long time (potentially some hours due to timezone differences), then you +may want to send your question to the list or issue tracker instead. + + Sending patches --------------- From 3ebbc6b5df5c2ff6f6b9d8ef1f49b7afc3dbb06e Mon Sep 17 00:00:00 2001 From: Matteo Valdina Date: Fri, 31 Aug 2018 09:47:59 -0500 Subject: [PATCH 0611/1642] gl-renderer: Explicitly zeroing the offset for 2^ plane of SHM_FOMRAT_YUYV This fix a crash, when gl-renderer uploads the 2^ texture for YUYV. The pixels buffer was offset of a random value. --- libweston/gl-renderer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 2c50d2da3..7a848cd19 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -1629,6 +1629,7 @@ gl_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer, pitch = wl_shm_buffer_get_stride(shm_buffer) / 2; gl_pixel_type = GL_UNSIGNED_BYTE; num_planes = 2; + gs->offset[1] = 0; gs->hsub[1] = 2; gs->vsub[1] = 1; if (gr->has_gl_texture_rg) From 4fc1ee8d5b5c2a66fcc1a5bafad3eb95c3759bac Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 13:13:41 +0200 Subject: [PATCH 0612/1642] protocol: add weston-debug.xml This is a new debugging extension for non-production environments. The aim is to replace all build-time choosable debug prints in the compositor with runtime subscribable debug streams. Signed-off-by: Pekka Paalanen Added new libweston-$MAJOR-protocols.pc file and install that for external projects to find the XML files installed by libweston. Signed-off-by: Maniraj Devadoss Use noarch_pkgconfig_DATA instead, add ${pc_sysrootdir}, drop unnecessary EXTRA_DIST of weston-debug.xml. Signed-off-by: Pekka Paalanen Add explicit advertisement of available debug interfaces. Signed-off-by: Daniel Stone Reviewed-by: Emre Ucan --- Makefile.am | 10 ++ configure.ac | 1 + libweston/libweston-protocols.pc.in | 7 ++ protocol/weston-debug.xml | 139 ++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 libweston/libweston-protocols.pc.in create mode 100644 protocol/weston-debug.xml diff --git a/Makefile.am b/Makefile.am index 83546b7ce..e38ac009b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -104,6 +104,10 @@ libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ shared/platform.h \ shared/weston-egl-ext.h +libweston_@LIBWESTON_MAJOR@_datadir = $(datadir)/weston/protocols +dist_libweston_@LIBWESTON_MAJOR@_data_DATA = \ + protocol/weston-debug.xml + lib_LTLIBRARIES += libweston-desktop-@LIBWESTON_MAJOR@.la libweston_desktop_@LIBWESTON_MAJOR@_la_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON libweston_desktop_@LIBWESTON_MAJOR@_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) @@ -150,6 +154,8 @@ endif nodist_libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ protocol/weston-screenshooter-protocol.c \ protocol/weston-screenshooter-server-protocol.h \ + protocol/weston-debug-protocol.c \ + protocol/weston-debug-server-protocol.h \ protocol/text-cursor-position-protocol.c \ protocol/text-cursor-position-server-protocol.h \ protocol/text-input-unstable-v1-protocol.c \ @@ -274,6 +280,10 @@ pkgconfig_DATA = \ libweston-desktop/libweston-desktop-${LIBWESTON_MAJOR}.pc \ compositor/weston.pc +noarch_pkgconfigdir = $(datadir)/pkgconfig +noarch_pkgconfig_DATA = \ + libweston/libweston-${LIBWESTON_MAJOR}-protocols.pc + wayland_sessiondir = $(datadir)/wayland-sessions dist_wayland_session_DATA = compositor/weston.desktop diff --git a/configure.ac b/configure.ac index ab9ff7c72..cb62a386c 100644 --- a/configure.ac +++ b/configure.ac @@ -695,6 +695,7 @@ AC_CONFIG_FILES([Makefile libweston/version.h compositor/weston.pc]) # libweston_abi_version here, and outside [] because of m4 quoting rules AC_CONFIG_FILES([libweston/libweston-]libweston_major_version[.pc:libweston/libweston.pc.in]) AC_CONFIG_FILES([libweston/libweston-]libweston_major_version[-uninstalled.pc:libweston/libweston-uninstalled.pc.in]) +AC_CONFIG_FILES([libweston/libweston-]libweston_major_version[-protocols.pc:libweston/libweston-protocols.pc.in]) AC_CONFIG_FILES([libweston-desktop/libweston-desktop-]libweston_major_version[.pc:libweston-desktop/libweston-desktop.pc.in]) AC_CONFIG_FILES([libweston-desktop/libweston-desktop-]libweston_major_version[-uninstalled.pc:libweston-desktop/libweston-desktop-uninstalled.pc.in]) diff --git a/libweston/libweston-protocols.pc.in b/libweston/libweston-protocols.pc.in new file mode 100644 index 000000000..6547a0d5a --- /dev/null +++ b/libweston/libweston-protocols.pc.in @@ -0,0 +1,7 @@ +prefix=@prefix@ +datarootdir=@datarootdir@ +pkgdatadir=${pc_sysrootdir}@datadir@/@PACKAGE@/protocols + +Name: libWeston Protocols +Description: libWeston protocol files +Version: @WESTON_VERSION@ diff --git a/protocol/weston-debug.xml b/protocol/weston-debug.xml new file mode 100644 index 000000000..effa1a190 --- /dev/null +++ b/protocol/weston-debug.xml @@ -0,0 +1,139 @@ + + + + + Copyright © 2017 Pekka Paalanen pq@iki.fi + Copyright © 2018 Zodiac Inflight Innovations + + 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. + + + + + This is a generic debugging interface for Weston internals, the global + object advertized through wl_registry. + + WARNING: This interface by design allows a denial-of-service attack. It + should not be offered in production, or proper authorization mechnisms + must be enforced. + + The idea is for a client to provide a file descriptor that the server + uses for printing debug information. The server uses the file + descriptor in blocking writes mode, which exposes the denial-of-service + risk. The blocking mode is necessary to ensure all debug messages can + be easily printed in place. It also ensures message ordering if a + client subcribes to more than one debug stream. + + The available debugging features depend on the server. + + A debug stream can be one-shot where the server prints the requested + information and then closes it, or continuous where server keeps on + printing until the client stops it. Or anything in between. + + + + + Destroys the factory object, but does not affect any other objects. + + + + + + Advertises an available debug scope which the client may be able to + bind to. No information is provided by the server about the content + contained within the debug streams provided by the scope, once a + client has subscribed. + + + + + + + + + Subscribe to a named debug stream. The server will start printing + to the given file descriptor. + + If the named debug stream is a one-shot dump, the server will send + weston_debug_stream_v1.complete event once all requested data has + been printed. Otherwise, the server will continue streaming debug + prints until the subscription object is destroyed. + + If the debug stream name is unknown to the server, the server will + immediately respond with weston_debug_stream_v1.failure event. + + + + + + + + + + + Represents one subscribed debug stream, created with + weston_debug_v1.subscribe. When the object is created, it is associated + with a given file descriptor. The server will continue writing to the + file descriptor until the object is destroyed or the server sends an + event through the object. + + + + + Destroys the object, which causes the server to stop writing into + and closes the associated file descriptor if it was not closed + already. + + Use a wl_display.sync if the clients needs to guarantee the file + descriptor is closed before continuing. + + + + + + The server has successfully finished writing to and has closed the + associated file descriptor. + + This event is delivered only for one-shot debug streams where the + server dumps some data and stop. This is never delivered for + continuous debbug streams because they by definition never complete. + + + + + + The server has stopped writing to and has closed the + associated file descriptor. The data already written to the file + descriptor is correct, but it may be truncated. + + This event may be delivered at any time and for any kind of debug + stream. It may be due to a failure in or shutdown of the server. + The message argument may provide a hint of the reason. + + + + + + From a5630eafec4f139adf1da4a5ba54894715d7b50f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 13:13:42 +0200 Subject: [PATCH 0613/1642] libweston: add weston_debug API and implementation weston_debug is both a libweston API for relaying debugging messages, and the compositor-debug wayland protocol implementation for accessing those debug messages from a Wayland client. weston_debug_compositor_{create,destroy}() are private API, hence not exported. Signed-off-by: Pekka Paalanen append the debug scope name along with the timestamp in weston_debug_scope_timestamp API Signed-off-by: Maniraj Devadoss Reviewed-by: Pekka Paalanen Add explicit advertisement of debug scope names. Signed-off-by: Daniel Stone Reviewed-by: Emre Ucan --- Makefile.am | 2 + libweston/compositor.c | 5 + libweston/compositor.h | 9 + libweston/weston-debug.c | 732 +++++++++++++++++++++++++++++++++++++++ libweston/weston-debug.h | 107 ++++++ 5 files changed, 855 insertions(+) create mode 100644 libweston/weston-debug.c create mode 100644 libweston/weston-debug.h diff --git a/Makefile.am b/Makefile.am index e38ac009b..d5ed3e584 100644 --- a/Makefile.am +++ b/Makefile.am @@ -96,6 +96,8 @@ libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ libweston/linux-dmabuf.h \ libweston/pixel-formats.c \ libweston/pixel-formats.h \ + libweston/weston-debug.c \ + libweston/weston-debug.h \ shared/helpers.h \ shared/matrix.c \ shared/matrix.h \ diff --git a/libweston/compositor.c b/libweston/compositor.c index 9deb7817f..016165505 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6361,6 +6361,9 @@ weston_compositor_create(struct wl_display *display, void *user_data) ec, bind_presentation)) goto fail; + if (weston_debug_compositor_create(ec) < 0) + goto fail; + if (weston_input_init(ec) != 0) goto fail; @@ -6702,6 +6705,8 @@ weston_compositor_destroy(struct weston_compositor *compositor) if (compositor->heads_changed_source) wl_event_source_remove(compositor->heads_changed_source); + weston_debug_compositor_destroy(compositor); + free(compositor); } diff --git a/libweston/compositor.h b/libweston/compositor.h index 8b7a1020b..33f02b18e 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1048,6 +1048,7 @@ struct weston_touch_calibrator; struct weston_desktop_xwayland; struct weston_desktop_xwayland_interface; +struct weston_debug_compositor; struct weston_compositor { struct wl_signal destroy_signal; @@ -1160,6 +1161,8 @@ struct weston_compositor { weston_touch_calibration_save_func touch_calibration_save; struct weston_layer calibrator_layer; struct weston_touch_calibrator *touch_calibrator; + + struct weston_debug_compositor *weston_debug; }; struct weston_buffer { @@ -2318,6 +2321,12 @@ int weston_compositor_enable_touch_calibrator(struct weston_compositor *compositor, weston_touch_calibration_save_func save); +int +weston_debug_compositor_create(struct weston_compositor *compositor); + +void +weston_debug_compositor_destroy(struct weston_compositor *compositor); + #ifdef __cplusplus } #endif diff --git a/libweston/weston-debug.c b/libweston/weston-debug.c new file mode 100644 index 000000000..04895ad53 --- /dev/null +++ b/libweston/weston-debug.c @@ -0,0 +1,732 @@ +/* + * Copyright © 2017 Pekka Paalanen + * Copyright © 2018 Zodiac Inflight Innovations + * + * 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 "weston-debug.h" +#include "helpers.h" +#include "compositor.h" + +#include "weston-debug-server-protocol.h" + +#include +#include +#include +#include +#include +#include + +/** Main weston-debug context + * + * One per weston_compositor. + * + * \internal + */ +struct weston_debug_compositor { + struct weston_compositor *compositor; + struct wl_listener compositor_destroy_listener; + struct wl_global *global; + struct wl_list scope_list; /**< weston_debug_scope::compositor_link */ +}; + +/** weston-debug message scope + * + * This is used for scoping debugging messages. Clients can subscribe to + * only the scopes they are interested in. A scope is identified by its name + * (also referred to as debug stream name). + */ +struct weston_debug_scope { + char *name; + char *desc; + weston_debug_scope_cb begin_cb; + void *user_data; + struct wl_list stream_list; /**< weston_debug_stream::scope_link */ + struct wl_list compositor_link; +}; + +/** A debug stream created by a client + * + * A client provides a file descriptor for the server to write debug + * messages into. A weston_debug_stream is associated to one + * weston_debug_scope via the scope name, and the scope provides the messages. + * There can be several streams for the same scope, all streams getting the + * same messages. + */ +struct weston_debug_stream { + int fd; /**< client provided fd */ + struct wl_resource *resource; /**< weston_debug_stream_v1 object */ + struct wl_list scope_link; +}; + +static struct weston_debug_scope * +get_scope(struct weston_debug_compositor *wdc, const char *name) +{ + struct weston_debug_scope *scope; + + wl_list_for_each(scope, &wdc->scope_list, compositor_link) + if (strcmp(name, scope->name) == 0) + return scope; + + return NULL; +} + +static void +stream_close_unlink(struct weston_debug_stream *stream) +{ + if (stream->fd != -1) + close(stream->fd); + stream->fd = -1; + + wl_list_remove(&stream->scope_link); + wl_list_init(&stream->scope_link); +} + +static void WL_PRINTF(2, 3) +stream_close_on_failure(struct weston_debug_stream *stream, + const char *fmt, ...) +{ + char *msg; + va_list ap; + int ret; + + stream_close_unlink(stream); + + va_start(ap, fmt); + ret = vasprintf(&msg, fmt, ap); + va_end(ap); + + if (ret > 0) { + weston_debug_stream_v1_send_failure(stream->resource, msg); + free(msg); + } else { + weston_debug_stream_v1_send_failure(stream->resource, + "MEMFAIL"); + } +} + +static struct weston_debug_stream * +stream_create(struct weston_debug_compositor *wdc, const char *name, + int32_t streamfd, struct wl_resource *stream_resource) +{ + struct weston_debug_stream *stream; + struct weston_debug_scope *scope; + + stream = zalloc(sizeof *stream); + if (!stream) + return NULL; + + stream->fd = streamfd; + stream->resource = stream_resource; + + scope = get_scope(wdc, name); + if (scope) { + wl_list_insert(&scope->stream_list, &stream->scope_link); + + if (scope->begin_cb) + scope->begin_cb(stream, scope->user_data); + } else { + wl_list_init(&stream->scope_link); + stream_close_on_failure(stream, + "Debug stream name '%s' is unknown.", + name); + } + + return stream; +} + +static void +stream_destroy(struct wl_resource *stream_resource) +{ + struct weston_debug_stream *stream; + + stream = wl_resource_get_user_data(stream_resource); + + if (stream->fd != -1) + close(stream->fd); + wl_list_remove(&stream->scope_link); + free(stream); +} + +static void +weston_debug_stream_destroy(struct wl_client *client, + struct wl_resource *stream_resource) +{ + wl_resource_destroy(stream_resource); +} + +static const struct weston_debug_stream_v1_interface + weston_debug_stream_impl = { + weston_debug_stream_destroy +}; + +static void +weston_debug_destroy(struct wl_client *client, + struct wl_resource *global_resource) +{ + wl_resource_destroy(global_resource); +} + +static void +weston_debug_subscribe(struct wl_client *client, + struct wl_resource *global_resource, + const char *name, + int32_t streamfd, + uint32_t new_stream_id) +{ + struct weston_debug_compositor *wdc; + struct wl_resource *stream_resource; + uint32_t version; + struct weston_debug_stream *stream; + + wdc = wl_resource_get_user_data(global_resource); + version = wl_resource_get_version(global_resource); + + stream_resource = wl_resource_create(client, + &weston_debug_stream_v1_interface, + version, new_stream_id); + if (!stream_resource) + goto fail; + + stream = stream_create(wdc, name, streamfd, stream_resource); + if (!stream) + goto fail; + + wl_resource_set_implementation(stream_resource, + &weston_debug_stream_impl, + stream, stream_destroy); + return; + +fail: + close(streamfd); + wl_client_post_no_memory(client); +} + +static const struct weston_debug_v1_interface weston_debug_impl = { + weston_debug_destroy, + weston_debug_subscribe +}; + +static void +bind_weston_debug(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct weston_debug_compositor *wdc = data; + struct weston_debug_scope *scope; + struct wl_resource *resource; + + resource = wl_resource_create(client, + &weston_debug_v1_interface, + version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &weston_debug_impl, + wdc, NULL); + + wl_list_for_each(scope, &wdc->scope_list, compositor_link) { + weston_debug_v1_send_available(resource, scope->name, + scope->desc); + } +} + +/** Initialize weston-debug structure + * + * \param compositor The libweston compositor. + * \return 0 on success, -1 on failure. + * + * weston_debug_compositor is a singleton for each weston_compositor. + * + * Sets weston_compositor::weston_debug. + * + * \internal + */ +int +weston_debug_compositor_create(struct weston_compositor *compositor) +{ + struct weston_debug_compositor *wdc; + + if (compositor->weston_debug) + return -1; + + wdc = zalloc(sizeof *wdc); + if (!wdc) + return -1; + + wdc->compositor = compositor; + wl_list_init(&wdc->scope_list); + + compositor->weston_debug = wdc; + + return 0; +} + +/** Destroy weston_debug_compositor structure + * + * \param compositor The libweston compositor whose weston-debug to tear down. + * + * Clears weston_compositor::weston_debug. + * + * \internal + */ +void +weston_debug_compositor_destroy(struct weston_compositor *compositor) +{ + struct weston_debug_compositor *wdc = compositor->weston_debug; + struct weston_debug_scope *scope; + + if (wdc->global) + wl_global_destroy(wdc->global); + + wl_list_for_each(scope, &wdc->scope_list, compositor_link) + weston_log("Internal warning: debug scope '%s' has not been destroyed.\n", + scope->name); + + /* Remove head to not crash if scope removed later. */ + wl_list_remove(&wdc->scope_list); + + free(wdc); + + compositor->weston_debug = NULL; +} + +/** Enable weston-debug protocol extension + * + * \param compositor The libweston compositor where to enable. + * + * This enables the weston_debug_v1 Wayland protocol extension which any client + * can use to get debug messsages from the compositor. + * + * WARNING: This feature should not be used in production. If a client + * provides a file descriptor that blocks writes, it will block the whole + * compositor indefinitely. + * + * There is no control on which client is allowed to subscribe to debug + * messages. Any and all clients are allowed. + * + * The debug extension is disabled by default, and once enabled, cannot be + * disabled again. + */ +WL_EXPORT void +weston_compositor_enable_debug_protocol(struct weston_compositor *compositor) +{ + struct weston_debug_compositor *wdc = compositor->weston_debug; + + assert(wdc); + if (wdc->global) + return; + + wdc->global = wl_global_create(compositor->wl_display, + &weston_debug_v1_interface, 1, + wdc, bind_weston_debug); + if (!wdc->global) + return; + + weston_log("WARNING: debug protocol has been enabled. " + "This is a potential denial-of-service attack vector and " + "information leak.\n"); +} + +/** Register a new debug stream name, creating a debug scope + * + * \param compositor The libweston compositor where to add. + * \param name The debug stream/scope name; must not be NULL. + * \param desc The debug scope description for humans; must not be NULL. + * \param begin_cb Optional callback when a client subscribes to this scope. + * \param user_data Optional user data pointer for the callback. + * \return A valid pointer on success, NULL on failure. + * + * This function is used to create a debug scope. All debug message printing + * happens for a scope, which allows clients to subscribe to the kind of + * debug messages they want by \c name. + * + * \c name must be unique in the \c weston_compositor instance. \c name and + * \c description must both be provided. The description is printed when a + * client asks for a list of supported debug scopes. + * + * \c begin_cb, if not NULL, is called when a client subscribes to the + * debug scope creating a debug stream. This is for debug scopes that need + * to print messages as a response to a client appearing, e.g. printing a + * list of windows on demand or a static preamble. The argument \c user_data + * is passed in to the callback and is otherwise unused. + * + * For one-shot debug streams, \c begin_cb should finally call + * weston_debug_stream_complete() to close the stream and tell the client + * the printing is complete. Otherwise the client expects more to be written + * to its file descriptor. + * + * The debug scope must be destroyed before destroying the + * \c weston_compositor. + * + * \memberof weston_debug_scope + * \sa weston_debug_stream, weston_debug_scope_cb + */ +WL_EXPORT struct weston_debug_scope * +weston_compositor_add_debug_scope(struct weston_compositor *compositor, + const char *name, + const char *description, + weston_debug_scope_cb begin_cb, + void *user_data) +{ + struct weston_debug_compositor *wdc; + struct weston_debug_scope *scope; + + if (!compositor || !name || !description) { + weston_log("Error: cannot add a debug scope without name or description.\n"); + return NULL; + } + + wdc = compositor->weston_debug; + if (!wdc) { + weston_log("Error: cannot add debug scope '%s', infra not initialized.\n", + name); + return NULL; + } + + if (get_scope(wdc, name)){ + weston_log("Error: debug scope named '%s' is already registered.\n", + name); + return NULL; + } + + scope = zalloc(sizeof *scope); + if (!scope) { + weston_log("Error adding debug scope '%s': out of memory.\n", + name); + return NULL; + } + + scope->name = strdup(name); + scope->desc = strdup(description); + scope->begin_cb = begin_cb; + scope->user_data = user_data; + wl_list_init(&scope->stream_list); + + if (!scope->name || !scope->desc) { + weston_log("Error adding debug scope '%s': out of memory.\n", + name); + free(scope->name); + free(scope->desc); + free(scope); + return NULL; + } + + wl_list_insert(wdc->scope_list.prev, &scope->compositor_link); + + return scope; +} + +/** Destroy a debug scope + * + * \param scope The debug scope to destroy; may be NULL. + * + * Destroys the debug scope, closing all open streams subscribed to it and + * sending them each a \c weston_debug_stream_v1.failure event. + * + * \memberof weston_debug_scope + */ +WL_EXPORT void +weston_debug_scope_destroy(struct weston_debug_scope *scope) +{ + struct weston_debug_stream *stream; + + if (!scope) + return; + + while (!wl_list_empty(&scope->stream_list)) { + stream = wl_container_of(scope->stream_list.prev, + stream, scope_link); + + stream_close_on_failure(stream, "debug name removed"); + } + + wl_list_remove(&scope->compositor_link); + free(scope->name); + free(scope->desc); + free(scope); +} + +/** Are there any active subscriptions to the scope? + * + * \param scope The debug scope to check; may be NULL. + * \return True if any streams are open for this scope, false otherwise. + * + * As printing some debugging messages may be relatively expensive, one + * can use this function to determine if there is a need to gather the + * debugging information at all. If this function returns false, all + * printing for this scope is dropped, so gathering the information is + * pointless. + * + * The return value of this function should not be stored, as new clients + * may subscribe to the debug scope later. + * + * If the given scope is NULL, this function will always return false, + * making it safe to use in teardown or destroy code, provided the + * scope is initialized to NULL before creation and set to NULL after + * destruction. + * + * \memberof weston_debug_scope + */ +WL_EXPORT bool +weston_debug_scope_is_enabled(struct weston_debug_scope *scope) +{ + if (!scope) + return false; + + return !wl_list_empty(&scope->stream_list); +} + +/** Write data into a specific debug stream + * + * \param stream The debug stream to write into; must not be NULL. + * \param data[in] Pointer to the data to write. + * \param len Number of bytes to write. + * + * Writes the given data (binary verbatim) into the debug stream. + * If \c len is zero or negative, the write is silently dropped. + * + * Writing is continued until all data has been written or + * a write fails. If the write fails due to a signal, it is re-tried. + * Otherwise on failure, the stream is closed and + * \c weston_debug_stream_v1.failure event is sent to the client. + * + * \memberof weston_debug_stream + */ +WL_EXPORT void +weston_debug_stream_write(struct weston_debug_stream *stream, + const char *data, size_t len) +{ + ssize_t len_ = len; + ssize_t ret; + int e; + + if (stream->fd == -1) + return; + + while (len_ > 0) { + ret = write(stream->fd, data, len_); + e = errno; + if (ret < 0) { + if (e == EINTR) + continue; + + stream_close_on_failure(stream, + "Error writing %zd bytes: %s (%d)", + len_, strerror(e), e); + break; + } + + len_ -= ret; + data += ret; + } +} + +/** Write a formatted string into a specific debug stream (varargs) + * + * \param stream The debug stream to write into. + * \param fmt Printf-style format string. + * \param ap Formatting arguments. + * + * The behavioral details are the same as for weston_debug_stream_write(). + * + * \memberof weston_debug_stream + */ +WL_EXPORT void +weston_debug_stream_vprintf(struct weston_debug_stream *stream, + const char *fmt, va_list ap) +{ + char *str; + int len; + + len = vasprintf(&str, fmt, ap); + if (len >= 0) { + weston_debug_stream_write(stream, str, len); + free(str); + } else { + stream_close_on_failure(stream, "Out of memory"); + } +} + +/** Write a formatted string into a specific debug stream + * + * \param stream The debug stream to write into. + * \param fmt Printf-style format string and arguments. + * + * The behavioral details are the same as for weston_debug_stream_write(). + * + * \memberof weston_debug_stream + */ +WL_EXPORT void +weston_debug_stream_printf(struct weston_debug_stream *stream, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + weston_debug_stream_vprintf(stream, fmt, ap); + va_end(ap); +} + +/** Close the debug stream and send success event + * + * \param stream The debug stream to close. + * + * Closes the debug stream and sends \c weston_debug_stream_v1.complete + * event to the client. This tells the client the debug information dump + * is complete. + * + * \memberof weston_debug_stream + */ +WL_EXPORT void +weston_debug_stream_complete(struct weston_debug_stream *stream) +{ + stream_close_unlink(stream); + weston_debug_stream_v1_send_complete(stream->resource); +} + +/** Write debug data for a scope + * + * \param scope The debug scope to write for; may be NULL, in which case + * nothing will be written. + * \param data[in] Pointer to the data to write. + * \param len Number of bytes to write. + * + * Writes the given data to all subscribed clients' streams. + * + * The behavioral details for each stream are the same as for + * weston_debug_stream_write(). + * + * \memberof weston_debug_scope + */ +WL_EXPORT void +weston_debug_scope_write(struct weston_debug_scope *scope, + const char *data, size_t len) +{ + struct weston_debug_stream *stream; + + if (!scope) + return; + + wl_list_for_each(stream, &scope->stream_list, scope_link) + weston_debug_stream_write(stream, data, len); +} + +/** Write a formatted string for a scope (varargs) + * + * \param scope The debug scope to write for; may be NULL, in which case + * nothing will be written. + * \param fmt Printf-style format string. + * \param ap Formatting arguments. + * + * Writes to formatted string to all subscribed clients' streams. + * + * The behavioral details for each stream are the same as for + * weston_debug_stream_write(). + * + * \memberof weston_debug_scope + */ +WL_EXPORT void +weston_debug_scope_vprintf(struct weston_debug_scope *scope, + const char *fmt, va_list ap) +{ + static const char oom[] = "Out of memory"; + char *str; + int len; + + if (!weston_debug_scope_is_enabled(scope)) + return; + + len = vasprintf(&str, fmt, ap); + if (len >= 0) { + weston_debug_scope_write(scope, str, len); + free(str); + } else { + weston_debug_scope_write(scope, oom, sizeof oom - 1); + } +} + +/** Write a formatted string for a scope + * + * \param scope The debug scope to write for; may be NULL, in which case + * nothing will be written. + * \param fmt Printf-style format string and arguments. + * + * Writes to formatted string to all subscribed clients' streams. + * + * The behavioral details for each stream are the same as for + * weston_debug_stream_write(). + * + * \memberof weston_debug_scope + */ +WL_EXPORT void +weston_debug_scope_printf(struct weston_debug_scope *scope, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + weston_debug_scope_vprintf(scope, fmt, ap); + va_end(ap); +} + +/** Write debug scope name and current time into string + * + * \param scope[in] debug scope; may be NULL + * \param buf[out] Buffer to store the string. + * \param len Available size in the buffer in bytes. + * \return \c buf + * + * Reads the current local wall-clock time and formats it into a string. + * and append the debug scope name to it, if a scope is available. + * The string is NUL-terminated, even if truncated. + */ +WL_EXPORT char * +weston_debug_scope_timestamp(struct weston_debug_scope *scope, + char *buf, size_t len) +{ + struct timeval tv; + struct tm *bdt; + char string[128]; + size_t ret = 0; + + gettimeofday(&tv, NULL); + + bdt = localtime(&tv.tv_sec); + if (bdt) + ret = strftime(string, sizeof string, + "%Y-%m-%d %H:%M:%S", bdt); + + if (ret > 0) { + snprintf(buf, len, "[%s.%03ld][%s]", string, + tv.tv_usec / 1000, + (scope) ? scope->name : "no scope"); + } else { + snprintf(buf, len, "[?][%s]", + (scope) ? scope->name : "no scope"); + } + + return buf; +} diff --git a/libweston/weston-debug.h b/libweston/weston-debug.h new file mode 100644 index 000000000..c76cec852 --- /dev/null +++ b/libweston/weston-debug.h @@ -0,0 +1,107 @@ +/* + * Copyright © 2017 Pekka Paalanen + * + * 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. + */ + +#ifndef WESTON_DEBUG_H +#define WESTON_DEBUG_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct weston_compositor; + +void +weston_compositor_enable_debug_protocol(struct weston_compositor *); + +struct weston_debug_scope; +struct weston_debug_stream; + +/** weston_debug_scope callback + * + * \param stream The debug stream. + * \param user_data The \c user_data argument given to + * weston_compositor_add_debug_scope() + * + * \memberof weston_debug_scope + * \sa weston_debug_stream + */ +typedef void (*weston_debug_scope_cb)(struct weston_debug_stream *stream, + void *user_data); + +struct weston_debug_scope * +weston_compositor_add_debug_scope(struct weston_compositor *compositor, + const char *name, + const char *description, + weston_debug_scope_cb begin_cb, + void *user_data); + +void +weston_debug_scope_destroy(struct weston_debug_scope *scope); + +bool +weston_debug_scope_is_enabled(struct weston_debug_scope *scope); + +void +weston_debug_scope_write(struct weston_debug_scope *scope, + const char *data, size_t len); + +void +weston_debug_scope_vprintf(struct weston_debug_scope *scope, + const char *fmt, va_list ap); + +void +weston_debug_scope_printf(struct weston_debug_scope *scope, + const char *fmt, ...) + __attribute__ ((format (printf, 2, 3))); + +void +weston_debug_stream_write(struct weston_debug_stream *stream, + const char *data, size_t len); + +void +weston_debug_stream_vprintf(struct weston_debug_stream *stream, + const char *fmt, va_list ap); + +void +weston_debug_stream_printf(struct weston_debug_stream *stream, + const char *fmt, ...) + __attribute__ ((format (printf, 2, 3))); + +void +weston_debug_stream_complete(struct weston_debug_stream *stream); + +char * +weston_debug_scope_timestamp(struct weston_debug_scope *scope, + char *buf, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_DEBUG_H */ From 771b7cfc11cec3638b0a4f47edeeaabe2ba46cb6 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 13:13:43 +0200 Subject: [PATCH 0614/1642] compositor: add option to enable weston_debug Let users enable the compositor debug protocol on the compositor command line. This allows weston-debug tool to work. Signed-off-by: Pekka Paalanen Signed-off-by: Maniraj Devadoss Reviewed-by: Pekka Paalanen --- compositor/main.c | 7 +++++++ man/weston.man | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index b5b4fc594..2f34e1115 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -60,6 +60,7 @@ #include "compositor-x11.h" #include "compositor-wayland.h" #include "windowed-output-api.h" +#include "weston-debug.h" #define WINDOW_TITLE "Weston Compositor" @@ -508,6 +509,7 @@ usage(int error_code) " -c, --config=FILE\tConfig file to load, defaults to weston.ini\n" " --no-config\t\tDo not read weston.ini\n" " --wait-for-debugger\tRaise SIGSTOP on start-up\n" + " --debug\t\tEnable debug extension\n" " -h, --help\t\tThis help message\n\n"); #if defined(BUILD_DRM_COMPOSITOR) @@ -2375,6 +2377,7 @@ int main(int argc, char *argv[]) char *socket_name = NULL; int32_t version = 0; int32_t noconfig = 0; + int32_t debug_protocol = 0; int32_t numlock_on; char *config_file = NULL; struct weston_config *config = NULL; @@ -2399,6 +2402,7 @@ int main(int argc, char *argv[]) { WESTON_OPTION_BOOLEAN, "no-config", 0, &noconfig }, { WESTON_OPTION_STRING, "config", 'c', &config_file }, { WESTON_OPTION_BOOLEAN, "wait-for-debugger", 0, &wait_for_debugger }, + { WESTON_OPTION_BOOLEAN, "debug", 0, &debug_protocol }, }; wl_list_init(&wet.layoutput_list); @@ -2486,6 +2490,9 @@ int main(int argc, char *argv[]) } segv_compositor = wet.compositor; + if (debug_protocol) + weston_compositor_enable_debug_protocol(wet.compositor); + if (weston_compositor_init_config(wet.compositor, config) < 0) goto out; diff --git a/man/weston.man b/man/weston.man index 44a73fa02..c09d4c2dc 100644 --- a/man/weston.man +++ b/man/weston.man @@ -133,6 +133,17 @@ If also .B --no-config is given, no configuration file will be read. .TP +.BR \-\-debug +Enable debug protocol extension +.I weston_debug_v1 +which any client can use to receive debugging messages from the compositor. + +.B WARNING: +This is risky for two reasons. First, a client may cause a denial-of-service +blocking the compositor by providing an unsuitable file descriptor, and +second, the debug messages may expose sensitive information. This option +should not be used in production. +.TP .BR \-\-version Print the program version. .TP From b9fdc14b2a518de3a6610438c50cf89ac6b061ce Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 13:13:44 +0200 Subject: [PATCH 0615/1642] clients: add weston-debug A tool for accessing the zcompositor_debug_v1 interface features. Installed along weston-info, because it should be potentially useful for people running libweston-based compositors. Signed-off-by: Pekka Paalanen Added a man page for weston-debug client Signed-off-by: Maniraj Devadoss [Pekka: fixed 'missing braces aroudn initializer' warning] Add --list and --all arguments, using interface advertisement. Signed-off-by: Daniel Stone Reviewed-by: Emre Ucan --- Makefile.am | 16 +- clients/weston-debug.c | 499 +++++++++++++++++++++++++++++++++++++++++ man/weston-debug.man | 51 +++++ 3 files changed, 564 insertions(+), 2 deletions(-) create mode 100644 clients/weston-debug.c create mode 100644 man/weston-debug.man diff --git a/Makefile.am b/Makefile.am index d5ed3e584..333af16da 100644 --- a/Makefile.am +++ b/Makefile.am @@ -529,7 +529,7 @@ spring_tool_SOURCES = \ if BUILD_CLIENTS -bin_PROGRAMS += weston-terminal weston-info +bin_PROGRAMS += weston-terminal weston-info weston-debug libexec_PROGRAMS += \ weston-desktop-shell \ @@ -860,6 +860,15 @@ nodist_weston_info_SOURCES = \ weston_info_LDADD = $(WESTON_INFO_LIBS) libshared.la weston_info_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) +weston_debug_SOURCES = \ + clients/weston-debug.c \ + shared/helpers.h +nodist_weston_debug_SOURCES = \ + protocol/weston-debug-protocol.c \ + protocol/weston-debug-client-protocol.h +weston_debug_LDADD = $(WESTON_INFO_LIBS) libshared.la +weston_debug_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) + weston_desktop_shell_SOURCES = \ clients/desktop-shell.c \ shared/helpers.h @@ -896,6 +905,8 @@ BUILT_SOURCES += \ protocol/weston-screenshooter-client-protocol.h \ protocol/weston-touch-calibration-protocol.c \ protocol/weston-touch-calibration-client-protocol.h \ + protocol/weston-debug-protocol.c \ + protocol/weston-debug-client-protocol.h \ protocol/text-cursor-position-client-protocol.h \ protocol/text-cursor-position-protocol.c \ protocol/text-input-unstable-v1-protocol.c \ @@ -1593,7 +1604,7 @@ surface_screenshot_la_SOURCES = tests/surface-screenshot.c # Documentation # -man_MANS = weston.1 weston.ini.5 +man_MANS = weston.1 weston.ini.5 weston-debug.1 if ENABLE_DRM_COMPOSITOR man_MANS += weston-drm.7 @@ -1624,6 +1635,7 @@ EXTRA_DIST += \ doc/wayland-screenshot.jpg \ doc/calibration-helper.bash \ man/weston.man \ + man/weston-debug.man \ man/weston-drm.man \ man/weston-rdp.man \ man/weston.ini.man diff --git a/clients/weston-debug.c b/clients/weston-debug.c new file mode 100644 index 000000000..563c64ea5 --- /dev/null +++ b/clients/weston-debug.c @@ -0,0 +1,499 @@ +/* + * Copyright © 2017 Pekka Paalanen + * Copyright © 2018 Zodiac Inflight Innovations + * + * 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 +#include + +#include + +#include "shared/helpers.h" +#include "shared/zalloc.h" +#include "weston-debug-client-protocol.h" + +struct debug_app { + struct { + bool help; + bool list; + bool bind_all; + char *output; + char *outfd; + } opt; + + int out_fd; + struct wl_display *dpy; + struct wl_registry *registry; + struct weston_debug_v1 *debug_iface; + struct wl_list stream_list; +}; + +struct debug_stream { + struct wl_list link; + bool should_bind; + char *name; + char *desc; + struct weston_debug_stream_v1 *obj; +}; + +/** + * Called either through stream_find in response to an advertisement + * event (see comment on stream_find) when we have all the information, + * or directly from option parsing to make a placeholder entry when the + * stream was explicitly named on the command line to bind to. + */ +static struct debug_stream * +stream_alloc(struct debug_app *app, const char *name, const char *desc) +{ + struct debug_stream *stream; + + stream = zalloc(sizeof *stream); + if (!stream) + return NULL; + + stream->name = strdup(name); + if (!stream->name) { + free(stream); + return NULL; + } + + if (desc) { + stream->desc = strdup(desc); + if (!stream->desc) { + free(stream->name); + free(stream); + return NULL; + } + } + + stream->should_bind = app->opt.bind_all; + wl_list_insert(app->stream_list.prev, &stream->link); + + return stream; +} + +/** + * Called in response to a stream advertisement event. If our stream was + * manually specified on the command line, then it will already have a + * dummy entry in stream_list: we fill in its description and return. + * If there's no entry in the list, we make a new one and return that. + */ +static struct debug_stream * +stream_find(struct debug_app *app, const char *name, const char *desc) +{ + struct debug_stream *stream; + + wl_list_for_each(stream, &app->stream_list, link) { + if (strcmp(stream->name, name) == 0) { + assert(stream->desc == NULL); + if (desc) + stream->desc = strdup(desc); + return stream; + } + } + + return stream_alloc(app, name, desc); +} + +static void +stream_destroy(struct debug_stream *stream) +{ + if (stream->obj) + weston_debug_stream_v1_destroy(stream->obj); + + wl_list_remove(&stream->link); + free(stream->name); + free(stream); +} + +static void +destroy_streams(struct debug_app *app) +{ + struct debug_stream *stream; + struct debug_stream *tmp; + + wl_list_for_each_safe(stream, tmp, &app->stream_list, link) + stream_destroy(stream); +} + +static void +debug_advertise(void *data, struct weston_debug_v1 *debug, const char *name, + const char *desc) +{ + struct debug_app *app = data; + (void) stream_find(app, name, desc); +} + +static const struct weston_debug_v1_listener debug_listener = { + debug_advertise, +}; + +static void +global_handler(void *data, struct wl_registry *registry, uint32_t id, + const char *interface, uint32_t version) +{ + struct debug_app *app = data; + uint32_t myver; + + assert(app->registry == registry); + + if (!strcmp(interface, weston_debug_v1_interface.name)) { + if (app->debug_iface) + return; + + myver = MIN(1, version); + app->debug_iface = + wl_registry_bind(registry, id, + &weston_debug_v1_interface, myver); + weston_debug_v1_add_listener(app->debug_iface, &debug_listener, + app); + } +} + +static void +global_remove_handler(void *data, struct wl_registry *registry, uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + global_handler, + global_remove_handler +}; + +static void +handle_stream_complete(void *data, struct weston_debug_stream_v1 *obj) +{ + struct debug_stream *stream = data; + + assert(stream->obj == obj); + + stream_destroy(stream); +} + +static void +handle_stream_failure(void *data, struct weston_debug_stream_v1 *obj, + const char *msg) +{ + struct debug_stream *stream = data; + + assert(stream->obj == obj); + + fprintf(stderr, "Debug stream '%s' aborted: %s\n", stream->name, msg); + + stream_destroy(stream); +} + +static const struct weston_debug_stream_v1_listener stream_listener = { + handle_stream_complete, + handle_stream_failure +}; + +static void +start_streams(struct debug_app *app) +{ + struct debug_stream *stream; + + wl_list_for_each(stream, &app->stream_list, link) { + if (!stream->should_bind) + continue; + + stream->obj = weston_debug_v1_subscribe(app->debug_iface, + stream->name, + app->out_fd); + weston_debug_stream_v1_add_listener(stream->obj, + &stream_listener, stream); + } +} + +static void +list_streams(struct debug_app *app) +{ + struct debug_stream *stream; + + fprintf(stderr, "Available debug streams:\n"); + + wl_list_for_each(stream, &app->stream_list, link) { + if (stream->should_bind && stream->desc) { + fprintf(stderr, " %s [will bind]\n", stream->name); + fprintf(stderr, " %s\n", stream->desc); + } else if (stream->should_bind) { + fprintf(stderr, " %s [wanted but not found]\n", + stream->name); + } else { + fprintf(stderr, " %s [will not bind]\n", + stream->name); + fprintf(stderr, " %s\n", stream->desc); + } + } +} + +static int +setup_out_fd(const char *output, const char *outfd) +{ + int fd = -1; + int flags; + + assert(!(output && outfd)); + + if (output) { + if (strcmp(output, "-") == 0) { + fd = STDOUT_FILENO; + } else { + fd = open(output, + O_WRONLY | O_APPEND | O_CREAT, 0644); + if (fd < 0) { + fprintf(stderr, + "Error: opening file '%s' failed: %m\n", + output); + } + return fd; + } + } else if (outfd) { + fd = atoi(outfd); + } else { + fd = STDOUT_FILENO; + } + + flags = fcntl(fd, F_GETFL); + if (flags == -1) { + fprintf(stderr, + "Error: cannot use file descriptor %d: %m\n", fd); + return -1; + } + + if ((flags & O_ACCMODE) != O_WRONLY && + (flags & O_ACCMODE) != O_RDWR) { + fprintf(stderr, + "Error: file descriptor %d is not writable.\n", fd); + return -1; + } + + return fd; +} + +static void +print_help(void) +{ + fprintf(stderr, + "Usage: weston-debug [options] [names]\n" + "Where options may be:\n" + " -h, --help\n" + " This help text, and exit with success.\n" + " -l, --list\n" + " Print a list of available debug streams to stderr.\n" + " -a, --all-streams\n" + " Bind to all available streams.\n" + " -o FILE, --output FILE\n" + " Direct output to file named FILE. Use - for stdout.\n" + " Stdout is the default. Mutually exclusive with -f.\n" + " -f FD, --outfd FD\n" + " Direct output to the file descriptor FD.\n" + " Stdout (1) is the default. Mutually exclusive with -o.\n" + "Names are whatever debug stream names the compositor supports.\n" + ); +} + +static int +parse_cmdline(struct debug_app *app, int argc, char **argv) +{ + static const struct option opts[] = { + { "help", no_argument, NULL, 'h' }, + { "list", no_argument, NULL, 'l' }, + { "all-streams", no_argument, NULL, 'a' }, + { "output", required_argument, NULL, 'o' }, + { "outfd", required_argument, NULL, 'f' }, + { 0 } + }; + static const char optstr[] = "hlao:f:"; + int c; + bool failed = false; + + while (1) { + c = getopt_long(argc, argv, optstr, opts, NULL); + if (c == -1) + break; + + switch (c) { + case 'h': + app->opt.help = true; + break; + case 'l': + app->opt.list = true; + break; + case 'a': + app->opt.bind_all = true; + break; + case 'o': + free(app->opt.output); + app->opt.output = strdup(optarg); + break; + case 'f': + free(app->opt.outfd); + app->opt.outfd = strdup(optarg); + break; + case '?': + failed = true; + break; + default: + fprintf(stderr, "huh? getopt => %c (%d)\n", c, c); + failed = true; + } + } + + if (failed) + return -1; + + while (optind < argc) { + struct debug_stream *stream = + stream_alloc(app, argv[optind++], NULL); + stream->should_bind = true; + } + + return 0; +} + +int +main(int argc, char **argv) +{ + struct debug_app app = {}; + int ret = 0; + + wl_list_init(&app.stream_list); + app.out_fd = -1; + + if (parse_cmdline(&app, argc, argv) < 0) { + ret = 1; + goto out_parse; + } + + if (app.opt.help) { + print_help(); + goto out_parse; + } + + if (!app.opt.list && !app.opt.bind_all && + wl_list_empty(&app.stream_list)) { + fprintf(stderr, "Error: no options given.\n\n"); + ret = 1; + print_help(); + goto out_parse; + } + + if (app.opt.bind_all && !wl_list_empty(&app.stream_list)) { + fprintf(stderr, "Error: --all and specific stream names cannot be used simultaneously.\n"); + ret = 1; + goto out_parse; + } + + if (app.opt.output && app.opt.outfd) { + fprintf(stderr, "Error: options --output and --outfd cannot be used simultaneously.\n"); + ret = 1; + goto out_parse; + } + + app.out_fd = setup_out_fd(app.opt.output, app.opt.outfd); + if (app.out_fd < 0) { + ret = 1; + goto out_parse; + } + + app.dpy = wl_display_connect(NULL); + if (!app.dpy) { + fprintf(stderr, "Error: Could not connect to Wayland display: %m\n"); + ret = 1; + goto out_parse; + } + + app.registry = wl_display_get_registry(app.dpy); + wl_registry_add_listener(app.registry, ®istry_listener, &app); + wl_display_roundtrip(app.dpy); + + if (!app.debug_iface) { + ret = 1; + fprintf(stderr, + "The Wayland server does not support %s interface.\n", + weston_debug_v1_interface.name); + goto out_conn; + } + + wl_display_roundtrip(app.dpy); /* for weston_debug_v1::advertise */ + + if (app.opt.list) + list_streams(&app); + + start_streams(&app); + + weston_debug_v1_destroy(app.debug_iface); + + while (1) { + struct debug_stream *stream; + bool empty = true; + + wl_list_for_each(stream, &app.stream_list, link) { + if (stream->obj) { + empty = false; + break; + } + } + + if (empty) + break; + + if (wl_display_dispatch(app.dpy) < 0) { + ret = 1; + break; + } + } + +out_conn: + destroy_streams(&app); + + /* Wait for server to close all files */ + wl_display_roundtrip(app.dpy); + + wl_registry_destroy(app.registry); + wl_display_disconnect(app.dpy); + +out_parse: + if (app.out_fd != -1) + close(app.out_fd); + + destroy_streams(&app); + free(app.opt.output); + free(app.opt.outfd); + + return ret; +} diff --git a/man/weston-debug.man b/man/weston-debug.man new file mode 100644 index 000000000..b3e0a5b75 --- /dev/null +++ b/man/weston-debug.man @@ -0,0 +1,51 @@ +.TH WESTON-DEBUG 1 "2018-09-11" "Weston @version@" +.SH NAME +weston-debug \- a tool for getting debug messages from compositor. +.SH SYNOPSIS +.B weston-debug [options] [names] +. +.\" *************************************************************** +.SH DESCRIPTION + +.B weston-debug +is a debugging tool which uses weston_debug_v1 interface to get the +debug messages from the compositor. The debug messages are categorized into different +debug streams by the compositor (example: logs, proto, list, etc.,) and the compositor +requires a file descriptor to stream the messages. + +This tool accepts a file name or a file desciptor (not both) and any desired debug stream +names from the user as command line arguments and subscribes the desired streams from the +compositor by using the weston_debug_v1 interface. After the subscription, the +compositor will start to write the debug messages to the shared file descriptor. + +If no file name or file descriptor argument is given, the tool will use the stdout file +descriptor. + +. +.\" *************************************************************** +.SH OPTIONS +. +.B weston-debug +accepts the following command line options. +.TP +. B \-h, \-\-help +Print the help text and exit with success. +.TP +. B \-l, \-\-list +List the available debug streams supported by the compositor. May be used +together with --all or a list of debug stream names. +.TP +. B \-a, \-\-all +Bind all debug streams offered by the compositor. Mututally exclusive with +explicitly specifying stream names. +.TP +. B \-o FILE, \-\-output FILE +Direct output to file named FILE. Use - for stdout. +Stdout is the default. Mutually exclusive with -f. +.TP +. B \-f FD, \-\-outfd FD +Direct output to the file descriptor FD. +Stdout (1) is the default. Mutually exclusive with -o. +.TP +.B [names] +A list of debug streams to bind to. Mutually exclusive with --all. From d3630ed489533b5943037bdfb8f168a05ce8d761 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 13:18:11 +0200 Subject: [PATCH 0616/1642] compositor: offer logs via weston-debug This registers a new weston-debug scope "log" through which one can get live log output interspersed with possible other debugging prints. This is implemented by passing the format and varargs received in our usual logging entrypoints through to the debug scope as well. Anywhere where the varargs set is used twice (once for vfprintf, another for the debug scope), we copy the va_list in order to avoid reusing it, which leads to memory safety issues. Signed-off-by: Pekka Paalanen Signed-off-by: Maniraj Devadoss Reviewed-by: Pekka Paalanen Reviewed-by: Daniel Stone --- compositor/main.c | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 2f34e1115..a82431544 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -119,6 +119,7 @@ struct wet_compositor { }; static FILE *weston_logfile = NULL; +static struct weston_debug_scope *log_scope; static int cached_tm_mday = -1; @@ -149,9 +150,19 @@ static int weston_log_timestamp(void) static void custom_handler(const char *fmt, va_list arg) { + char timestr[128]; + va_list arg2; + + va_copy(arg2, arg); weston_log_timestamp(); fprintf(weston_logfile, "libwayland: "); - vfprintf(weston_logfile, fmt, arg); + vfprintf(weston_logfile, fmt, arg2); + va_end(arg2); + + weston_debug_scope_printf(log_scope, "%s libwayland: ", + weston_debug_scope_timestamp(log_scope, + timestr, sizeof timestr)); + weston_debug_scope_vprintf(log_scope, fmt, arg); } static void @@ -183,9 +194,21 @@ static int vlog(const char *fmt, va_list ap) { int l; + char timestr[128]; + va_list ap2; + + va_copy(ap2, ap); + + if (weston_debug_scope_is_enabled(log_scope)) { + weston_debug_scope_printf(log_scope, "%s ", + weston_debug_scope_timestamp(log_scope, + timestr, sizeof timestr)); + weston_debug_scope_vprintf(log_scope, fmt, ap); + } l = weston_log_timestamp(); - l += vfprintf(weston_logfile, fmt, ap); + l += vfprintf(weston_logfile, fmt, ap2); + va_end(ap2); return l; } @@ -193,6 +216,12 @@ vlog(const char *fmt, va_list ap) static int vlog_continue(const char *fmt, va_list argp) { + va_list argp2; + + va_copy(argp2, argp); + weston_debug_scope_vprintf(log_scope, fmt, argp2); + va_end(argp2); + return vfprintf(weston_logfile, fmt, argp); } @@ -2490,6 +2519,9 @@ int main(int argc, char *argv[]) } segv_compositor = wet.compositor; + log_scope = weston_compositor_add_debug_scope(wet.compositor, "log", + "Weston and Wayland log\n", NULL, NULL); + if (debug_protocol) weston_compositor_enable_debug_protocol(wet.compositor); @@ -2602,6 +2634,8 @@ int main(int argc, char *argv[]) /* free(NULL) is valid, and it won't be NULL if it's used */ free(wet.parsed_options); + weston_debug_scope_destroy(log_scope); + log_scope = NULL; weston_compositor_destroy(wet.compositor); out_signals: From 0a3ef9902a210ab1fa0e4ed317ad7782f0399b51 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 13:18:12 +0200 Subject: [PATCH 0617/1642] xwm: dump_property() to use FILE internally Write the output of dump_property() out in one log call. When multiple processes (weston and Xwayland) are writing to the same file, this will keep the property dump uninterrupted by Xwayland debug prints. This is also preparation for more development in the same direction. Signed-off-by: Pekka Paalanen Signed-off-by: Maniraj Devadoss Reviewed-by: Pekka Paalanen Reviewed-by: Daniel Stone --- xwayland/window-manager.c | 58 ++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 2b3defb70..3bf323a42 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -410,20 +410,14 @@ dump_cardinal_array_elem(FILE *fp, unsigned format, } static void -dump_cardinal_array(xcb_get_property_reply_t *reply) +dump_cardinal_array(FILE *fp, xcb_get_property_reply_t *reply) { unsigned i = 0; - FILE *fp; void *arr; char *str = NULL; - size_t size = 0; assert(reply->type == XCB_ATOM_CARDINAL); - fp = open_memstream(&str, &size); - if (!fp) - return; - arr = xcb_get_property_value(reply); fprintf(fp, "["); @@ -432,10 +426,6 @@ dump_cardinal_array(xcb_get_property_reply_t *reply) arr, reply->value_len, i); fprintf(fp, "]"); - if (fclose(fp) != 0) - return; - - wm_log_continue("%s\n", str); free(str); } @@ -449,22 +439,29 @@ dump_property(struct weston_wm *wm, xcb_window_t *window_value; int width, len; uint32_t i; + FILE *fp; + char *logstr; + size_t logsize; - width = wm_log_continue("%s: ", get_atom_name(wm->conn, property)); - if (reply == NULL) { - wm_log_continue("(no reply)\n"); + fp = open_memstream(&logstr, &logsize); + if (!fp) return; + + width = fprintf(fp, "%s: ", get_atom_name(wm->conn, property)); + if (reply == NULL) { + fprintf(fp, "(no reply)\n"); + goto out; } - width += wm_log_continue("%s/%d, length %d (value_len %d): ", - get_atom_name(wm->conn, reply->type), - reply->format, - xcb_get_property_value_length(reply), - reply->value_len); + width += fprintf(fp, "%s/%d, length %d (value_len %d): ", + get_atom_name(wm->conn, reply->type), + reply->format, + xcb_get_property_value_length(reply), + reply->value_len); if (reply->type == wm->atom.incr) { incr_value = xcb_get_property_value(reply); - wm_log_continue("%d\n", *incr_value); + fprintf(fp, "%d\n", *incr_value); } else if (reply->type == wm->atom.utf8_string || reply->type == wm->atom.string) { text_value = xcb_get_property_value(reply); @@ -472,29 +469,34 @@ dump_property(struct weston_wm *wm, len = 40; else len = reply->value_len; - wm_log_continue("\"%.*s\"\n", len, text_value); + fprintf(fp, "\"%.*s\"\n", len, text_value); } else if (reply->type == XCB_ATOM_ATOM) { atom_value = xcb_get_property_value(reply); for (i = 0; i < reply->value_len; i++) { name = get_atom_name(wm->conn, atom_value[i]); if (width + strlen(name) + 2 > 78) { - wm_log_continue("\n "); + fprintf(fp, "\n "); width = 4; } else if (i > 0) { - width += wm_log_continue(", "); + width += fprintf(fp, ", "); } - width += wm_log_continue("%s", name); + width += fprintf(fp, "%s", name); } - wm_log_continue("\n"); + fprintf(fp, "\n"); } else if (reply->type == XCB_ATOM_CARDINAL) { - dump_cardinal_array(reply); + dump_cardinal_array(fp, reply); } else if (reply->type == XCB_ATOM_WINDOW && reply->format == 32) { window_value = xcb_get_property_value(reply); - wm_log_continue("win %u\n", *window_value); + fprintf(fp, "win %u\n", *window_value); } else { - wm_log_continue("huh?\n"); + fprintf(fp, "huh?\n"); } + +out: + if (fclose(fp) == 0) + wm_log_continue("%s", logstr); + free(logstr); } static void From b3b006559856037ab97200b93641640b88ec7db8 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 13:18:13 +0200 Subject: [PATCH 0618/1642] xwm: move FILE to the callers of dump_property() This is preparation for using the weston-debug infrastructure for WM_DEBUG. dump_property() may be called from different debugging contexts and often needs to be prefixed with more information. An alternative to this patch would be to pass in the weston_debug_scope as an argument to dump_property(), but then all callers would need to be converted to weston-debug infra in a single commit. Therefore require the callers to provide the FILE* to print to. Signed-off-by: Pekka Paalanen Signed-off-by: Maniraj Devadoss Reviewed-by: Pekka Paalanen Reviewed-by: Daniel Stone --- xwayland/selection.c | 39 +++++++++++++++++++++-- xwayland/window-manager.c | 67 ++++++++++++++++----------------------- xwayland/xwayland.h | 3 +- 3 files changed, 65 insertions(+), 44 deletions(-) diff --git a/xwayland/selection.c b/xwayland/selection.c index 59702246d..e0eb3ffc2 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -34,6 +34,12 @@ #include "xwayland.h" #include "shared/helpers.h" +#ifdef WM_DEBUG +#define wm_log(...) weston_log(__VA_ARGS__) +#else +#define wm_log(...) do {} while (0) +#endif + static int writable_callback(int fd, uint32_t mask, void *data) { @@ -102,6 +108,9 @@ weston_wm_get_incr_chunk(struct weston_wm *wm) { xcb_get_property_cookie_t cookie; xcb_get_property_reply_t *reply; + FILE *fp; + char *logstr; + size_t logsize; cookie = xcb_get_property(wm->conn, 0, /* delete */ @@ -115,7 +124,13 @@ weston_wm_get_incr_chunk(struct weston_wm *wm) if (reply == NULL) return; - dump_property(wm, wm->atom.wl_selection, reply); + fp = open_memstream(&logstr, &logsize); + if (fp) { + dump_property(fp, wm, wm->atom.wl_selection, reply); + if (fclose(fp) == 0) + wm_log("%s", logstr); + free(logstr); + } if (xcb_get_property_value_length(reply) > 0) { /* reply's ownership is transferred to wm, which is responsible @@ -178,6 +193,9 @@ weston_wm_get_selection_targets(struct weston_wm *wm) xcb_atom_t *value; char **p; uint32_t i; + FILE *fp; + char *logstr; + size_t logsize; cookie = xcb_get_property(wm->conn, 1, /* delete */ @@ -191,7 +209,13 @@ weston_wm_get_selection_targets(struct weston_wm *wm) if (reply == NULL) return; - dump_property(wm, wm->atom.wl_selection, reply); + fp = open_memstream(&logstr, &logsize); + if (fp) { + dump_property(fp, wm, wm->atom.wl_selection, reply); + if (fclose(fp) == 0) + wm_log("%s", logstr); + free(logstr); + } if (reply->type != XCB_ATOM_ATOM) { free(reply); @@ -232,6 +256,9 @@ weston_wm_get_selection_data(struct weston_wm *wm) { xcb_get_property_cookie_t cookie; xcb_get_property_reply_t *reply; + FILE *fp; + char *logstr; + size_t logsize; cookie = xcb_get_property(wm->conn, 1, /* delete */ @@ -243,7 +270,13 @@ weston_wm_get_selection_data(struct weston_wm *wm) reply = xcb_get_property_reply(wm->conn, cookie, NULL); - dump_property(wm, wm->atom.wl_selection, reply); + fp = open_memstream(&logstr, &logsize); + if (fp) { + dump_property(fp, wm, wm->atom.wl_selection, reply); + if (fclose(fp) == 0) + wm_log("%s", logstr); + free(logstr); + } if (reply == NULL) { return; diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 3bf323a42..4a26f6e7f 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -210,23 +210,6 @@ wm_log(const char *fmt, ...) #endif } -static int __attribute__ ((format (printf, 1, 2))) -wm_log_continue(const char *fmt, ...) -{ -#ifdef WM_DEBUG - int l; - va_list argp; - - va_start(argp, fmt); - l = weston_vlog_continue(fmt, argp); - va_end(argp); - - return l; -#else - return 0; -#endif -} - static void weston_output_weak_ref_init(struct weston_output_weak_ref *ref) { @@ -430,7 +413,7 @@ dump_cardinal_array(FILE *fp, xcb_get_property_reply_t *reply) } void -dump_property(struct weston_wm *wm, +dump_property(FILE *fp, struct weston_wm *wm, xcb_atom_t property, xcb_get_property_reply_t *reply) { int32_t *incr_value; @@ -439,18 +422,11 @@ dump_property(struct weston_wm *wm, xcb_window_t *window_value; int width, len; uint32_t i; - FILE *fp; - char *logstr; - size_t logsize; - - fp = open_memstream(&logstr, &logsize); - if (!fp) - return; width = fprintf(fp, "%s: ", get_atom_name(wm->conn, property)); if (reply == NULL) { fprintf(fp, "(no reply)\n"); - goto out; + return; } width += fprintf(fp, "%s/%d, length %d (value_len %d): ", @@ -492,15 +468,10 @@ dump_property(struct weston_wm *wm, } else { fprintf(fp, "huh?\n"); } - -out: - if (fclose(fp) == 0) - wm_log_continue("%s", logstr); - free(logstr); } static void -read_and_dump_property(struct weston_wm *wm, +read_and_dump_property(FILE *fp, struct weston_wm *wm, xcb_window_t window, xcb_atom_t property) { xcb_get_property_reply_t *reply; @@ -510,7 +481,7 @@ read_and_dump_property(struct weston_wm *wm, property, XCB_ATOM_ANY, 0, 2048); reply = xcb_get_property_reply(wm->conn, cookie, NULL); - dump_property(wm, property, reply); + dump_property(fp, wm, property, reply); free(reply); } @@ -1389,19 +1360,35 @@ weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *even xcb_property_notify_event_t *property_notify = (xcb_property_notify_event_t *) event; struct weston_wm_window *window; + FILE *fp; + char *logstr; + size_t logsize; if (!wm_lookup_window(wm, property_notify->window, &window)) return; window->properties_dirty = 1; - wm_log("XCB_PROPERTY_NOTIFY: window %d, ", property_notify->window); - if (property_notify->state == XCB_PROPERTY_DELETE) - wm_log_continue("deleted %s\n", - get_atom_name(wm->conn, property_notify->atom)); - else - read_and_dump_property(wm, property_notify->window, - property_notify->atom); + fp = open_memstream(&logstr, &logsize); + if (fp) { + fprintf(fp, "XCB_PROPERTY_NOTIFY: window %d, ", property_notify->window); + if (property_notify->state == XCB_PROPERTY_DELETE) + fprintf(fp, "deleted %s\n", + get_atom_name(wm->conn, property_notify->atom)); + else + read_and_dump_property(fp, wm, property_notify->window, + property_notify->atom); + + if (fclose(fp) == 0) + wm_log("%s", logstr); + free(logstr); + } else { + /* read_and_dump_property() is a X11 roundtrip. + * Mimic it to maintain ordering semantics between debug + * and non-debug paths. + */ + get_atom_name(wm->conn, property_notify->atom); + } if (property_notify->atom == wm->atom.net_wm_name || property_notify->atom == XCB_ATOM_WM_NAME) diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index ca75f5b7c..52da67869 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -23,6 +23,7 @@ * SOFTWARE. */ +#include #include #include #include @@ -159,7 +160,7 @@ struct weston_wm { }; void -dump_property(struct weston_wm *wm, xcb_atom_t property, +dump_property(FILE *fp, struct weston_wm *wm, xcb_atom_t property, xcb_get_property_reply_t *reply); const char * From 9b72eb7930ef60aa44ab49c53b2aec9e7242cf1c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 13:18:14 +0200 Subject: [PATCH 0619/1642] xwm: convert WM_DEBUG into a weston-debug scope Instead of a compile time choice, offer the XWM debugging messages through the weston-debug protocol and tool on demand. Users will not need to recompile weston to get XWM debugging, and it won't flood the weston log. The debug scope needs to be initialized in launcher.c for it be available from start, before the first X11 client tries to connect and initializes XWM. Signed-off-by: Pekka Paalanen pass the wm_debug scope to weston_debug_scope_printf API to append the scopename to the timestr Signed-off-by: Maniraj Devadoss Reviewed-by: Pekka Paalanen Reviewed-by: Daniel Stone --- xwayland/launcher.c | 7 ++ xwayland/window-manager.c | 166 ++++++++++++++++++++------------------ xwayland/xwayland.h | 3 + 3 files changed, 99 insertions(+), 77 deletions(-) diff --git a/xwayland/launcher.c b/xwayland/launcher.c index 0ecdb205e..c5b993851 100644 --- a/xwayland/launcher.c +++ b/xwayland/launcher.c @@ -229,6 +229,8 @@ weston_xserver_destroy(struct wl_listener *l, void *data) if (wxs->loop) weston_xserver_shutdown(wxs); + weston_debug_scope_destroy(wxs->wm_debug); + free(wxs); } @@ -391,5 +393,10 @@ weston_module_init(struct weston_compositor *compositor) wxs->destroy_listener.notify = weston_xserver_destroy; wl_signal_add(&compositor->destroy_signal, &wxs->destroy_listener); + wxs->wm_debug = weston_compositor_add_debug_scope(wxs->compositor, + "xwm-wm-x11", + "XWM's window management X11 events\n", + NULL, NULL); + return 0; } diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 4a26f6e7f..ccdae57fd 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -193,23 +193,27 @@ static void xserver_map_shell_surface(struct weston_wm_window *window, struct weston_surface *surface); -static int __attribute__ ((format (printf, 1, 2))) -wm_log(const char *fmt, ...) +static bool +wm_debug_is_enabled(struct weston_wm *wm) { -#ifdef WM_DEBUG - int l; - va_list argp; + return weston_debug_scope_is_enabled(wm->server->wm_debug); +} - va_start(argp, fmt); - l = weston_vlog(fmt, argp); - va_end(argp); +static void __attribute__ ((format (printf, 2, 3))) +wm_printf(struct weston_wm *wm, const char *fmt, ...) +{ + va_list ap; + char timestr[128]; - return l; -#else - return 0; -#endif -} + if (wm_debug_is_enabled(wm)) + weston_debug_scope_printf(wm->server->wm_debug, "%s ", + weston_debug_scope_timestamp(wm->server->wm_debug, + timestr, sizeof timestr)); + va_start(ap, fmt); + weston_debug_scope_vprintf(wm->server->wm_debug, fmt, ap); + va_end(ap); +} static void weston_output_weak_ref_init(struct weston_output_weak_ref *ref) { @@ -717,10 +721,10 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev uint32_t mask, values[16]; int x, y, width, height, i = 0; - wm_log("XCB_CONFIGURE_REQUEST (window %d) %d,%d @ %dx%d\n", - configure_request->window, - configure_request->x, configure_request->y, - configure_request->width, configure_request->height); + wm_printf(wm, "XCB_CONFIGURE_REQUEST (window %d) %d,%d @ %dx%d\n", + configure_request->window, + configure_request->x, configure_request->y, + configure_request->width, configure_request->height); if (!wm_lookup_window(wm, configure_request->window, &window)) return; @@ -786,11 +790,11 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve wm->server->compositor->xwayland_interface; struct weston_wm_window *window; - wm_log("XCB_CONFIGURE_NOTIFY (window %d) %d,%d @ %dx%d%s\n", - configure_notify->window, - configure_notify->x, configure_notify->y, - configure_notify->width, configure_notify->height, - configure_notify->override_redirect ? ", override" : ""); + wm_printf(wm, "XCB_CONFIGURE_NOTIFY (window %d) %d,%d @ %dx%d%s\n", + configure_notify->window, + configure_notify->x, configure_notify->y, + configure_notify->width, configure_notify->height, + configure_notify->override_redirect ? ", override" : ""); if (!wm_lookup_window(wm, configure_notify->window, &window)) return; @@ -839,7 +843,7 @@ weston_wm_create_surface(struct wl_listener *listener, void *data) if (wl_resource_get_client(surface->resource) != wm->server->client) return; - wm_log("XWM: create weston_surface %p\n", surface); + wm_printf(wm, "XWM: create weston_surface %p\n", surface); wl_list_for_each(window, &wm->unpaired_window_list, link) if (window->surface_id == @@ -1096,8 +1100,8 @@ weston_wm_handle_map_request(struct weston_wm *wm, xcb_generic_event_t *event) struct weston_output *output; if (our_resource(wm, map_request->window)) { - wm_log("XCB_MAP_REQUEST (window %d, ours)\n", - map_request->window); + wm_printf(wm, "XCB_MAP_REQUEST (window %d, ours)\n", + map_request->window); return; } @@ -1126,10 +1130,10 @@ weston_wm_handle_map_request(struct weston_wm *wm, xcb_generic_event_t *event) weston_wm_window_create_frame(window); /* sets frame_id */ assert(window->frame_id != XCB_WINDOW_NONE); - wm_log("XCB_MAP_REQUEST (window %d, %p, frame %d, %dx%d @ %d,%d)\n", - window->id, window, window->frame_id, - window->width, window->height, - window->map_request_x, window->map_request_y); + wm_printf(wm, "XCB_MAP_REQUEST (window %d, %p, frame %d, %dx%d @ %d,%d)\n", + window->id, window, window->frame_id, + window->width, window->height, + window->map_request_x, window->map_request_y); weston_wm_window_set_allow_commits(window, false); weston_wm_window_set_wm_state(window, ICCCM_NORMAL_STATE); @@ -1157,13 +1161,13 @@ weston_wm_handle_map_notify(struct weston_wm *wm, xcb_generic_event_t *event) xcb_map_notify_event_t *map_notify = (xcb_map_notify_event_t *) event; if (our_resource(wm, map_notify->window)) { - wm_log("XCB_MAP_NOTIFY (window %d, ours)\n", - map_notify->window); + wm_printf(wm, "XCB_MAP_NOTIFY (window %d, ours)\n", + map_notify->window); return; } - wm_log("XCB_MAP_NOTIFY (window %d%s)\n", map_notify->window, - map_notify->override_redirect ? ", override" : ""); + wm_printf(wm, "XCB_MAP_NOTIFY (window %d%s)\n", map_notify->window, + map_notify->override_redirect ? ", override" : ""); } static void @@ -1173,10 +1177,10 @@ weston_wm_handle_unmap_notify(struct weston_wm *wm, xcb_generic_event_t *event) (xcb_unmap_notify_event_t *) event; struct weston_wm_window *window; - wm_log("XCB_UNMAP_NOTIFY (window %d, event %d%s)\n", - unmap_notify->window, - unmap_notify->event, - our_resource(wm, unmap_notify->window) ? ", ours" : ""); + wm_printf(wm, "XCB_UNMAP_NOTIFY (window %d, event %d%s)\n", + unmap_notify->window, + unmap_notify->event, + our_resource(wm, unmap_notify->window) ? ", ours" : ""); if (our_resource(wm, unmap_notify->window)) return; @@ -1216,7 +1220,7 @@ weston_wm_window_draw_decoration(struct weston_wm_window *window) cairo_t *cr; int width, height; - wm_log("XWM: draw decoration, win %d\n", window->id); + wm_printf(window->wm, "XWM: draw decoration, win %d\n", window->id); weston_wm_window_get_frame_size(window, &width, &height); @@ -1279,8 +1283,8 @@ weston_wm_window_set_pending_state(struct weston_wm_window *window) input_h = height; } - wm_log("XWM: win %d geometry: %d,%d %dx%d\n", - window->id, input_x, input_y, input_w, input_h); + wm_printf(window->wm, "XWM: win %d geometry: %d,%d %dx%d\n", + window->id, input_x, input_y, input_w, input_h); pixman_region32_fini(&window->surface->pending.input); pixman_region32_init_rect(&window->surface->pending.input, @@ -1347,7 +1351,7 @@ weston_wm_window_schedule_repaint(struct weston_wm_window *window) if (window->repaint_source) return; - wm_log("XWM: schedule repaint, win %d\n", window->id); + wm_printf(wm, "XWM: schedule repaint, win %d\n", window->id); window->repaint_source = wl_event_loop_add_idle(wm->server->loop, @@ -1360,18 +1364,24 @@ weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *even xcb_property_notify_event_t *property_notify = (xcb_property_notify_event_t *) event; struct weston_wm_window *window; - FILE *fp; + FILE *fp = NULL; char *logstr; size_t logsize; + char timestr[128]; if (!wm_lookup_window(wm, property_notify->window, &window)) return; window->properties_dirty = 1; - fp = open_memstream(&logstr, &logsize); + if (wm_debug_is_enabled(wm)) + fp = open_memstream(&logstr, &logsize); + if (fp) { - fprintf(fp, "XCB_PROPERTY_NOTIFY: window %d, ", property_notify->window); + fprintf(fp, "%s XCB_PROPERTY_NOTIFY: window %d, ", + weston_debug_scope_timestamp(wm->server->wm_debug, + timestr, sizeof timestr), + property_notify->window); if (property_notify->state == XCB_PROPERTY_DELETE) fprintf(fp, "deleted %s\n", get_atom_name(wm->conn, property_notify->atom)); @@ -1380,7 +1390,8 @@ weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *even property_notify->atom); if (fclose(fp) == 0) - wm_log("%s", logstr); + weston_debug_scope_write(wm->server->wm_debug, + logstr, logsize); free(logstr); } else { /* read_and_dump_property() is a X11 roundtrip. @@ -1406,7 +1417,7 @@ weston_wm_window_create(struct weston_wm *wm, window = zalloc(sizeof *window); if (window == NULL) { - wm_log("failed to allocate window\n"); + wm_printf(wm, "failed to allocate window\n"); return; } @@ -1479,12 +1490,12 @@ weston_wm_handle_create_notify(struct weston_wm *wm, xcb_generic_event_t *event) xcb_create_notify_event_t *create_notify = (xcb_create_notify_event_t *) event; - wm_log("XCB_CREATE_NOTIFY (window %d, at (%d, %d), width %d, height %d%s%s)\n", - create_notify->window, - create_notify->x, create_notify->y, - create_notify->width, create_notify->height, - create_notify->override_redirect ? ", override" : "", - our_resource(wm, create_notify->window) ? ", ours" : ""); + wm_printf(wm, "XCB_CREATE_NOTIFY (window %d, at (%d, %d), width %d, height %d%s%s)\n", + create_notify->window, + create_notify->x, create_notify->y, + create_notify->width, create_notify->height, + create_notify->override_redirect ? ", override" : "", + our_resource(wm, create_notify->window) ? ", ours" : ""); if (our_resource(wm, create_notify->window)) return; @@ -1502,10 +1513,10 @@ weston_wm_handle_destroy_notify(struct weston_wm *wm, xcb_generic_event_t *event (xcb_destroy_notify_event_t *) event; struct weston_wm_window *window; - wm_log("XCB_DESTROY_NOTIFY, win %d, event %d%s\n", - destroy_notify->window, - destroy_notify->event, - our_resource(wm, destroy_notify->window) ? ", ours" : ""); + wm_printf(wm, "XCB_DESTROY_NOTIFY, win %d, event %d%s\n", + destroy_notify->window, + destroy_notify->event, + our_resource(wm, destroy_notify->window) ? ", ours" : ""); if (our_resource(wm, destroy_notify->window)) return; @@ -1523,11 +1534,11 @@ weston_wm_handle_reparent_notify(struct weston_wm *wm, xcb_generic_event_t *even (xcb_reparent_notify_event_t *) event; struct weston_wm_window *window; - wm_log("XCB_REPARENT_NOTIFY (window %d, parent %d, event %d%s)\n", - reparent_notify->window, - reparent_notify->parent, - reparent_notify->event, - reparent_notify->override_redirect ? ", override" : ""); + wm_printf(wm, "XCB_REPARENT_NOTIFY (window %d, parent %d, event %d%s)\n", + reparent_notify->window, + reparent_notify->parent, + reparent_notify->event, + reparent_notify->override_redirect ? ", override" : ""); if (reparent_notify->parent == wm->screen->root) { weston_wm_window_create(wm, reparent_notify->window, 10, 10, @@ -1734,7 +1745,7 @@ surface_destroy(struct wl_listener *listener, void *data) container_of(listener, struct weston_wm_window, surface_destroy_listener); - wm_log("surface for xid %d destroyed\n", window->id); + wm_printf(window->wm, "surface for xid %d destroyed\n", window->id); /* This should have been freed by the shell. * Don't try to use it later. */ @@ -1750,7 +1761,8 @@ weston_wm_window_handle_surface_id(struct weston_wm_window *window, struct wl_resource *resource; if (window->surface_id != 0) { - wm_log("already have surface id for window %d\n", window->id); + wm_printf(wm, "already have surface id for window %d\n", + window->id); return; } @@ -1782,14 +1794,14 @@ weston_wm_handle_client_message(struct weston_wm *wm, (xcb_client_message_event_t *) event; struct weston_wm_window *window; - wm_log("XCB_CLIENT_MESSAGE (%s %d %d %d %d %d win %d)\n", - get_atom_name(wm->conn, client_message->type), - client_message->data.data32[0], - client_message->data.data32[1], - client_message->data.data32[2], - client_message->data.data32[3], - client_message->data.data32[4], - client_message->window); + wm_printf(wm, "XCB_CLIENT_MESSAGE (%s %d %d %d %d %d win %d)\n", + get_atom_name(wm->conn, client_message->type), + client_message->data.data32[0], + client_message->data.data32[1], + client_message->data.data32[2], + client_message->data.data32[3], + client_message->data.data32[4], + client_message->window); /* The window may get created and destroyed before we actually * handle the message. If it doesn't exist, bail. @@ -2007,9 +2019,9 @@ weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) uint32_t button_id; uint32_t double_click = 0; - wm_log("XCB_BUTTON_%s (detail %d)\n", - button->response_type == XCB_BUTTON_PRESS ? - "PRESS" : "RELEASE", button->detail); + wm_printf(wm, "XCB_BUTTON_%s (detail %d)\n", + button->response_type == XCB_BUTTON_PRESS ? + "PRESS" : "RELEASE", button->detail); if (!wm_lookup_window(wm, button->event, &window) || !window->decorate) @@ -2221,7 +2233,7 @@ weston_wm_handle_event(int fd, uint32_t mask, void *data) weston_wm_handle_destroy_notify(wm, event); break; case XCB_MAPPING_NOTIFY: - wm_log("XCB_MAPPING_NOTIFY\n"); + wm_printf(wm, "XCB_MAPPING_NOTIFY\n"); break; case XCB_PROPERTY_NOTIFY: weston_wm_handle_property_notify(wm, event); @@ -2837,8 +2849,8 @@ xserver_map_shell_surface(struct weston_wm_window *window, window->surface, &shell_client); - wm_log("XWM: map shell surface, win %d, weston_surface %p, xwayland surface %p\n", - window->id, window->surface, window->shsurf); + wm_printf(wm, "XWM: map shell surface, win %d, weston_surface %p, xwayland surface %p\n", + window->id, window->surface, window->shsurf); if (window->name) xwayland_interface->set_title(window->shsurf, window->name); diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index 52da67869..507d534d3 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -33,6 +33,7 @@ #include "compositor.h" #include "compositor/weston.h" #include "xwayland-api.h" +#include "weston-debug.h" #define SEND_EVENT_MASK (0x80) #define EVENT_TYPE(event) ((event)->response_type & ~SEND_EVENT_MASK) @@ -52,6 +53,8 @@ struct weston_xserver { struct wl_listener destroy_listener; weston_xwayland_spawn_xserver_func_t spawn_func; void *user_data; + + struct weston_debug_scope *wm_debug; }; struct weston_wm { From 5c91bb8d28ee86df6e75de43117dc11a7f908853 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 12 Oct 2017 13:18:15 +0200 Subject: [PATCH 0620/1642] compositor: protocol logger This is better than running Weston with WAYLAND_DEBUG=server: - It is enabled on demand, no unnecessary flooding and no need to restart the compositor if debug was enabled. - It prints client pointers so that messages with different clients can be seen apart. Signed-off-by: Pekka Paalanen parse and print message arguments in protocol_log_fn Signed-off-by: Maniraj Devadoss Reviewed-by: Pekka Paalanen Reviewed-by: Daniel Stone --- compositor/main.c | 130 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index a82431544..22f3a2754 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -120,6 +120,7 @@ struct wet_compositor { static FILE *weston_logfile = NULL; static struct weston_debug_scope *log_scope; +static struct weston_debug_scope *protocol_scope; static int cached_tm_mday = -1; @@ -225,6 +226,116 @@ vlog_continue(const char *fmt, va_list argp) return vfprintf(weston_logfile, fmt, argp); } +static const char * +get_next_argument(const char *signature, char* type) +{ + for(; *signature; ++signature) { + switch(*signature) { + case 'i': + case 'u': + case 'f': + case 's': + case 'o': + case 'n': + case 'a': + case 'h': + *type = *signature; + return signature + 1; + } + } + *type = '\0'; + return signature; +} + +static void +protocol_log_fn(void *user_data, + enum wl_protocol_logger_type direction, + const struct wl_protocol_logger_message *message) +{ + FILE *fp; + char *logstr; + size_t logsize; + char timestr[128]; + struct wl_resource *res = message->resource; + const char *signature = message->message->signature; + int i; + char type; + + if (!weston_debug_scope_is_enabled(protocol_scope)) + return; + + fp = open_memstream(&logstr, &logsize); + if (!fp) + return; + + weston_debug_scope_timestamp(protocol_scope, + timestr, sizeof timestr); + fprintf(fp, "%s ", timestr); + fprintf(fp, "client %p %s ", wl_resource_get_client(res), + direction == WL_PROTOCOL_LOGGER_REQUEST ? "rq" : "ev"); + fprintf(fp, "%s@%u.%s(", + wl_resource_get_class(res), + wl_resource_get_id(res), + message->message->name); + + for (i = 0; i < message->arguments_count; i++) { + signature = get_next_argument(signature, &type); + + if (i > 0) + fprintf(fp, ", "); + + switch (type) { + case 'u': + fprintf(fp, "%u", message->arguments[i].u); + break; + case 'i': + fprintf(fp, "%d", message->arguments[i].i); + break; + case 'f': + fprintf(fp, "%f", + wl_fixed_to_double(message->arguments[i].f)); + break; + case 's': + fprintf(fp, "\"%s\"", message->arguments[i].s); + break; + case 'o': + if (message->arguments[i].o) { + struct wl_resource* resource; + resource = (struct wl_resource*) message->arguments[i].o; + fprintf(fp, "%s@%u", + wl_resource_get_class(resource), + wl_resource_get_id(resource)); + } + else + fprintf(fp, "nil"); + break; + case 'n': + fprintf(fp, "new id %s@", + (message->message->types[i]) ? + message->message->types[i]->name : + "[unknown]"); + if (message->arguments[i].n != 0) + fprintf(fp, "%u", message->arguments[i].n); + else + fprintf(fp, "nil"); + break; + case 'a': + fprintf(fp, "array"); + break; + case 'h': + fprintf(fp, "fd %d", message->arguments[i].h); + break; + } + } + + fprintf(fp, ")\n"); + + if (fclose(fp) == 0) + weston_debug_scope_write(protocol_scope, logstr, logsize); + + free(logstr); +} + static struct wl_list child_process_list; static struct weston_compositor *segv_compositor; @@ -2417,6 +2528,7 @@ int main(int argc, char *argv[]) struct wet_compositor wet = { 0 }; int require_input; int32_t wait_for_debugger = 0; + struct wl_protocol_logger *protologger = NULL; const struct weston_option core_options[] = { { WESTON_OPTION_STRING, "backend", 'B', &backend }, @@ -2521,9 +2633,18 @@ int main(int argc, char *argv[]) log_scope = weston_compositor_add_debug_scope(wet.compositor, "log", "Weston and Wayland log\n", NULL, NULL); - - if (debug_protocol) + protocol_scope = + weston_compositor_add_debug_scope(wet.compositor, + "proto", + "Wayland protocol dump for all clients.\n", + NULL, NULL); + + if (debug_protocol) { + protologger = wl_display_add_protocol_logger(display, + protocol_log_fn, + NULL); weston_compositor_enable_debug_protocol(wet.compositor); + } if (weston_compositor_init_config(wet.compositor, config) < 0) goto out; @@ -2634,6 +2755,11 @@ int main(int argc, char *argv[]) /* free(NULL) is valid, and it won't be NULL if it's used */ free(wet.parsed_options); + if (protologger) + wl_protocol_logger_destroy(protologger); + + weston_debug_scope_destroy(protocol_scope); + protocol_scope = NULL; weston_debug_scope_destroy(log_scope); log_scope = NULL; weston_compositor_destroy(wet.compositor); From 3b7756351d31a72da11ff09c56efa17960db1b20 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 20 Jul 2018 08:38:25 +0100 Subject: [PATCH 0621/1642] compositor: Add weston_layer_mask_is_infinite As a counterpart to weston_layer_set_mask_infinite(), returning if the mask is the same as what is set. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor.c | 9 +++++++++ libweston/compositor.h | 3 +++ 2 files changed, 12 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index 016165505..a38c4c1bd 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2746,6 +2746,15 @@ weston_layer_set_mask_infinite(struct weston_layer *layer) UINT32_MAX, UINT32_MAX); } +WL_EXPORT bool +weston_layer_mask_is_infinite(struct weston_layer *layer) +{ + return layer->mask.x1 == INT32_MIN && + layer->mask.y1 == INT32_MIN && + layer->mask.x2 == INT32_MIN + UINT32_MAX && + layer->mask.y2 == INT32_MIN + UINT32_MAX; +} + WL_EXPORT void weston_output_schedule_repaint(struct weston_output *output) { diff --git a/libweston/compositor.h b/libweston/compositor.h index 33f02b18e..069fb03d4 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1685,6 +1685,9 @@ weston_layer_set_mask(struct weston_layer *layer, int x, int y, int width, int h void weston_layer_set_mask_infinite(struct weston_layer *layer); +bool +weston_layer_mask_is_infinite(struct weston_layer *layer); + void weston_plane_init(struct weston_plane *plane, struct weston_compositor *ec, From ce62cb3d05505777893ccdfacbf2a013c82e4ce2 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 20 Jul 2018 09:46:24 +0100 Subject: [PATCH 0622/1642] compositor: Add scene-graph debug scope Add a 'scene-graph' debug scope which will dump out the current set of outputs, layers, and views and as much information as possible about how they are rendered and composited. Signed-off-by: Daniel Stone --- libweston/compositor.c | 225 +++++++++++++++++++++++++++++++++++++++++ libweston/compositor.h | 4 + 2 files changed, 229 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index a38c4c1bd..2ca3da3b4 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -55,6 +55,8 @@ #include "timeline.h" #include "compositor.h" +#include "weston-debug.h" +#include "linux-dmabuf.h" #include "viewporter-server-protocol.h" #include "presentation-time-server-protocol.h" #include "shared/helpers.h" @@ -6306,6 +6308,221 @@ timeline_key_binding_handler(struct weston_keyboard *keyboard, weston_timeline_open(compositor); } +static const char * +output_repaint_status_text(struct weston_output *output) +{ + switch (output->repaint_status) { + case REPAINT_NOT_SCHEDULED: + return "no repaint"; + case REPAINT_BEGIN_FROM_IDLE: + return "start_repaint_loop scheduled"; + case REPAINT_SCHEDULED: + return "repaint scheduled"; + case REPAINT_AWAITING_COMPLETION: + return "awaiting completion"; + } + + assert(!"output_repaint_status_text missing enum"); + return NULL; +} + +static void +debug_scene_view_print_buffer(FILE *fp, struct weston_view *view) +{ + struct weston_buffer *buffer = view->surface->buffer_ref.buffer; + struct wl_shm_buffer *shm; + struct linux_dmabuf_buffer *dmabuf; + + if (!buffer) { + fprintf(fp, "\t\t[buffer not available]\n"); + return; + } + + shm = wl_shm_buffer_get(buffer->resource); + if (shm) { + fprintf(fp, "\t\tSHM buffer\n"); + fprintf(fp, "\t\t\tformat: 0x%lx\n", + (unsigned long) wl_shm_buffer_get_format(shm)); + return; + } + + dmabuf = linux_dmabuf_buffer_get(buffer->resource); + if (dmabuf) { + fprintf(fp, "\t\tdmabuf buffer\n"); + fprintf(fp, "\t\t\tformat: 0x%lx\n", + (unsigned long) dmabuf->attributes.format); + fprintf(fp, "\t\t\tmodifier: 0x%llx\n", + (unsigned long long) dmabuf->attributes.modifier[0]); + return; + } + + fprintf(fp, "\t\tEGL buffer"); +} + +static void +debug_scene_view_print(FILE *fp, struct weston_view *view, int view_idx) +{ + struct weston_compositor *ec = view->surface->compositor; + struct weston_output *output; + char desc[512]; + pixman_box32_t *box; + uint32_t surface_id = 0; + pid_t pid = 0; + + if (view->surface->resource) { + struct wl_resource *resource = view->surface->resource; + wl_client_get_credentials(wl_resource_get_client(resource), + &pid, NULL, NULL); + surface_id = wl_resource_get_id(view->surface->resource); + } + + if (!view->surface->get_label || + view->surface->get_label(view->surface, desc, sizeof(desc)) < 0) { + strcpy(desc, "[no description available]"); + } + fprintf(fp, "\tView %d (role %s, PID %d, surface ID %u, %s, %p):\n", + view_idx, view->surface->role_name, pid, surface_id, + desc, view); + + box = pixman_region32_extents(&view->transform.boundingbox); + fprintf(fp, "\t\tposition: (%d, %d) -> (%d, %d)\n", + box->x1, box->y1, box->x2, box->y2); + box = pixman_region32_extents(&view->transform.opaque); + + if (pixman_region32_equal(&view->transform.opaque, + &view->transform.boundingbox)) { + fprintf(fp, "\t\t[fully opaque]\n"); + } else if (!pixman_region32_not_empty(&view->transform.opaque)) { + fprintf(fp, "\t\t[not opaque]\n"); + } else { + fprintf(fp, "\t\t[opaque: (%d, %d) -> (%d, %d)]\n", + box->x1, box->y1, box->x2, box->y2); + } + + if (view->alpha < 1.0) + fprintf(fp, "\t\talpha: %f\n", view->alpha); + + if (view->output_mask != 0) { + bool first_output = true; + fprintf(fp, "\t\toutputs: "); + wl_list_for_each(output, &ec->output_list, link) { + if (!(view->output_mask & (1 << output->id))) + continue; + fprintf(fp, "%s%d (%s)%s", + (first_output) ? "" : ", ", + output->id, output->name, + (view->output == output) ? " (primary)" : ""); + first_output = false; + } + } else { + fprintf(fp, "\t\t[no outputs]"); + } + + fprintf(fp, "\n"); + + debug_scene_view_print_buffer(fp, view); +} + +/** + * Output information on how libweston is currently composing the scene + * graph. + */ +WL_EXPORT char * +weston_compositor_print_scene_graph(struct weston_compositor *ec) +{ + struct weston_output *output; + struct weston_layer *layer; + struct timespec now; + int layer_idx = 0; + FILE *fp; + char *ret; + size_t len; + int err; + + fp = open_memstream(&ret, &len); + assert(fp); + + weston_compositor_read_presentation_clock(ec, &now); + fprintf(fp, "Weston scene graph at %ld.%09ld:\n\n", + now.tv_sec, now.tv_nsec); + + wl_list_for_each(output, &ec->output_list, link) { + struct weston_head *head; + int head_idx = 0; + + fprintf(fp, "Output %d (%s):\n", output->id, output->name); + assert(output->enabled); + + fprintf(fp, "\tposition: (%d, %d) -> (%d, %d)\n", + output->x, output->y, + output->x + output->width, + output->y + output->height); + fprintf(fp, "\tmode: %dx%d@%.3fHz\n", + output->current_mode->width, + output->current_mode->height, + output->current_mode->refresh / 1000.0); + fprintf(fp, "\tscale: %d\n", output->scale); + + fprintf(fp, "\trepaint status: %s\n", + output_repaint_status_text(output)); + if (output->repaint_status == REPAINT_SCHEDULED) + fprintf(fp, "\tnext repaint: %ld.%09ld\n", + output->next_repaint.tv_sec, + output->next_repaint.tv_nsec); + + wl_list_for_each(head, &output->head_list, output_link) { + fprintf(fp, "\tHead %d (%s): %sconnected\n", + head_idx++, head->name, + (head->connected) ? "" : "not "); + } + } + + fprintf(fp, "\n"); + + wl_list_for_each(layer, &ec->layer_list, link) { + struct weston_view *view; + int view_idx = 0; + + fprintf(fp, "Layer %d (pos 0x%lx):\n", layer_idx++, + (unsigned long) layer->position); + + if (!weston_layer_mask_is_infinite(layer)) { + fprintf(fp, "\t[mask: (%d, %d) -> (%d,%d)]\n\n", + layer->mask.x1, layer->mask.y1, + layer->mask.x2, layer->mask.y2); + } + + wl_list_for_each(view, &layer->view_list.link, layer_link.link) + debug_scene_view_print(fp, view, view_idx++); + + if (wl_list_empty(&layer->view_list.link)) + fprintf(fp, "\t[no views]\n"); + + fprintf(fp, "\n"); + } + + err = fclose(fp); + assert(err == 0); + + return ret; +} + +/** + * Called when the 'scene-graph' debug scope is bound by a client. This + * one-shot weston-debug scope prints the current scene graph when bound, + * and then terminates the stream. + */ +static void +debug_scene_graph_cb(struct weston_debug_stream *stream, void *data) +{ + struct weston_compositor *ec = data; + char *str = weston_compositor_print_scene_graph(ec); + + weston_debug_stream_printf(stream, "%s", str); + free(str); + weston_debug_stream_complete(stream); +} + /** Create the compositor. * * This functions creates and initializes a compositor instance. @@ -6415,6 +6632,12 @@ weston_compositor_create(struct wl_display *display, void *user_data) weston_compositor_add_debug_binding(ec, KEY_T, timeline_key_binding_handler, ec); + ec->debug_scene = + weston_compositor_add_debug_scope(ec, "scene-graph", + "Scene graph details\n", + debug_scene_graph_cb, + ec); + return ec; fail: @@ -6714,6 +6937,8 @@ weston_compositor_destroy(struct weston_compositor *compositor) if (compositor->heads_changed_source) wl_event_source_remove(compositor->heads_changed_source); + weston_debug_scope_destroy(compositor->debug_scene); + compositor->debug_scene = NULL; weston_debug_compositor_destroy(compositor); free(compositor); diff --git a/libweston/compositor.h b/libweston/compositor.h index 069fb03d4..49013e14d 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1163,6 +1163,7 @@ struct weston_compositor { struct weston_touch_calibrator *touch_calibrator; struct weston_debug_compositor *weston_debug; + struct weston_debug_scope *debug_scene; }; struct weston_buffer { @@ -1933,6 +1934,9 @@ weston_buffer_reference(struct weston_buffer_reference *ref, void weston_compositor_get_time(struct timespec *time); +char * +weston_compositor_print_scene_graph(struct weston_compositor *ec); + void weston_compositor_destroy(struct weston_compositor *ec); struct weston_compositor * From 3158a2d42ec602c9dd138fcff1b9ad0c8e7db13d Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 20 Jul 2018 19:35:05 +0100 Subject: [PATCH 0623/1642] compositor-drm: Calculate atomic-commit flags earlier Shift up our calculation of the flags we use for atomic commits. We will later use this to differentiate between test-only and full commits when printing debug information inside drm_output_state_apply_atomic. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen --- libweston/compositor-drm.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 38911763c..94d787522 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2497,12 +2497,24 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, struct drm_output_state *output_state, *tmp; struct drm_plane *plane; drmModeAtomicReq *req = drmModeAtomicAlloc(); - uint32_t flags = 0; + uint32_t flags; int ret = 0; if (!req) return -1; + switch (mode) { + case DRM_STATE_APPLY_SYNC: + flags = 0; + break; + case DRM_STATE_APPLY_ASYNC: + flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; + break; + case DRM_STATE_TEST_ONLY: + flags = DRM_MODE_ATOMIC_TEST_ONLY; + break; + } + if (b->state_invalid) { struct weston_head *head_base; struct drm_head *head; @@ -2595,17 +2607,6 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, goto out; } - switch (mode) { - case DRM_STATE_APPLY_SYNC: - break; - case DRM_STATE_APPLY_ASYNC: - flags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; - break; - case DRM_STATE_TEST_ONLY: - flags |= DRM_MODE_ATOMIC_TEST_ONLY; - break; - } - ret = drmModeAtomicCommit(b->drm.fd, req, flags, b); /* Test commits do not take ownership of the state; return From 64dbbee7f6570949edc8bca30e6cd026a9e70a59 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 20 Jul 2018 19:00:06 +0100 Subject: [PATCH 0624/1642] compositor-drm: Add backend pointer to drm_output Add this for convenience, so it's easier to access when we add the DRM backend debug scope. Signed-off-by: Daniel Stone --- libweston/compositor-drm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 94d787522..612e6a8f0 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -463,6 +463,7 @@ struct drm_head { struct drm_output { struct weston_output base; + struct drm_backend *backend; uint32_t crtc_id; /* object ID to pass to DRM functions */ int pipe; /* index of CRTC in resource array / bitmasks */ @@ -6104,6 +6105,8 @@ drm_output_create(struct weston_compositor *compositor, const char *name) if (output == NULL) return NULL; + output->backend = b; + weston_output_init(&output->base, compositor, name); output->base.enable = drm_output_enable; From 1cbe1f952de2b22539e163c0055fa3dcbb2e2d54 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 20 Jul 2018 10:21:28 +0100 Subject: [PATCH 0625/1642] compositor-drm: Add drm-backend log debug scope Add a 'drm-debug' scope which prints verbose information about the DRM backend's repaint cycle, including the decision tree on how views are assigned (or not) to planes. Signed-off-by: Daniel Stone --- libweston/compositor-drm.c | 285 +++++++++++++++++++++++++++++++++---- 1 file changed, 258 insertions(+), 27 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 612e6a8f0..b21793e5a 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -51,6 +51,7 @@ #include "compositor.h" #include "compositor-drm.h" +#include "weston-debug.h" #include "shared/helpers.h" #include "shared/timespec-util.h" #include "gl-renderer.h" @@ -73,6 +74,42 @@ #define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64 #endif +/** + * A small wrapper to print information into the 'drm-backend' debug scope. + * + * The following conventions are used to print variables: + * + * - fixed uint32_t values, including Weston object IDs such as weston_output + * IDs, DRM object IDs such as CRTCs or properties, and GBM/DRM formats: + * "%lu (0x%lx)" (unsigned long) value, (unsigned long) value + * + * - fixed uint64_t values, such as DRM property values (including object IDs + * when used as a value): + * "%llu (0x%llx)" (unsigned long long) value, (unsigned long long) value + * + * - non-fixed-width signed int: + * "%d" value + * + * - non-fixed-width unsigned int: + * "%u (0x%x)" value, value + * + * - non-fixed-width unsigned long: + * "%lu (0x%lx)" value, value + * + * Either the integer or hexadecimal forms may be omitted if it is known that + * one representation is not useful (e.g. width/height in hex are rarely what + * you want). + * + * This is to avoid implicit widening or narrowing when we use fixed-size + * types: uint32_t can be resolved by either unsigned int or unsigned long + * on a 32-bit system but only unsigned int on a 64-bit system, with uint64_t + * being unsigned long long on a 32-bit system and unsigned long on a 64-bit + * system. To avoid confusing side effects, we explicitly cast to the widest + * possible type and use a matching format specifier. + */ +#define drm_debug(b, ...) \ + weston_debug_scope_printf((b)->debug, __VA_ARGS__) + #define MAX_CLONED_CONNECTORS 4 /** @@ -302,6 +339,8 @@ struct drm_backend { bool shutting_down; bool aspect_ratio_supported; + + struct weston_debug_scope *debug; }; struct drm_mode { @@ -2357,6 +2396,10 @@ crtc_add_prop(drmModeAtomicReq *req, struct drm_output *output, ret = drmModeAtomicAddProperty(req, output->crtc_id, info->prop_id, val); + drm_debug(output->backend, "\t\t\t[CRTC:%lu] %lu (%s) -> %llu (0x%llx)\n", + (unsigned long) output->crtc_id, + (unsigned long) info->prop_id, info->name, + (unsigned long long) val, (unsigned long long) val); return (ret <= 0) ? -1 : 0; } @@ -2372,6 +2415,10 @@ connector_add_prop(drmModeAtomicReq *req, struct drm_head *head, ret = drmModeAtomicAddProperty(req, head->connector_id, info->prop_id, val); + drm_debug(head->backend, "\t\t\t[CONN:%lu] %lu (%s) -> %llu (0x%llx)\n", + (unsigned long) head->connector_id, + (unsigned long) info->prop_id, info->name, + (unsigned long long) val, (unsigned long long) val); return (ret <= 0) ? -1 : 0; } @@ -2387,6 +2434,10 @@ plane_add_prop(drmModeAtomicReq *req, struct drm_plane *plane, ret = drmModeAtomicAddProperty(req, plane->plane_id, info->prop_id, val); + drm_debug(plane->backend, "\t\t\t[PLANE:%lu] %lu (%s) -> %llu (0x%llx)\n", + (unsigned long) plane->plane_id, + (unsigned long) info->prop_id, info->name, + (unsigned long long) val, (unsigned long long) val); return (ret <= 0) ? -1 : 0; } @@ -2405,6 +2456,9 @@ drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode) if (ret != 0) weston_log("failed to create mode property blob: %m\n"); + drm_debug(backend, "\t\t\t[atomic] created new mode blob %lu for %s", + (unsigned long) mode->blob_id, mode->mode_info.name); + return ret; } @@ -2414,17 +2468,23 @@ drm_output_apply_state_atomic(struct drm_output_state *state, uint32_t *flags) { struct drm_output *output = state->output; - struct drm_backend *backend = to_drm_backend(output->base.compositor); + struct drm_backend *b = to_drm_backend(output->base.compositor); struct drm_plane_state *plane_state; struct drm_mode *current_mode = to_drm_mode(output->base.current_mode); struct drm_head *head; int ret = 0; - if (state->dpms != output->state_cur->dpms) + drm_debug(b, "\t\t[atomic] %s output %lu (%s) state\n", + (*flags & DRM_MODE_ATOMIC_TEST_ONLY) ? "testing" : "applying", + (unsigned long) output->base.id, output->base.name); + + if (state->dpms != output->state_cur->dpms) { + drm_debug(b, "\t\t\t[atomic] DPMS state differs, modeset OK\n"); *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } if (state->dpms == WESTON_DPMS_ON) { - ret = drm_mode_ensure_blob(backend, current_mode); + ret = drm_mode_ensure_blob(b, current_mode); if (ret != 0) return ret; @@ -2522,6 +2582,9 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, uint32_t *unused; int err; + drm_debug(b, "\t\t[atomic] previous state invalid; " + "starting with fresh state\n"); + /* If we need to reset all our state (e.g. because we've * just started, or just been VT-switched in), explicitly * disable all the CRTCs and connectors we aren't using. */ @@ -2534,9 +2597,16 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, head = to_drm_head(head_base); + drm_debug(b, "\t\t[atomic] disabling inactive head %s\n", + head_base->name); + info = &head->props_conn[WDRM_CONNECTOR_CRTC_ID]; err = drmModeAtomicAddProperty(req, head->connector_id, info->prop_id, 0); + drm_debug(b, "\t\t\t[CONN:%lu] %lu (%s) -> 0\n", + (unsigned long) head->connector_id, + (unsigned long) info->prop_id, + info->name); if (err <= 0) ret = -1; } @@ -2573,12 +2643,21 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, continue; } + drm_debug(b, "\t\t[atomic] disabling unused CRTC %lu\n", + (unsigned long) *unused); + + drm_debug(b, "\t\t\t[CRTC:%lu] %lu (%s) -> 0\n", + (unsigned long) *unused, + (unsigned long) info->prop_id, info->name); err = drmModeAtomicAddProperty(req, *unused, info->prop_id, 0); if (err <= 0) ret = -1; info = &infos[WDRM_CRTC_MODE_ID]; + drm_debug(b, "\t\t\t[CRTC:%lu] %lu (%s) -> 0\n", + (unsigned long) *unused, + (unsigned long) info->prop_id, info->name); err = drmModeAtomicAddProperty(req, *unused, info->prop_id, 0); if (err <= 0) @@ -2590,6 +2669,8 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, /* Disable all the planes; planes which are being used will * override this state in the output-state application. */ wl_list_for_each(plane, &b->plane_list, link) { + drm_debug(b, "\t\t[atomic] starting with plane %lu disabled\n", + (unsigned long) plane->plane_id); plane_add_prop(req, plane, WDRM_PLANE_CRTC_ID, 0); plane_add_prop(req, plane, WDRM_PLANE_FB_ID, 0); } @@ -2972,6 +3053,14 @@ drm_repaint_begin(struct weston_compositor *compositor) ret = drm_pending_state_alloc(b); b->repaint_data = ret; + if (weston_debug_scope_is_enabled(b->debug)) { + char *dbg = weston_compositor_print_scene_graph(compositor); + drm_debug(b, "[repaint] Beginning repaint; pending_state %p\n", + ret); + drm_debug(b, "%s", dbg); + free(dbg); + } + return ret; } @@ -2991,6 +3080,7 @@ drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) struct drm_pending_state *pending_state = repaint_data; drm_pending_state_apply(pending_state); + drm_debug(b, "[repaint] flushed pending_state %p\n", pending_state); b->repaint_data = NULL; } @@ -3007,6 +3097,7 @@ drm_repaint_cancel(struct weston_compositor *compositor, void *repaint_data) struct drm_pending_state *pending_state = repaint_data; drm_pending_state_free(pending_state); + drm_debug(b, "[repaint] cancel pending_state %p\n", pending_state); b->repaint_data = NULL; } @@ -3050,12 +3141,21 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, struct drm_fb *fb; unsigned int i; int ret; + enum { + NO_PLANES, + NO_PLANES_WITH_FORMAT, + NO_PLANES_ACCEPTED, + PLACED_ON_PLANE, + } availability = NO_PLANES; assert(!b->sprites_are_broken); fb = drm_fb_get_from_view(output_state, ev); - if (!fb) + if (!fb) { + drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " + " couldn't get fb\n", ev); return NULL; + } wl_list_for_each(p, &b->plane_list, link) { if (p->type != WDRM_PLANE_TYPE_OVERLAY) @@ -3064,6 +3164,15 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, if (!drm_plane_is_available(p, output)) continue; + state = drm_output_state_get_plane(output_state, p); + if (state->fb) { + state = NULL; + continue; + } + + if (availability == NO_PLANES) + availability = NO_PLANES_WITH_FORMAT; + /* Check whether the format is supported */ for (i = 0; i < p->count_formats; i++) { unsigned int j; @@ -3084,15 +3193,14 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, if (i == p->count_formats) continue; - state = drm_output_state_get_plane(output_state, p); - if (state->fb) { - state = NULL; - continue; - } + if (availability == NO_PLANES_WITH_FORMAT) + availability = NO_PLANES_ACCEPTED; state->ev = ev; state->output = output; if (!drm_plane_state_coords_for_view(state, ev)) { + drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " + "unsuitable transform\n", ev); drm_plane_state_put_back(state); state = NULL; continue; @@ -3100,6 +3208,8 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, if (!b->atomic_modeset && (state->src_w != state->dest_w << 16 || state->src_h != state->dest_h << 16)) { + drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " + "no scaling without atomic\n", ev); drm_plane_state_put_back(state); state = NULL; continue; @@ -3113,17 +3223,48 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, /* In planes-only mode, we don't have an incremental state to * test against, so we just hope it'll work. */ - if (mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) + if (mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) { + drm_debug(b, "\t\t\t\t[overlay] provisionally placing " + "view %p on overlay %lu in planes-only mode\n", + ev, (unsigned long) p->plane_id); + availability = PLACED_ON_PLANE; goto out; + } ret = drm_pending_state_test(output_state->pending_state); - if (ret == 0) + if (ret == 0) { + drm_debug(b, "\t\t\t\t[overlay] provisionally placing " + "view %p on overlay %d in mixed mode\n", + ev, p->plane_id); + availability = PLACED_ON_PLANE; goto out; + } + + drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay %lu " + "in mixed mode: kernel test failed\n", + ev, (unsigned long) p->plane_id); drm_plane_state_put_back(state); state = NULL; } + switch (availability) { + case NO_PLANES: + drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " + "no free overlay planes\n", ev); + break; + case NO_PLANES_WITH_FORMAT: + drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " + "no free overlay planes matching format 0x%lx, " + "modifier 0x%llx\n", + ev, (unsigned long) fb->format, + (unsigned long long) fb->modifier); + break; + case NO_PLANES_ACCEPTED: + case PLACED_ON_PLANE: + break; + } + out: drm_fb_unref(fb); return state; @@ -3192,13 +3333,23 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, if (b->gbm == NULL) return NULL; - if (ev->surface->buffer_ref.buffer == NULL) + if (ev->surface->buffer_ref.buffer == NULL) { + drm_debug(b, "\t\t\t\t[cursor] not assigning view %p to cursor plane " + "(no buffer available)\n", ev); return NULL; + } shmbuf = wl_shm_buffer_get(ev->surface->buffer_ref.buffer->resource); - if (!shmbuf) + if (!shmbuf) { + drm_debug(b, "\t\t\t\t[cursor] not assigning view %p to cursor plane " + "(buffer isn't SHM)\n", ev); return NULL; - if (wl_shm_buffer_get_format(shmbuf) != WL_SHM_FORMAT_ARGB8888) + } + if (wl_shm_buffer_get_format(shmbuf) != WL_SHM_FORMAT_ARGB8888) { + drm_debug(b, "\t\t\t\t[cursor] not assigning view %p to cursor plane " + "(format 0x%lx unsuitable)\n", + ev, (unsigned long) wl_shm_buffer_get_format(shmbuf)); return NULL; + } plane_state = drm_output_state_get_plane(output_state, output->cursor_plane); @@ -3216,8 +3367,11 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, plane_state->src_w > (unsigned) b->cursor_width << 16 || plane_state->src_h > (unsigned) b->cursor_height << 16 || plane_state->src_w != plane_state->dest_w << 16 || - plane_state->src_h != plane_state->dest_h << 16) + plane_state->src_h != plane_state->dest_h << 16) { + drm_debug(b, "\t\t\t\t[cursor] not assigning view %p to cursor plane " + "(positioning requires cropping or scaling)\n", ev); goto err; + } /* Since we're setting plane state up front, we need to work out * whether or not we need to upload a new cursor. We can't use the @@ -3240,8 +3394,10 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, plane_state->fb = drm_fb_ref(output->gbm_cursor_fb[output->current_cursor]); - if (needs_update) + if (needs_update) { + drm_debug(b, "\t\t\t\t[cursor] copying new content to cursor BO\n"); cursor_bo_update(plane_state, ev); + } /* The cursor API is somewhat special: in cursor_bo_update(), we upload * a buffer which is always cursor_width x cursor_height, even if the @@ -3252,6 +3408,9 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, plane_state->dest_w = b->cursor_width; plane_state->dest_h = b->cursor_height; + drm_debug(b, "\t\t\t\t[cursor] provisionally assigned view %p to cursor\n", + ev); + return plane_state; err: @@ -3347,18 +3506,32 @@ drm_output_propose_state(struct weston_output *output_base, if (!scanout_fb || (scanout_fb->type != BUFFER_GBM_SURFACE && scanout_fb->type != BUFFER_PIXMAN_DUMB)) { + drm_debug(b, "\t\t[state] cannot propose mixed mode: " + "for output %s (%lu): no previous renderer " + "fb\n", + output->base.name, + (unsigned long) output->base.id); drm_output_state_free(state); return NULL; } if (scanout_fb->width != output_base->current_mode->width || scanout_fb->height != output_base->current_mode->height) { + drm_debug(b, "\t\t[state] cannot propose mixed mode " + "for output %s (%lu): previous fb has " + "different size\n", + output->base.name, + (unsigned long) output->base.id); drm_output_state_free(state); return NULL; } scanout_state = drm_plane_state_duplicate(state, plane->state_cur); + drm_debug(b, "\t\t[state] using renderer FB ID %lu for mixed " + "mode for output %s (%lu)\n", + (unsigned long) scanout_fb->fb_id, output->base.name, + (unsigned long) output->base.id); } /* @@ -3384,18 +3557,32 @@ drm_output_propose_state(struct weston_output *output_base, bool totally_occluded = false; bool overlay_occluded = false; + drm_debug(b, "\t\t\t[view] evaluating view %p for " + "output %s (%lu)\n", + ev, output->base.name, + (unsigned long) output->base.id); + /* If this view doesn't touch our output at all, there's no * reason to do anything with it. */ - if (!(ev->output_mask & (1u << output->base.id))) + if (!(ev->output_mask & (1u << output->base.id))) { + drm_debug(b, "\t\t\t\t[view] ignoring view %p " + "(not on our output)\n", ev); continue; + } /* We only assign planes to views which are exclusively present * on our output. */ - if (ev->output_mask != (1u << output->base.id)) + if (ev->output_mask != (1u << output->base.id)) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(on multiple outputs)\n", ev); force_renderer = true; + } - if (!ev->surface->buffer_ref.buffer) + if (!ev->surface->buffer_ref.buffer) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(no buffer available)\n", ev); force_renderer = true; + } /* Ignore views we know to be totally occluded. */ pixman_region32_init(&clipped_view); @@ -3408,6 +3595,8 @@ drm_output_propose_state(struct weston_output *output_base, &occluded_region); totally_occluded = !pixman_region32_not_empty(&surface_overlap); if (totally_occluded) { + drm_debug(b, "\t\t\t\t[view] ignoring view %p " + "(occluded on our output)\n", ev); pixman_region32_fini(&surface_overlap); pixman_region32_fini(&clipped_view); continue; @@ -3418,8 +3607,11 @@ drm_output_propose_state(struct weston_output *output_base, * be part of, or occluded by, it, and cannot go on a plane. */ pixman_region32_intersect(&surface_overlap, &renderer_region, &clipped_view); - if (pixman_region32_not_empty(&surface_overlap)) + if (pixman_region32_not_empty(&surface_overlap)) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(occluded by renderer views)\n", ev); force_renderer = true; + } /* We do not control the stacking order of overlay planes; * the scanout plane is strictly stacked bottom and the cursor @@ -3428,8 +3620,11 @@ drm_output_propose_state(struct weston_output *output_base, * planes overlapping each other. */ pixman_region32_intersect(&surface_overlap, &occluded_region, &clipped_view); - if (pixman_region32_not_empty(&surface_overlap)) + if (pixman_region32_not_empty(&surface_overlap)) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(occluded by other overlay planes)\n", ev); overlay_occluded = true; + } pixman_region32_fini(&surface_overlap); /* The cursor plane is 'special' in the sense that we can still @@ -3441,10 +3636,16 @@ drm_output_propose_state(struct weston_output *output_base, /* If sprites are disabled or the view is not fully opaque, we * must put the view into the renderer - unless it has already * been placed in the cursor plane, which can handle alpha. */ - if (!ps && !planes_ok) + if (!ps && !planes_ok) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(precluded by mode)\n", ev); force_renderer = true; - if (!ps && !drm_view_is_opaque(ev)) + } + if (!ps && !drm_view_is_opaque(ev)) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(view not fully opaque)\n", ev); force_renderer = true; + } /* Only try to place scanout surfaces in planes-only mode; in * mixed mode, we have already failed to place a view on the @@ -3477,6 +3678,9 @@ drm_output_propose_state(struct weston_output *output_base, * check if this is OK, and add ourselves to the renderer * region if so. */ if (!renderer_ok) { + drm_debug(b, "\t\t[view] failing state generation: " + "placing view %p to renderer not allowed\n", + ev); pixman_region32_fini(&clipped_view); goto err_region; } @@ -3496,8 +3700,11 @@ drm_output_propose_state(struct weston_output *output_base, /* Check to see if this state will actually work. */ ret = drm_pending_state_test(state->pending_state); - if (ret != 0) + if (ret != 0) { + drm_debug(b, "\t\t[view] failing state generation: " + "atomic test not OK\n"); goto err; + } /* Counterpart to duplicating scanout state at the top of this * function: if we have taken a renderer framebuffer and placed it in @@ -3530,12 +3737,24 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) struct weston_view *ev; struct weston_plane *primary = &output_base->compositor->primary_plane; + drm_debug(b, "\t[repaint] preparing state for output %s (%lu)\n", + output_base->name, (unsigned long) output_base->id); + if (!b->sprites_are_broken) { state = drm_output_propose_state(output_base, pending_state, DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); - if (!state) + if (!state) { + drm_debug(b, "\t[repaint] could not build planes-only " + "state, trying mixed\n"); state = drm_output_propose_state(output_base, pending_state, DRM_OUTPUT_PROPOSE_STATE_MIXED); + } + if (!state) { + drm_debug(b, "\t[repaint] could not build mixed-mode " + "state, trying renderer-only\n"); + } + } else { + drm_debug(b, "\t[state] no overlay plane support\n"); } if (!state) @@ -3582,10 +3801,16 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) } } - if (target_plane) + if (target_plane) { + drm_debug(b, "\t[repaint] view %p on %s plane %lu\n", + ev, plane_type_enums[target_plane->type].name, + (unsigned long) target_plane->plane_id); weston_view_move_to_plane(ev, &target_plane->base); - else + } else { + drm_debug(b, "\t[repaint] view %p using renderer " + "composition\n", ev); weston_view_move_to_plane(ev, primary); + } if (!target_plane || target_plane->type == WDRM_PLANE_TYPE_CURSOR) { @@ -6264,6 +6489,8 @@ drm_destroy(struct weston_compositor *ec) destroy_sprites(b); + weston_debug_scope_destroy(b->debug); + b->debug = NULL; weston_compositor_shutdown(ec); wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) @@ -6725,6 +6952,10 @@ drm_backend_create(struct weston_compositor *compositor, b->pageflip_timeout = config->pageflip_timeout; b->use_pixman_shadow = config->use_pixman_shadow; + b->debug = weston_compositor_add_debug_scope(compositor, "drm-backend", + "Debug messages from DRM/KMS backend\n", + NULL, NULL); + compositor->backend = &b->base; if (parse_gbm_format(config->gbm_format, GBM_FORMAT_XRGB8888, &b->gbm_format) < 0) From 3b1c1efe345ed93713747923792240068c437a9a Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 22 Aug 2018 14:09:18 +0200 Subject: [PATCH 0626/1642] main: do not exit in case stdin is a closed stream Weston should not exit if stdin is a closed stream. This allows to launch with a closed stdin: # weston <&- This fixes screen sharing using weston: Weston closes the stdin before forking itself to execute the screen sharing instance of weston. Before this patch screen sharing failed with: Screen share failed: No wl_shm found unknown child process exited Fixes: f0d39b2243e5 ("weston: Set CLOEXEC on stdin") Signed-off-by: Stefan Agner --- compositor/main.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 22f3a2754..14ad241ae 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2548,10 +2548,7 @@ int main(int argc, char *argv[]) wl_list_init(&wet.layoutput_list); - if (os_fd_set_cloexec(fileno(stdin))) { - printf("Unable to set stdin as close on exec().\n"); - return EXIT_FAILURE; - } + os_fd_set_cloexec(fileno(stdin)); cmdline = copy_command_line(argc, argv); parse_options(core_options, ARRAY_LENGTH(core_options), &argc, argv); From 1c1e4fdaf44fadd83d09077e4c1571ff559c8736 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 22 Aug 2018 23:33:10 +0200 Subject: [PATCH 0627/1642] desktop-shell: always paint background color first Only draw the background once, using the the current default background color or the user specified background color. This allows for non-filling background image implemenation while still using the specified background color. Signed-off-by: Stefan Agner --- clients/desktop-shell.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index fcc0b657b..4d0b1d028 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -756,7 +756,10 @@ background_draw(struct widget *widget, void *data) cr = widget_cairo_create(background->widget); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_rgba(cr, 0.0, 0.0, 0.2, 1.0); + if (background->color == 0) + cairo_set_source_rgba(cr, 0.0, 0.0, 0.2, 1.0); + else + set_hex_color(cr, background->color); cairo_paint(cr); widget_get_allocation(widget, &allocation); @@ -802,8 +805,6 @@ background_draw(struct widget *widget, void *data) cairo_set_source(cr, pattern); cairo_pattern_destroy (pattern); cairo_surface_destroy(image); - } else { - set_hex_color(cr, background->color); } cairo_paint(cr); From 20b241691b9a337e2ebaca23621c8f30ae54ecc7 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 22 Aug 2018 23:56:07 +0200 Subject: [PATCH 0628/1642] desktop-shell: allow to center background image Add the centered option as background-type. This draws the image once in the center of the screen. If the image is larger, it will be cropped like scale-crop. Signed-off-by: Stefan Agner --- clients/desktop-shell.c | 20 ++++++++++++++++++-- man/weston.ini.man | 5 ++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index 4d0b1d028..0dc0f0ba3 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -736,7 +736,8 @@ panel_add_launcher(struct panel *panel, const char *icon, const char *path) enum { BACKGROUND_SCALE, BACKGROUND_SCALE_CROP, - BACKGROUND_TILE + BACKGROUND_TILE, + BACKGROUND_CENTERED }; static void @@ -800,14 +801,27 @@ background_draw(struct widget *widget, void *data) case BACKGROUND_TILE: cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); break; + case BACKGROUND_CENTERED: + s = (sx < sy) ? sx : sy; + if (s < 1.0) + s = 1.0; + + /* align center */ + tx = (im_w - s * allocation.width) * 0.5; + ty = (im_h - s * allocation.height) * 0.5; + + cairo_matrix_init_translate(&matrix, tx, ty); + cairo_matrix_scale(&matrix, s, s); + cairo_pattern_set_matrix(pattern, &matrix); + break; } cairo_set_source(cr, pattern); cairo_pattern_destroy (pattern); cairo_surface_destroy(image); + cairo_mask(cr, pattern); } - cairo_paint(cr); cairo_destroy(cr); cairo_surface_destroy(surface); @@ -1143,6 +1157,8 @@ background_create(struct desktop *desktop, struct output *output) background->type = BACKGROUND_SCALE_CROP; } else if (strcmp(type, "tile") == 0) { background->type = BACKGROUND_TILE; + } else if (strcmp(type, "centered") == 0) { + background->type = BACKGROUND_CENTERED; } else { background->type = -1; fprintf(stderr, "invalid background-type: %s\n", diff --git a/man/weston.ini.man b/man/weston.ini.man index d4e367a6e..86a9c5dfd 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -260,7 +260,10 @@ sets the path for the background image file (string). .TP 7 .BI "background-type=" tile determines how the background image is drawn (string). Can be -.BR scale ", " scale-crop " or " tile " (default)." +.BR centered ", " scale ", " scale-crop " or " tile " (default)." +Centered shows the image once centered. If the image is smaller than the +output, the rest of the surface will be in background color. If the image +size does fit the output it will be cropped left and right, or top and bottom. Scale means scaled to fit the output precisely, not preserving aspect ratio. Scale-crop preserves aspect ratio, scales the background image just big enough to cover the output, and centers it. The image ends up cropped from From b49af1ed199a2b69350447f5d8aa4f66c6fa7796 Mon Sep 17 00:00:00 2001 From: emersion Date: Mon, 17 Sep 2018 23:51:13 +0200 Subject: [PATCH 0629/1642] weston-info: add xdg-output support --- Makefile.am | 8 +- clients/weston-info.c | 181 +++++++++++++++++++++++++++++++++++++++++- configure.ac | 2 +- 3 files changed, 186 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index 333af16da..4cd3ce9ed 100644 --- a/Makefile.am +++ b/Makefile.am @@ -856,7 +856,9 @@ nodist_weston_info_SOURCES = \ protocol/linux-dmabuf-unstable-v1-protocol.c \ protocol/linux-dmabuf-unstable-v1-client-protocol.h \ protocol/tablet-unstable-v2-protocol.c \ - protocol/tablet-unstable-v2-client-protocol.h + protocol/tablet-unstable-v2-client-protocol.h \ + protocol/xdg-output-unstable-v1-protocol.c \ + protocol/xdg-output-unstable-v1-client-protocol.h weston_info_LDADD = $(WESTON_INFO_LIBS) libshared.la weston_info_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) @@ -932,7 +934,9 @@ BUILT_SOURCES += \ protocol/tablet-unstable-v2-protocol.c \ protocol/tablet-unstable-v2-client-protocol.h \ protocol/input-timestamps-unstable-v1-protocol.c \ - protocol/input-timestamps-unstable-v1-client-protocol.h + protocol/input-timestamps-unstable-v1-client-protocol.h \ + protocol/xdg-output-unstable-v1-protocol.c \ + protocol/xdg-output-unstable-v1-client-protocol.h westondatadir = $(datadir)/weston dist_westondata_DATA = \ diff --git a/clients/weston-info.c b/clients/weston-info.c index 609e270a4..3cfeed6d1 100644 --- a/clients/weston-info.c +++ b/clients/weston-info.c @@ -42,6 +42,7 @@ #include "presentation-time-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "tablet-unstable-v2-client-protocol.h" +#include "xdg-output-unstable-v1-client-protocol.h" typedef void (*print_info_t)(void *info); typedef void (*destroy_info_t)(void *info); @@ -67,6 +68,7 @@ struct output_mode { struct output_info { struct global_info global; + struct wl_list global_link; struct wl_output *output; @@ -138,7 +140,7 @@ struct tablet_tool_info { uint64_t hardware_serial; uint64_t hardware_id_wacom; enum zwp_tablet_tool_v2_type type; - + bool has_tilt; bool has_pressure; bool has_distance; @@ -195,6 +197,28 @@ struct tablet_v2_info { struct wl_list seats; }; +struct xdg_output_v1_info { + struct wl_list link; + + struct zxdg_output_v1 *xdg_output; + struct output_info *output; + + struct { + int32_t x, y; + int32_t width, height; + } logical; + + char *name, *description; +}; + +struct xdg_output_manager_v1_info { + struct global_info global; + struct zxdg_output_manager_v1 *manager; + struct weston_info *info; + + struct wl_list outputs; +}; + struct presentation_info { struct global_info global; struct wp_presentation *presentation; @@ -212,6 +236,10 @@ struct weston_info { /* required for tablet-unstable-v2 */ struct wl_list seats; struct tablet_v2_info *tablet_info; + + /* required for xdg-output-unstable-v1 */ + struct wl_list outputs; + struct xdg_output_manager_v1_info *xdg_output_manager_v1_info; }; static void @@ -844,7 +872,7 @@ handle_tablet_v2_tablet_tool_capability(void *data, { struct tablet_tool_info *info = data; enum zwp_tablet_tool_v2_capability cap = capability; - + switch(cap) { case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT: info->has_tilt = true; @@ -1295,6 +1323,146 @@ add_tablet_v2_info(struct weston_info *info, uint32_t id, uint32_t version) info->tablet_info = tablet; } +static void +destroy_xdg_output_v1_info(struct xdg_output_v1_info *info) +{ + wl_list_remove(&info->link); + zxdg_output_v1_destroy(info->xdg_output); + free(info->name); + free(info->description); + free(info); +} + +static void +print_xdg_output_v1_info(const struct xdg_output_v1_info *info) +{ + printf("\txdg_output_v1\n"); + printf("\t\toutput: %d\n", info->output->global.id); + if (info->name) + printf("\t\tname: '%s'\n", info->name); + if (info->description) + printf("\t\tdescription: '%s'\n", info->description); + printf("\t\tlogical_x: %d, logical_y: %d\n", + info->logical.x, info->logical.y); + printf("\t\tlogical_width: %d, logical_height: %d\n", + info->logical.width, info->logical.height); +} + +static void +print_xdg_output_manager_v1_info(void *data) +{ + struct xdg_output_manager_v1_info *info = data; + struct xdg_output_v1_info *output; + + print_global_info(data); + + wl_list_for_each(output, &info->outputs, link) + print_xdg_output_v1_info(output); +} + +static void +destroy_xdg_output_manager_v1_info(void *data) +{ + struct xdg_output_manager_v1_info *info = data; + struct xdg_output_v1_info *output, *tmp; + + zxdg_output_manager_v1_destroy(info->manager); + + wl_list_for_each_safe(output, tmp, &info->outputs, link) + destroy_xdg_output_v1_info(output); +} + +static void +handle_xdg_output_v1_logical_position(void *data, struct zxdg_output_v1 *output, + int32_t x, int32_t y) +{ + struct xdg_output_v1_info *xdg_output = data; + xdg_output->logical.x = x; + xdg_output->logical.y = y; +} + +static void +handle_xdg_output_v1_logical_size(void *data, struct zxdg_output_v1 *output, + int32_t width, int32_t height) +{ + struct xdg_output_v1_info *xdg_output = data; + xdg_output->logical.width = width; + xdg_output->logical.height = height; +} + +static void +handle_xdg_output_v1_done(void *data, struct zxdg_output_v1 *output) +{ + /* Don't bother waiting for this; there's no good reason a + * compositor will wait more than one roundtrip before sending + * these initial events. */ +} + +static void +handle_xdg_output_v1_name(void *data, struct zxdg_output_v1 *output, + const char *name) +{ + struct xdg_output_v1_info *xdg_output = data; + xdg_output->name = strdup(name); +} + +static void +handle_xdg_output_v1_description(void *data, struct zxdg_output_v1 *output, + const char *description) +{ + struct xdg_output_v1_info *xdg_output = data; + xdg_output->description = strdup(description); +} + +static const struct zxdg_output_v1_listener xdg_output_v1_listener = { + .logical_position = handle_xdg_output_v1_logical_position, + .logical_size = handle_xdg_output_v1_logical_size, + .done = handle_xdg_output_v1_done, + .name = handle_xdg_output_v1_name, + .description = handle_xdg_output_v1_description, +}; + +static void +add_xdg_output_v1_info(struct xdg_output_manager_v1_info *manager_info, + struct output_info *output) +{ + struct xdg_output_v1_info *xdg_output = xzalloc(sizeof *xdg_output); + + wl_list_insert(&manager_info->outputs, &xdg_output->link); + xdg_output->xdg_output = zxdg_output_manager_v1_get_xdg_output( + manager_info->manager, output->output); + zxdg_output_v1_add_listener(xdg_output->xdg_output, + &xdg_output_v1_listener, xdg_output); + + xdg_output->output = output; + + manager_info->info->roundtrip_needed = true; +} + +static void +add_xdg_output_manager_v1_info(struct weston_info *info, uint32_t id, + uint32_t version) +{ + struct output_info *output; + struct xdg_output_manager_v1_info *manager = xzalloc(sizeof *manager); + + wl_list_init(&manager->outputs); + manager->info = info; + + init_global_info(info, &manager->global, id, + zxdg_output_manager_v1_interface.name, version); + manager->global.print = print_xdg_output_manager_v1_info; + manager->global.destroy = destroy_xdg_output_manager_v1_info; + + manager->manager = wl_registry_bind(info->registry, id, + &zxdg_output_manager_v1_interface, version > 2 ? 2 : version); + + wl_list_for_each(output, &info->outputs, global_link) + add_xdg_output_v1_info(manager, output); + + info->xdg_output_manager_v1_info = manager; +} + static void add_seat_info(struct weston_info *info, uint32_t id, uint32_t version) { @@ -1522,6 +1690,11 @@ add_output_info(struct weston_info *info, uint32_t id, uint32_t version) output); info->roundtrip_needed = true; + wl_list_insert(&info->outputs, &output->global_link); + + if (info->xdg_output_manager_v1_info) + add_xdg_output_v1_info(info->xdg_output_manager_v1_info, + output); } static void @@ -1629,6 +1802,8 @@ global_handler(void *data, struct wl_registry *registry, uint32_t id, add_presentation_info(info, id, version); else if (!strcmp(interface, zwp_tablet_manager_v2_interface.name)) add_tablet_v2_info(info, id, version); + else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)) + add_xdg_output_manager_v1_info(info, id, version); else add_global_info(info, id, interface, version); } @@ -1683,8 +1858,10 @@ main(int argc, char **argv) } info.tablet_info = NULL; + info.xdg_output_manager_v1_info = NULL; wl_list_init(&info.infos); wl_list_init(&info.seats); + wl_list_init(&info.outputs); info.registry = wl_display_get_registry(info.display); wl_registry_add_listener(info.registry, ®istry_listener, &info); diff --git a/configure.ac b/configure.ac index cb62a386c..32b9f1404 100644 --- a/configure.ac +++ b/configure.ac @@ -227,7 +227,7 @@ fi PKG_CHECK_MODULES(LIBINPUT_BACKEND, [libinput >= 0.8.0]) PKG_CHECK_MODULES(COMPOSITOR, [$COMPOSITOR_MODULES]) -PKG_CHECK_MODULES(WAYLAND_PROTOCOLS, [wayland-protocols >= 1.13], +PKG_CHECK_MODULES(WAYLAND_PROTOCOLS, [wayland-protocols >= 1.14], [ac_wayland_protocols_pkgdatadir=`$PKG_CONFIG --variable=pkgdatadir wayland-protocols`]) AC_SUBST(WAYLAND_PROTOCOLS_DATADIR, $ac_wayland_protocols_pkgdatadir) From e0b3022ebb4ba82bfc2a641ae628d37632169f1d Mon Sep 17 00:00:00 2001 From: Sruthik P Date: Thu, 7 Jun 2018 11:54:59 +0530 Subject: [PATCH 0630/1642] Fix and remove broken links to confluence pages --- ivi-shell/README | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ivi-shell/README b/ivi-shell/README index 412e59893..a54d1aea2 100644 --- a/ivi-shell/README +++ b/ivi-shell/README @@ -14,7 +14,7 @@ IVI-shell contains two main features: to develop own shell, linking Common layout library. For the time being, the library refers Genivi ilm interface. - http://projects.genivi.org/wayland-ivi-extension/ + https://at.projects.genivi.org/wiki/display/WIE/Wayland+IVI+Extension+Home - Extension protocol; ivi-application to tie wl_surface and a given ID. With this ID, shell can identify which wl_surface is drawn by which @@ -37,7 +37,7 @@ The actual software components delivered with Weston are: - ivi-layout.so: Implements the IVI window management concepts: Screen, Layer, Surface, groups of Layers, groups of Surfaces, see: - http://projects.genivi.org/ivi-layer-management/node/13 + https://at.projects.genivi.org/wiki/display/WIE/Summary+of+Layer+manager+APIs Offers a stable API for writing IVI-controller modules like hmi-controller.so against the IVI concepts. In other words, it offers an API to write IVI window manager modules. @@ -49,8 +49,7 @@ The actual software components delivered with Weston are: This implementation keeps all window management inside the module, while IVI-systems may use another module that exposes all window management via Wayland or other protocol for an external process - to control: - http://git.projects.genivi.org/?p=wayland-ivi-extension.git;a=summary + to control - ivi-hmi-controller.xml: Wayland protocol extension for IVI display control; the private From 9fd254d7b2a6a5c1f48daf37bb5938c205fa3125 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Tue, 18 Sep 2018 11:54:29 +0200 Subject: [PATCH 0631/1642] ivi-shell: Add missing sentence point Signed-off-by: Emmanuel Gil Peyrot --- ivi-shell/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivi-shell/README b/ivi-shell/README index a54d1aea2..1d2212ea0 100644 --- a/ivi-shell/README +++ b/ivi-shell/README @@ -49,7 +49,7 @@ The actual software components delivered with Weston are: This implementation keeps all window management inside the module, while IVI-systems may use another module that exposes all window management via Wayland or other protocol for an external process - to control + to control. - ivi-hmi-controller.xml: Wayland protocol extension for IVI display control; the private From f97d2508479c3aa28c164f727b89599ff3df2a03 Mon Sep 17 00:00:00 2001 From: Changwoo Cho Date: Sat, 5 Aug 2017 00:30:47 +0900 Subject: [PATCH 0632/1642] libweston: fix typo in comment --- libweston/compositor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 2ca3da3b4..e867ec1e5 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -3798,7 +3798,7 @@ weston_surface_get_bounding_box(struct weston_surface *surface) * The rectangle defined by src_x, src_y, width, height must fit in * the surface contents. Otherwise an error is returned. * - * Use surface_get_data_size to determine the content size; the + * Use weston_surface_get_content_size to determine the content size; the * needed target buffer size and rectangle limits. * * CURRENT IMPLEMENTATION RESTRICTIONS: From 195dadeb2add729735c32b9e394d7d1397cf065c Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Mon, 3 Sep 2018 19:44:59 +0200 Subject: [PATCH 0633/1642] libweston: add weston_surface is_opaque property Add an is_opaque property that is set to true if the attached buffer does not have an alpha component, or if the solid color is non-transparent. Signed-off-by: Philipp Zabel --- libweston/compositor.c | 1 + libweston/compositor.h | 1 + libweston/gl-renderer.c | 29 +++++++++++++++++++++++++++++ libweston/pixman-renderer.c | 3 +++ 4 files changed, 34 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index e867ec1e5..0dcb1df3f 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -532,6 +532,7 @@ weston_surface_set_color(struct weston_surface *surface, float red, float green, float blue, float alpha) { surface->compositor->renderer->surface_set_color(surface, red, green, blue, alpha); + surface->is_opaque = !(alpha < 1.0); } WL_EXPORT void diff --git a/libweston/compositor.h b/libweston/compositor.h index 49013e14d..6a57b8292 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1481,6 +1481,7 @@ struct weston_surface { struct weston_timeline_object timeline; bool is_mapped; + bool is_opaque; /* An list of per seat pointer constraints. */ struct wl_list pointer_constraints; diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 7a848cd19..95cbc22e1 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -54,6 +54,7 @@ #include "vertex-clipping.h" #include "linux-dmabuf.h" #include "linux-dmabuf-unstable-v1-server-protocol.h" +#include "pixel-formats.h" #include "shared/helpers.h" #include "shared/platform.h" @@ -1570,18 +1571,21 @@ gl_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer, pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; gl_format[0] = GL_BGRA_EXT; gl_pixel_type = GL_UNSIGNED_BYTE; + es->is_opaque = true; break; case WL_SHM_FORMAT_ARGB8888: gs->shader = &gr->texture_shader_rgba; pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; gl_format[0] = GL_BGRA_EXT; gl_pixel_type = GL_UNSIGNED_BYTE; + es->is_opaque = false; break; case WL_SHM_FORMAT_RGB565: gs->shader = &gr->texture_shader_rgbx; pitch = wl_shm_buffer_get_stride(shm_buffer) / 2; gl_format[0] = GL_RGB; gl_pixel_type = GL_UNSIGNED_SHORT_5_6_5; + es->is_opaque = true; break; case WL_SHM_FORMAT_YUV420: gs->shader = &gr->texture_shader_y_u_v; @@ -1605,6 +1609,7 @@ gl_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer, gl_format[1] = GL_LUMINANCE; gl_format[2] = GL_LUMINANCE; } + es->is_opaque = true; break; case WL_SHM_FORMAT_NV12: pitch = wl_shm_buffer_get_stride(shm_buffer); @@ -1623,6 +1628,7 @@ gl_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer, gl_format[0] = GL_LUMINANCE; gl_format[1] = GL_LUMINANCE_ALPHA; } + es->is_opaque = true; break; case WL_SHM_FORMAT_YUYV: gs->shader = &gr->texture_shader_y_xuxv; @@ -1637,6 +1643,7 @@ gl_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer, else gl_format[0] = GL_LUMINANCE_ALPHA; gl_format[1] = GL_BGRA_EXT; + es->is_opaque = true; break; default: weston_log("warning: unknown shm buffer format: %08x\n", @@ -1695,8 +1702,11 @@ gl_renderer_attach_egl(struct weston_surface *es, struct weston_buffer *buffer, } gs->num_images = 0; gs->target = GL_TEXTURE_2D; + es->is_opaque = false; switch (format) { case EGL_TEXTURE_RGB: + es->is_opaque = true; + /* fallthrough */ case EGL_TEXTURE_RGBA: default: num_planes = 1; @@ -1710,14 +1720,17 @@ gl_renderer_attach_egl(struct weston_surface *es, struct weston_buffer *buffer, case EGL_TEXTURE_Y_UV_WL: num_planes = 2; gs->shader = &gr->texture_shader_y_uv; + es->is_opaque = true; break; case EGL_TEXTURE_Y_U_V_WL: num_planes = 3; gs->shader = &gr->texture_shader_y_u_v; + es->is_opaque = true; break; case EGL_TEXTURE_Y_XUXV_WL: num_planes = 2; gs->shader = &gr->texture_shader_y_xuxv; + es->is_opaque = true; break; } @@ -2233,6 +2246,19 @@ import_known_dmabuf(struct gl_renderer *gr, return true; } +static bool +dmabuf_is_opaque(struct linux_dmabuf_buffer *dmabuf) +{ + const struct pixel_format_info *info; + + info = pixel_format_get_info(dmabuf->attributes.format & + ~DRM_FORMAT_BIG_ENDIAN); + if (!info) + return false; + + return pixel_format_is_opaque(info); +} + static void gl_renderer_attach_dmabuf(struct weston_surface *surface, struct weston_buffer *buffer, @@ -2305,6 +2331,7 @@ gl_renderer_attach_dmabuf(struct weston_surface *surface, gs->height = buffer->height; gs->buffer_type = BUFFER_TYPE_EGL; gs->y_inverted = buffer->y_inverted; + surface->is_opaque = dmabuf_is_opaque(dmabuf); } static void @@ -2330,6 +2357,7 @@ gl_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) gs->num_textures = 0; gs->buffer_type = BUFFER_TYPE_NULL; gs->y_inverted = 1; + es->is_opaque = false; return; } @@ -2348,6 +2376,7 @@ gl_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) weston_buffer_reference(&gs->buffer_ref, NULL); gs->buffer_type = BUFFER_TYPE_NULL; gs->y_inverted = 1; + es->is_opaque = false; } } diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c index 1ea275b49..a316766e0 100644 --- a/libweston/pixman-renderer.c +++ b/libweston/pixman-renderer.c @@ -650,12 +650,15 @@ pixman_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) switch (wl_shm_buffer_get_format(shm_buffer)) { case WL_SHM_FORMAT_XRGB8888: pixman_format = PIXMAN_x8r8g8b8; + es->is_opaque = true; break; case WL_SHM_FORMAT_ARGB8888: pixman_format = PIXMAN_a8r8g8b8; + es->is_opaque = false; break; case WL_SHM_FORMAT_RGB565: pixman_format = PIXMAN_r5g6b5; + es->is_opaque = true; break; default: weston_log("Unsupported SHM buffer format\n"); From 70decd5b2b4937390f0037c0b00b845f5a97fc62 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Mon, 3 Sep 2018 20:11:15 +0200 Subject: [PATCH 0634/1642] libweston: add weston_view_is_opaque() Use the weston_surface is_opaque property, the opaque region, and the view alpha value to determine whether the weston_view is opaque in a specific region. Signed-off-by: Philipp Zabel --- libweston/compositor.c | 37 +++++++++++++++++++++++++++++++++++++ libweston/compositor.h | 3 +++ 2 files changed, 40 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index 0dcb1df3f..e09973842 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -1681,6 +1681,43 @@ weston_view_is_mapped(struct weston_view *view) return view->is_mapped; } +/* Check if view is opaque in specified region + * + * \param view The view to check for opacity. + * \param region The region to check for opacity, in view coordinates. + * + * Returns true if the view is opaque in the specified region, because view + * alpha is 1.0 and either the opaque region set by the client contains the + * specified region, or the buffer pixel format or solid color is opaque. + */ +WL_EXPORT bool +weston_view_is_opaque(struct weston_view *ev, pixman_region32_t *region) +{ + pixman_region32_t r; + bool ret = false; + + if (ev->alpha < 1.0) + return false; + + if (ev->surface->is_opaque) + return true; + + if (ev->transform.dirty) { + weston_log("%s: transform dirty", __func__); + return false; + } + + pixman_region32_init(&r); + pixman_region32_subtract(&r, region, &ev->transform.opaque); + + if (!pixman_region32_not_empty(&r)) + ret = true; + + pixman_region32_fini(&r); + + return ret; +} + /* Check if a surface has a view assigned to it * * The indicator is set manually when mapping diff --git a/libweston/compositor.h b/libweston/compositor.h index 6a57b8292..34432ba9d 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1868,6 +1868,9 @@ weston_view_set_mask_infinite(struct weston_view *view); bool weston_view_is_mapped(struct weston_view *view); +bool +weston_view_is_opaque(struct weston_view *ev, pixman_region32_t *region); + void weston_view_schedule_repaint(struct weston_view *view); From fff2797c88a258e2c683ed1d5cfe05e8c576ac54 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Mon, 3 Sep 2018 20:13:52 +0200 Subject: [PATCH 0635/1642] compositor-drm: use weston_view_is_opaque() Implement drm_view_is_opaque() using weston_view_is_opaque(). Also, use weston_view_is_opaque() directly in drm_output_propose_state(), with the clipped_view. Signed-off-by: Philipp Zabel --- libweston/compositor-drm.c | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index b21793e5a..c1ee990b7 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1557,32 +1557,13 @@ drm_plane_state_coords_for_view(struct drm_plane_state *state, return true; } -static bool -drm_view_is_opaque(struct weston_view *ev) -{ - pixman_region32_t r; - bool ret = false; - - pixman_region32_init_rect(&r, 0, 0, - ev->surface->width, - ev->surface->height); - pixman_region32_subtract(&r, &r, &ev->surface->opaque); - - if (!pixman_region32_not_empty(&r)) - ret = true; - - pixman_region32_fini(&r); - - return ret; -} - static struct drm_fb * drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) { struct drm_output *output = state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; - bool is_opaque = drm_view_is_opaque(ev); + bool is_opaque = weston_view_is_opaque(ev, &ev->transform.boundingbox); struct linux_dmabuf_buffer *dmabuf; struct drm_fb *fb; @@ -3641,7 +3622,7 @@ drm_output_propose_state(struct weston_output *output_base, "(precluded by mode)\n", ev); force_renderer = true; } - if (!ps && !drm_view_is_opaque(ev)) { + if (!ps && !weston_view_is_opaque(ev, &clipped_view)) { drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " "(view not fully opaque)\n", ev); force_renderer = true; From c18ffd3939499897fb3eefc6ff3627140d88a23d Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Thu, 30 Aug 2018 17:38:03 +0200 Subject: [PATCH 0636/1642] libweston: add weston_head_{is,set}_non_desktop() Add non-desktop property for weston_heads representing displays that the desktop should not be extended to by default, e.g. head mounted displays. Signed-off-by: Philipp Zabel --- libweston/compositor.c | 35 +++++++++++++++++++++++++++++++++++ libweston/compositor.h | 7 +++++++ 2 files changed, 42 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index e09973842..c94fa3153 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -4939,6 +4939,25 @@ weston_head_set_monitor_strings(struct weston_head *head, weston_head_set_device_changed(head); } +/** Store display non-desktop status + * + * \param head The head to modify. + * \param non_desktop Whether the head connects to a non-desktop display. + * + * \memberof weston_head + * \internal + */ +WL_EXPORT void +weston_head_set_non_desktop(struct weston_head *head, bool non_desktop) +{ + if (head->non_desktop == non_desktop) + return; + + head->non_desktop = non_desktop; + + weston_head_set_device_changed(head); +} + /** Store physical image size * * \param head The head to modify. @@ -5103,6 +5122,22 @@ weston_head_is_device_changed(struct weston_head *head) return head->device_changed; } +/** Does the head represent a non-desktop display? + * + * \param head The head to query. + * \return True if the device is a non-desktop display. + * + * Non-desktop heads are not attached to outputs by default. + * This stops weston from extending the desktop onto head mounted displays. + * + * \memberof weston_head + */ +WL_EXPORT bool +weston_head_is_non_desktop(struct weston_head *head) +{ + return head->non_desktop; +} + /** Acknowledge device information change * * \param head The head to acknowledge. diff --git a/libweston/compositor.h b/libweston/compositor.h index 34432ba9d..cd718390a 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -196,6 +196,7 @@ struct weston_head { char *name; /**< head name, e.g. connector name */ bool connected; /**< is physically connected */ + bool non_desktop; /**< non-desktop display, e.g. HMD */ }; struct weston_output { @@ -2222,6 +2223,9 @@ weston_head_set_monitor_strings(struct weston_head *head, const char *model, const char *serialno); +void +weston_head_set_non_desktop(struct weston_head *head, bool non_desktop); + void weston_head_set_physical_size(struct weston_head *head, int32_t mm_width, int32_t mm_height); @@ -2245,6 +2249,9 @@ weston_head_is_enabled(struct weston_head *head); bool weston_head_is_device_changed(struct weston_head *head); +bool +weston_head_is_non_desktop(struct weston_head *head); + void weston_head_reset_device_changed(struct weston_head *head); From 61dc4ca92ec885cff0dabb88a5fda5567a489316 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Thu, 30 Aug 2018 17:39:51 +0200 Subject: [PATCH 0637/1642] compositor-drm: check connector non-desktop property and mark head accordingly Use the DRM connector "non-desktop" property to mark weston_heads that represent head mounted displays and other non-standard displays that the desktop should not be extended to. Signed-off-by: Philipp Zabel --- libweston/compositor-drm.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c1ee990b7..bb0351ba4 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -217,6 +217,7 @@ enum wdrm_connector_property { WDRM_CONNECTOR_EDID = 0, WDRM_CONNECTOR_DPMS, WDRM_CONNECTOR_CRTC_ID, + WDRM_CONNECTOR_NON_DESKTOP, WDRM_CONNECTOR__COUNT }; @@ -251,6 +252,7 @@ static const struct drm_property_info connector_props[] = { .num_enum_values = WDRM_DPMS_STATE__COUNT, }, [WDRM_CONNECTOR_CRTC_ID] = { .name = "CRTC_ID", }, + [WDRM_CONNECTOR_NON_DESKTOP] = { .name = "non-desktop", }, }; /** @@ -5159,6 +5161,15 @@ find_and_parse_output_edid(struct drm_head *head, drmModeFreePropertyBlob(edid_blob); } +static bool +check_non_desktop(struct drm_head *head, drmModeObjectPropertiesPtr props) +{ + struct drm_property_info *non_desktop_info = + &head->props_conn[WDRM_CONNECTOR_NON_DESKTOP]; + + return drm_property_get_value(non_desktop_info, props, 0); +} + static int parse_modeline(const char *s, drmModeModeInfo *mode) { @@ -6147,6 +6158,8 @@ drm_head_assign_connector_info(struct drm_head *head, WDRM_CONNECTOR__COUNT, props); find_and_parse_output_edid(head, props, &make, &model, &serial_number); weston_head_set_monitor_strings(&head->base, make, model, serial_number); + weston_head_set_non_desktop(&head->base, + check_non_desktop(head, props)); weston_head_set_subpixel(&head->base, drm_subpixel_to_wayland(head->connector->subpixel)); From b9454fde9fb864539548fbdeb076a3c560f95c5b Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Thu, 30 Aug 2018 17:41:42 +0200 Subject: [PATCH 0638/1642] weston: keep non-desktop displays turned off by default Keep non-desktop heads representing e.g. head mounted displays turned off by default. When using the DRM backend they can still be enabled by setting an explicit [output] mode (or "mode=preferred") in weston.ini. Signed-off-by: Philipp Zabel --- compositor/main.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 14ad241ae..4ede185d7 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1411,13 +1411,15 @@ simple_heads_changed(struct wl_listener *listener, void *arg) bool connected; bool enabled; bool changed; + bool non_desktop; while ((head = weston_compositor_iterate_heads(wet->compositor, head))) { connected = weston_head_is_connected(head); enabled = weston_head_is_enabled(head); changed = weston_head_is_device_changed(head); + non_desktop = weston_head_is_non_desktop(head); - if (connected && !enabled) { + if (connected && !enabled && !non_desktop) { simple_head_enable(wet, head); } else if (!connected && enabled) { simple_head_disable(head); @@ -1715,14 +1717,16 @@ drm_head_prepare_enable(struct wet_compositor *wet, section = drm_config_find_controlling_output_section(wet->config, name); if (section) { - /* skip outputs that are explicitly off, the backend turns - * them off automatically. + /* skip outputs that are explicitly off, or non-desktop and not + * explicitly enabled. The backend turns them off automatically. */ weston_config_section_get_string(section, "mode", &mode, NULL); if (mode && strcmp(mode, "off") == 0) { free(mode); return; } + if (!mode && weston_head_is_non_desktop(head)) + return; free(mode); weston_config_section_get_string(section, "name", From a65d55e13393bd4ecb8c02caf9620b2c1bc02d29 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 21 Sep 2018 16:57:13 +0100 Subject: [PATCH 0639/1642] gl-renderer: Remove warning on missing extensions Not having swap_buffers_with_damage could cause a performance impact on some backends, but at least on GBM it causes no issues. It also seems to confuse users into thinking it's a legitimate error which could explain session slowness. Similarly with buffer_age, whilst we do lose a little bit of performance by not being able to do partial renders, it is not a great deal, and the user is unlikely to be able to do anything about it in any event. Remove the warning; we print the full extension list at startup, so we already have enough information from the logs to easily diagnose any real errors. Signed-off-by: Daniel Stone --- libweston/gl-renderer.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 95cbc22e1..b6f32737a 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -3235,9 +3235,6 @@ gl_renderer_setup_egl_extensions(struct weston_compositor *ec) if (weston_check_egl_extension(extensions, "EGL_EXT_buffer_age")) gr->has_egl_buffer_age = 1; - else - weston_log("warning: EGL_EXT_buffer_age not supported. " - "Performance could be affected.\n"); for (i = 0; i < ARRAY_LENGTH(swap_damage_ext_to_entrypoint); i++) { if (weston_check_egl_extension(extensions, @@ -3248,11 +3245,6 @@ gl_renderer_setup_egl_extensions(struct weston_compositor *ec) break; } } - if (!gr->swap_buffers_with_damage) - weston_log("warning: neither %s or %s is supported. " - "Performance could be affected.\n", - swap_damage_ext_to_entrypoint[0].extension, - swap_damage_ext_to_entrypoint[1].extension); if (weston_check_egl_extension(extensions, "EGL_KHR_no_config_context") || weston_check_egl_extension(extensions, "EGL_MESA_configless_context")) From bff27cb835095a724161a5f50dbaadc8eb7b71da Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 21 Sep 2018 16:59:08 +0100 Subject: [PATCH 0640/1642] compositor-drm: Don't warn about missing backlight control Not every output will have a backlight control, and even if it does we may just not be able to find it. Not having backlight control isn't an error, so don't spam the log with it, as doing so can confuse users into thinking this is an actual error which is responsible for their real problems. Signed-off-by: Daniel Stone --- libweston/compositor-drm.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index bb0351ba4..509f50590 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4664,11 +4664,6 @@ drm_output_init_backlight(struct drm_output *output) } } } - - if (!output->base.set_backlight) { - weston_log("No backlight control for output '%s'\n", - output->base.name); - } } /** From 7e2d4beb08a61d8376b1ee382bd4e5e5ae485c2a Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 12 Oct 2018 12:52:42 +0300 Subject: [PATCH 0641/1642] xwayland: Silence format-truncation compilation warnings We are currently formatting 32-bit signed integers into 8 byte buffers, which are too small, causing the compiler to complain. Update the buffer size to the minimum required value of 12 bytes: 1 for the sign, 10 for the number, and 1 for the null byte terminator. Signed-off-by: Alexandros Frantzis --- compositor/xwayland.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compositor/xwayland.c b/compositor/xwayland.c index 9881cd9c1..61580c7cd 100644 --- a/compositor/xwayland.c +++ b/compositor/xwayland.c @@ -63,7 +63,7 @@ spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd { struct wet_xwayland *wxw = user_data; pid_t pid; - char s[8], abstract_fd_str[8], unix_fd_str[8], wm_fd_str[8]; + char s[12], abstract_fd_str[12], unix_fd_str[12], wm_fd_str[12]; int sv[2], wm[2], fd; char *xserver = NULL; struct weston_config *config = wet_get_config(wxw->compositor); From 3bb047b6057a367636e288152bab898a90679915 Mon Sep 17 00:00:00 2001 From: Emil Velikov Date: Thu, 18 Oct 2018 15:55:28 +0100 Subject: [PATCH 0642/1642] libweston: split EGL and GL info logging Split the two into separate functions. Former requires an initialized EGL display, while the latter a current context. We will use that distinction with the next patch. Signed-off-by: Emil Velikov --- libweston/gl-renderer.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index b6f32737a..afa08d6c9 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -2845,7 +2845,7 @@ log_extensions(const char *name, const char *extensions) } static void -log_egl_gl_info(EGLDisplay egldpy) +log_egl_info(EGLDisplay egldpy) { const char *str; @@ -2860,6 +2860,12 @@ log_egl_gl_info(EGLDisplay egldpy) str = eglQueryString(egldpy, EGL_EXTENSIONS); log_extensions("EGL extensions", str ? str : "(null)"); +} + +static void +log_gl_info(void) +{ + const char *str; str = (char *)glGetString(GL_VERSION); weston_log("GL version: %s\n", str ? str : "(null)"); @@ -3735,7 +3741,8 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) gr->gl_version = GR_GL_VERSION(2, 0); } - log_egl_gl_info(gr->egl_display); + log_egl_info(gr->egl_display); + log_gl_info(); gr->image_target_texture_2d = (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); From da4f185faae9ea7e42d2c6f6ddfa95ea63592ab7 Mon Sep 17 00:00:00 2001 From: Emil Velikov Date: Thu, 18 Oct 2018 15:58:09 +0100 Subject: [PATCH 0643/1642] libweston: print EGL information as early as possible In the case where CreateContext/MakeCurrent fails, we still want to know what the EGL driver is capable of. Move the EGL info printing, just after the eglInitialize() call to ensure that. Signed-off-by: Emil Velikov --- libweston/gl-renderer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index afa08d6c9..0fde2ad25 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -3505,6 +3505,8 @@ gl_renderer_display_create(struct weston_compositor *ec, EGLenum platform, goto fail_with_error; } + log_egl_info(gr->egl_display); + if (egl_choose_config(gr, config_attribs, visual_id, n_ids, &gr->egl_config) < 0) { weston_log("failed to choose EGL config\n"); @@ -3741,7 +3743,6 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) gr->gl_version = GR_GL_VERSION(2, 0); } - log_egl_info(gr->egl_display); log_gl_info(); gr->image_target_texture_2d = From 4976b09a7ee9c879be431dc3ef0b2364b1c3a248 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Mon, 10 Sep 2018 11:44:17 +0900 Subject: [PATCH 0644/1642] compositor-drm: add num_planes to drm_fb structure Add new member to store number of planes to drm_fb structure. Signed-off-by: Tomohito Esaki --- libweston/compositor-drm.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 509f50590..37cc730f0 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -369,6 +369,7 @@ struct drm_fb { uint32_t handles[4]; uint32_t strides[4]; uint32_t offsets[4]; + int num_planes; const struct pixel_format_info *format; uint64_t modifier; int width, height; @@ -1033,6 +1034,7 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, fb->modifier = DRM_FORMAT_MOD_INVALID; fb->handles[0] = create_arg.handle; fb->strides[0] = create_arg.pitch; + fb->num_planes = 1; fb->size = create_arg.size; fb->width = width; fb->height = height; @@ -1204,6 +1206,7 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, goto err_free; } + fb->num_planes = dmabuf->attributes.n_planes; for (i = 0; i < dmabuf->attributes.n_planes; i++) { fb->handles[i] = gbm_bo_get_handle_for_plane(fb->bo, i).u32; if (!fb->handles[i]) @@ -1251,12 +1254,14 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, #ifdef HAVE_GBM_MODIFIERS fb->modifier = gbm_bo_get_modifier(bo); - for (i = 0; i < gbm_bo_get_plane_count(bo); i++) { + fb->num_planes = gbm_bo_get_plane_count(bo); + for (i = 0; i < fb->num_planes; i++) { fb->strides[i] = gbm_bo_get_stride_for_plane(bo, i); fb->handles[i] = gbm_bo_get_handle_for_plane(bo, i).u32; fb->offsets[i] = gbm_bo_get_offset(bo, i); } #else + fb->num_planes = 1; fb->strides[0] = gbm_bo_get_stride(bo); fb->handles[0] = gbm_bo_get_handle(bo).u32; fb->modifier = DRM_FORMAT_MOD_INVALID; From 1506e99fd3f2f9a4eed7db6cc9106cb630ecd152 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Tue, 22 May 2018 12:05:14 +0900 Subject: [PATCH 0645/1642] gl-renderer: provide fence sync fd for synchronizing with GPU rendering Add new API to gl-renderer interface for providing fence sync fd. the backend can wait for GPU rendering by this API. Signed-off-by: Tomohito Esaki --- libweston/gl-renderer.c | 54 ++++++++++++++++++++++++++++++++--------- libweston/gl-renderer.h | 7 ++++++ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 0fde2ad25..f2da0d34a 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -106,6 +106,8 @@ struct gl_output_state { struct weston_matrix output_matrix; + EGLSyncKHR begin_render_sync, end_render_sync; + /* struct timeline_render_point::link */ struct wl_list timeline_render_point_list; }; @@ -367,11 +369,11 @@ timeline_render_point_handler(int fd, uint32_t mask, void *data) } static EGLSyncKHR -timeline_create_render_sync(struct gl_renderer *gr) +create_render_sync(struct gl_renderer *gr) { static const EGLint attribs[] = { EGL_NONE }; - if (!weston_timeline_enabled_ || !gr->has_native_fence_sync) + if (!gr->has_native_fence_sync) return EGL_NO_SYNC_KHR; return gr->create_sync(gr->egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, @@ -400,12 +402,12 @@ timeline_submit_render_sync(struct gl_renderer *gr, fd = gr->dup_native_fence_fd(gr->egl_display, sync); if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) - goto out; + return; trp = zalloc(sizeof *trp); if (trp == NULL) { close(fd); - goto out; + return; } trp->type = type; @@ -417,9 +419,6 @@ timeline_submit_render_sync(struct gl_renderer *gr, trp); wl_list_insert(&go->timeline_render_point_list, &trp->link); - -out: - gr->destroy_sync(gr->egl_display, sync); } static struct egl_image* @@ -1254,12 +1253,16 @@ gl_renderer_repaint_output(struct weston_output *output, pixman_box32_t *rects; pixman_region32_t buffer_damage, total_damage; enum gl_border_status border_damage = BORDER_STATUS_CLEAN; - EGLSyncKHR begin_render_sync, end_render_sync; if (use_output(output) < 0) return; - begin_render_sync = timeline_create_render_sync(gr); + if (go->begin_render_sync != EGL_NO_SYNC_KHR) + gr->destroy_sync(gr->egl_display, go->begin_render_sync); + if (go->end_render_sync != EGL_NO_SYNC_KHR) + gr->destroy_sync(gr->egl_display, go->end_render_sync); + + go->begin_render_sync = create_render_sync(gr); /* Calculate the viewport */ glViewport(go->borders[GL_RENDERER_BORDER_LEFT].width, @@ -1309,7 +1312,7 @@ gl_renderer_repaint_output(struct weston_output *output, pixman_region32_copy(&output->previous_damage, output_damage); wl_signal_emit(&output->frame_signal, output); - end_render_sync = timeline_create_render_sync(gr); + go->end_render_sync = create_render_sync(gr); if (gr->swap_buffers_with_damage) { pixman_region32_init(&buffer_damage); @@ -1360,9 +1363,10 @@ gl_renderer_repaint_output(struct weston_output *output, /* We have to submit the render sync objects after swap buffers, since * the objects get assigned a valid sync file fd only after a gl flush. */ - timeline_submit_render_sync(gr, compositor, output, begin_render_sync, + timeline_submit_render_sync(gr, compositor, output, + go->begin_render_sync, TIMELINE_RENDER_POINT_TYPE_BEGIN); - timeline_submit_render_sync(gr, compositor, output, end_render_sync, + timeline_submit_render_sync(gr, compositor, output, go->end_render_sync, TIMELINE_RENDER_POINT_TYPE_END); } @@ -3065,6 +3069,9 @@ gl_renderer_output_create(struct weston_output *output, wl_list_init(&go->timeline_render_point_list); + go->begin_render_sync = EGL_NO_SYNC_KHR; + go->end_render_sync = EGL_NO_SYNC_KHR; + output->renderer_state = go; return 0; @@ -3124,6 +3131,11 @@ gl_renderer_output_destroy(struct weston_output *output) wl_list_for_each_safe(trp, tmp, &go->timeline_render_point_list, link) timeline_render_point_destroy(trp); + if (go->begin_render_sync != EGL_NO_SYNC_KHR) + gr->destroy_sync(gr->egl_display, go->begin_render_sync); + if (go->end_render_sync != EGL_NO_SYNC_KHR) + gr->destroy_sync(gr->egl_display, go->end_render_sync); + free(go); } @@ -3133,6 +3145,23 @@ gl_renderer_output_surface(struct weston_output *output) return get_output_state(output)->egl_surface; } +static int +gl_renderer_create_fence_fd(struct weston_output *output) +{ + struct gl_output_state *go = get_output_state(output); + struct gl_renderer *gr = get_renderer(output->compositor); + int fd; + + if (go->end_render_sync == EGL_NO_SYNC_KHR) + return -1; + + fd = gr->dup_native_fence_fd(gr->egl_display, go->end_render_sync); + if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) + return -1; + + return fd; +} + static void gl_renderer_destroy(struct weston_compositor *ec) { @@ -3815,5 +3844,6 @@ WL_EXPORT struct gl_renderer_interface gl_renderer_interface = { .output_destroy = gl_renderer_output_destroy, .output_surface = gl_renderer_output_surface, .output_set_border = gl_renderer_output_set_border, + .create_fence_fd = gl_renderer_create_fence_fd, .print_egl_error_state = gl_renderer_print_egl_error_state }; diff --git a/libweston/gl-renderer.h b/libweston/gl-renderer.h index b47ea07fe..202702b5a 100644 --- a/libweston/gl-renderer.h +++ b/libweston/gl-renderer.h @@ -113,6 +113,13 @@ struct gl_renderer_interface { int32_t width, int32_t height, int32_t tex_width, unsigned char *data); + /* Create fence sync FD to wait for GPU rendering. + * + * Return FD on success, -1 on failure or unsupported + * EGL_ANDROID_native_fence_sync extension. + */ + int (*create_fence_fd)(struct weston_output *output); + void (*print_egl_error_state)(void); }; From 718a40b49c1aff55a7ec460c34dd8b8a7cc777a7 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Wed, 31 Jan 2018 17:50:15 +0900 Subject: [PATCH 0646/1642] compositor-drm: store gbm bo flags in drm_output Store usage flags of gbm bo in drm_output in order to specify the bo format for each output. A following patch will add a new type of drm_output which requires different gbm_bo_flags. Signed-off-by: Tomohito Esaki --- libweston/compositor-drm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 37cc730f0..2c1f60465 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -527,6 +527,7 @@ struct drm_output { struct gbm_surface *gbm_surface; uint32_t gbm_format; + uint32_t gbm_bo_flags; /* Plane being displayed directly on the CRTC */ struct drm_plane *scanout_plane; @@ -4876,7 +4877,7 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) output->gbm_surface = gbm_surface_create(b->gbm, mode->width, mode->height, output->gbm_format, - GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT); + output->gbm_bo_flags); } if (!output->gbm_surface) { @@ -6325,6 +6326,7 @@ drm_output_create(struct weston_compositor *compositor, const char *name) return NULL; output->backend = b; + output->gbm_bo_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING; weston_output_init(&output->base, compositor, name); From b1fb00dbcdc8898d6bebf70040aded01ff54d365 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Wed, 31 Jan 2018 17:50:48 +0900 Subject: [PATCH 0647/1642] compositor-drm: Add Support virtual output Add support virtual output for streaming image to remote output by remoting-plugin which will be added by the patch: "Add remoting plugin for output streaming." The gbm bo of virtual output is the linear format. Virtual output is implemented based on a patch by Grigory Kletsko . Signed-off-by: Tomohito Esaki --- libweston/compositor-drm.c | 361 ++++++++++++++++++++++++++++++++++++- libweston/compositor-drm.h | 73 ++++++++ 2 files changed, 433 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 2c1f60465..e024e66fc 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -74,6 +74,10 @@ #define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64 #endif +#ifndef GBM_BO_USE_LINEAR +#define GBM_BO_USE_LINEAR (1 << 4) +#endif + /** * A small wrapper to print information into the 'drm-backend' debug scope. * @@ -547,6 +551,10 @@ struct drm_output { struct wl_listener recorder_frame_listener; struct wl_event_source *pageflip_timer; + + bool virtual; + + submit_frame_cb virtual_submit_frame; }; static const char *const aspect_ratio_as_string[] = { @@ -859,6 +867,9 @@ drm_output_update_msc(struct drm_output *output, unsigned int seq); static void drm_output_destroy(struct weston_output *output_base); +static void +drm_virtual_output_destroy(struct weston_output *output_base); + /** * Returns true if the plane can be used on the given output for its current * repaint cycle. @@ -868,6 +879,9 @@ drm_plane_is_available(struct drm_plane *plane, struct drm_output *output) { assert(plane->state_cur); + if (output->virtual) + return false; + /* The plane still has a request not yet completed by the kernel. */ if (!plane->state_cur->complete) return false; @@ -2668,6 +2682,8 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, } wl_list_for_each(output_state, &pending_state->output_list, link) { + if (output_state->output->virtual) + continue; if (mode == DRM_STATE_APPLY_SYNC) assert(output_state->dpms == WESTON_DPMS_OFF); ret |= drm_output_apply_state_atomic(output_state, req, &flags); @@ -2777,6 +2793,12 @@ drm_pending_state_apply(struct drm_pending_state *pending_state) struct drm_output *output = output_state->output; int ret; + if (output->virtual) { + drm_output_assign_state(output_state, + DRM_STATE_APPLY_ASYNC); + continue; + } + ret = drm_output_apply_state_legacy(output_state); if (ret != 0) { weston_log("Couldn't apply state for output %s\n", @@ -2855,6 +2877,8 @@ drm_output_repaint(struct weston_output *output_base, struct drm_output_state *state = NULL; struct drm_plane_state *scanout_state; + assert(!output->virtual); + if (output->disable_pending || output->destroy_pending) goto err; @@ -3729,7 +3753,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) drm_debug(b, "\t[repaint] preparing state for output %s (%lu)\n", output_base->name, (unsigned long) output_base->id); - if (!b->sprites_are_broken) { + if (!b->sprites_are_broken && !output->virtual) { state = drm_output_propose_state(output_base, pending_state, DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); if (!state) { @@ -4448,6 +4472,63 @@ drm_plane_destroy(struct drm_plane *plane) free(plane); } +/** + * Create a drm_plane for virtual output + * + * Call drm_virtual_plane_destroy to clean up the plane. + * + * @param b DRM compositor backend + * @param output Output to create internal plane for + */ +static struct drm_plane * +drm_virtual_plane_create(struct drm_backend *b, struct drm_output *output) +{ + struct drm_plane *plane; + + /* num of formats is one */ + plane = zalloc(sizeof(*plane) + sizeof(plane->formats[0])); + if (!plane) { + weston_log("%s: out of memory\n", __func__); + return NULL; + } + + plane->type = WDRM_PLANE_TYPE_PRIMARY; + plane->backend = b; + plane->state_cur = drm_plane_state_alloc(NULL, plane); + plane->state_cur->complete = true; + plane->formats[0].format = output->gbm_format; + plane->count_formats = 1; + if (output->gbm_bo_flags & GBM_BO_USE_LINEAR) { + uint64_t *modifiers = zalloc(sizeof *modifiers); + if (modifiers) { + *modifiers = DRM_FORMAT_MOD_LINEAR; + plane->formats[0].modifiers = modifiers; + plane->formats[0].count_modifiers = 1; + } + } + + weston_plane_init(&plane->base, b->compositor, 0, 0); + wl_list_insert(&b->plane_list, &plane->link); + + return plane; +} + +/** + * Destroy one DRM plane + * + * @param plane Plane to deallocate (will be freed) + */ +static void +drm_virtual_plane_destroy(struct drm_plane *plane) +{ + drm_plane_state_free(plane->state_cur, true); + weston_plane_release(&plane->base); + wl_list_remove(&plane->link); + if (plane->formats[0].modifiers) + free(plane->formats[0].modifiers); + free(plane); +} + /** * Initialise sprites (overlay planes) * @@ -4681,6 +4762,8 @@ drm_output_init_backlight(struct drm_output *output) * * If we are called as part of repaint, we simply set the relevant bit in * state and return. + * + * This function is never called on a virtual output. */ static void drm_set_dpms(struct weston_output *output_base, enum dpms_enum level) @@ -4691,6 +4774,8 @@ drm_set_dpms(struct weston_output *output_base, enum dpms_enum level) struct drm_output_state *state; int ret; + assert(!output->virtual); + if (output->state_cur->dpms == level) return; @@ -5591,6 +5676,9 @@ drm_output_set_mode(struct weston_output *base, struct drm_mode *current; + if (output->virtual) + return -1; + if (drm_output_update_modelist_from_heads(output) < 0) return -1; @@ -5948,6 +6036,8 @@ drm_output_enable(struct weston_output *base) drmModeRes *resources; int ret; + assert(!output->virtual); + resources = drmModeGetResources(b->drm.fd); if (!resources) { weston_log("drmModeGetResources failed\n"); @@ -6045,6 +6135,8 @@ drm_output_destroy(struct weston_output *base) struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); + assert(!output->virtual); + if (output->page_flip_pending || output->vblank_pending || output->atomic_complete_pending) { output->destroy_pending = 1; @@ -6073,6 +6165,8 @@ drm_output_disable(struct weston_output *base) { struct drm_output *output = to_drm_output(base); + assert(!output->virtual); + if (output->page_flip_pending || output->vblank_pending || output->atomic_complete_pending) { output->disable_pending = 1; @@ -6909,12 +7003,269 @@ renderer_switch_binding(struct weston_keyboard *keyboard, switch_to_gl_renderer(b); } +static void +drm_virtual_output_start_repaint_loop(struct weston_output *output_base) +{ + weston_output_finish_frame(output_base, NULL, + WP_PRESENTATION_FEEDBACK_INVALID); +} + +static int +drm_virtual_output_submit_frame(struct drm_output *output, + struct drm_fb *fb) +{ + struct drm_backend *b = to_drm_backend(output->base.compositor); + int fd, ret; + + assert(fb->num_planes == 1); + ret = drmPrimeHandleToFD(b->drm.fd, fb->handles[0], DRM_CLOEXEC, &fd); + if (ret) { + weston_log("drmPrimeHandleFD failed, errno=%d\n", errno); + return -1; + } + + drm_fb_ref(fb); + ret = output->virtual_submit_frame(&output->base, fd, fb->strides[0], + fb); + if (ret < 0) { + drm_fb_unref(fb); + close(fd); + } + return ret; +} + +static int +drm_virtual_output_repaint(struct weston_output *output_base, + pixman_region32_t *damage, + void *repaint_data) +{ + struct drm_pending_state *pending_state = repaint_data; + struct drm_output_state *state = NULL; + struct drm_output *output = to_drm_output(output_base); + struct drm_plane *scanout_plane = output->scanout_plane; + struct drm_plane_state *scanout_state; + + assert(output->virtual); + + if (output->disable_pending || output->destroy_pending) + goto err; + + /* Drop frame if there isn't free buffers */ + if (!gbm_surface_has_free_buffers(output->gbm_surface)) { + weston_log("%s: Drop frame!!\n", __func__); + return -1; + } + + assert(!output->state_last); + + /* If planes have been disabled in the core, we might not have + * hit assign_planes at all, so might not have valid output state + * here. */ + state = drm_pending_state_get_output(pending_state, output); + if (!state) + state = drm_output_state_duplicate(output->state_cur, + pending_state, + DRM_OUTPUT_STATE_CLEAR_PLANES); + + drm_output_render(state, damage); + scanout_state = drm_output_state_get_plane(state, scanout_plane); + if (!scanout_state || !scanout_state->fb) + goto err; + + if (drm_virtual_output_submit_frame(output, scanout_state->fb) < 0) + goto err; + + return 0; + +err: + drm_output_state_free(state); + return -1; +} + +static void +drm_virtual_output_deinit(struct weston_output *base) +{ + struct drm_output *output = to_drm_output(base); + + drm_output_fini_egl(output); + + drm_virtual_plane_destroy(output->scanout_plane); +} + +static void +drm_virtual_output_destroy(struct weston_output *base) +{ + struct drm_output *output = to_drm_output(base); + + assert(output->virtual); + + if (output->base.enabled) + drm_virtual_output_deinit(&output->base); + + weston_output_release(&output->base); + + drm_output_state_free(output->state_cur); + + free(output); +} + +static int +drm_virtual_output_enable(struct weston_output *output_base) +{ + struct drm_output *output = to_drm_output(output_base); + struct drm_backend *b = to_drm_backend(output_base->compositor); + + assert(output->virtual); + + if (b->use_pixman) { + weston_log("Not support pixman renderer on Virtual output\n"); + goto err; + } + + if (!output->virtual_submit_frame) { + weston_log("The virtual_submit_frame hook is not set\n"); + goto err; + } + + output->scanout_plane = drm_virtual_plane_create(b, output); + if (!output->scanout_plane) { + weston_log("Failed to find primary plane for output %s\n", + output->base.name); + return -1; + } + + if (drm_output_init_egl(output, b) < 0) { + weston_log("Failed to init output gl state\n"); + goto err; + } + + output->base.start_repaint_loop = drm_virtual_output_start_repaint_loop; + output->base.repaint = drm_virtual_output_repaint; + output->base.assign_planes = drm_assign_planes; + output->base.set_dpms = NULL; + output->base.switch_mode = NULL; + output->base.gamma_size = 0; + output->base.set_gamma = NULL; + + weston_compositor_stack_plane(b->compositor, + &output->scanout_plane->base, + &b->compositor->primary_plane); + + return 0; +err: + return -1; +} + +static int +drm_virtual_output_disable(struct weston_output *base) +{ + struct drm_output *output = to_drm_output(base); + + assert(output->virtual); + + if (output->base.enabled) + drm_virtual_output_deinit(&output->base); + + return 0; +} + +static struct weston_output * +drm_virtual_output_create(struct weston_compositor *c, char *name) +{ + struct drm_output *output; + + output = zalloc(sizeof *output); + if (!output) + return NULL; + + output->virtual = true; + output->gbm_bo_flags = GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING; + + weston_output_init(&output->base, c, name); + + output->base.enable = drm_virtual_output_enable; + output->base.destroy = drm_virtual_output_destroy; + output->base.disable = drm_virtual_output_disable; + output->base.attach_head = NULL; + + output->state_cur = drm_output_state_alloc(output, NULL); + + weston_compositor_add_pending_output(&output->base, c); + + return &output->base; +} + +static uint32_t +drm_virtual_output_set_gbm_format(struct weston_output *base, + const char *gbm_format) +{ + struct drm_output *output = to_drm_output(base); + struct drm_backend *b = to_drm_backend(base->compositor); + + if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1) + output->gbm_format = b->gbm_format; + + return output->gbm_format; +} + +static void +drm_virtual_output_set_submit_frame_cb(struct weston_output *output_base, + submit_frame_cb cb) +{ + struct drm_output *output = to_drm_output(output_base); + + output->virtual_submit_frame = cb; +} + +static int +drm_virtual_output_get_fence_fd(struct weston_output *output_base) +{ + return gl_renderer->create_fence_fd(output_base); +} + +static void +drm_virtual_output_buffer_released(struct drm_fb *fb) +{ + drm_fb_unref(fb); +} + +static void +drm_virtual_output_finish_frame(struct weston_output *output_base, + struct timespec *stamp, + uint32_t presented_flags) +{ + struct drm_output *output = to_drm_output(output_base); + struct drm_plane_state *ps; + + wl_list_for_each(ps, &output->state_cur->plane_list, link) + ps->complete = true; + + drm_output_state_free(output->state_last); + output->state_last = NULL; + + weston_output_finish_frame(&output->base, stamp, presented_flags); + + /* We can't call this from frame_notify, because the output's + * repaint needed flag is cleared just after that */ + if (output->recorder) + weston_output_schedule_repaint(&output->base); +} + static const struct weston_drm_output_api api = { drm_output_set_mode, drm_output_set_gbm_format, drm_output_set_seat, }; +static const struct weston_drm_virtual_output_api virt_api = { + drm_virtual_output_create, + drm_virtual_output_set_gbm_format, + drm_virtual_output_set_submit_frame_cb, + drm_virtual_output_get_fence_fd, + drm_virtual_output_buffer_released, + drm_virtual_output_finish_frame +}; + static struct drm_backend * drm_backend_create(struct weston_compositor *compositor, struct weston_drm_backend_config *config) @@ -7079,6 +7430,14 @@ drm_backend_create(struct weston_compositor *compositor, goto err_udev_monitor; } + ret = weston_plugin_api_register(compositor, + WESTON_DRM_VIRTUAL_OUTPUT_API_NAME, + &virt_api, sizeof(virt_api)); + if (ret < 0) { + weston_log("Failed to register virtual output API.\n"); + goto err_udev_monitor; + } + return b; err_udev_monitor: diff --git a/libweston/compositor-drm.h b/libweston/compositor-drm.h index 9c37c1539..71a306fda 100644 --- a/libweston/compositor-drm.h +++ b/libweston/compositor-drm.h @@ -90,6 +90,79 @@ weston_drm_output_get_api(struct weston_compositor *compositor) return (const struct weston_drm_output_api *)api; } +#define WESTON_DRM_VIRTUAL_OUTPUT_API_NAME "weston_drm_virtual_output_api_v1" + +struct drm_fb; +typedef int (*submit_frame_cb)(struct weston_output *output, int fd, + int stride, struct drm_fb *buffer); + +struct weston_drm_virtual_output_api { + /** Create virtual output. + * This is a low-level function, where the caller is expected to wrap + * the weston_output function pointers as necessary to make the virtual + * output useful. The caller must set up output make, model, serial, + * physical size, the mode list and current mode. + * + * Returns output on success, NULL on failure. + */ + struct weston_output* (*create_output)(struct weston_compositor *c, + char *name); + + /** Set pixel format same as drm_output set_gbm_format(). + * + * Returns the set format. + */ + uint32_t (*set_gbm_format)(struct weston_output *output, + const char *gbm_format); + + /** Set a callback to be called when the DRM-backend has drawn a new + * frame and submits it for display. + * The callback will deliver a buffer to the virtual output's the + * owner and assumes the buffer is now reserved for the owner. The + * callback is called in virtual output repaint function. + * The caller must call buffer_released() and finish_frame(). + * + * The callback parameters are output, FD and stride (bytes) of dmabuf, + * and buffer (drm_fb) pointer. + * The callback returns 0 on success, -1 on failure. + * + * The submit_frame_cb callback hook is responsible for closing the fd + * if it returns success. One needs to call the buffer release and + * finish frame functions if and only if this hook returns success. + */ + void (*set_submit_frame_cb)(struct weston_output *output, + submit_frame_cb cb); + + /** Get fd for renderer fence. + * The returned fence signals when the renderer job has completed and + * the buffer is fully drawn. + * + * Returns fd on success, -1 on failure. + */ + int (*get_fence_sync_fd)(struct weston_output *output); + + /** Notify that the caller has finished using buffer */ + void (*buffer_released)(struct drm_fb *fb); + + /** Notify finish frame + * This function allows the output repainting mechanism to advance to + * the next frame. + */ + void (*finish_frame)(struct weston_output *output, + struct timespec *stamp, + uint32_t presented_flags); +}; + +static inline const struct weston_drm_virtual_output_api * +weston_drm_virtual_output_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, + WESTON_DRM_VIRTUAL_OUTPUT_API_NAME, + sizeof(struct weston_drm_virtual_output_api)); + return (const struct weston_drm_virtual_output_api *)api; +} + /** The backend configuration struct. * * weston_drm_backend_config contains the configuration used by a DRM From f59dc1112be50467b7c0f8aeba68f3aa10d36725 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Tue, 4 Sep 2018 17:18:06 +0900 Subject: [PATCH 0648/1642] weston: Add set up SIGUSR1 blocking early using pthread_sigmask() Xwayland block SIGUSR1 signal for handling this signal. However, if some weston plugins creates additional threads before xwayland is loaded, this signal get delivered these threads and causes weston quit. Therefore, we should set up SIGUSR1 blocking early so that these threads can inherit the setting when created. Signed-off-by: Tomohito Esaki --- Makefile.am | 4 +- compositor/main.c | 10 + configure.ac | 3 + m4/ax_pthread.m4 | 485 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 m4/ax_pthread.m4 diff --git a/Makefile.am b/Makefile.am index 4cd3ce9ed..cdb48fb90 100644 --- a/Makefile.am +++ b/Makefile.am @@ -187,11 +187,13 @@ weston_LDFLAGS = -export-dynamic weston_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON \ -DMODULEDIR='"$(moduledir)"' \ -DXSERVER_PATH='"@XSERVER_PATH@"' -weston_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBINPUT_BACKEND_CFLAGS) +weston_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBINPUT_BACKEND_CFLAGS) \ + $(PTHREAD_CFLAGS) weston_LDADD = libshared.la libweston-@LIBWESTON_MAJOR@.la \ $(COMPOSITOR_LIBS) \ $(DL_LIBS) $(LIBINPUT_BACKEND_LIBS) \ $(CLOCK_GETRES_LIBS) \ + $(PTHREAD_LIBS) \ -lm weston_SOURCES = \ diff --git a/compositor/main.c b/compositor/main.c index 4ede185d7..371673173 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2531,6 +2531,8 @@ int main(int argc, char *argv[]) struct weston_seat *seat; struct wet_compositor wet = { 0 }; int require_input; + sigset_t mask; + int32_t wait_for_debugger = 0; struct wl_protocol_logger *protologger = NULL; @@ -2601,6 +2603,14 @@ int main(int argc, char *argv[]) if (!signals[0] || !signals[1] || !signals[2] || !signals[3]) goto out_signals; + /* Xwayland uses SIGUSR1 for communicating with weston. Since some + weston plugins may create additional threads, set up any necessary + signal blocking early so that these threads can inherit the settings + when created. */ + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + pthread_sigmask(SIG_BLOCK, &mask, NULL); + if (load_configuration(&config, noconfig, config_file) < 0) goto out_signals; wet.config = config; diff --git a/configure.ac b/configure.ac index 32b9f1404..e414e85c8 100644 --- a/configure.ac +++ b/configure.ac @@ -91,6 +91,9 @@ AC_ARG_VAR([WESTON_SHELL_CLIENT], PKG_PROG_PKG_CONFIG() +# Check pthread +AX_PTHREAD + # Check for dlsym instead of dlopen because ASAN hijacks the latter WESTON_SEARCH_LIBS([DL], [dl], [dlsym]) diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 new file mode 100644 index 000000000..5fbf9fe0d --- /dev/null +++ b/m4/ax_pthread.m4 @@ -0,0 +1,485 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC to any special C compiler that is needed for +# multi-threaded programs (defaults to the value of CC otherwise). (This +# is necessary on AIX to use the special cc_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also to link with them as well. For example, you might link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threaded programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to +# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2011 Daniel Richard G. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 24 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_CC]) +AC_REQUIRE([AC_PROG_SED]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on Tru64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then + ax_pthread_save_CC="$CC" + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) + AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + CC="$ax_pthread_save_CC" + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 +# (Note: HP C rejects this with "bad form for `-t' option") +# -pthreads: Solaris/gcc (Note: HP C also rejects) +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads and +# -D_REENTRANT too), HP C (must be checked before -lpthread, which +# is present but should not be used directly; and before -mthreads, +# because the compiler interprets this as "-mt" + "-hreads") +# -mthreads: Mingw32/gcc, Lynx/gcc +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case $host_os in + + freebsd*) + + # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) + # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) + + ax_pthread_flags="-kthread lthread $ax_pthread_flags" + ;; + + hpux*) + + # From the cc(1) man page: "[-mt] Sets various -D flags to enable + # multi-threading and also sets -lpthread." + + ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" + ;; + + openedition*) + + # IBM z/OS requires a feature-test macro to be defined in order to + # enable POSIX threads at all, so give the user a hint if this is + # not set. (We don't define these ourselves, as they can affect + # other portions of the system API in unpredictable ways.) + + AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], + [ +# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) + AX_PTHREAD_ZOS_MISSING +# endif + ], + [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) + ;; + + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (N.B.: The stubs are missing + # pthread_cleanup_push, or rather a function called by this macro, + # so we could check for that, but who knows whether they'll stub + # that too in a future libc.) So we'll check first for the + # standard Solaris way of linking pthreads (-mt -lpthread). + + ax_pthread_flags="-mt,pthread pthread $ax_pthread_flags" + ;; +esac + +# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) + +AS_IF([test "x$GCC" = "xyes"], + [ax_pthread_flags="-pthread -pthreads $ax_pthread_flags"]) + +# The presence of a feature test macro requesting re-entrant function +# definitions is, on some systems, a strong hint that pthreads support is +# correctly enabled + +case $host_os in + darwin* | hpux* | linux* | osf* | solaris*) + ax_pthread_check_macro="_REENTRANT" + ;; + + aix*) + ax_pthread_check_macro="_THREAD_SAFE" + ;; + + *) + ax_pthread_check_macro="--" + ;; +esac +AS_IF([test "x$ax_pthread_check_macro" = "x--"], + [ax_pthread_check_cond=0], + [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) + +# Are we compiling with Clang? + +AC_CACHE_CHECK([whether $CC is Clang], + [ax_cv_PTHREAD_CLANG], + [ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], + [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + ], + [ax_cv_PTHREAD_CLANG=yes]) + fi + ]) +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + +ax_pthread_clang_warning=no + +# Clang needs special handling, because older versions handle the -pthread +# option in a rather... idiosyncratic way + +if test "x$ax_pthread_clang" = "xyes"; then + + # Clang takes -pthread; it has never supported any other flag + + # (Note 1: This will need to be revisited if a system that Clang + # supports has POSIX threads in a separate library. This tends not + # to be the way of modern systems, but it's conceivable.) + + # (Note 2: On some systems, notably Darwin, -pthread is not needed + # to get POSIX threads support; the API is always present and + # active. We could reasonably leave PTHREAD_CFLAGS empty. But + # -pthread does define _REENTRANT, and while the Darwin headers + # ignore this macro, third-party headers might not.) + + PTHREAD_CFLAGS="-pthread" + PTHREAD_LIBS= + + ax_pthread_ok=yes + + # However, older versions of Clang make a point of warning the user + # that, in an invocation where only linking and no compilation is + # taking place, the -pthread option has no effect ("argument unused + # during compilation"). They expect -pthread to be passed in only + # when source code is being compiled. + # + # Problem is, this is at odds with the way Automake and most other + # C build frameworks function, which is that the same flags used in + # compilation (CFLAGS) are also used in linking. Many systems + # supported by AX_PTHREAD require exactly this for POSIX threads + # support, and in fact it is often not straightforward to specify a + # flag that is used only in the compilation phase and not in + # linking. Such a scenario is extremely rare in practice. + # + # Even though use of the -pthread flag in linking would only print + # a warning, this can be a nuisance for well-run software projects + # that build with -Werror. So if the active version of Clang has + # this misfeature, we search for an option to squash it. + + AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown + # Create an alternate version of $ac_link that compiles and + # links in two steps (.c -> .o, .o -> exe) instead of one + # (.c -> exe), because the warning occurs only in the second + # step + ax_pthread_save_ac_link="$ac_link" + ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' + ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"` + ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" + ax_pthread_save_CFLAGS="$CFLAGS" + for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do + AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) + CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" + ac_link="$ax_pthread_save_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [ac_link="$ax_pthread_2step_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [break]) + ]) + done + ac_link="$ax_pthread_save_ac_link" + CFLAGS="$ax_pthread_save_CFLAGS" + AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" + ]) + + case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in + no | unknown) ;; + *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; + esac + +fi # $ax_pthread_clang = yes + +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -mt,pthread) + AC_MSG_CHECKING([whether pthreads work with -mt -lpthread]) + PTHREAD_CFLAGS="-mt" + PTHREAD_LIBS="-lpthread" + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void routine(void *a) { a = 0; } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + AC_MSG_RESULT([$ax_pthread_ok]) + AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_CACHE_CHECK([for joinable pthread attribute], + [ax_cv_PTHREAD_JOINABLE_ATTR], + [ax_cv_PTHREAD_JOINABLE_ATTR=unknown + for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [int attr = $ax_pthread_attr; return attr /* ; */])], + [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], + []) + done + ]) + AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ + test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ + test "x$ax_pthread_joinable_attr_defined" != "xyes"], + [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], + [$ax_cv_PTHREAD_JOINABLE_ATTR], + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + ax_pthread_joinable_attr_defined=yes + ]) + + AC_CACHE_CHECK([whether more special flags are required for pthreads], + [ax_cv_PTHREAD_SPECIAL_FLAGS], + [ax_cv_PTHREAD_SPECIAL_FLAGS=no + case $host_os in + solaris*) + ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" + ;; + esac + ]) + AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ + test "x$ax_pthread_special_flags_added" != "xyes"], + [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" + ax_pthread_special_flags_added=yes]) + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + [ax_cv_PTHREAD_PRIO_INHERIT], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[int i = PTHREAD_PRIO_INHERIT;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ + test "x$ax_pthread_prio_inherit_defined" != "xyes"], + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) + ax_pthread_prio_inherit_defined=yes + ]) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != "xyes"; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], + [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" + +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD From f709d220388f1c57a699412f6df13ab5342b9453 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Wed, 24 Jan 2018 17:08:02 +0900 Subject: [PATCH 0649/1642] Add remoting plugin for output streaming Remoting plugin support streaming image of virtual output on drm-backend to remote output. By appending remote-output section in weston.ini, weston loads remoting plugin module and creates virtual outputs via remoting plugin. The mode, host, and port properties are configurable in remote-output section. This plugin send motion jpeg images to client via RTP using gstreamer. Client can receive by using following pipeline of gst-launch. gst-launch-1.0 rtpbin name=rtpbin \ udpsrc caps="application/x-rtp,media=(string)video,clock-rate=(int)90000, encoding-name=JPEG,payload=26" port=[PORTNUMBER] ! rtpbin.recv_rtp_sink_0 \ rtpbin. ! rtpjpegdepay ! jpegdec ! autovideosink \ udpsrc port=[PORTNUMBER+1] ! rtpbin.recv_rtcp_sink_0 \ rtpbin.send_rtcp_src_0 ! udpsink port=[PORTNUMBER+2] sync=false async=false where, PORTNUMBER is specified in weston.ini. Signed-off-by: Tomohito Esaki --- Makefile.am | 17 + compositor/main.c | 152 ++++++ configure.ac | 11 + doc/remoting-client-receive.bash | 38 ++ man/weston-drm.man | 31 ++ man/weston.ini.man | 13 + remoting/README | 28 + remoting/remoting-plugin.c | 907 +++++++++++++++++++++++++++++++ remoting/remoting-plugin.h | 74 +++ 9 files changed, 1271 insertions(+) create mode 100755 doc/remoting-client-receive.bash create mode 100644 remoting/README create mode 100644 remoting/remoting-plugin.c create mode 100644 remoting/remoting-plugin.h diff --git a/Makefile.am b/Makefile.am index cdb48fb90..223b510e1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -402,6 +402,23 @@ drm_backend_la_LIBADD += $(LIBVA_LIBS) drm_backend_la_LDFLAGS += -pthread drm_backend_la_CFLAGS += $(LIBVA_CFLAGS) endif + +# remoting +if ENABLE_REMOTING +libweston_module_LTLIBRARIES += remoting-plugin.la +remoting_plugin_la_LDFLAGS = -module -avoid-version +remoting_plugin_la_LIBADD = \ + $(COMPOSITOR_LIBS) \ + $(REMOTING_GST_LIBS) +remoting_plugin_la_CFLAGS = \ + $(COMPOSITOR_CFLAGS) \ + $(REMOTING_GST_CFLAGS) \ + $(AM_CFLAGS) +remoting_plugin_la_SOURCES = \ + remoting/remoting-plugin.c \ + remoting/remoting-plugin.h +endif + endif if ENABLE_WAYLAND_COMPOSITOR diff --git a/compositor/main.c b/compositor/main.c index 371673173..383a88b9f 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -61,6 +61,7 @@ #include "compositor-wayland.h" #include "windowed-output-api.h" #include "weston-debug.h" +#include "../remoting/remoting-plugin.h" #define WINDOW_TITLE "Weston Compositor" @@ -1973,6 +1974,154 @@ drm_heads_changed(struct wl_listener *listener, void *arg) wet->init_failed = true; } +static int +drm_backend_remoted_output_configure(struct weston_output *output, + struct weston_config_section *section, + char *modeline, + const struct weston_remoting_api *api) +{ + char *gbm_format = NULL; + char *seat = NULL; + char *host = NULL; + int port, ret; + + ret = api->set_mode(output, modeline); + if (ret < 0) { + weston_log("Cannot configure an output \"%s\" using " + "weston_remoting_api. Invalid mode\n", + output->name); + return -1; + } + + wet_output_set_scale(output, section, 1, 0); + wet_output_set_transform(output, section, WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX); + + weston_config_section_get_string(section, "gbm-format", &gbm_format, + NULL); + api->set_gbm_format(output, gbm_format); + free(gbm_format); + + weston_config_section_get_string(section, "seat", &seat, ""); + + api->set_seat(output, seat); + free(seat); + + weston_config_section_get_string(section, "host", &host, NULL); + if (!host) { + weston_log("Cannot configure an output \"%s\". Invalid host\n", + output->name); + return -1; + } + api->set_host(output, host); + free(host); + + weston_config_section_get_int(section, "port", &port, 0); + if (port <= 0 || 65533 < port) { + weston_log("Cannot configure an output \"%s\". Invalid port\n", + output->name); + return -1; + } + api->set_port(output, port); + + return 0; +} + +static void +remoted_output_init(struct weston_compositor *c, + struct weston_config_section *section, + const struct weston_remoting_api *api) +{ + struct weston_output *output = NULL; + char *output_name, *modeline = NULL; + int ret; + + weston_config_section_get_string(section, "name", &output_name, + NULL); + if (!output_name) + return; + + weston_config_section_get_string(section, "mode", &modeline, "off"); + if (strcmp(modeline, "off") == 0) + goto err; + + output = api->create_output(c, output_name); + if (!output) { + weston_log("Cannot create remoted output \"%s\".\n", + output_name); + goto err; + } + + ret = drm_backend_remoted_output_configure(output, section, modeline, + api); + if (ret < 0) { + weston_log("Cannot configure remoted output \"%s\".\n", + output_name); + goto err; + } + + if (weston_output_enable(output) < 0) { + weston_log("Enabling remoted output \"%s\" failed.\n", + output_name); + goto err; + } + + free(modeline); + free(output_name); + weston_log("remoted output '%s' enabled\n", output->name); + return; + +err: + free(modeline); + free(output_name); + if (output) + weston_output_destroy(output); +} + +static void +load_remoting(struct weston_compositor *c, struct weston_config *wc) +{ + const struct weston_remoting_api *api = NULL; + int (*module_init)(struct weston_compositor *ec); + struct weston_config_section *section = NULL; + const char *section_name; + + /* read remote-output section in weston.ini */ + while (weston_config_next_section(wc, §ion, §ion_name)) { + if (strcmp(section_name, "remote-output")) + continue; + + if (!api) { + char *module_name; + struct weston_config_section *core_section = + weston_config_get_section(wc, "core", NULL, + NULL); + + weston_config_section_get_string(core_section, + "remoting", + &module_name, + "remoting-plugin.so"); + module_init = weston_load_module(module_name, + "weston_module_init"); + free(module_name); + if (!module_init) { + weston_log("Can't load remoting-plugin\n"); + return; + } + if (module_init(c) < 0) { + weston_log("Remoting-plugin init failed\n"); + return; + } + + api = weston_remoting_get_api(c); + if (!api) + return; + } + + remoted_output_init(c, section, api); + } +} + static int load_drm_backend(struct weston_compositor *c, int *argc, char **argv, struct weston_config *wc) @@ -2015,6 +2164,9 @@ load_drm_backend(struct weston_compositor *c, ret = weston_compositor_load_backend(c, WESTON_BACKEND_DRM, &config.base); + /* remoting */ + load_remoting(c, wc); + free(config.gbm_format); free(config.seat_id); diff --git a/configure.ac b/configure.ac index e414e85c8..4bd1c331c 100644 --- a/configure.ac +++ b/configure.ac @@ -226,6 +226,16 @@ if test x$enable_drm_compositor = xyes; then [AC_MSG_WARN([GBM does not support dmabuf import, will omit that capability])]) fi +AC_ARG_ENABLE(remoting, [ --enable-remoting],, + enable_remoting=no) +AM_CONDITIONAL(ENABLE_REMOTING, test x$enable_remoting = xyes) +if test x$enable_remoting = xyes; then + if test x$enable_drm_compositor != xyes; then + AC_MSG_WARN([The remoting-plugin.so module requires the DRM backend.]) + fi + PKG_CHECK_MODULES(REMOTING_GST, [gstreamer-1.0 gstreamer-allocators-1.0 gstreamer-app-1.0 gstreamer-video-1.0]) +fi + PKG_CHECK_MODULES(LIBINPUT_BACKEND, [libinput >= 0.8.0]) PKG_CHECK_MODULES(COMPOSITOR, [$COMPOSITOR_MODULES]) @@ -727,6 +737,7 @@ AC_MSG_RESULT([ systemd notify support ${enable_systemd_notify} DRM Compositor ${enable_drm_compositor} + Remoting ${enable_remoting} X11 Compositor ${enable_x11_compositor} Wayland Compositor ${enable_wayland_compositor} Headless Compositor ${enable_headless_compositor} diff --git a/doc/remoting-client-receive.bash b/doc/remoting-client-receive.bash new file mode 100755 index 000000000..b518689a6 --- /dev/null +++ b/doc/remoting-client-receive.bash @@ -0,0 +1,38 @@ +#!/bin/bash + +# Copyright © 2018 Renesas Electronics Corp. +# +# 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. +# +# Authors: IGEL Co., Ltd. + +# By using this script, client can receive remoted output via gstreamer. +# Usage: +# remoting-client-receive.bash + +gst-launch-1.0 rtpbin name=rtpbin \ + udpsrc caps="application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=JPEG,payload=26" port=$1 ! \ + rtpbin.recv_rtp_sink_0 \ + rtpbin. ! rtpjpegdepay ! jpegdec ! autovideosink \ + udpsrc port=$(($1 + 1)) ! rtpbin.recv_rtcp_sink_0 \ + rtpbin.send_rtcp_src_0 ! \ + udpsink port=$(($1 + 2)) sync=false async=false diff --git a/man/weston-drm.man b/man/weston-drm.man index 6244a8237..6f8fa0244 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -45,6 +45,13 @@ including can run on other VTs. On switching back to Weston's VT, input devices and DRM master are re-acquired through the parent process .BR weston-launch . + +The DRM backend also supports virtual outputs that are transmitted over +an RTP session as a series of JPEG images (RTP payload type 26) to a remote +client. Virtual outputs are configured in the +.BR remote-output +section of +.BR weston.ini. . .\" *************************************************************** .SH CONFIGURATION @@ -138,6 +145,30 @@ Defaults to false. Note that When a connector is disconnected, there is no EDID information to provide a list of video modes. Therefore a forced output should also have a detailed mode line specified. + +.SS Section remote-output +.TP +\fBname\fR=\fIname\fR +Specify unique name for the output. +.TP +\fBmode\fR=\fImode\fR +Specify the video mode for the output. The argument +.I mode +is a resolution setting, such as: +.TP +\fBmode\fR=\fIwidthxheight\fR +.TP +\fBmode\fR=\fIwidthxheight@refresh_rate +If refresh_rate is not specified it will default to a 60Hz. +.TP +\fBhost\fR=\fIhost\fR +Specify the host name or IP Address that the remote output will be +transmitted to. +.TP +\fBport\fR=\fIport\fR +Specify the port number to transmit the remote output to. Usable port range +is 1-65533. + . .\" *************************************************************** .SH OPTIONS diff --git a/man/weston.ini.man b/man/weston.ini.man index 86a9c5dfd..c12e0505b 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -189,6 +189,19 @@ useful for debugging a crash on start-up when it would be inconvenient to launch weston directly from a debugger. Boolean, defaults to .BR false . There is also a command line option to do the same. +.TP 7 +.BI "remoting="remoting-plugin.so +specifies a plugin for remote output to load (string). This can be used to load +your own implemented remoting plugin or one with Weston as default. Available +remoting plugins in the +.IR "__libweston_modules_dir__" +directroy are: +.PP +.RS 10 +.nf +.BR remoting-plugin.so +.fi +.RE .SH "LIBINPUT SECTION" The diff --git a/remoting/README b/remoting/README new file mode 100644 index 000000000..2166d30e0 --- /dev/null +++ b/remoting/README @@ -0,0 +1,28 @@ + Remoting plugin for Weston + + +The Remoting plugin creates a streaming image of a virtual output and transmits +it to a remote host. It is currently only supported on the drm-backend. Virtual +outputs are created and configured by adding a remote-output section to +weston.ini. See man weston-drm(7) for configuration details. This plugin is +loaded automatically if any remote-output sections are present. + +This plugin sends motion jpeg images to a client via RTP using gstreamer, and +so requires gstreamer-1.0. This plugin starts sending images immediately when +weston is run, and keeps sending them until weston shuts down. The image stream +can be received by any appropriately configured RTP client, but a sample +gstreamer RTP client script can be found at doc/remoting-client-receive.bash. + +Script usage: + remoting-client-receive.bash + + +How to compile +--------------- +Set --enable-remoting=true when configuring weston. The remoting-plugin.so +module is created and installed in the libweston path. + + +How to configure weston.ini +---------------------------- +See man weston-drm(7). diff --git a/remoting/remoting-plugin.c b/remoting/remoting-plugin.c new file mode 100644 index 000000000..3715b22bf --- /dev/null +++ b/remoting/remoting-plugin.c @@ -0,0 +1,907 @@ +/* + * Copyright © 2018 Renesas Electronics Corp. + * + * Based on vaapi-recorder by: + * Copyright (c) 2012 Intel Corporation. All Rights Reserved. + * Copyright © 2013 Intel Corporation + * + * 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. + * + * Authors: IGEL Co., Ltd. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "remoting-plugin.h" +#include "compositor-drm.h" +#include "shared/helpers.h" +#include "shared/timespec-util.h" + +#define MAX_RETRY_COUNT 3 + +struct weston_remoting { + struct weston_compositor *compositor; + struct wl_list output_list; + struct wl_listener destroy_listener; + const struct weston_drm_virtual_output_api *virtual_output_api; + + GstAllocator *allocator; +}; + +struct remoted_gstpipe { + int readfd; + int writefd; + struct wl_event_source *source; +}; + +/* supported gbm format list */ +struct remoted_output_support_gbm_format { + uint32_t gbm_format; + const char *gst_format_string; + GstVideoFormat gst_video_format; +}; + +static const struct remoted_output_support_gbm_format supported_formats[] = { + { + .gbm_format = GBM_FORMAT_XRGB8888, + .gst_format_string = "BGRx", + .gst_video_format = GST_VIDEO_FORMAT_BGRx, + }, { + .gbm_format = GBM_FORMAT_RGB565, + .gst_format_string = "RGB16", + .gst_video_format = GST_VIDEO_FORMAT_RGB16, + }, { + .gbm_format = GBM_FORMAT_XRGB2101010, + .gst_format_string = "r210", + .gst_video_format = GST_VIDEO_FORMAT_r210, + } +}; + +struct remoted_output { + struct weston_output *output; + void (*saved_destroy)(struct weston_output *output); + int (*saved_enable)(struct weston_output *output); + int (*saved_disable)(struct weston_output *output); + void (*saved_start_repaint_loop)(struct weston_output *output); + + char *host; + int port; + const struct remoted_output_support_gbm_format *format; + + struct weston_head *head; + + struct weston_remoting *remoting; + struct wl_event_source *finish_frame_timer; + struct wl_list link; + bool submitted_frame; + int fence_sync_fd; + struct wl_event_source *fence_sync_event_source; + + GstElement *pipeline; + GstAppSrc *appsrc; + GstBus *bus; + struct remoted_gstpipe gstpipe; + GstClockTime start_time; + int retry_count; +}; + +struct mem_free_cb_data { + struct remoted_output *output; + struct drm_fb *output_buffer; +}; + +struct gst_frame_buffer_data { + struct remoted_output *output; + GstBuffer *buffer; +}; + +/* message type for pipe */ +#define GSTPIPE_MSG_BUS_SYNC 1 +#define GSTPIPE_MSG_BUFFER_RELEASE 2 + +struct gstpipe_msg_data { + int type; + void *data; +}; + +static int +remoting_gst_init(struct weston_remoting *remoting) +{ + GError *err = NULL; + + if (!gst_init_check(NULL, NULL, &err)) { + weston_log("GStreamer initialization error: %s\n", + err->message); + g_error_free(err); + return -1; + } + + remoting->allocator = gst_dmabuf_allocator_new(); + + return 0; +} + +static void +remoting_gst_deinit(struct weston_remoting *remoting) +{ + gst_object_unref(remoting->allocator); +} + +static GstBusSyncReply +remoting_gst_bus_sync_handler(GstBus *bus, GstMessage *message, + gpointer user_data) +{ + struct remoted_gstpipe *pipe = user_data; + struct gstpipe_msg_data msg = { + .type = GSTPIPE_MSG_BUS_SYNC, + .data = NULL + }; + ssize_t ret; + + ret = write(pipe->writefd, &msg, sizeof(msg)); + if (ret != sizeof(msg)) + weston_log("ERROR: failed to write, ret=%zd, errno=%d\n", + ret, errno); + + return GST_BUS_PASS; +} + +static int +remoting_gst_pipeline_init(struct remoted_output *output) +{ + char pipeline_str[1024]; + GstCaps *caps; + GError *err = NULL; + GstStateChangeReturn ret; + struct weston_mode *mode = output->output->current_mode; + + /* TODO: use encodebin instead of jpegenc */ + snprintf(pipeline_str, sizeof(pipeline_str), + "rtpbin name=rtpbin " + "appsrc name=src ! videoconvert ! video/x-raw,format=I420 ! " + "jpegenc ! rtpjpegpay ! rtpbin.send_rtp_sink_0 " + "rtpbin.send_rtp_src_0 ! udpsink name=sink host=%s port=%d " + "rtpbin.send_rtcp_src_0 ! " + "udpsink host=%s port=%d sync=false async=false " + "udpsrc port=%d ! rtpbin.recv_rtcp_sink_0", + output->host, output->port, output->host, output->port + 1, + output->port + 2); + weston_log("GST pipeline: %s\n", pipeline_str); + + output->pipeline = gst_parse_launch(pipeline_str, &err); + if (!output->pipeline) { + weston_log("Could not create gstreamer pipeline. Error: %s\n", + err->message); + g_error_free(err); + return -1; + } + + output->appsrc = (GstAppSrc*) + gst_bin_get_by_name(GST_BIN(output->pipeline), "src"); + if (!output->appsrc) { + weston_log("Could not get appsrc from gstreamer pipeline\n"); + goto err; + } + + caps = gst_caps_new_simple("video/x-raw", + "format", G_TYPE_STRING, + output->format->gst_format_string, + "width", G_TYPE_INT, mode->width, + "height", G_TYPE_INT, mode->height, + "framerate", GST_TYPE_FRACTION, + mode->refresh, 1000, + NULL); + if (!caps) { + weston_log("Could not create gstreamer caps.\n"); + goto err; + } + g_object_set(G_OBJECT(output->appsrc), + "caps", caps, + "stream-type", 0, + "format", GST_FORMAT_TIME, + "is-live", TRUE, + NULL); + gst_caps_unref(caps); + + output->bus = gst_pipeline_get_bus(GST_PIPELINE(output->pipeline)); + if (!output->bus) { + weston_log("Could not get bus from gstreamer pipeline\n"); + goto err; + } + gst_bus_set_sync_handler(output->bus, remoting_gst_bus_sync_handler, + &output->gstpipe, NULL); + + output->start_time = 0; + ret = gst_element_set_state(output->pipeline, GST_STATE_PLAYING); + if (ret == GST_STATE_CHANGE_FAILURE) { + weston_log("Couldn't set GST_STATE_PLAYING to pipeline\n"); + goto err; + } + + return 0; + +err: + gst_object_unref(GST_OBJECT(output->pipeline)); + output->pipeline = NULL; + return -1; +} + +static void +remoting_gst_pipeline_deinit(struct remoted_output *output) +{ + if (!output->pipeline) + return; + + gst_element_set_state(output->pipeline, GST_STATE_NULL); + if (output->bus) + gst_object_unref(GST_OBJECT(output->bus)); + gst_object_unref(GST_OBJECT(output->pipeline)); + output->pipeline = NULL; +} + +static int +remoting_output_disable(struct weston_output *output); + +static void +remoting_gst_restart(void *data) +{ + struct remoted_output *output = data; + + if (remoting_gst_pipeline_init(output) < 0) { + weston_log("gst: Could not restart pipeline!!\n"); + remoting_output_disable(output->output); + } +} + +static void +remoting_gst_schedule_restart(struct remoted_output *output) +{ + struct wl_event_loop *loop; + struct weston_compositor *c = output->remoting->compositor; + + loop = wl_display_get_event_loop(c->wl_display); + wl_event_loop_add_idle(loop, remoting_gst_restart, output); +} + +static void +remoting_gst_bus_message_handler(struct remoted_output *output) +{ + GstMessage *message; + GError *error; + gchar *debug; + + /* get message from bus queue */ + message = gst_bus_pop(output->bus); + if (!message) + return; + + switch (GST_MESSAGE_TYPE(message)) { + case GST_MESSAGE_STATE_CHANGED: { + GstState new_state; + gst_message_parse_state_changed(message, NULL, &new_state, + NULL); + if (!strcmp(GST_OBJECT_NAME(message->src), "sink") && + new_state == GST_STATE_PLAYING) + output->retry_count = 0; + break; + } + case GST_MESSAGE_WARNING: + gst_message_parse_warning(message, &error, &debug); + weston_log("gst: Warning: %s: %s\n", + GST_OBJECT_NAME(message->src), error->message); + break; + case GST_MESSAGE_ERROR: + gst_message_parse_error(message, &error, &debug); + weston_log("gst: Error: %s: %s\n", + GST_OBJECT_NAME(message->src), error->message); + if (output->retry_count < MAX_RETRY_COUNT) { + output->retry_count++; + remoting_gst_pipeline_deinit(output); + remoting_gst_schedule_restart(output); + } else { + remoting_output_disable(output->output); + } + break; + default: + break; + } +} + +static void +remoting_output_buffer_release(struct remoted_output *output, void *buffer) +{ + const struct weston_drm_virtual_output_api *api + = output->remoting->virtual_output_api; + + api->buffer_released(buffer); +} + +static int +remoting_gstpipe_handler(int fd, uint32_t mask, void *data) +{ + ssize_t ret; + struct gstpipe_msg_data msg; + struct remoted_output *output = data; + + /* recieve message */ + ret = read(fd, &msg, sizeof(msg)); + if (ret != sizeof(msg)) { + weston_log("ERROR: failed to read, ret=%zd, errno=%d\n", + ret, errno); + remoting_output_disable(output->output); + return 0; + } + + switch (msg.type) { + case GSTPIPE_MSG_BUS_SYNC: + remoting_gst_bus_message_handler(output); + break; + case GSTPIPE_MSG_BUFFER_RELEASE: + remoting_output_buffer_release(output, msg.data); + break; + default: + weston_log("Recieved unknown message! msg=%d\n", msg.type); + } + return 1; +} + +static int +remoting_gstpipe_init(struct weston_compositor *c, + struct remoted_output *output) +{ + struct wl_event_loop *loop; + int fd[2]; + + if (pipe2(fd, O_CLOEXEC) == -1) + return -1; + + output->gstpipe.readfd = fd[0]; + output->gstpipe.writefd = fd[1]; + loop = wl_display_get_event_loop(c->wl_display); + output->gstpipe.source = + wl_event_loop_add_fd(loop, output->gstpipe.readfd, + WL_EVENT_READABLE, + remoting_gstpipe_handler, output); + if (!output->gstpipe.source) { + close(fd[0]); + close(fd[1]); + return -1; + } + + return 0; +} + +static void +remoting_gstpipe_release(struct remoted_gstpipe *pipe) +{ + wl_event_source_remove(pipe->source); + close(pipe->readfd); + close(pipe->writefd); +} + +static void +remoting_output_destroy(struct weston_output *output); + +static void +weston_remoting_destroy(struct wl_listener *l, void *data) +{ + struct weston_remoting *remoting = + container_of(l, struct weston_remoting, destroy_listener); + struct remoted_output *output, *next; + + wl_list_for_each_safe(output, next, &remoting->output_list, link) + remoting_output_destroy(output->output); + + /* Finalize gstreamer */ + remoting_gst_deinit(remoting); + + wl_list_remove(&remoting->destroy_listener.link); + free(remoting); +} + +static struct weston_remoting * +weston_remoting_get(struct weston_compositor *compositor) +{ + struct wl_listener *listener; + struct weston_remoting *remoting; + + listener = wl_signal_get(&compositor->destroy_signal, + weston_remoting_destroy); + if (!listener) + return NULL; + + remoting = wl_container_of(listener, remoting, destroy_listener); + return remoting; +} + +static int +remoting_output_finish_frame_handler(void *data) +{ + struct remoted_output *output = data; + const struct weston_drm_virtual_output_api *api + = output->remoting->virtual_output_api; + struct timespec now; + int64_t msec; + + if (output->submitted_frame) { + struct weston_compositor *c = output->remoting->compositor; + output->submitted_frame = false; + weston_compositor_read_presentation_clock(c, &now); + api->finish_frame(output->output, &now, 0); + } + + msec = millihz_to_nsec(output->output->current_mode->refresh) / 1000000; + wl_event_source_timer_update(output->finish_frame_timer, msec); + return 0; +} + +static void +remoting_gst_mem_free_cb(struct mem_free_cb_data *cb_data, GstMiniObject *obj) +{ + struct remoted_output *output = cb_data->output; + struct remoted_gstpipe *pipe = &output->gstpipe; + struct gstpipe_msg_data msg = { + .type = GSTPIPE_MSG_BUFFER_RELEASE, + .data = cb_data->output_buffer + }; + ssize_t ret; + + ret = write(pipe->writefd, &msg, sizeof(msg)); + if (ret != sizeof(msg)) + weston_log("ERROR: failed to write, ret=%zd, errno=%d\n", ret, + errno); + free(cb_data); +} + +static struct remoted_output * +lookup_remoted_output(struct weston_output *output) +{ + struct weston_compositor *c = output->compositor; + struct weston_remoting *remoting = weston_remoting_get(c); + struct remoted_output *remoted_output; + + wl_list_for_each(remoted_output, &remoting->output_list, link) { + if (remoted_output->output == output) + return remoted_output; + } + + weston_log("%s: %s: could not find output\n", __FILE__, __func__); + return NULL; +} + +static void +remoting_output_gst_push_buffer(struct remoted_output *output, + GstBuffer *buffer) +{ + struct timespec current_frame_ts; + GstClockTime ts, current_frame_time; + + weston_compositor_read_presentation_clock(output->remoting->compositor, + ¤t_frame_ts); + current_frame_time = GST_TIMESPEC_TO_TIME(current_frame_ts); + if (output->start_time == 0) + output->start_time = current_frame_time; + ts = current_frame_time - output->start_time; + + if (GST_CLOCK_TIME_IS_VALID(ts)) + GST_BUFFER_PTS(buffer) = ts; + else + GST_BUFFER_PTS(buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE; + + gst_app_src_push_buffer(output->appsrc, buffer); + output->submitted_frame = true; +} + +static int +remoting_output_fence_sync_handler(int fd, uint32_t mask, void *data) +{ + struct gst_frame_buffer_data *frame_data = data; + struct remoted_output *output = frame_data->output; + + remoting_output_gst_push_buffer(output, frame_data->buffer); + + wl_event_source_remove(output->fence_sync_event_source); + close(output->fence_sync_fd); + free(frame_data); + + return 0; +} + +static int +remoting_output_frame(struct weston_output *output_base, int fd, int stride, + struct drm_fb *output_buffer) +{ + struct remoted_output *output = lookup_remoted_output(output_base); + struct weston_remoting *remoting = output->remoting; + struct weston_mode *mode; + const struct weston_drm_virtual_output_api *api + = output->remoting->virtual_output_api; + struct wl_event_loop *loop; + GstBuffer *buf; + GstMemory *mem; + gsize offset = 0; + struct mem_free_cb_data *cb_data; + struct gst_frame_buffer_data *frame_data; + + if (!output) + return -1; + + cb_data = zalloc(sizeof *cb_data); + if (!cb_data) + return -1; + + mode = output->output->current_mode; + buf = gst_buffer_new(); + mem = gst_dmabuf_allocator_alloc(remoting->allocator, fd, + stride * mode->height); + gst_buffer_append_memory(buf, mem); + gst_buffer_add_video_meta_full(buf, + GST_VIDEO_FRAME_FLAG_NONE, + output->format->gst_video_format, + mode->width, + mode->height, + 1, + &offset, + &stride); + + cb_data->output = output; + cb_data->output_buffer = output_buffer; + gst_mini_object_weak_ref(GST_MINI_OBJECT(mem), + (GstMiniObjectNotify)remoting_gst_mem_free_cb, + cb_data); + + output->fence_sync_fd = api->get_fence_sync_fd(output->output); + /* Push buffer to gstreamer immediately on get_fence_sync_fd failure */ + if (output->fence_sync_fd == -1) { + remoting_output_gst_push_buffer(output, buf); + return 0; + } + + frame_data = zalloc(sizeof *frame_data); + if (!frame_data) { + close(output->fence_sync_fd); + remoting_output_gst_push_buffer(output, buf); + return 0; + } + + frame_data->output = output; + frame_data->buffer = buf; + loop = wl_display_get_event_loop(remoting->compositor->wl_display); + output->fence_sync_event_source = + wl_event_loop_add_fd(loop, output->fence_sync_fd, + WL_EVENT_READABLE, + remoting_output_fence_sync_handler, + frame_data); + + return 0; +} + +static void +remoting_output_destroy(struct weston_output *output) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + struct weston_mode *mode, *next; + + wl_list_for_each_safe(mode, next, &output->mode_list, link) { + wl_list_remove(&mode->link); + free(mode); + } + + remoted_output->saved_destroy(output); + + remoting_gst_pipeline_deinit(remoted_output); + remoting_gstpipe_release(&remoted_output->gstpipe); + + if (remoted_output->host) + free(remoted_output->host); + + wl_list_remove(&remoted_output->link); + weston_head_release(remoted_output->head); + free(remoted_output->head); + free(remoted_output); +} + +static void +remoting_output_start_repaint_loop(struct weston_output *output) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + int64_t msec; + + remoted_output->saved_start_repaint_loop(output); + + msec = millihz_to_nsec(remoted_output->output->current_mode->refresh) + / 1000000; + wl_event_source_timer_update(remoted_output->finish_frame_timer, msec); +} + +static int +remoting_output_enable(struct weston_output *output) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + struct weston_compositor *c = output->compositor; + const struct weston_drm_virtual_output_api *api + = remoted_output->remoting->virtual_output_api; + struct wl_event_loop *loop; + int ret; + + api->set_submit_frame_cb(output, remoting_output_frame); + + ret = remoted_output->saved_enable(output); + if (ret < 0) + return ret; + + remoted_output->saved_start_repaint_loop = output->start_repaint_loop; + output->start_repaint_loop = remoting_output_start_repaint_loop; + + ret = remoting_gst_pipeline_init(remoted_output); + if (ret < 0) { + remoted_output->saved_disable(output); + return ret; + } + + loop = wl_display_get_event_loop(c->wl_display); + remoted_output->finish_frame_timer = + wl_event_loop_add_timer(loop, + remoting_output_finish_frame_handler, + remoted_output); + + return 0; +} + +static int +remoting_output_disable(struct weston_output *output) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + + wl_event_source_remove(remoted_output->finish_frame_timer); + remoting_gst_pipeline_deinit(remoted_output); + + return remoted_output->saved_disable(output); +} + +static struct weston_output * +remoting_output_create(struct weston_compositor *c, char *name) +{ + struct weston_remoting *remoting = weston_remoting_get(c); + struct remoted_output *output; + struct weston_head *head; + const struct weston_drm_virtual_output_api *api; + const char *make = "Renesas"; + const char *model = "Virtual Display"; + const char *serial_number = "unknown"; + const char *connector_name = "remoting"; + + if (!name || !strlen(name)) + return NULL; + + api = remoting->virtual_output_api; + + output = zalloc(sizeof *output); + if (!output) + return NULL; + + head = zalloc(sizeof *head); + if (!head) + goto err; + + if (remoting_gstpipe_init(c, output) < 0) { + weston_log("Can not create pipe for gstreamer\n"); + goto err; + } + + output->output = api->create_output(c, name); + if (!output->output) { + weston_log("Can not create virtual output\n"); + goto err; + } + + output->saved_destroy = output->output->destroy; + output->output->destroy = remoting_output_destroy; + output->saved_enable = output->output->enable; + output->output->enable = remoting_output_enable; + output->saved_disable = output->output->disable; + output->output->disable = remoting_output_disable; + output->remoting = remoting; + wl_list_insert(remoting->output_list.prev, &output->link); + + weston_head_init(head, connector_name); + weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_NONE); + weston_head_set_monitor_strings(head, make, model, serial_number); + head->compositor = c; + + weston_output_attach_head(output->output, head); + output->head = head; + + /* set XRGB8888 format */ + output->format = &supported_formats[0]; + + return output->output; + +err: + if (output->gstpipe.source) + remoting_gstpipe_release(&output->gstpipe); + if (head) + free(head); + free(output); + return NULL; +} + +static bool +remoting_output_is_remoted(struct weston_output *output) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + + if (remoted_output) + return true; + + return false; +} + +static int +remoting_output_set_mode(struct weston_output *output, const char *modeline) +{ + struct weston_mode *mode; + int n, width, height, refresh = 0; + + if (!remoting_output_is_remoted(output)) { + weston_log("Output is not remoted.\n"); + return -1; + } + + if (!modeline) + return -1; + + n = sscanf(modeline, "%dx%d@%d", &width, &height, &refresh); + if (n != 2 && n != 3) + return -1; + + mode = zalloc(sizeof *mode); + if (!mode) + return -1; + + mode->flags = WL_OUTPUT_MODE_CURRENT; + mode->width = width; + mode->height = height; + mode->refresh = (refresh ? refresh : 60) * 1000LL; + + wl_list_insert(output->mode_list.prev, &mode->link); + + output->current_mode = mode; + + return 0; +} + +static void +remoting_output_set_gbm_format(struct weston_output *output, + const char *gbm_format) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + const struct weston_drm_virtual_output_api *api; + uint32_t format, i; + + if (!remoted_output) + return; + + api = remoted_output->remoting->virtual_output_api; + format = api->set_gbm_format(output, gbm_format); + + for (i = 0; i < ARRAY_LENGTH(supported_formats); i++) { + if (format == supported_formats[i].gbm_format) { + remoted_output->format = &supported_formats[i]; + return; + } + } +} + +static void +remoting_output_set_seat(struct weston_output *output, const char *seat) +{ + /* for now, nothing todo */ +} + +static void +remoting_output_set_host(struct weston_output *output, char *host) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + + if (!remoted_output) + return; + + if (remoted_output->host) + free(remoted_output->host); + remoted_output->host = strdup(host); +} + +static void +remoting_output_set_port(struct weston_output *output, int port) +{ + struct remoted_output *remoted_output = lookup_remoted_output(output); + + if (remoted_output) + remoted_output->port = port; +} + +static const struct weston_remoting_api remoting_api = { + remoting_output_create, + remoting_output_is_remoted, + remoting_output_set_mode, + remoting_output_set_gbm_format, + remoting_output_set_seat, + remoting_output_set_host, + remoting_output_set_port, +}; + +WL_EXPORT int +weston_module_init(struct weston_compositor *compositor) +{ + int ret; + struct weston_remoting *remoting; + const struct weston_drm_virtual_output_api *api = + weston_drm_virtual_output_get_api(compositor); + + if (!api) + return -1; + + remoting = zalloc(sizeof *remoting); + if (!remoting) + return -1; + + remoting->virtual_output_api = api; + remoting->compositor = compositor; + wl_list_init(&remoting->output_list); + + ret = weston_plugin_api_register(compositor, WESTON_REMOTING_API_NAME, + &remoting_api, sizeof(remoting_api)); + + if (ret < 0) { + weston_log("Failed to register remoting API.\n"); + goto failed; + } + + /* Initialize gstreamer */ + ret = remoting_gst_init(remoting); + if (ret < 0) { + weston_log("Failed to initialize gstreamer.\n"); + goto failed; + } + + remoting->destroy_listener.notify = weston_remoting_destroy; + wl_signal_add(&compositor->destroy_signal, &remoting->destroy_listener); + return 0; + +failed: + free(remoting); + return -1; +} diff --git a/remoting/remoting-plugin.h b/remoting/remoting-plugin.h new file mode 100644 index 000000000..9a7ca365c --- /dev/null +++ b/remoting/remoting-plugin.h @@ -0,0 +1,74 @@ +/* + * Copyright © 2018 Renesas Electronics Corp. + * + * 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. + * + * Authors: IGEL Co., Ltd. + */ + +#ifndef REMOTING_PLUGIN_H +#define REMOTING_PLUGIN_H + +#include "compositor.h" +#include "plugin-registry.h" + +#define WESTON_REMOTING_API_NAME "weston_remoting_api_v1" + +struct weston_remoting_api { + /** Create remoted outputs + * + * Returns 0 on success, -1 on failure. + */ + struct weston_output *(*create_output)(struct weston_compositor *c, + char *name); + + /** Check if output is remoted */ + bool (*is_remoted_output)(struct weston_output *output); + + /** Set mode */ + int (*set_mode)(struct weston_output *output, const char *modeline); + + /** Set gbm format */ + void (*set_gbm_format)(struct weston_output *output, + const char *gbm_format); + + /** Set seat */ + void (*set_seat)(struct weston_output *output, const char *seat); + + /** Set the destination Host(IP Address) */ + void (*set_host)(struct weston_output *output, char *ip); + + /** Set the port number */ + void (*set_port)(struct weston_output *output, int port); +}; + +static inline const struct weston_remoting_api * +weston_remoting_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_REMOTING_API_NAME, + sizeof(struct weston_remoting_api)); + + return (const struct weston_remoting_api *)api; +} + +#endif /* REMOTING_PLUGIN_H */ From e1e7ebdbea8884e8b4eaa06ef1a5b1597233c07c Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Fri, 26 Oct 2018 14:15:15 +0200 Subject: [PATCH 0650/1642] ivi-shell: Add build_view_list function Move the implementation from commit_screen_list to build_view_list function Signed-off-by: Emre Ucan --- ivi-shell/ivi-layout.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index d9a0c2dee..b27fbab77 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -780,10 +780,6 @@ commit_screen_list(struct ivi_layout *layout) struct ivi_layout_screen *iviscrn = NULL; struct ivi_layout_layer *ivilayer = NULL; struct ivi_layout_layer *next = NULL; - struct ivi_layout_view *ivi_view = NULL; - - /* Clear view list of layout ivi_layer */ - wl_list_init(&layout->layout_layer.view_list.link); wl_list_for_each(iviscrn, &layout->screen_list, link) { if (iviscrn->order.dirty) { @@ -810,7 +806,20 @@ commit_screen_list(struct ivi_layout *layout) iviscrn->order.dirty = 0; } + } +} +static void +build_view_list(struct ivi_layout *layout) +{ + struct ivi_layout_screen *iviscrn; + struct ivi_layout_layer *ivilayer; + struct ivi_layout_view *ivi_view; + + /* Clear view list of layout ivi_layer */ + wl_list_init(&layout->layout_layer.view_list.link); + + wl_list_for_each(iviscrn, &layout->screen_list, link) { wl_list_for_each(ivilayer, &iviscrn->order.layer_list, order.link) { if (ivilayer->prop.visibility == false) continue; @@ -1751,6 +1760,7 @@ ivi_layout_commit_changes(void) commit_surface_list(layout); commit_layer_list(layout); commit_screen_list(layout); + build_view_list(layout); commit_transition(layout); From f6638a7f0f8e8bdaa87a74a724542e91d303fce0 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Fri, 26 Oct 2018 15:07:03 +0200 Subject: [PATCH 0651/1642] ivi-shell: unmap views which are not in scenegraph From Michael Olbrich: "Both the core in weston_compositor_build_view_list() with view.link and the ivi-shell in commit_screen_list() with view.layer_link don't remove the old views from the list. As a result, all views that are not currently in the list have old broken links. Destroying such a view tries to remove the view from these lists and will access the old, invalid pointers." Therefore, we have to unmap weston_views which are not in current scenegraph of ivi-shell. I implemented ivi_view_is_mapped() function to check mappedness of ivi_views. The functions checks: - the view is on a layer's order list - the layer is on a screen - the layer and view's ivi_surface are visible If ivi_view is not mapped but weston_view is still mapped, we have to unmap the weston_view with weston_view_unmap() call. Reported-by: Michael Olbrich Signed-off-by: Emre Ucan --- ivi-shell/ivi-layout.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index b27fbab77..d1e53df62 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -586,6 +586,15 @@ update_prop(struct ivi_layout_view *ivi_view) weston_view_schedule_repaint(ivi_view->view); } +static bool +ivi_view_is_mapped(struct ivi_layout_view *ivi_view) +{ + return (!wl_list_empty(&ivi_view->order_link) && + ivi_view->on_layer->on_screen && + ivi_view->on_layer->prop.visibility && + ivi_view->ivisurf->prop.visibility); +} + static void commit_changes(struct ivi_layout *layout) { @@ -816,6 +825,14 @@ build_view_list(struct ivi_layout *layout) struct ivi_layout_layer *ivilayer; struct ivi_layout_view *ivi_view; + /* If ivi_view is not part of the scenegrapgh, we have to unmap + * weston_views + */ + wl_list_for_each(ivi_view, &layout->view_list, link) { + if (!ivi_view_is_mapped(ivi_view)) + weston_view_unmap(ivi_view->view); + } + /* Clear view list of layout ivi_layer */ wl_list_init(&layout->layout_layer.view_list.link); From d93a52a6f9c8efd5345ef36c3a82ae76f6889c90 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Fri, 26 Oct 2018 14:42:21 +0200 Subject: [PATCH 0652/1642] ivi-shell: check ivi_view mappedness in commit_changes() If the view is not mapped, we do not need to update its properties. We can use ivi_view_is_mapped() function to check it. Also we don't need to call weston_view_damage_below() for weston_views, which were in the scenegraph. Because we are calling weston_view_unmap for views of unmapped ivi_views in build_view_list() function Signed-off-by: Emre Ucan --- ivi-shell/ivi-layout.c | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index d1e53df62..b9bcfc804 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -598,40 +598,15 @@ ivi_view_is_mapped(struct ivi_layout_view *ivi_view) static void commit_changes(struct ivi_layout *layout) { - struct ivi_layout_screen *iviscrn = NULL; - struct ivi_layout_layer *ivilayer = NULL; - struct ivi_layout_surface *ivisurf = NULL; struct ivi_layout_view *ivi_view = NULL; wl_list_for_each(ivi_view, &layout->view_list, link) { - ivisurf = ivi_view->ivisurf; - ivilayer = ivi_view->on_layer; - iviscrn = ivilayer->on_screen; - /* * If the view is not on the currently rendered scenegraph, * we do not need to update its properties. */ - if (wl_list_empty(&ivi_view->order_link) || !iviscrn) - continue; - - /* - * If the view's layer or surface is invisible, we do not need - * to update its properties. - */ - if (!ivilayer->prop.visibility || !ivisurf->prop.visibility) { - /* - * If ivilayer or ivisurf of ivi_view is made invisible - * in this commit_changes call, we have to damage - * the weston_view below this ivi_view. Otherwise content - * of this ivi_view will stay visible. - */ - if ((ivilayer->prop.event_mask | ivisurf->prop.event_mask) & - IVI_NOTIFICATION_VISIBILITY) - weston_view_damage_below(ivi_view->view); - + if (!ivi_view_is_mapped(ivi_view)) continue; - } update_prop(ivi_view); } From a864f58f44d701164dbb32bfcdde8c6d761f28ee Mon Sep 17 00:00:00 2001 From: Deepak Rawat Date: Fri, 24 Aug 2018 13:16:03 -0700 Subject: [PATCH 0653/1642] compositor-drm: Read FB2_MODIFIERS capability Not all drivers support fb2 modifiers so read the capability before using drmModeAddFB2WithModifiers. Signed-off-by: Deepak Rawat --- libweston/compositor-drm.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index e024e66fc..4cf0d31f7 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -346,6 +346,8 @@ struct drm_backend { bool aspect_ratio_supported; + bool fb_modifiers; + struct weston_debug_scope *debug; }; @@ -961,7 +963,7 @@ drm_fb_destroy_gbm(struct gbm_bo *bo, void *data) } static int -drm_fb_addfb(struct drm_fb *fb) +drm_fb_addfb(struct drm_backend *b, struct drm_fb *fb) { int ret = -EINVAL; #ifdef HAVE_DRM_ADDFB2_MODIFIERS @@ -971,7 +973,7 @@ drm_fb_addfb(struct drm_fb *fb) /* If we have a modifier set, we must only use the WithModifiers * entrypoint; we cannot import it through legacy ioctls. */ - if (fb->modifier != DRM_FORMAT_MOD_INVALID) { + if (b->fb_modifiers && fb->modifier != DRM_FORMAT_MOD_INVALID) { /* KMS demands that if a modifier is set, it must be the same * for all planes. */ #ifdef HAVE_DRM_ADDFB2_MODIFIERS @@ -1055,7 +1057,7 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, fb->height = height; fb->fd = b->drm.fd; - if (drm_fb_addfb(fb) != 0) { + if (drm_fb_addfb(b, fb) != 0) { weston_log("failed to create kms fb: %m\n"); goto err_bo; } @@ -1228,7 +1230,7 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, goto err_free; } - if (drm_fb_addfb(fb) != 0) + if (drm_fb_addfb(backend, fb) != 0) goto err_free; return fb; @@ -1301,7 +1303,7 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, goto err_free; } - if (drm_fb_addfb(fb) != 0) { + if (drm_fb_addfb(backend, fb) != 0) { if (type == BUFFER_GBM_SURFACE) weston_log("failed to create kms fb: %m\n"); goto err_free; @@ -4062,6 +4064,14 @@ init_kms_caps(struct drm_backend *b) weston_log("DRM: %s atomic modesetting\n", b->atomic_modeset ? "supports" : "does not support"); +#ifdef HAVE_DRM_ADDFB2_MODIFIERS + ret = drmGetCap(b->drm.fd, DRM_CAP_ADDFB2_MODIFIERS, &cap); + if (ret == 0) + b->fb_modifiers = cap; + else +#endif + b->fb_modifiers = 0; + /* * KMS support for hardware planes cannot properly synchronize * without nuclear page flip. Without nuclear/atomic, hw plane From 1f3fae2f1ad13ecdf4b26fb87bb620bf0f3f6435 Mon Sep 17 00:00:00 2001 From: orbitcowboy Date: Fri, 5 Oct 2018 16:02:10 +0000 Subject: [PATCH 0654/1642] Fixed potential memory leaks in simple-dmabuf-drm.c found by Cppcheck. --- clients/simple-dmabuf-drm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index bd0f92240..82f4bd438 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -392,8 +392,11 @@ drm_device_init(struct buffer *buf) #ifdef HAVE_LIBDRM_INTEL else if (!strcmp(dev->name, "i915")) { buf->bufmgr = drm_intel_bufmgr_gem_init(buf->drm_fd, 32); - if (!buf->bufmgr) + if (!buf->bufmgr) { + free(dev->name); + free(dev); return 0; + } dev->alloc_bo = intel_alloc_bo; dev->free_bo = intel_free_bo; dev->export_bo_to_prime = intel_bo_export_to_prime; @@ -425,6 +428,7 @@ drm_device_init(struct buffer *buf) else { fprintf(stderr, "Error: drm device %s unsupported.\n", dev->name); + free(dev->name); free(dev); return 0; } From 807cd2e589140e069cd1c4863650d8019c997d57 Mon Sep 17 00:00:00 2001 From: emersion Date: Wed, 10 Oct 2018 09:46:12 +0200 Subject: [PATCH 0655/1642] clients: configure cursor theme from XCURSOR_* env If XCURSOR_THEME or XCURSOR_SIZE is set, use it as the cursor theme or cursor size. This is similar to what Qt and some X11 apps do. --- clients/window.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/clients/window.c b/clients/window.c index b64e96cf5..12939cb76 100644 --- a/clients/window.c +++ b/clients/window.c @@ -1340,16 +1340,20 @@ create_cursors(struct display *display) const char *config_file; struct weston_config *config; struct weston_config_section *s; - int size; + int size = 32; char *theme = NULL; unsigned int i, j; struct wl_cursor *cursor; + theme = getenv("XCURSOR_THEME"); + if (getenv("XCURSOR_SIZE")) + size = atoi(getenv("XCURSOR_SIZE")); + config_file = weston_config_get_name_from_env(); config = weston_config_parse(config_file); s = weston_config_get_section(config, "shell", NULL, NULL); - weston_config_section_get_string(s, "cursor-theme", &theme, NULL); - weston_config_section_get_int(s, "cursor-size", &size, 32); + weston_config_section_get_string(s, "cursor-theme", &theme, theme); + weston_config_section_get_int(s, "cursor-size", &size, size); weston_config_destroy(config); display->cursor_theme = wl_cursor_theme_load(theme, size, display->shm); From 486b463a18526298e42b5a9ed6f3a6e29022992d Mon Sep 17 00:00:00 2001 From: Vasilis Liaskovitis Date: Wed, 10 Oct 2018 16:14:55 +0200 Subject: [PATCH 0656/1642] gl-renderer, pixman: disconnect the client on unhandled buffer type. Introduce a helper function to disconnect the client on unhandled buffer types, and use it in the gl and pixman renderers. The function is modeled after linux_dmabuf_buffer_send_server_error. Also print the egl error state in the gl renderer, in case the unrecognized buffer error happens when querying an egl buffer. https://gitlab.freedesktop.org/wayland/weston/issues/148 --- libweston/compositor.c | 35 +++++++++++++++++++++++++++++++++++ libweston/compositor.h | 4 ++++ libweston/gl-renderer.c | 6 ++++++ libweston/pixman-renderer.c | 5 ++++- 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index c94fa3153..b60f35def 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -7103,3 +7103,38 @@ weston_compositor_load_xwayland(struct weston_compositor *compositor) return -1; return 0; } + +/** Resolve an internal compositor error by disconnecting the client. + * + * This function is used in cases when the wl_buffer turns out + * unusable and there is no fallback path. + * + * It is possible the fault is caused by a compositor bug, the underlying + * graphics stack bug or normal behaviour, or perhaps a client mistake. + * In any case, the options are to either composite garbage or nothing, + * or disconnect the client. This is a helper function for the latter. + * + * The error is sent as an INVALID_OBJECT error on the client's wl_display. + * + * \param buffer The weston buffer that is unusable. + * \param msg A custom error message attached to the protocol error. + */ +WL_EXPORT void +weston_buffer_send_server_error(struct weston_buffer *buffer, + const char *msg) +{ + struct wl_client *client; + struct wl_resource *display_resource; + uint32_t id; + + assert(buffer->resource); + id = wl_resource_get_id(buffer->resource); + client = wl_resource_get_client(buffer->resource); + display_resource = wl_client_get_object(client, 1); + + assert(display_resource); + wl_resource_post_error(display_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "server error with " + "wl_buffer@%u: %s", id, msg); +} diff --git a/libweston/compositor.h b/libweston/compositor.h index cd718390a..b4b751977 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -2345,6 +2345,10 @@ weston_debug_compositor_create(struct weston_compositor *compositor); void weston_debug_compositor_destroy(struct weston_compositor *compositor); +void +weston_buffer_send_server_error(struct weston_buffer *buffer, + const char *msg); + #ifdef __cplusplus } #endif diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index f2da0d34a..c9e5a8fc7 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -2377,10 +2377,16 @@ gl_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) gl_renderer_attach_dmabuf(es, buffer, dmabuf); else { weston_log("unhandled buffer type!\n"); + if (gr->has_bind_display) { + weston_log("eglQueryWaylandBufferWL failed\n"); + gl_renderer_print_egl_error_state(); + } weston_buffer_reference(&gs->buffer_ref, NULL); gs->buffer_type = BUFFER_TYPE_NULL; gs->y_inverted = 1; es->is_opaque = false; + weston_buffer_send_server_error(buffer, + "disconnecting due to unhandled buffer type"); } } diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c index a316766e0..9640b6168 100644 --- a/libweston/pixman-renderer.c +++ b/libweston/pixman-renderer.c @@ -661,8 +661,11 @@ pixman_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) es->is_opaque = true; break; default: - weston_log("Unsupported SHM buffer format\n"); + weston_log("Unsupported SHM buffer format 0x%x\n", + wl_shm_buffer_get_format(shm_buffer)); weston_buffer_reference(&ps->buffer_ref, NULL); + weston_buffer_send_server_error(buffer, + "disconnecting due to unhandled buffer type"); return; break; } From 399a224a18429b5724222c8ec4f9e005b2051a16 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Wed, 10 Oct 2018 13:49:45 +0200 Subject: [PATCH 0657/1642] libweston-desktop/xdg-shell: update view transforms for xdg popup surfaces For toplevel surfaces, the shell will do the same thing. Without this, the commit does not trigger repaints, because the output mask for the surface is not set. Without this the popup is not shown unless something else triggers a repaint. This is usually not seen because the mouse cursor triggers a repaint at the same time. Signed-off-by: Michael Olbrich --- libweston-desktop/xdg-shell-v6.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index ccdd19377..b6cb599c7 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -829,6 +829,13 @@ weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface static void weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) { + struct weston_surface *wsurface = + weston_desktop_surface_get_surface (popup->base.desktop_surface); + struct weston_view *view; + + wl_list_for_each(view, &wsurface->views, surface_link) + weston_view_update_transform(view); + if (!popup->committed) weston_desktop_xdg_surface_schedule_configure(&popup->base); popup->committed = true; From 5a5cbc0245cb831b427bd98d526fca0d0e6d25fa Mon Sep 17 00:00:00 2001 From: Arkadiusz Hiler Date: Mon, 15 Oct 2018 11:06:11 +0300 Subject: [PATCH 0658/1642] compositor-drm: Log atomic commits and flips Add a couple log points for tracking atomic commits and flip processing. Signed-off-by: Arkadiusz Hiler --- libweston/compositor-drm.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 4cf0d31f7..e1e42b031 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1940,8 +1940,10 @@ drm_output_assign_state(struct drm_output_state *state, output->state_cur = state; - if (b->atomic_modeset && mode == DRM_STATE_APPLY_ASYNC) + if (b->atomic_modeset && mode == DRM_STATE_APPLY_ASYNC) { + drm_debug(b, "\t[CRTC:%u] setting pending flip\n", output->crtc_id); output->atomic_complete_pending = 1; + } /* Replace state_cur on each affected plane with the new state, being * careful to dispose of orphaned (but only orphaned) previous state. @@ -2697,6 +2699,7 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, } ret = drmModeAtomicCommit(b->drm.fd, req, flags, b); + drm_debug(b, "[atomic] drmModeAtomicCommit\n"); /* Test commits do not take ownership of the state; return * without freeing here. */ @@ -3135,11 +3138,13 @@ atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, drm_output_update_msc(output, frame); + drm_debug(b, "[atomic][CRTC:%u] flip processing started\n", crtc_id); assert(b->atomic_modeset); assert(output->atomic_complete_pending); output->atomic_complete_pending = 0; drm_output_update_complete(output, flags, sec, usec); + drm_debug(b, "[atomic][CRTC:%u] flip processing completed\n", crtc_id); } #endif From 71f9ee1d22717fd222bc37762b8d3a2738d7205a Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Fri, 2 Nov 2018 15:04:49 +0100 Subject: [PATCH 0659/1642] ivi-shell: don't use input panel implementation input panel implementation puts contents of an application, which uses input_method protocol, on top of all other surfaces. It is not controllable with ivi-layout interface. This is not acceptable for an In-Vehicle Infotainment platform. Because we have to ensure configured scenegraph cannot be hijacked by any rogue application. Therefore, I am removing input panel implementation Signed-off-by: Emre Ucan --- ivi-shell/ivi-shell.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 58f53bc55..7e3e4aec9 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -359,10 +359,6 @@ shell_destroy(struct wl_listener *listener, void *data) struct ivi_shell_surface *ivisurf, *next; wl_list_remove(&shell->destroy_listener.link); - - text_backend_destroy(shell->text_backend); - input_panel_destroy(shell); - wl_list_remove(&shell->wake_listener.link); wl_list_for_each_safe(ivisurf, next, &shell->ivi_surface_list, link) { @@ -404,8 +400,6 @@ init_ivi_shell(struct weston_compositor *compositor, struct ivi_shell *shell) wl_list_init(&shell->ivi_surface_list); - weston_layer_init(&shell->input_panel_layer, compositor); - section = weston_config_get_section(config, "ivi-shell", NULL, NULL); weston_config_section_get_bool(section, "developermode", @@ -498,13 +492,6 @@ wet_shell_init(struct weston_compositor *compositor, shell->wake_listener.notify = wake_handler; wl_signal_add(&compositor->wake_signal, &shell->wake_listener); - if (input_panel_setup(shell) < 0) - goto out; - - shell->text_backend = text_backend_init(compositor); - if (!shell->text_backend) - goto out; - if (wl_global_create(compositor->wl_display, &ivi_application_interface, 1, shell, bind_ivi_application) == NULL) From eefb8b9ac54782f583f85c243e5aba161380ec63 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Fri, 2 Nov 2018 15:09:48 +0100 Subject: [PATCH 0660/1642] ivi-shell: remove input panel implementation it is unused now Signed-off-by: Emre Ucan --- Makefile.am | 1 - ivi-shell/input-panel-ivi.c | 413 ------------------------------------ 2 files changed, 414 deletions(-) delete mode 100644 ivi-shell/input-panel-ivi.c diff --git a/Makefile.am b/Makefile.am index 223b510e1..64e07c44c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1079,7 +1079,6 @@ ivi_shell_la_SOURCES = \ ivi-shell/ivi-layout-transition.c \ ivi-shell/ivi-shell.h \ ivi-shell/ivi-shell.c \ - ivi-shell/input-panel-ivi.c \ shared/helpers.h nodist_ivi_shell_la_SOURCES = \ protocol/ivi-application-protocol.c \ diff --git a/ivi-shell/input-panel-ivi.c b/ivi-shell/input-panel-ivi.c deleted file mode 100644 index 219494dc2..000000000 --- a/ivi-shell/input-panel-ivi.c +++ /dev/null @@ -1,413 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * - * 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 "ivi-shell.h" -#include "input-method-unstable-v1-server-protocol.h" -#include "ivi-layout-private.h" -#include "shared/helpers.h" - -struct input_panel_surface { - struct wl_resource *resource; - struct wl_signal destroy_signal; - - struct ivi_shell *shell; - - struct wl_list link; - struct weston_surface *surface; - struct weston_view *view; - struct wl_listener surface_destroy_listener; - - struct weston_view_animation *anim; - - struct weston_output *output; - uint32_t panel; -}; - -static void -input_panel_slide_done(struct weston_view_animation *animation, void *data) -{ - struct input_panel_surface *ipsurf = data; - - ipsurf->anim = NULL; -} - -static void -show_input_panel_surface(struct input_panel_surface *ipsurf) -{ - struct ivi_shell *shell = ipsurf->shell; - struct weston_seat *seat; - struct weston_surface *focus; - float x, y; - - wl_list_for_each(seat, &shell->compositor->seat_list, link) { - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - if (!keyboard || !keyboard->focus) - continue; - focus = weston_surface_get_main_surface(keyboard->focus); - ipsurf->output = focus->output; - x = ipsurf->output->x + (ipsurf->output->width - ipsurf->surface->width) / 2; - y = ipsurf->output->y + ipsurf->output->height - ipsurf->surface->height; - weston_view_set_position(ipsurf->view, x, y); - } - - weston_layer_entry_insert(&shell->input_panel_layer.view_list, - &ipsurf->view->layer_link); - weston_view_geometry_dirty(ipsurf->view); - weston_view_update_transform(ipsurf->view); - ipsurf->surface->is_mapped = true; - ipsurf->view->is_mapped = true; - weston_surface_damage(ipsurf->surface); - - if (ipsurf->anim) - weston_view_animation_destroy(ipsurf->anim); - - ipsurf->anim = - weston_slide_run(ipsurf->view, - ipsurf->surface->height * 0.9, 0, - input_panel_slide_done, ipsurf); -} - -static void -show_input_panels(struct wl_listener *listener, void *data) -{ - struct ivi_shell *shell = - container_of(listener, struct ivi_shell, - show_input_panel_listener); - struct input_panel_surface *ipsurf, *next; - - shell->text_input.surface = (struct weston_surface*)data; - - if (shell->showing_input_panels) - return; - - shell->showing_input_panels = true; - - if (!shell->locked) - weston_layer_set_position(&shell->input_panel_layer, - WESTON_LAYER_POSITION_TOP_UI); - - wl_list_for_each_safe(ipsurf, next, - &shell->input_panel.surfaces, link) { - if (ipsurf->surface->width == 0) - continue; - - show_input_panel_surface(ipsurf); - } -} - -static void -hide_input_panels(struct wl_listener *listener, void *data) -{ - struct ivi_shell *shell = - container_of(listener, struct ivi_shell, - hide_input_panel_listener); - struct weston_view *view, *next; - - if (!shell->showing_input_panels) - return; - - shell->showing_input_panels = false; - - if (!shell->locked) - weston_layer_unset_position(&shell->input_panel_layer); - - wl_list_for_each_safe(view, next, - &shell->input_panel_layer.view_list.link, - layer_link.link) - weston_view_unmap(view); -} - -static void -update_input_panels(struct wl_listener *listener, void *data) -{ - struct ivi_shell *shell = - container_of(listener, struct ivi_shell, - update_input_panel_listener); - - memcpy(&shell->text_input.cursor_rectangle, data, sizeof(pixman_box32_t)); -} - -static int -input_panel_get_label(struct weston_surface *surface, char *buf, size_t len) -{ - return snprintf(buf, len, "input panel"); -} - -static void -input_panel_committed(struct weston_surface *surface, int32_t sx, int32_t sy) -{ - struct input_panel_surface *ip_surface = surface->committed_private; - struct ivi_shell *shell = ip_surface->shell; - struct weston_view *view; - float x, y; - - if (surface->width == 0) - return; - - if (ip_surface->panel) { - view = get_default_view(shell->text_input.surface); - if (view == NULL) - return; - x = view->geometry.x + shell->text_input.cursor_rectangle.x2; - y = view->geometry.y + shell->text_input.cursor_rectangle.y2; - } else { - x = ip_surface->output->x + (ip_surface->output->width - surface->width) / 2; - y = ip_surface->output->y + ip_surface->output->height - surface->height; - } - - weston_view_set_position(ip_surface->view, x, y); - - if (!weston_surface_is_mapped(surface) && shell->showing_input_panels) - show_input_panel_surface(ip_surface); -} - -static void -destroy_input_panel_surface(struct input_panel_surface *input_panel_surface) -{ - wl_signal_emit(&input_panel_surface->destroy_signal, input_panel_surface); - - wl_list_remove(&input_panel_surface->surface_destroy_listener.link); - wl_list_remove(&input_panel_surface->link); - - input_panel_surface->surface->committed = NULL; - weston_surface_set_label_func(input_panel_surface->surface, NULL); - weston_view_destroy(input_panel_surface->view); - - free(input_panel_surface); -} - -static struct input_panel_surface * -get_input_panel_surface(struct weston_surface *surface) -{ - if (surface->committed == input_panel_committed) { - return surface->committed_private; - } else { - return NULL; - } -} - -static void -input_panel_handle_surface_destroy(struct wl_listener *listener, void *data) -{ - struct input_panel_surface *ipsurface = container_of(listener, - struct input_panel_surface, - surface_destroy_listener); - - if (ipsurface->resource) { - wl_resource_destroy(ipsurface->resource); - } else { - destroy_input_panel_surface(ipsurface); - } -} - -static struct input_panel_surface * -create_input_panel_surface(struct ivi_shell *shell, - struct weston_surface *surface) -{ - struct input_panel_surface *input_panel_surface; - - input_panel_surface = calloc(1, sizeof *input_panel_surface); - if (!input_panel_surface) - return NULL; - - surface->committed = input_panel_committed; - surface->committed_private = input_panel_surface; - weston_surface_set_label_func(surface, input_panel_get_label); - - input_panel_surface->shell = shell; - - input_panel_surface->surface = surface; - input_panel_surface->view = weston_view_create(surface); - - wl_signal_init(&input_panel_surface->destroy_signal); - input_panel_surface->surface_destroy_listener.notify = input_panel_handle_surface_destroy; - wl_signal_add(&surface->destroy_signal, - &input_panel_surface->surface_destroy_listener); - - wl_list_init(&input_panel_surface->link); - - return input_panel_surface; -} - -static void -input_panel_surface_set_toplevel(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *output_resource, - uint32_t position) -{ - struct input_panel_surface *input_panel_surface = - wl_resource_get_user_data(resource); - struct ivi_shell *shell = input_panel_surface->shell; - struct weston_head *head; - - wl_list_insert(&shell->input_panel.surfaces, - &input_panel_surface->link); - - head = weston_head_from_resource(output_resource); - input_panel_surface->output = head->output; - input_panel_surface->panel = 0; -} - -static void -input_panel_surface_set_overlay_panel(struct wl_client *client, - struct wl_resource *resource) -{ - struct input_panel_surface *input_panel_surface = - wl_resource_get_user_data(resource); - struct ivi_shell *shell = input_panel_surface->shell; - - wl_list_insert(&shell->input_panel.surfaces, - &input_panel_surface->link); - - input_panel_surface->panel = 1; -} - -static const struct zwp_input_panel_surface_v1_interface input_panel_surface_implementation = { - input_panel_surface_set_toplevel, - input_panel_surface_set_overlay_panel -}; - -static void -destroy_input_panel_surface_resource(struct wl_resource *resource) -{ - struct input_panel_surface *ipsurf = - wl_resource_get_user_data(resource); - - destroy_input_panel_surface(ipsurf); -} - -static void -input_panel_get_input_panel_surface(struct wl_client *client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *surface_resource) -{ - struct weston_surface *surface = - wl_resource_get_user_data(surface_resource); - struct ivi_shell *shell = wl_resource_get_user_data(resource); - struct input_panel_surface *ipsurf; - - if (get_input_panel_surface(surface)) { - wl_resource_post_error(surface_resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "wl_input_panel::get_input_panel_surface already requested"); - return; - } - - ipsurf = create_input_panel_surface(shell, surface); - if (!ipsurf) { - wl_resource_post_error(surface_resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "surface->committed already set"); - return; - } - - ipsurf->resource = - wl_resource_create(client, - &zwp_input_panel_surface_v1_interface, - 1, - id); - wl_resource_set_implementation(ipsurf->resource, - &input_panel_surface_implementation, - ipsurf, - destroy_input_panel_surface_resource); -} - -static const struct zwp_input_panel_v1_interface input_panel_implementation = { - input_panel_get_input_panel_surface -}; - -static void -unbind_input_panel(struct wl_resource *resource) -{ - struct ivi_shell *shell = wl_resource_get_user_data(resource); - - shell->input_panel.binding = NULL; -} - -static void -bind_input_panel(struct wl_client *client, - void *data, uint32_t version, uint32_t id) -{ - struct ivi_shell *shell = data; - struct wl_resource *resource; - - resource = wl_resource_create(client, - &zwp_input_panel_v1_interface, 1, id); - - if (shell->input_panel.binding == NULL) { - wl_resource_set_implementation(resource, - &input_panel_implementation, - shell, unbind_input_panel); - shell->input_panel.binding = resource; - return; - } - - wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, - "interface object already bound"); -} - -void -input_panel_destroy(struct ivi_shell *shell) -{ - wl_list_remove(&shell->show_input_panel_listener.link); - wl_list_remove(&shell->hide_input_panel_listener.link); -} - -int -input_panel_setup(struct ivi_shell *shell) -{ - struct weston_compositor *ec = shell->compositor; - - shell->show_input_panel_listener.notify = show_input_panels; - wl_signal_add(&ec->show_input_panel_signal, - &shell->show_input_panel_listener); - shell->hide_input_panel_listener.notify = hide_input_panels; - wl_signal_add(&ec->hide_input_panel_signal, - &shell->hide_input_panel_listener); - shell->update_input_panel_listener.notify = update_input_panels; - wl_signal_add(&ec->update_input_panel_signal, - &shell->update_input_panel_listener); - - wl_list_init(&shell->input_panel.surfaces); - - if (wl_global_create(shell->compositor->wl_display, - &zwp_input_panel_v1_interface, 1, - shell, bind_input_panel) == NULL) - return -1; - - return 0; -} From 27839fe913ed683f77cd65266ab89c632403eb02 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Fri, 2 Nov 2018 15:17:00 +0100 Subject: [PATCH 0661/1642] ivi-shell: remove unused functions and members input panel related members of ivi_shell struct are not required anymore. Also get_default_view(), input_panel_setup() and input_panel_destroy() are not used. Therefore, we can remove them. Signed-off-by: Emre Ucan --- ivi-shell/ivi-shell.c | 17 ----------------- ivi-shell/ivi-shell.h | 30 ------------------------------ 2 files changed, 47 deletions(-) diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 7e3e4aec9..54f35ac93 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -331,23 +331,6 @@ bind_ivi_application(struct wl_client *client, shell, NULL); } -struct weston_view * -get_default_view(struct weston_surface *surface) -{ - struct weston_view *view; - - if (!surface || wl_list_empty(&surface->views)) - return NULL; - - wl_list_for_each(view, &surface->views, surface_link) { - if (weston_view_is_mapped(view)) - return view; - } - - return container_of(surface->views.next, - struct weston_view, surface_link); -} - /* * Called through the compositor's destroy signal. */ diff --git a/ivi-shell/ivi-shell.h b/ivi-shell/ivi-shell.h index 2c0064d16..78bf57dc5 100644 --- a/ivi-shell/ivi-shell.h +++ b/ivi-shell/ivi-shell.h @@ -39,38 +39,8 @@ struct ivi_shell struct weston_compositor *compositor; struct wl_list ivi_surface_list; /* struct ivi_shell_surface::link */ - - struct text_backend *text_backend; - - struct wl_listener show_input_panel_listener; - struct wl_listener hide_input_panel_listener; - struct wl_listener update_input_panel_listener; - - struct weston_layer input_panel_layer; - - bool locked; - bool showing_input_panels; - - struct { - struct weston_surface *surface; - pixman_box32_t cursor_rectangle; - } text_input; - - struct { - struct wl_resource *binding; - struct wl_list surfaces; - } input_panel; }; -struct weston_view * -get_default_view(struct weston_surface *surface); - -int -input_panel_setup(struct ivi_shell *shell); - -void -input_panel_destroy(struct ivi_shell *shell); - void shell_surface_send_configure(struct weston_surface *surface, int32_t width, int32_t height); From 61eb170b73fb5207e5c8cd1c9a43a00281e6cbe5 Mon Sep 17 00:00:00 2001 From: Emre Ucan Date: Fri, 2 Nov 2018 15:42:17 +0100 Subject: [PATCH 0662/1642] ivi-shell: remove input-method section from config input_method protocol is no longer supported. Therefore, we should remove it from the example config Signed-off-by: Emre Ucan --- ivi-shell/weston.ini.in | 3 --- 1 file changed, 3 deletions(-) diff --git a/ivi-shell/weston.ini.in b/ivi-shell/weston.ini.in index ae3ac5193..3bdfbebb4 100644 --- a/ivi-shell/weston.ini.in +++ b/ivi-shell/weston.ini.in @@ -37,9 +37,6 @@ home-id=1007 workspace-background-color=0x99000000 workspace-background-id=2001 -[input-method] -path=weston-keyboard - [ivi-launcher] workspace-id=0 icon-id=4001 From ad0d83bd6fdd03d6d0bf9c347ec445e0fe17c093 Mon Sep 17 00:00:00 2001 From: Dima Ryazanov Date: Wed, 14 Nov 2018 22:55:22 -0800 Subject: [PATCH 0663/1642] Don't look for weston.ini in the current working directory It's a bit surprising that Weston looks different when launched from the root of the git repo vs from elsewhere. But it's also technically a security vulnerability: if I launch it from a directory like /tmp, it might pick up a weston.ini created by another user, which could then load modules with arbitrary code. Basically, it's the same problem as including "." in $PATH. Signed-off-by: Dima Ryazanov --- man/weston.ini.man | 1 - man/weston.man | 4 +--- shared/config-parser.c | 8 ++------ 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/man/weston.ini.man b/man/weston.ini.man index c12e0505b..2171b960d 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -27,7 +27,6 @@ server is started: .B "weston/weston.ini in each" .BR "\ \ \ \ $XDG_CONFIG_DIR " "(if $XDG_CONFIG_DIRS is set)" .BR "/etc/xdg/weston/weston.ini " "(if $XDG_CONFIG_DIRS is not set)" -.BR "/weston.ini " "(if no variables were set)" .fi .RE .PP diff --git a/man/weston.man b/man/weston.man index c09d4c2dc..c1aa64766 100644 --- a/man/weston.man +++ b/man/weston.man @@ -261,14 +261,12 @@ See .SH FILES . If the environment variable is set, the configuration file is read -from the respective path, or the current directory if neither is set. +from the respective path. .PP .BI $XDG_CONFIG_HOME /weston.ini .br .BI $HOME /.config/weston.ini .br -.I ./weston.ini -.br . .\" *************************************************************** .SH ENVIRONMENT diff --git a/shared/config-parser.c b/shared/config-parser.c index ae5f80350..7b1402d2c 100644 --- a/shared/config-parser.c +++ b/shared/config-parser.c @@ -75,8 +75,7 @@ open_config_file(struct weston_config *c, const char *name) } /* Precedence is given to config files in the home directory, - * and then to directories listed in XDG_CONFIG_DIRS and - * finally to the current working directory. */ + * then to directories listed in XDG_CONFIG_DIRS. */ /* $XDG_CONFIG_HOME */ if (config_dir) { @@ -111,10 +110,7 @@ open_config_file(struct weston_config *c, const char *name) next++; } - /* Current working directory. */ - snprintf(c->path, sizeof c->path, "./%s", name); - - return open(c->path, O_RDONLY | O_CLOEXEC); + return -1; } static struct weston_config_entry * From e7c91b61c72ca7335a777e5bead68d5a6e889528 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 26 Sep 2018 14:00:34 +0300 Subject: [PATCH 0664/1642] pixel-formats: add name string There is often a need to print the name of a pixel format. Printing the raw numeric value is hard to decipher, printing the four ASCII characters is slightly more human-friendly but still needs a decoder table. Add a name that can be printed easily. The bulk of this patch was done with: sed -i -e 's/\.format = DRM_FORMAT_\(.\+\),/DRM_FORMAT(\1),/' libweston/pixel-formats.c Signed-off-by: Pekka Paalanen --- libweston/pixel-formats.c | 115 +++++++++++++++++++------------------- libweston/pixel-formats.h | 4 ++ 2 files changed, 63 insertions(+), 56 deletions(-) diff --git a/libweston/pixel-formats.c b/libweston/pixel-formats.c index df84a9f3f..7197455e4 100644 --- a/libweston/pixel-formats.c +++ b/libweston/pixel-formats.c @@ -1,5 +1,6 @@ /* * Copyright © 2016 Collabora, Ltd. + * Copyright (c) 2018 DisplayLink (UK) Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -49,6 +50,8 @@ #define SAMPLER_TYPE(type) .sampler_type = 0 #endif +#define DRM_FORMAT(f) .format = DRM_FORMAT_ ## f, .drm_format_name = #f + #include "weston-egl-ext.h" /** @@ -58,28 +61,28 @@ */ static const struct pixel_format_info pixel_format_table[] = { { - .format = DRM_FORMAT_XRGB4444, + DRM_FORMAT(XRGB4444), }, { - .format = DRM_FORMAT_ARGB4444, + DRM_FORMAT(ARGB4444), .opaque_substitute = DRM_FORMAT_XRGB4444, }, { - .format = DRM_FORMAT_XBGR4444, + DRM_FORMAT(XBGR4444), }, { - .format = DRM_FORMAT_ABGR4444, + DRM_FORMAT(ABGR4444), .opaque_substitute = DRM_FORMAT_XBGR4444, }, { - .format = DRM_FORMAT_RGBX4444, + DRM_FORMAT(RGBX4444), # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), GL_TYPE(GL_UNSIGNED_SHORT_4_4_4_4), #endif }, { - .format = DRM_FORMAT_RGBA4444, + DRM_FORMAT(RGBA4444), .opaque_substitute = DRM_FORMAT_RGBX4444, # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), @@ -87,37 +90,37 @@ static const struct pixel_format_info pixel_format_table[] = { #endif }, { - .format = DRM_FORMAT_BGRX4444, + DRM_FORMAT(BGRX4444), }, { - .format = DRM_FORMAT_BGRA4444, + DRM_FORMAT(BGRA4444), .opaque_substitute = DRM_FORMAT_BGRX4444, }, { - .format = DRM_FORMAT_XRGB1555, + DRM_FORMAT(XRGB1555), .depth = 15, .bpp = 16, }, { - .format = DRM_FORMAT_ARGB1555, + DRM_FORMAT(ARGB1555), .opaque_substitute = DRM_FORMAT_XRGB1555, }, { - .format = DRM_FORMAT_XBGR1555, + DRM_FORMAT(XBGR1555), }, { - .format = DRM_FORMAT_ABGR1555, + DRM_FORMAT(ABGR1555), .opaque_substitute = DRM_FORMAT_XBGR1555, }, { - .format = DRM_FORMAT_RGBX5551, + DRM_FORMAT(RGBX5551), # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), GL_TYPE(GL_UNSIGNED_SHORT_5_5_5_1), #endif }, { - .format = DRM_FORMAT_RGBA5551, + DRM_FORMAT(RGBA5551), .opaque_substitute = DRM_FORMAT_RGBX5551, # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), @@ -125,14 +128,14 @@ static const struct pixel_format_info pixel_format_table[] = { #endif }, { - .format = DRM_FORMAT_BGRX5551, + DRM_FORMAT(BGRX5551), }, { - .format = DRM_FORMAT_BGRA5551, + DRM_FORMAT(BGRA5551), .opaque_substitute = DRM_FORMAT_BGRX5551, }, { - .format = DRM_FORMAT_RGB565, + DRM_FORMAT(RGB565), .depth = 16, .bpp = 16, # if __BYTE_ORDER == __LITTLE_ENDIAN @@ -141,25 +144,25 @@ static const struct pixel_format_info pixel_format_table[] = { #endif }, { - .format = DRM_FORMAT_BGR565, + DRM_FORMAT(BGR565), }, { - .format = DRM_FORMAT_RGB888, + DRM_FORMAT(RGB888), }, { - .format = DRM_FORMAT_BGR888, + DRM_FORMAT(BGR888), GL_FORMAT(GL_RGB), GL_TYPE(GL_UNSIGNED_BYTE), }, { - .format = DRM_FORMAT_XRGB8888, + DRM_FORMAT(XRGB8888), .depth = 24, .bpp = 32, GL_FORMAT(GL_BGRA_EXT), GL_TYPE(GL_UNSIGNED_BYTE), }, { - .format = DRM_FORMAT_ARGB8888, + DRM_FORMAT(ARGB8888), .opaque_substitute = DRM_FORMAT_XRGB8888, .depth = 32, .bpp = 32, @@ -167,48 +170,48 @@ static const struct pixel_format_info pixel_format_table[] = { GL_TYPE(GL_UNSIGNED_BYTE), }, { - .format = DRM_FORMAT_XBGR8888, + DRM_FORMAT(XBGR8888), GL_FORMAT(GL_RGBA), GL_TYPE(GL_UNSIGNED_BYTE), }, { - .format = DRM_FORMAT_ABGR8888, + DRM_FORMAT(ABGR8888), .opaque_substitute = DRM_FORMAT_XBGR8888, GL_FORMAT(GL_RGBA), GL_TYPE(GL_UNSIGNED_BYTE), }, { - .format = DRM_FORMAT_RGBX8888, + DRM_FORMAT(RGBX8888), }, { - .format = DRM_FORMAT_RGBA8888, + DRM_FORMAT(RGBA8888), .opaque_substitute = DRM_FORMAT_RGBX8888, }, { - .format = DRM_FORMAT_BGRX8888, + DRM_FORMAT(BGRX8888), }, { - .format = DRM_FORMAT_BGRA8888, + DRM_FORMAT(BGRA8888), .opaque_substitute = DRM_FORMAT_BGRX8888, }, { - .format = DRM_FORMAT_XRGB2101010, + DRM_FORMAT(XRGB2101010), .depth = 30, .bpp = 32, }, { - .format = DRM_FORMAT_ARGB2101010, + DRM_FORMAT(ARGB2101010), .opaque_substitute = DRM_FORMAT_XRGB2101010, }, { - .format = DRM_FORMAT_XBGR2101010, + DRM_FORMAT(XBGR2101010), # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), GL_TYPE(GL_UNSIGNED_INT_2_10_10_10_REV_EXT), #endif }, { - .format = DRM_FORMAT_ABGR2101010, + DRM_FORMAT(ABGR2101010), .opaque_substitute = DRM_FORMAT_XBGR2101010, # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), @@ -216,41 +219,41 @@ static const struct pixel_format_info pixel_format_table[] = { #endif }, { - .format = DRM_FORMAT_RGBX1010102, + DRM_FORMAT(RGBX1010102), }, { - .format = DRM_FORMAT_RGBA1010102, + DRM_FORMAT(RGBA1010102), .opaque_substitute = DRM_FORMAT_RGBX1010102, }, { - .format = DRM_FORMAT_BGRX1010102, + DRM_FORMAT(BGRX1010102), }, { - .format = DRM_FORMAT_BGRA1010102, + DRM_FORMAT(BGRA1010102), .opaque_substitute = DRM_FORMAT_BGRX1010102, }, { - .format = DRM_FORMAT_YUYV, + DRM_FORMAT(YUYV), SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), .num_planes = 1, .hsub = 2, }, { - .format = DRM_FORMAT_YVYU, + DRM_FORMAT(YVYU), SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), .num_planes = 1, .chroma_order = ORDER_VU, .hsub = 2, }, { - .format = DRM_FORMAT_UYVY, + DRM_FORMAT(UYVY), SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), .num_planes = 1, .luma_chroma_order = ORDER_CHROMA_LUMA, .hsub = 2, }, { - .format = DRM_FORMAT_VYUY, + DRM_FORMAT(VYUY), SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), .num_planes = 1, .luma_chroma_order = ORDER_CHROMA_LUMA, @@ -258,14 +261,14 @@ static const struct pixel_format_info pixel_format_table[] = { .hsub = 2, }, { - .format = DRM_FORMAT_NV12, + DRM_FORMAT(NV12), SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), .num_planes = 2, .hsub = 2, .vsub = 2, }, { - .format = DRM_FORMAT_NV21, + DRM_FORMAT(NV21), SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), .num_planes = 2, .chroma_order = ORDER_VU, @@ -273,14 +276,14 @@ static const struct pixel_format_info pixel_format_table[] = { .vsub = 2, }, { - .format = DRM_FORMAT_NV16, + DRM_FORMAT(NV16), SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), .num_planes = 2, .hsub = 2, .vsub = 1, }, { - .format = DRM_FORMAT_NV61, + DRM_FORMAT(NV61), SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), .num_planes = 2, .chroma_order = ORDER_VU, @@ -288,25 +291,25 @@ static const struct pixel_format_info pixel_format_table[] = { .vsub = 1, }, { - .format = DRM_FORMAT_NV24, + DRM_FORMAT(NV24), SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), .num_planes = 2, }, { - .format = DRM_FORMAT_NV42, + DRM_FORMAT(NV42), SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), .num_planes = 2, .chroma_order = ORDER_VU, }, { - .format = DRM_FORMAT_YUV410, + DRM_FORMAT(YUV410), SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), .num_planes = 3, .hsub = 4, .vsub = 4, }, { - .format = DRM_FORMAT_YVU410, + DRM_FORMAT(YVU410), SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), .num_planes = 3, .chroma_order = ORDER_VU, @@ -314,14 +317,14 @@ static const struct pixel_format_info pixel_format_table[] = { .vsub = 4, }, { - .format = DRM_FORMAT_YUV411, + DRM_FORMAT(YUV411), SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), .num_planes = 3, .hsub = 4, .vsub = 1, }, { - .format = DRM_FORMAT_YVU411, + DRM_FORMAT(YVU411), SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), .num_planes = 3, .chroma_order = ORDER_VU, @@ -329,14 +332,14 @@ static const struct pixel_format_info pixel_format_table[] = { .vsub = 1, }, { - .format = DRM_FORMAT_YUV420, + DRM_FORMAT(YUV420), SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), .num_planes = 3, .hsub = 2, .vsub = 2, }, { - .format = DRM_FORMAT_YVU420, + DRM_FORMAT(YVU420), SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), .num_planes = 3, .chroma_order = ORDER_VU, @@ -344,14 +347,14 @@ static const struct pixel_format_info pixel_format_table[] = { .vsub = 2, }, { - .format = DRM_FORMAT_YUV422, + DRM_FORMAT(YUV422), SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), .num_planes = 3, .hsub = 2, .vsub = 1, }, { - .format = DRM_FORMAT_YVU422, + DRM_FORMAT(YVU422), SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), .num_planes = 3, .chroma_order = ORDER_VU, @@ -359,12 +362,12 @@ static const struct pixel_format_info pixel_format_table[] = { .vsub = 1, }, { - .format = DRM_FORMAT_YUV444, + DRM_FORMAT(YUV444), SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), .num_planes = 3, }, { - .format = DRM_FORMAT_YVU444, + DRM_FORMAT(YVU444), SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), .num_planes = 3, .chroma_order = ORDER_VU, diff --git a/libweston/pixel-formats.h b/libweston/pixel-formats.h index b16aae323..10b9084df 100644 --- a/libweston/pixel-formats.h +++ b/libweston/pixel-formats.h @@ -1,5 +1,6 @@ /* * Copyright © 2016 Collabora, Ltd. + * Copyright (c) 2018 DisplayLink (UK) Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -37,6 +38,9 @@ struct pixel_format_info { /** DRM/wl_shm format code */ uint32_t format; + /** The DRM format name without the DRM_FORMAT_ prefix. */ + const char *drm_format_name; + /** If non-zero, number of planes in base (non-modified) format. */ int num_planes; From f5ed7431e53794c13ac9412e1148f485c8507d6f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 26 Sep 2018 14:26:53 +0300 Subject: [PATCH 0665/1642] pixel-formats: search by name Add a function to find a format description by the DRM format name. This will be useful when parsing configuration strings. While at it, fix the two function formattings in pixel-formats.h to match everything else in the file. Signed-off-by: Pekka Paalanen --- libweston/pixel-formats.c | 16 ++++++++++++++++ libweston/pixel-formats.h | 24 ++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/libweston/pixel-formats.c b/libweston/pixel-formats.c index 7197455e4..c64a9b9e5 100644 --- a/libweston/pixel-formats.c +++ b/libweston/pixel-formats.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "helpers.h" @@ -387,6 +388,21 @@ pixel_format_get_info(uint32_t format) return NULL; } +WL_EXPORT const struct pixel_format_info * +pixel_format_get_info_by_drm_name(const char *drm_format_name) +{ + const struct pixel_format_info *info; + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(pixel_format_table); i++) { + info = &pixel_format_table[i]; + if (strcasecmp(info->drm_format_name, drm_format_name) == 0) + return info; + } + + return NULL; +} + WL_EXPORT unsigned int pixel_format_get_plane_count(const struct pixel_format_info *info) { diff --git a/libweston/pixel-formats.h b/libweston/pixel-formats.h index 10b9084df..1f0ec1bb0 100644 --- a/libweston/pixel-formats.h +++ b/libweston/pixel-formats.h @@ -112,7 +112,26 @@ struct pixel_format_info { * @returns A pixel format structure (must not be freed), or NULL if the * format could not be found */ -const struct pixel_format_info *pixel_format_get_info(uint32_t format); +const struct pixel_format_info * +pixel_format_get_info(uint32_t format); + +/** + * Get pixel format information for a named DRM format + * + * Given a DRM format name, return a pixel format info structure describing + * the properties of that format. + * + * The DRM format name is the preprocessor token name from drm_fourcc.h + * without the DRM_FORMAT_ prefix. The search is also case-insensitive. + * Both "xrgb8888" and "XRGB8888" searches will find DRM_FORMAT_XRGB8888 + * for example. + * + * @param drm_format_name DRM format name to get info for (not NULL) + * @returns A pixel format structure (must not be freed), or NULL if the + * name could not be found + */ +const struct pixel_format_info * +pixel_format_get_info_by_drm_name(const char *drm_format_name); /** * Get number of planes used by a pixel format @@ -140,7 +159,8 @@ pixel_format_get_plane_count(const struct pixel_format_info *format); * @param format Pixel format info structure * @returns True if the format is opaque, or false if it has significant alpha */ -bool pixel_format_is_opaque(const struct pixel_format_info *format); +bool +pixel_format_is_opaque(const struct pixel_format_info *format); /** * Get compatible opaque equivalent for a format From 62a9436417eb4e1ba53f5c54ef9a0e8b5a4eb53f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 26 Sep 2018 14:33:36 +0300 Subject: [PATCH 0666/1642] compositor-drm: parse all DRM format names Use the pixel format table to parse format names. This makes the parser recognize almost all DRM format names. Not all formats are usable, but we rely on the use to fail appropriately. What we can use depends on the drivers anyway. Signed-off-by: Pekka Paalanen --- libweston/compositor-drm.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index e1e42b031..b1be49220 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3,6 +3,7 @@ * Copyright © 2011 Intel Corporation * Copyright © 2017, 2018 Collabora, Ltd. * Copyright © 2017, 2018 General Electric Company + * Copyright (c) 2018 DisplayLink (UK) Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -5390,22 +5391,25 @@ drm_output_detach_head(struct weston_output *output_base, static int parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format) { - int ret = 0; + const struct pixel_format_info *pinfo; - if (s == NULL) + if (s == NULL) { *gbm_format = default_value; - else if (strcmp(s, "xrgb8888") == 0) - *gbm_format = GBM_FORMAT_XRGB8888; - else if (strcmp(s, "rgb565") == 0) - *gbm_format = GBM_FORMAT_RGB565; - else if (strcmp(s, "xrgb2101010") == 0) - *gbm_format = GBM_FORMAT_XRGB2101010; - else { + + return 0; + } + + pinfo = pixel_format_get_info_by_drm_name(s); + if (!pinfo) { weston_log("fatal: unrecognized pixel format: %s\n", s); - ret = -1; + + return -1; } - return ret; + /* GBM formats and DRM formats are identical. */ + *gbm_format = pinfo->format; + + return 0; } static uint32_t From a9a630401f553d680220764b0436c51e415fb26b Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Tue, 20 Nov 2018 17:04:57 +0200 Subject: [PATCH 0667/1642] pixel-formats: Added pixel_format_get_info_shm() helper for printing SHM buffers In current form SHM buffers pixel format can only be printed as 0 and 1. With the help of this helper we align with DRM_FORMAT_ pixel format. Signed-off-by: Marius Vlad --- libweston/pixel-formats.c | 12 ++++++++++++ libweston/pixel-formats.h | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/libweston/pixel-formats.c b/libweston/pixel-formats.c index c64a9b9e5..b96f3b21b 100644 --- a/libweston/pixel-formats.c +++ b/libweston/pixel-formats.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "helpers.h" #include "wayland-util.h" @@ -375,6 +376,17 @@ static const struct pixel_format_info pixel_format_table[] = { }, }; +WL_EXPORT const struct pixel_format_info * +pixel_format_get_info_shm(uint32_t format) +{ + if (format == WL_SHM_FORMAT_XRGB8888) + return pixel_format_get_info(DRM_FORMAT_XRGB8888); + else if (format == WL_SHM_FORMAT_ARGB8888) + return pixel_format_get_info(DRM_FORMAT_ARGB8888); + else + return pixel_format_get_info(format); +} + WL_EXPORT const struct pixel_format_info * pixel_format_get_info(uint32_t format) { diff --git a/libweston/pixel-formats.h b/libweston/pixel-formats.h index 1f0ec1bb0..4e651bbbb 100644 --- a/libweston/pixel-formats.h +++ b/libweston/pixel-formats.h @@ -115,6 +115,19 @@ struct pixel_format_info { const struct pixel_format_info * pixel_format_get_info(uint32_t format); +/** + * Get pixel format information for a SHM format code + * + * Given a SHM format code, return a DRM pixel format info structure describing + * the properties of that format. + * + * @param format SHM format code to get info for. + * @returns A pixel format structure (must not be freed), or NULL if the + * format could not be found. + */ +const struct pixel_format_info * +pixel_format_get_info_shm(uint32_t format); + /** * Get pixel format information for a named DRM format * From 00a6e01d533b1cf99704b30f4ebbd57fba9933ef Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Tue, 20 Nov 2018 17:52:31 +0200 Subject: [PATCH 0668/1642] compositor: Make pixel format printing in human-friendly form This would make weston-debug much more readable when looking at the pixel format of the buffer. Signed-off-by: Marius Vlad --- libweston/compositor.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index b60f35def..3ef472e3b 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -66,6 +66,7 @@ #include "git-version.h" #include "version.h" #include "plugin-registry.h" +#include "pixel-formats.h" #define DEFAULT_REPAINT_WINDOW 7 /* milliseconds */ @@ -6405,6 +6406,7 @@ debug_scene_view_print_buffer(FILE *fp, struct weston_view *view) struct weston_buffer *buffer = view->surface->buffer_ref.buffer; struct wl_shm_buffer *shm; struct linux_dmabuf_buffer *dmabuf; + const struct pixel_format_info *pixel_info = NULL; if (!buffer) { fprintf(fp, "\t\t[buffer not available]\n"); @@ -6413,17 +6415,22 @@ debug_scene_view_print_buffer(FILE *fp, struct weston_view *view) shm = wl_shm_buffer_get(buffer->resource); if (shm) { + uint32_t _format = wl_shm_buffer_get_format(shm); + pixel_info = pixel_format_get_info_shm(_format); fprintf(fp, "\t\tSHM buffer\n"); - fprintf(fp, "\t\t\tformat: 0x%lx\n", - (unsigned long) wl_shm_buffer_get_format(shm)); + fprintf(fp, "\t\t\tformat: 0x%lx %s\n", + (unsigned long) _format, + pixel_info ? pixel_info->drm_format_name : "UNKNOWN"); return; } dmabuf = linux_dmabuf_buffer_get(buffer->resource); if (dmabuf) { + pixel_info = pixel_format_get_info(dmabuf->attributes.format); fprintf(fp, "\t\tdmabuf buffer\n"); - fprintf(fp, "\t\t\tformat: 0x%lx\n", - (unsigned long) dmabuf->attributes.format); + fprintf(fp, "\t\t\tformat: 0x%lx %s\n", + (unsigned long) dmabuf->attributes.format, + pixel_info ? pixel_info->drm_format_name : "UNKNOWN"); fprintf(fp, "\t\t\tmodifier: 0x%llx\n", (unsigned long long) dmabuf->attributes.modifier[0]); return; From 7b7d9d316a9ce9b4f5f50ba766c2963b0fdcbaa5 Mon Sep 17 00:00:00 2001 From: David Fort Date: Tue, 4 Dec 2018 23:25:39 +0100 Subject: [PATCH 0669/1642] rdp-compositor: fix compilation with FreeRDP 2.0-rc4 Some members have been removed from FreeRDP structs, so let's use local variables. --- libweston/compositor-rdp.c | 86 +++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 134e7298f..871a0a3e8 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -67,19 +67,19 @@ #endif #ifdef HAVE_SURFACE_BITS_BMP -#define SURFACE_BPP(cmd) cmd->bmp.bpp -#define SURFACE_CODECID(cmd) cmd->bmp.codecID -#define SURFACE_WIDTH(cmd) cmd->bmp.width -#define SURFACE_HEIGHT(cmd) cmd->bmp.height -#define SURFACE_BITMAP_DATA(cmd) cmd->bmp.bitmapData -#define SURFACE_BITMAP_DATA_LEN(cmd) cmd->bmp.bitmapDataLength +#define SURFACE_BPP(cmd) cmd.bmp.bpp +#define SURFACE_CODECID(cmd) cmd.bmp.codecID +#define SURFACE_WIDTH(cmd) cmd.bmp.width +#define SURFACE_HEIGHT(cmd) cmd.bmp.height +#define SURFACE_BITMAP_DATA(cmd) cmd.bmp.bitmapData +#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bmp.bitmapDataLength #else -#define SURFACE_BPP(cmd) cmd->bpp -#define SURFACE_CODECID(cmd) cmd->codecID -#define SURFACE_WIDTH(cmd) cmd->width -#define SURFACE_HEIGHT(cmd) cmd->height -#define SURFACE_BITMAP_DATA(cmd) cmd->bitmapData -#define SURFACE_BITMAP_DATA_LEN(cmd) cmd->bitmapDataLength +#define SURFACE_BPP(cmd) cmd.bpp +#define SURFACE_CODECID(cmd) cmd.codecID +#define SURFACE_WIDTH(cmd) cmd.width +#define SURFACE_HEIGHT(cmd) cmd.height +#define SURFACE_BITMAP_DATA(cmd) cmd.bitmapData +#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bitmapDataLength #endif #include @@ -198,7 +198,7 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p uint32_t *ptr; RFX_RECT *rfxRect; rdpUpdate *update = peer->update; - SURFACE_BITS_COMMAND *cmd = &update->surface_bits_command; + SURFACE_BITS_COMMAND cmd; RdpPeerContext *context = (RdpPeerContext *)peer->context; Stream_Clear(context->encode_stream); @@ -208,14 +208,14 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p height = (damage->extents.y2 - damage->extents.y1); #ifdef HAVE_SKIP_COMPRESSION - cmd->skipCompression = TRUE; + cmd.skipCompression = TRUE; #else - memset(cmd, 0, sizeof(*cmd)); + memset(&cmd, 0, sizeof(*cmd)); #endif - cmd->destLeft = damage->extents.x1; - cmd->destTop = damage->extents.y1; - cmd->destRight = damage->extents.x2; - cmd->destBottom = damage->extents.y2; + cmd.destLeft = damage->extents.x1; + cmd.destTop = damage->extents.y1; + cmd.destRight = damage->extents.x2; + cmd.destBottom = damage->extents.y2; SURFACE_BPP(cmd) = 32; SURFACE_CODECID(cmd) = peer->settings->RemoteFxCodecId; SURFACE_WIDTH(cmd) = width; @@ -245,7 +245,7 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p SURFACE_BITMAP_DATA_LEN(cmd) = Stream_GetPosition(context->encode_stream); SURFACE_BITMAP_DATA(cmd) = Stream_Buffer(context->encode_stream); - update->SurfaceBits(update->context, cmd); + update->SurfaceBits(update->context, &cmd); } @@ -255,7 +255,7 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p int width, height; uint32_t *ptr; rdpUpdate *update = peer->update; - SURFACE_BITS_COMMAND *cmd = &update->surface_bits_command; + SURFACE_BITS_COMMAND cmd; RdpPeerContext *context = (RdpPeerContext *)peer->context; Stream_Clear(context->encode_stream); @@ -265,15 +265,15 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p height = (damage->extents.y2 - damage->extents.y1); #ifdef HAVE_SKIP_COMPRESSION - cmd->skipCompression = TRUE; + cmd.skipCompression = TRUE; #else memset(cmd, 0, sizeof(*cmd)); #endif - cmd->destLeft = damage->extents.x1; - cmd->destTop = damage->extents.y1; - cmd->destRight = damage->extents.x2; - cmd->destBottom = damage->extents.y2; + cmd.destLeft = damage->extents.x1; + cmd.destTop = damage->extents.y1; + cmd.destRight = damage->extents.x2; + cmd.destBottom = damage->extents.y2; SURFACE_BPP(cmd) = 32; SURFACE_CODECID(cmd) = peer->settings->NSCodecId; SURFACE_WIDTH(cmd) = width; @@ -289,7 +289,7 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p SURFACE_BITMAP_DATA_LEN(cmd) = Stream_GetPosition(context->encode_stream); SURFACE_BITMAP_DATA(cmd) = Stream_Buffer(context->encode_stream); - update->SurfaceBits(update->context, cmd); + update->SurfaceBits(update->context, &cmd); } static void @@ -310,8 +310,8 @@ static void rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_peer *peer) { rdpUpdate *update = peer->update; - SURFACE_BITS_COMMAND *cmd = &update->surface_bits_command; - SURFACE_FRAME_MARKER *marker = &update->surface_frame_marker; + SURFACE_BITS_COMMAND cmd; + SURFACE_FRAME_MARKER marker; pixman_box32_t *rect, subrect; int nrects, i; int heightIncrement, remainingHeight, top; @@ -320,18 +320,18 @@ rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_p if (!nrects) return; - marker->frameId++; - marker->frameAction = SURFACECMD_FRAMEACTION_BEGIN; - update->SurfaceFrameMarker(peer->context, marker); + marker.frameId++; + marker.frameAction = SURFACECMD_FRAMEACTION_BEGIN; + update->SurfaceFrameMarker(peer->context, &marker); - memset(cmd, 0, sizeof(*cmd)); + memset(&cmd, 0, sizeof(cmd)); SURFACE_BPP(cmd) = 32; SURFACE_CODECID(cmd) = 0; for (i = 0; i < nrects; i++, rect++) { /*weston_log("rect(%d,%d, %d,%d)\n", rect->x1, rect->y1, rect->x2, rect->y2);*/ - cmd->destLeft = rect->x1; - cmd->destRight = rect->x2; + cmd.destLeft = rect->x1; + cmd.destRight = rect->x2; SURFACE_WIDTH(cmd) = rect->x2 - rect->x1; heightIncrement = peer->settings->MultifragMaxRequestSize / (16 + SURFACE_WIDTH(cmd) * 4); @@ -343,8 +343,8 @@ rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_p while (remainingHeight) { SURFACE_HEIGHT(cmd) = (remainingHeight > heightIncrement) ? heightIncrement : remainingHeight; - cmd->destTop = top; - cmd->destBottom = top + SURFACE_HEIGHT(cmd); + cmd.destTop = top; + cmd.destBottom = top + SURFACE_HEIGHT(cmd); SURFACE_BITMAP_DATA_LEN(cmd) = SURFACE_WIDTH(cmd) * SURFACE_HEIGHT(cmd) * 4; SURFACE_BITMAP_DATA(cmd) = (BYTE *)realloc(SURFACE_BITMAP_DATA(cmd), SURFACE_BITMAP_DATA_LEN(cmd)); @@ -353,15 +353,15 @@ rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_p pixman_image_flipped_subrect(&subrect, image, SURFACE_BITMAP_DATA(cmd)); /*weston_log("* sending (%d,%d, %d,%d)\n", subrect.x1, subrect.y1, subrect.x2, subrect.y2); */ - update->SurfaceBits(peer->context, cmd); + update->SurfaceBits(peer->context, &cmd); remainingHeight -= SURFACE_HEIGHT(cmd); top += SURFACE_HEIGHT(cmd); } } - marker->frameAction = SURFACECMD_FRAMEACTION_END; - update->SurfaceFrameMarker(peer->context, marker); + marker.frameAction = SURFACECMD_FRAMEACTION_END; + update->SurfaceFrameMarker(peer->context, &marker); } static void @@ -956,7 +956,7 @@ xf_peer_activate(freerdp_peer* client) pixman_box32_t box; pixman_region32_t damage; char seat_name[50]; - + POINTER_SYSTEM_UPDATE pointer_system; peerCtx = (RdpPeerContext *)client->context; b = peerCtx->rdpBackend; @@ -1056,8 +1056,8 @@ xf_peer_activate(freerdp_peer* client) /* disable pointer on the client side */ pointer = client->update->pointer; - pointer->pointer_system.type = SYSPTR_NULL; - pointer->PointerSystem(client->context, &pointer->pointer_system); + pointer_system.type = SYSPTR_NULL; + pointer->PointerSystem(client->context, &pointer_system); /* sends a full refresh */ box.x1 = 0; From 51c9b0e2c42ec432f223330f5e215535eb6240c4 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 28 Aug 2018 22:53:59 +0100 Subject: [PATCH 0670/1642] tests: Rename surface-screenshot Give it a more regular name, matching all the other test plugins. Signed-off-by: Daniel Stone --- Makefile.am | 2 +- tests/{surface-screenshot.c => surface-screenshot-test.c} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/{surface-screenshot.c => surface-screenshot-test.c} (100%) diff --git a/Makefile.am b/Makefile.am index 64e07c44c..321c24135 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1619,7 +1619,7 @@ noinst_LTLIBRARIES += \ surface_screenshot_la_LIBADD = libshared.la $(test_module_libadd) surface_screenshot_la_LDFLAGS = $(test_module_ldflags) surface_screenshot_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -surface_screenshot_la_SOURCES = tests/surface-screenshot.c +surface_screenshot_la_SOURCES = tests/surface-screenshot-test.c # diff --git a/tests/surface-screenshot.c b/tests/surface-screenshot-test.c similarity index 100% rename from tests/surface-screenshot.c rename to tests/surface-screenshot-test.c From 9d771fc506bc8448aea778098d3c992f03a11002 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Sat, 17 Nov 2018 14:48:04 +0200 Subject: [PATCH 0671/1642] clients/simple-egl: include weston-egl-ext.h correctly weston-egl-ext.h was moved to shared/ in ffff92d592e1635a7ee4511ad3e080f7576553bd Use the correct include path, so that Meson build does not need to add ../shared to the include path just for this. Signed-off-by: Pekka Paalanen --- clients/simple-egl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/simple-egl.c b/clients/simple-egl.c index a1e57aef5..779797f52 100644 --- a/clients/simple-egl.c +++ b/clients/simple-egl.c @@ -50,7 +50,7 @@ #include "shared/helpers.h" #include "shared/platform.h" -#include "weston-egl-ext.h" +#include "shared/weston-egl-ext.h" struct window; struct seat; From 67a97f2bc0cf13239815d9aeb43472b9e2de311f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Sat, 24 Nov 2018 11:39:48 +0200 Subject: [PATCH 0672/1642] shared: remove fallback definition of backtrace() The user of backtrace() was removed in bb707dc0fe331c9af112a0552b7aa6fde755dd83 and has been unused since. Signed-off-by: Pekka Paalanen --- configure.ac | 1 - shared/os-compatibility.h | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/configure.ac b/configure.ac index 4bd1c331c..132a3e937 100644 --- a/configure.ac +++ b/configure.ac @@ -110,7 +110,6 @@ AC_CHECK_DECL(TFD_CLOEXEC,[], AC_CHECK_DECL(CLOCK_MONOTONIC,[], [AC_MSG_ERROR("CLOCK_MONOTONIC is needed to compile weston")], [[#include ]]) -AC_CHECK_HEADERS([execinfo.h]) AC_CHECK_FUNCS([mkostemp strchrnul initgroups posix_fallocate]) diff --git a/shared/os-compatibility.h b/shared/os-compatibility.h index 06d257485..35e3a78d8 100644 --- a/shared/os-compatibility.h +++ b/shared/os-compatibility.h @@ -30,16 +30,6 @@ #include -#ifdef HAVE_EXECINFO_H -#include -#else -static inline int -backtrace(void **buffer, int size) -{ - return 0; -} -#endif - int os_fd_set_cloexec(int fd); From ed75c89fd570a587010b1dbbb2c0a466821ddd06 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Sun, 9 Dec 2018 14:49:07 +0200 Subject: [PATCH 0673/1642] tests: fix include in input-timestamps-helper.c No need to use the protocol directory prefix. This may even be necessary for the Meson build. Signed-off-by: Daniel Stone Extracted from the patch adding the Meson build system. Signed-off-by: Pekka Paalanen --- tests/input-timestamps-helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/input-timestamps-helper.c b/tests/input-timestamps-helper.c index 9e90fc07e..3dd8805f3 100644 --- a/tests/input-timestamps-helper.c +++ b/tests/input-timestamps-helper.c @@ -31,7 +31,7 @@ #include #include "input-timestamps-helper.h" -#include "protocol/input-timestamps-unstable-v1-client-protocol.h" +#include "input-timestamps-unstable-v1-client-protocol.h" #include "shared/timespec-util.h" #include "shared/zalloc.h" #include "weston-test-client-helper.h" From 8011b0fa0303741ef41f45ce0bf45484d9c5f115 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 24 Nov 2016 15:54:51 +0000 Subject: [PATCH 0674/1642] Add Meson build system Meson is a build system, currently implemented in Python, with multiple output backends, including Ninja and Make. The build file syntax is clean and easy to read unlike autotools. In practise, configuring and building with Meson and Ninja has been observed to be much faster than with autotools. Also cross-building support is excellent. More information at http://mesonbuild.com Since moving to Meson requires some changes from users in any case, we took this opportunity to revamp build options. Most of the build options still exist, some have changed names or more, and a few have been dropped. The option to choose the Cairo flavour is not implemented since for the longest time the Cairo image backend has been the only recommended one. This Meson build should be fully functional and it installs everything an all-enabled autotools build does. Installed pkg-config files have some minor differences that should be insignificant. Building of some developer documentation that was never installed with autotools is missing. It is expected that the autotools build system will be removed soon after the next Weston release. Signed-off-by: Daniel Stone Co-authored-by: Pekka Paalanen Signed-off-by: Pekka Paalanen --- clients/meson.build | 367 ++++++++++++++++++++++++++++ compositor/meson.build | 146 ++++++++++++ data/meson.build | 29 +++ desktop-shell/meson.build | 29 +++ fullscreen-shell/meson.build | 17 ++ ivi-shell/meson.build | 53 ++++ libweston-desktop/meson.build | 35 +++ libweston/git-version.h.meson | 1 + libweston/meson.build | 438 ++++++++++++++++++++++++++++++++++ man/meson.build | 53 ++++ meson.build | 174 ++++++++++++++ meson_options.txt | 206 ++++++++++++++++ protocol/meson.build | 72 ++++++ remoting/meson.build | 31 +++ shared/meson.build | 65 +++++ tests/meson.build | 371 ++++++++++++++++++++++++++++ wcap/meson.build | 21 ++ xwayland/meson.build | 31 +++ 18 files changed, 2139 insertions(+) create mode 100644 clients/meson.build create mode 100644 compositor/meson.build create mode 100644 data/meson.build create mode 100644 desktop-shell/meson.build create mode 100644 fullscreen-shell/meson.build create mode 100644 ivi-shell/meson.build create mode 100644 libweston-desktop/meson.build create mode 100644 libweston/git-version.h.meson create mode 100644 libweston/meson.build create mode 100644 man/meson.build create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 protocol/meson.build create mode 100644 remoting/meson.build create mode 100644 shared/meson.build create mode 100644 tests/meson.build create mode 100644 wcap/meson.build create mode 100644 xwayland/meson.build diff --git a/clients/meson.build b/clients/meson.build new file mode 100644 index 000000000..42590e1e5 --- /dev/null +++ b/clients/meson.build @@ -0,0 +1,367 @@ +if get_option('resize-pool') + config_h.set('USE_RESIZE_POOL', '1') +endif + +srcs_toytoolkit = [ + 'window.c', + xdg_shell_unstable_v6_client_protocol_h, + xdg_shell_unstable_v6_protocol_c, + text_cursor_position_client_protocol_h, + text_cursor_position_protocol_c, + relative_pointer_unstable_v1_client_protocol_h, + relative_pointer_unstable_v1_protocol_c, + pointer_constraints_unstable_v1_client_protocol_h, + pointer_constraints_unstable_v1_protocol_c, + ivi_application_client_protocol_h, + ivi_application_protocol_c, +] +deps_toytoolkit = [ + dep_wayland_client, + dep_lib_cairo_shared, + dep_xkbcommon, + dependency('wayland-cursor'), + cc.find_library('util'), +] +lib_toytoolkit = static_library( + 'toytoolkit', + srcs_toytoolkit, + include_directories: include_directories('..', '../shared'), + dependencies: deps_toytoolkit, + install: false, +) +dep_toytoolkit = declare_dependency( + link_with: lib_toytoolkit, + dependencies: deps_toytoolkit, +) + +simple_clients = [ + { + 'name': 'damage', + 'sources': [ + 'simple-damage.c', + viewporter_client_protocol_h, + viewporter_protocol_c, + xdg_shell_unstable_v6_client_protocol_h, + xdg_shell_unstable_v6_protocol_c, + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ], + 'dep_objs': [ dep_wayland_client, dep_libshared ] + }, + { + 'name': 'dmabuf-v4l', + 'sources': [ + 'simple-dmabuf-v4l.c', + linux_dmabuf_unstable_v1_client_protocol_h, + linux_dmabuf_unstable_v1_protocol_c, + xdg_shell_unstable_v6_client_protocol_h, + xdg_shell_unstable_v6_protocol_c, + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ], + 'dep_objs': [ dep_wayland_client, dep_libdrm_headers ] + }, + { + 'name': 'egl', + 'sources': [ + 'simple-egl.c', + xdg_shell_unstable_v6_client_protocol_h, + xdg_shell_unstable_v6_protocol_c, + ivi_application_client_protocol_h, + ivi_application_protocol_c, + ], + 'dep_objs': [ dep_wayland_client, dep_libshared, dep_libm ], + 'deps': [ 'egl', 'wayland-egl', 'glesv2', 'wayland-cursor' ] + }, + # weston-simple-im is handled specially separately due to install_dir and odd window.h usage + { + 'name': 'shm', + 'sources': [ + 'simple-shm.c', + xdg_shell_unstable_v6_client_protocol_h, + xdg_shell_unstable_v6_protocol_c, + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ivi_application_client_protocol_h, + ivi_application_protocol_c, + ], + 'dep_objs': [ dep_wayland_client, dep_libshared ] + }, + { + 'name': 'touch', + 'sources': [ + 'simple-touch.c', + ], + 'dep_objs': [ dep_wayland_client, dep_libshared ] + }, +] + +simple_clients_enabled = get_option('simple-clients') +simple_build_all = simple_clients_enabled.contains('all') +foreach t : simple_clients + if simple_build_all or simple_clients_enabled.contains(t.get('name')) + t_name = 'weston-simple-' + t.get('name') + t_deps = t.get('dep_objs', []) + foreach depname : t.get('deps', []) + dep = dependency(depname, required: false) + if not dep.found() + error('@0@ requires @1@ which was not found. If you rather not build this, drop "@2@" from simple-clients option.'.format(t_name, depname, t.get('name'))) + endif + t_deps += dep + endforeach + + executable( + t_name, t.get('sources'), + include_directories: include_directories('..'), + dependencies: t_deps, + install: true + ) + endif +endforeach + +if simple_build_all or simple_clients_enabled.contains('im') + executable( + 'weston-simple-im', [ + 'simple-im.c', + input_method_unstable_v1_client_protocol_h, + input_method_unstable_v1_protocol_c, + ], + include_directories: include_directories('..'), + dependencies: [ + dep_libshared, + dep_wayland_client, + dep_xkbcommon, + dependency('wayland-cursor'), + dependency('cairo') + ], + install: true, + install_dir: dir_libexec + ) +endif + +tools_enabled = get_option('tools') +tools_list = [ + { + 'name': 'calibrator', + 'sources': [ + 'calibrator.c', + '../shared/matrix.c', + ], + 'deps': [ dep_toytoolkit ], + }, + { + 'name': 'debug', + 'sources': [ + 'weston-debug.c', + weston_debug_client_protocol_h, + weston_debug_protocol_c, + ], + 'deps': [ dep_wayland_client ] + }, + { + 'name': 'info', + 'sources': [ + 'weston-info.c', + presentation_time_client_protocol_h, + presentation_time_protocol_c, + linux_dmabuf_unstable_v1_client_protocol_h, + linux_dmabuf_unstable_v1_protocol_c, + tablet_unstable_v2_client_protocol_h, + tablet_unstable_v2_protocol_c, + xdg_output_unstable_v1_client_protocol_h, + xdg_output_unstable_v1_protocol_c, + ], + 'deps': [ dep_wayland_client, dep_libshared ] + }, + { + 'name': 'terminal', + 'sources': [ 'terminal.c' ], + 'deps': [ dep_toytoolkit ], + }, + { + 'name': 'touch-calibrator', + 'sources': [ + 'touch-calibrator.c', + '../shared/matrix.c', + weston_touch_calibration_client_protocol_h, + weston_touch_calibration_protocol_c, + ], + 'deps': [ dep_toytoolkit ], + }, +] + +foreach t : tools_list + if tools_enabled.contains(t.get('name')) + executable( + 'weston-@0@'.format(t.get('name')), + t.get('sources'), + include_directories: include_directories('..', '../shared'), + dependencies: t.get('deps', []), + install: true + ) + endif +endforeach + +demo_clients = [ + { 'basename': 'clickdot' }, + { + 'basename': 'cliptest', + 'add_sources': [ '../libweston/vertex-clipping.c' ] + }, + { 'basename': 'confine' }, + { 'basename': 'dnd' }, + { + 'basename': 'editor', + 'add_sources': [ + text_input_unstable_v1_client_protocol_h, + text_input_unstable_v1_protocol_c, + ], + 'add_deps': [ dependency('pangocairo') ] + }, + { 'basename': 'eventdemo' }, + { 'basename': 'flower' }, + { + 'basename': 'fullscreen', + 'add_sources': [ + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ] + }, + { 'basename': 'image' }, + { 'basename': 'multi-resource' }, + { + 'basename': 'presentation-shm', + 'add_sources': [ + presentation_time_client_protocol_h, + presentation_time_protocol_c, + ] + }, + { 'basename': 'resizor' }, + { + 'basename': 'scaler', + 'add_sources': [ + viewporter_client_protocol_h, + viewporter_protocol_c, + ] + }, + { 'basename': 'smoke' }, + { 'basename': 'stacking' }, + { + 'basename': 'subsurfaces', + 'add_deps': [ dep_egl, dep_glesv2, dep_wl_egl ] + }, + { 'basename': 'transformed' }, +] + +if get_option('demo-clients') + foreach t : demo_clients + t_name = 'weston-' + t.get('basename') + t_srcs = [ t.get('basename') + '.c' ] + t.get('add_sources', []) + t_deps = [ dep_toytoolkit ] + t.get('add_deps', []) + + executable( + t_name, t_srcs, + include_directories: include_directories('..', '../shared'), + dependencies: t_deps, + install: true + ) + endforeach +endif + +simple_dmabuf_drm_opts = get_option('simple-dmabuf-drm') +simple_dmabuf_drm_deps = [] +foreach driver : [ 'etnaviv', 'intel', 'freedreno' ] + if simple_dmabuf_drm_opts.contains(driver) + required = true + enabled = true + elif simple_dmabuf_drm_opts.contains('auto') + required = get_option('auto_features').enabled() + enabled = not get_option('auto_features').disabled() + else + enabled = false + endif + + if enabled + dep = dependency('libdrm_' + driver, required: required) + if dep.found() + simple_dmabuf_drm_deps += dep + config_h.set('HAVE_LIBDRM_' + driver.to_upper(), 1) + endif + endif +endforeach +if simple_dmabuf_drm_deps.length() > 0 + executable( + 'weston-simple-dmabuf-drm', + 'simple-dmabuf-drm.c', + xdg_shell_unstable_v6_client_protocol_h, + xdg_shell_unstable_v6_protocol_c, + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + linux_dmabuf_unstable_v1_client_protocol_h, + linux_dmabuf_unstable_v1_protocol_c, + include_directories: include_directories('..'), + dependencies: [ + dep_wayland_client, + dep_libdrm, + simple_dmabuf_drm_deps + ], + install: true + ) +endif + +if get_option('shell-desktop') + exe_keyboard = executable( + 'weston-keyboard', + 'keyboard.c', + text_input_unstable_v1_client_protocol_h, + text_input_unstable_v1_protocol_c, + input_method_unstable_v1_client_protocol_h, + input_method_unstable_v1_protocol_c, + include_directories: include_directories('..'), + dependencies: dep_toytoolkit, + install_dir: get_option('libexecdir'), + install: true + ) + env_modmap += 'weston-keyboard=@0@;'.format(exe_keyboard.full_path()) + + exe_shooter = executable( + 'weston-screenshooter', + 'screenshot.c', + weston_screenshooter_client_protocol_h, + weston_screenshooter_protocol_c, + include_directories: include_directories('..'), + dependencies: dep_toytoolkit, + install_dir: get_option('libexecdir'), + install: true + ) + env_modmap += 'weston-screenshooter=@0@;'.format(exe_shooter.full_path()) + + exe_shell_desktop = executable( + 'weston-desktop-shell', + 'desktop-shell.c', + weston_desktop_shell_client_protocol_h, + weston_desktop_shell_protocol_c, + include_directories: include_directories('..'), + dependencies: dep_toytoolkit, + install_dir: get_option('libexecdir'), + install: true + ) + env_modmap += 'weston-desktop-shell=@0@;'.format(exe_shell_desktop.full_path()) +endif + + +if get_option('shell-ivi') + exe_shell_ivi_ui = executable( + 'weston-ivi-shell-user-interface', + 'ivi-shell-user-interface.c', + ivi_hmi_controller_client_protocol_h, + ivi_hmi_controller_protocol_c, + ivi_application_client_protocol_h, + ivi_application_protocol_c, + include_directories: include_directories('..'), + dependencies: dep_toytoolkit, + install: true, + install_dir: get_option('libexecdir') + ) + env_modmap += 'weston-ivi-shell-user-interface=@0@;'.format(exe_shell_ivi_ui.full_path()) +endif diff --git a/compositor/meson.build b/compositor/meson.build new file mode 100644 index 000000000..0cacae521 --- /dev/null +++ b/compositor/meson.build @@ -0,0 +1,146 @@ +srcs_weston = [ + git_version_h, + 'main.c', + 'text-backend.c', + 'weston-screenshooter.c', + text_input_unstable_v1_server_protocol_h, + text_input_unstable_v1_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + weston_screenshooter_server_protocol_h, + weston_screenshooter_protocol_c, +] +deps_weston = [ + dep_libshared, + dep_libweston, + dep_libinput, + dep_libdl, + dep_threads, +] + +if get_option('xwayland') + srcs_weston += 'xwayland.c' + config_h.set_quoted('XSERVER_PATH', get_option('xwayland-path')) +endif + +exe_weston = executable( + 'weston', + srcs_weston, + include_directories: include_directories('..', '../shared'), + link_args: [ '-export-dynamic' ], + dependencies: deps_weston, + install: true +) +install_headers('weston.h', subdir: 'weston') + +pkgconfig.generate( + filebase: 'weston', + name: 'Weston Plugin API', + version: version_weston, + description: 'Header files for Weston plugin development', + requires_private: [ lib_weston ], + variables: [ + 'libexecdir=' + join_paths('${prefix}', get_option('libexecdir')), + 'pkglibexecdir=${libexecdir}/weston' + ], + subdirs: 'weston' +) + +install_data( + 'weston.desktop', + install_dir: join_paths(dir_data, 'wayland-sessions') +) + +if get_option('screenshare') + srcs_screenshare = [ + 'screen-share.c', + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ] + deps_screenshare = [ + dep_libweston, + dep_wayland_client, + ] + plugin_screenshare = shared_library( + 'screen-share', + srcs_screenshare, + include_directories: include_directories('..', '../shared'), + dependencies: deps_screenshare, + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) + env_modmap += 'screen-share.so=@0@;'.format(plugin_screenshare.full_path()) +endif + +if get_option('color-management-lcms') + config_h.set('HAVE_LCMS', '1') + + srcs_lcms = [ + 'cms-static.c', + 'cms-helper.c', + ] + deps_lcms = [ + dep_libweston, + dependency('lcms2'), + ] + plugin_lcms = shared_library( + 'cms-static', + srcs_lcms, + include_directories: include_directories('..', '../shared'), + dependencies: deps_lcms, + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) + env_modmap += 'cms-static.so=@0@;'.format(plugin_lcms.full_path()) +endif + +if get_option('color-management-colord') + if not get_option('color-management-lcms') + error('LCMS must be enabled to support colord') + endif + + srcs_colord = [ + 'cms-colord.c', + 'cms-helper.c', + ] + deps_colord = [ + dep_libweston, + dependency('colord', version: '>= 0.1.27') + ] + plugin_colord = shared_library( + 'cms-colord', + srcs_colord, + include_directories: include_directories('..', '../shared'), + dependencies: deps_colord, + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) + env_modmap += 'cms-colord.so=@0@;'.format(plugin_colord.full_path()) +endif + +if get_option('systemd') + plugin_systemd_notify = shared_library( + 'systemd-notify', + 'systemd-notify.c', + include_directories: include_directories('..', '../shared'), + dependencies: [ dep_libweston, dependency('libsystemd') ], + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) + env_modmap += 'systemd-notify.so=@0@;'.format(plugin_systemd_notify.full_path()) +endif + +weston_ini_config = configuration_data() +weston_ini_config.set('bindir', dir_bin) +weston_ini_config.set('libexecdir', dir_libexec) +weston_ini_config.set('abs_top_srcdir', meson.source_root()) +weston_ini_config.set('abs_top_builddir', meson.build_root()) +configure_file( + input: '../weston.ini.in', + output: 'weston.ini', + configuration: weston_ini_config +) diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 000000000..16f172fec --- /dev/null +++ b/data/meson.build @@ -0,0 +1,29 @@ +install_data( + [ + 'background.png', + 'border.png', + 'fullscreen.png', + 'home.png', + 'icon_editor.png', + 'icon_flower.png', + 'icon_ivi_clickdot.png', + 'icon_ivi_flower.png', + 'icon_ivi_simple-egl.png', + 'icon_ivi_simple-shm.png', + 'icon_ivi_smoke.png', + 'icon_terminal.png', + 'icon_window.png', + 'panel.png', + 'pattern.png', + 'random.png', + 'sidebyside.png', + 'sign_close.png', + 'sign_maximize.png', + 'sign_minimize.png', + 'terminal.png', + 'tiling.png', + 'wayland.png', + 'wayland.svg', + ], + install_dir: join_paths(dir_data, 'weston') +) diff --git a/desktop-shell/meson.build b/desktop-shell/meson.build new file mode 100644 index 000000000..070134379 --- /dev/null +++ b/desktop-shell/meson.build @@ -0,0 +1,29 @@ +if get_option('shell-desktop') + config_h.set_quoted('WESTON_SHELL_CLIENT', get_option('desktop-shell-client-default')) + + srcs_shell_desktop = [ + 'shell.c', + 'exposay.c', + 'input-panel.c', + '../shared/matrix.c', + weston_desktop_shell_server_protocol_h, + weston_desktop_shell_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + ] + deps_shell_desktop = [ + dep_libshared, + dep_lib_desktop, + dep_libweston, + ] + plugin_shell_desktop = shared_library( + 'desktop-shell', + srcs_shell_desktop, + include_directories: include_directories('..', '../shared'), + dependencies: deps_shell_desktop, + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) + env_modmap += 'desktop-shell.so=@0@;'.format(plugin_shell_desktop.full_path()) +endif diff --git a/fullscreen-shell/meson.build b/fullscreen-shell/meson.build new file mode 100644 index 000000000..c9ca781a5 --- /dev/null +++ b/fullscreen-shell/meson.build @@ -0,0 +1,17 @@ +if get_option('shell-fullscreen') + srcs_shell_fullscreen = [ + 'fullscreen-shell.c', + '../shared/matrix.c', + fullscreen_shell_unstable_v1_server_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ] + shared_library( + 'fullscreen-shell', + srcs_shell_fullscreen, + include_directories: include_directories('..', '../shared'), + dependencies: dep_libweston, + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) +endif diff --git a/ivi-shell/meson.build b/ivi-shell/meson.build new file mode 100644 index 000000000..057e39736 --- /dev/null +++ b/ivi-shell/meson.build @@ -0,0 +1,53 @@ +if get_option('shell-ivi') + srcs_shell_ivi = [ + 'ivi-shell.c', + 'ivi-layout.c', + 'ivi-layout-transition.c', + ivi_application_server_protocol_h, + ivi_application_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + ] + plugin_shell_ivi = shared_library( + 'ivi-shell', + srcs_shell_ivi, + include_directories: include_directories('..', '../shared'), + dependencies: dep_libweston, + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) + env_modmap += 'ivi-shell.so=@0@;'.format(plugin_shell_ivi.full_path()) + + install_headers('ivi-layout-export.h', subdir: 'weston') + + srcs_ivi_hmi = [ + 'hmi-controller.c', + ivi_hmi_controller_server_protocol_h, + ivi_hmi_controller_protocol_c, + ] + plugin_ivi_hmi = shared_library( + 'hmi-controller', + srcs_ivi_hmi, + include_directories: include_directories('..', '../shared'), + dependencies: dep_libweston, + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) + env_modmap += 'hmi-controller.so=@0@;'.format(plugin_ivi_hmi.full_path()) + + ivi_test_config = configuration_data() + ivi_test_config.set('bindir', dir_bin) + ivi_test_config.set('libexecdir', dir_libexec) + ivi_test_config.set('abs_top_srcdir', meson.current_source_dir()) + ivi_test_config.set('abs_top_builddir', meson.current_build_dir()) + ivi_test_config.set('plugin_prefix', meson.current_build_dir()) + ivi_test_config.set('westondatadir', join_paths(dir_data, 'weston')) + ivi_test_ini = configure_file( + input: '../ivi-shell/weston.ini.in', + output: 'weston-ivi-test.ini', + install: false, + configuration: ivi_test_config + ) +endif diff --git a/libweston-desktop/meson.build b/libweston-desktop/meson.build new file mode 100644 index 000000000..81bd64e2e --- /dev/null +++ b/libweston-desktop/meson.build @@ -0,0 +1,35 @@ +srcs_libdesktop = [ + 'libweston-desktop.c', + 'client.c', + 'seat.c', + 'surface.c', + 'xwayland.c', + 'wl-shell.c', + 'xdg-shell-v6.c', + xdg_shell_unstable_v6_server_protocol_h, + xdg_shell_unstable_v6_protocol_c, +] +lib_desktop = shared_library( + 'weston-desktop-@0@'.format(libweston_major), + srcs_libdesktop, + include_directories: include_directories('..', '../shared'), + install: true, + version: '0.0.@0@'.format(libweston_revision), + dependencies: dep_libweston +) +dep_lib_desktop = declare_dependency( + link_with: lib_desktop, + dependencies: dep_libweston +) + +install_headers('libweston-desktop.h', subdir: dir_include_libweston) + +pkgconfig.generate( + filebase: 'libweston-desktop-@0@'.format(libweston_major), + name: 'libweston-desktop', + version: version_weston, + description: 'Desktop shells abstraction library for libweston compositors', + libraries: lib_desktop, + requires_private: [ lib_weston, dep_wayland_server ], + subdirs: dir_include_libweston +) diff --git a/libweston/git-version.h.meson b/libweston/git-version.h.meson new file mode 100644 index 000000000..d91f19c4c --- /dev/null +++ b/libweston/git-version.h.meson @@ -0,0 +1 @@ +#define BUILD_ID "@VCS_TAG@" diff --git a/libweston/meson.build b/libweston/meson.build new file mode 100644 index 000000000..6c87552e9 --- /dev/null +++ b/libweston/meson.build @@ -0,0 +1,438 @@ +deps_libweston = [ + dep_wayland_server, + dep_pixman, + dep_libm, + dep_libdl, + dep_libdrm_headers, + dep_libshared, + dep_xkbcommon, +] +srcs_libweston = [ + git_version_h, + 'animation.c', + 'bindings.c', + 'clipboard.c', + 'compositor.c', + 'data-device.c', + 'input.c', + 'linux-dmabuf.c', + 'log.c', + 'noop-renderer.c', + 'pixel-formats.c', + 'pixman-renderer.c', + 'plugin-registry.c', + 'screenshooter.c', + 'timeline.c', + 'touch-calibration.c', + 'weston-debug.c', + 'zoom.c', + '../shared/matrix.c', + linux_dmabuf_unstable_v1_protocol_c, + linux_dmabuf_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_timestamps_unstable_v1_protocol_c, + input_timestamps_unstable_v1_server_protocol_h, + presentation_time_protocol_c, + presentation_time_server_protocol_h, + pointer_constraints_unstable_v1_protocol_c, + pointer_constraints_unstable_v1_server_protocol_h, + relative_pointer_unstable_v1_protocol_c, + relative_pointer_unstable_v1_server_protocol_h, + weston_screenshooter_protocol_c, + weston_screenshooter_server_protocol_h, + text_cursor_position_protocol_c, + text_cursor_position_server_protocol_h, + text_input_unstable_v1_protocol_c, + text_input_unstable_v1_server_protocol_h, + weston_touch_calibration_protocol_c, + weston_touch_calibration_server_protocol_h, + viewporter_protocol_c, + viewporter_server_protocol_h, + weston_debug_protocol_c, + weston_debug_server_protocol_h, +] + +install_headers( + 'compositor.h', + 'plugin-registry.h', + 'timeline-object.h', + 'windowed-output-api.h', + '../shared/config-parser.h', + '../shared/matrix.h', + '../shared/zalloc.h', + subdir: dir_include_libweston +) + +lib_weston = shared_library( + 'weston-@0@'.format(libweston_major), + srcs_libweston, + include_directories: include_directories('..', '../shared'), + link_args: [ '-export-dynamic' ], + install: true, + version: '0.0.@0@'.format(libweston_revision), + dependencies: deps_libweston +) + +dep_libweston = declare_dependency( + link_with: lib_weston, + include_directories: include_directories('.'), + dependencies: deps_libweston +) + +pkgconfig.generate( + filebase: 'libweston-@0@'.format(libweston_major), + name: 'libweston API', + version: version_weston, + description: 'Header files for libweston compositors development', + libraries: lib_weston, + requires_private: [ dep_wayland_server, dep_pixman, dep_xkbcommon ], + subdirs: dir_include_libweston +) + +pkgconfig.generate( + filebase: 'libweston-@0@-protocols'.format(libweston_major), + name: 'libWeston Protocols', + version: version_weston, + description: 'libWeston protocol files', + variables: [ + 'datarootdir=' + join_paths('${prefix}', get_option('datadir')), + 'pkgdatadir=' + join_paths('${pc_sysrootdir}${datarootdir}', dir_protocol_libweston) + ], + install_dir: dir_data_pc +) + +srcs_session_helper = [ + 'launcher-direct.c', + 'launcher-util.c', + 'launcher-weston-launch.c', +] +deps_session_helper = [ + # for compositor.h needing pixman.h + dep_pixman.partial_dependency(compile_args: true) +] + +if get_option('backend-drm') + deps_session_helper += dep_libdrm +endif + +systemd_dep = dependency('', required: false) +if get_option('launcher-logind') + systemd_dep = dependency('libsystemd', version: '>= 209', required: false) + if systemd_dep.found() + config_h.set('HAVE_SYSTEMD_LOGIN_209', '1') + else + systemd_dep = dependency('libsystemd-login', version: '>= 198') + endif + config_h.set('HAVE_DBUS', '1') + config_h.set('HAVE_SYSTEMD_LOGIN', '1') + + srcs_session_helper += [ + 'dbus.c', + 'launcher-logind.c', + ] + deps_session_helper += [ + dependency('dbus-1', version: '>= 1.6'), + systemd_dep, + ] +endif + +lib_session_helper = static_library( + 'session-helper', + srcs_session_helper, + include_directories: include_directories('..', '../shared'), + dependencies: deps_session_helper, + install: false +) +dep_session_helper = declare_dependency(link_with: lib_session_helper) + + +if get_option('backend-drm') + config_h.set('BUILD_DRM_COMPOSITOR', '1') + + srcs_drm = [ + 'compositor-drm.c', + 'libbacklight.c', + 'libinput-device.c', + 'libinput-seat.c', + linux_dmabuf_unstable_v1_protocol_c, + linux_dmabuf_unstable_v1_server_protocol_h, + presentation_time_server_protocol_h, + ] + + deps_drm = [ + dep_libweston, + dep_session_helper, + dep_libdrm, + dep_libinput, + dependency('libudev', version: '>= 136'), + ] + + if get_option('renderer-gl') + dep_gbm = dependency('gbm') + if dep_gbm.version().version_compare('>= 17.1') + config_h.set('HAVE_GBM_MODIFIERS', '1') + endif + if dep_gbm.version().version_compare('>= 17.2') + config_h.set('HAVE_GBM_FD_IMPORT', '1') + endif + deps_drm += dep_gbm + endif + + if get_option('backend-drm-screencast-vaapi') + srcs_drm += 'vaapi-recorder.c' + deps_drm += [ + dependency('libva', version: '>= 0.34.0'), + dependency('libva-drm', version: '>= 0.34.0'), + dependency('threads'), + ] + config_h.set('BUILD_VAAPI_RECORDER', '1') + endif + + if dep_libdrm.version().version_compare('>= 2.4.71') + config_h.set('HAVE_DRM_ADDFB2_MODIFIERS', '1') + endif + + if dep_libdrm.version().version_compare('>= 2.4.78') + config_h.set('HAVE_DRM_ATOMIC', '1') + endif + + if dep_libdrm.version().version_compare('>= 2.4.83') + config_h.set('HAVE_DRM_FORMATS_BLOB', '1') + endif + + plugin_drm = shared_library( + 'drm-backend', + srcs_drm, + include_directories: include_directories('..', '../shared'), + dependencies: deps_drm, + name_prefix: '', + install: true, + install_dir: dir_module_libweston + ) + env_modmap += 'drm-backend.so=@0@;'.format(plugin_drm.full_path()) + + install_headers('compositor-drm.h', subdir: dir_include_libweston) +endif + + +if get_option('backend-headless') + config_h.set('BUILD_HEADLESS_COMPOSITOR', '1') + + srcs_headless = [ + 'compositor-headless.c', + presentation_time_server_protocol_h, + ] + plugin_headless = shared_library( + 'headless-backend', + srcs_headless, + include_directories: include_directories('..', '../shared'), + dependencies: dep_libweston, + name_prefix: '', + install: true, + install_dir: dir_module_libweston, + ) + env_modmap += 'headless-backend.so=@0@;'.format(plugin_headless.full_path()) + install_headers('compositor-headless.h', subdir: dir_include_libweston) +endif + + +if get_option('backend-rdp') + config_h.set('BUILD_RDP_COMPOSITOR', '1') + + dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) + if not dep_frdp.found() + dep_frdp = dependency('freerdp', version: '>= 1.1.0') + endif + + if cc.has_header('freerdp/version.h', dependencies: dep_frdp) + config_h.set('HAVE_FREERDP_VERSION_H', '1') + endif + + if cc.has_member( + 'SURFACE_BITS_COMMAND', 'bmp', + dependencies : dep_frdp, + prefix : '#include ' + ) + config_h.set('HAVE_SURFACE_BITS_BMP', '1') + endif + + deps_rdp = [ + dep_libweston, + dep_frdp, + ] + plugin_rdp = shared_library( + 'rdp-backend', + 'compositor-rdp.c', + include_directories: include_directories('..', '../shared'), + dependencies: deps_rdp, + name_prefix: '', + install: true, + install_dir: dir_module_libweston + ) + env_modmap += 'rdp-backend.so=@0@;'.format(plugin_rdp.full_path()) + install_headers('compositor-rdp.h', subdir: dir_include_libweston) +endif + + +if get_option('backend-wayland') + config_h.set('BUILD_WAYLAND_COMPOSITOR', '1') + + srcs_wlwl = [ + 'compositor-wayland.c', + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + presentation_time_protocol_c, + presentation_time_server_protocol_h, + xdg_shell_unstable_v6_client_protocol_h, + xdg_shell_unstable_v6_protocol_c, + ] + + deps_wlwl = [ + dependency('wayland-client'), + dependency('wayland-cursor'), + dep_pixman, + dep_libweston, + dep_lib_cairo_shared, + ] + + if get_option('renderer-gl') + deps_wlwl += dep_wl_egl + endif + + plugin_wlwl = shared_library( + 'wayland-backend', + srcs_wlwl, + include_directories: include_directories('..', '../shared'), + dependencies: deps_wlwl, + name_prefix: '', + install: true, + install_dir: dir_module_libweston + ) + env_modmap += 'wayland-backend.so=@0@;'.format(plugin_wlwl.full_path()) + install_headers('compositor-wayland.h', subdir: dir_include_libweston) +endif + + +if get_option('backend-x11') + config_h.set('BUILD_X11_COMPOSITOR', '1') + + srcs_x11 = [ + 'compositor-x11.c', + presentation_time_server_protocol_h, + ] + + deps_x11 = [ + dependency('xcb', version: '>= 1.8'), + dependency('xcb-shm'), + dependency('x11'), + dependency('x11-xcb'), + dep_lib_cairo_shared, + dep_pixman, + ] + + dep_xcb_xkb = dependency('xcb-xkb', version: '>= 1.9', required: false) + if dep_xcb_xkb.found() + deps_x11 += dep_xcb_xkb + config_h.set('HAVE_XCB_XKB', '1') + endif + + if get_option('renderer-gl') + deps_x11 += dependency('egl') + endif + + plugin_x11 = shared_library( + 'x11-backend', + srcs_x11, + include_directories: include_directories('..', '../shared'), + dependencies: deps_x11, + name_prefix: '', + install: true, + install_dir: dir_module_libweston + ) + env_modmap += 'x11-backend.so=@0@;'.format(plugin_x11.full_path()) + + install_headers('compositor-x11.h', subdir: dir_include_libweston) +endif + + +if get_option('backend-fbdev') + config_h.set('BUILD_FBDEV_COMPOSITOR', '1') + + srcs_fbdev = [ + 'compositor-fbdev.c', + 'libinput-device.c', + 'libinput-seat.c', + presentation_time_server_protocol_h, + ] + + deps_fbdev = [ + dep_libweston, + dep_session_helper, + dep_libinput, + dependency('libudev', version: '>= 136'), + ] + + plugin_fbdev = shared_library( + 'fbdev-backend', + srcs_fbdev, + include_directories: include_directories('..', '../shared'), + dependencies: deps_fbdev, + name_prefix: '', + install: true, + install_dir: dir_module_libweston + ) + env_modmap += 'fbdev-backend.so=@0@;'.format(plugin_fbdev.full_path()) + + install_headers('compositor-fbdev.h', subdir: dir_include_libweston) +endif + + +if get_option('renderer-gl') + config_h.set('ENABLE_EGL', '1') + + if not dep_egl.found() or not dep_glesv2.found() + error('gl-renderer enabled but EGL/GLESv2 not found') + endif + + srcs_renderer_gl = [ + 'gl-renderer.c', + 'vertex-clipping.c', + '../shared/matrix.c', + linux_dmabuf_unstable_v1_protocol_c, + linux_dmabuf_unstable_v1_server_protocol_h, + ] + deps_renderer_gl = [ + dep_egl, + dep_glesv2, + dep_pixman, + dep_libweston, + dep_libdrm_headers, + ] + plugin_gl = shared_library( + 'gl-renderer', + srcs_renderer_gl, + include_directories: include_directories('..', '../shared'), + dependencies: deps_renderer_gl, + name_prefix: '', + install: true, + install_dir: dir_module_libweston + ) + env_modmap += 'gl-renderer.so=@0@;'.format(plugin_gl.full_path()) +endif + +if get_option('weston-launch') + dep_pam = cc.find_library('pam') + + if not cc.has_function('pam_open_session', dependencies: dep_pam) + error('pam_open_session not found for weston-launch') + endif + + executable( + 'weston-launch', + 'weston-launch.c', + dependencies: [dep_pam, systemd_dep, dep_libdrm], + include_directories: include_directories('..'), + install: true + ) +endif diff --git a/man/meson.build b/man/meson.build new file mode 100644 index 000000000..ced569449 --- /dev/null +++ b/man/meson.build @@ -0,0 +1,53 @@ +man_conf = configuration_data() +man_conf.set('weston_native_backend', opt_backend_native) +man_conf.set('weston_modules_dir', dir_module_weston) +man_conf.set('libweston_modules_dir', dir_module_libweston) +man_conf.set('weston_shell_client', get_option('desktop-shell-client-default')) +man_conf.set('weston_libexecdir', dir_libexec) +man_conf.set('weston_bindir', dir_bin) +man_conf.set('xserver_path', get_option('xwayland-path')) +man_conf.set('version', version_weston) + +configure_file( + input: 'weston.man', + output: 'weston.1', + install: true, + install_dir: join_paths(dir_man, 'man1'), + configuration: man_conf +) + +configure_file( + input: 'weston-debug.man', + output: 'weston-debug.1', + install: true, + install_dir: join_paths(dir_man, 'man1'), + configuration: man_conf +) + +configure_file( + input: 'weston.ini.man', + output: 'weston.ini.5', + install: true, + install_dir: join_paths(dir_man, 'man5'), + configuration: man_conf +) + +if get_option('backend-drm') + configure_file( + input: 'weston-drm.man', + output: 'weston-drm.7', + install: true, + install_dir: join_paths(dir_man, 'man7'), + configuration: man_conf + ) +endif + +if get_option('backend-rdp') + configure_file( + input: 'weston-rdp.man', + output: 'weston-rdp.7', + install: true, + install_dir: join_paths(dir_man, 'man7'), + configuration: man_conf + ) +endif diff --git a/meson.build b/meson.build new file mode 100644 index 000000000..a5dcb7662 --- /dev/null +++ b/meson.build @@ -0,0 +1,174 @@ +project('weston', + 'c', + version: '5.0.90', + default_options: [ + 'warning_level=2', + 'c_std=gnu99', + 'b_lundef=false', + ], + meson_version: '>= 0.47', + license: 'MIT/Expat', +) + +libweston_major = 5 + +# libweston_revision is manufactured to follow the autotools build's +# library file naming, thanks to libtool +version_weston = meson.project_version() +version_weston_arr = version_weston.split('.') +if libweston_major > version_weston_arr[0].to_int() + if libweston_major > version_weston_arr[0].to_int() + 1 + error('Bad versions in meson.build: libweston_major is too high') + endif + libweston_revision = 0 +elif libweston_major == version_weston_arr[0].to_int() + libweston_revision = version_weston_arr[2].to_int() +else + error('Bad versions in meson.build: libweston_major is too low') +endif + +dir_prefix = get_option('prefix') +dir_bin = join_paths(dir_prefix, get_option('bindir')) +dir_data = join_paths(dir_prefix, get_option('datadir')) +dir_include = join_paths(dir_prefix, get_option('includedir')) +dir_include_libweston = 'libweston-@0@'.format(libweston_major) +dir_lib = join_paths(dir_prefix, get_option('libdir')) +dir_libexec = join_paths(dir_prefix, get_option('libexecdir')) +dir_module_weston = join_paths(dir_lib, 'weston') +dir_module_libweston = join_paths(dir_lib, 'libweston-@0@'.format(libweston_major)) +dir_data_pc = join_paths(dir_data, 'pkgconfig') +dir_lib_pc = join_paths(dir_lib, 'pkgconfig') +dir_man = join_paths(dir_prefix, get_option('mandir')) +dir_protocol_libweston = 'weston/protocols' # XXX: this should be 'libweston' + +pkgconfig = import('pkgconfig') + +libweston_version_h = configuration_data() +libweston_version_h.set('WESTON_VERSION_MAJOR', version_weston_arr[0]) +libweston_version_h.set('WESTON_VERSION_MINOR', version_weston_arr[1]) +libweston_version_h.set('WESTON_VERSION_MICRO', version_weston_arr[2]) +libweston_version_h.set('WESTON_VERSION', version_weston) +version_h = configure_file( + input: 'libweston/version.h.in', + output: 'version.h', + configuration: libweston_version_h +) +install_headers(version_h, subdir: dir_include_libweston) +git_version_h = vcs_tag( + input: 'libweston/git-version.h.meson', + output: 'git-version.h', + fallback: version_weston +) + +config_h = configuration_data() + +cc = meson.get_compiler('c') + +global_args = [] +global_args_maybe = [ + '-Wno-unused-parameter', + '-Wno-shift-negative-value', # required due to Pixman + '-Wno-missing-field-initializers', + '-fvisibility=hidden', + '-DIN_WESTON', +] +foreach a : global_args_maybe + if cc.has_argument(a) + global_args += a + endif +endforeach +add_global_arguments(global_args, language: 'c') + +if cc.has_header_symbol('sys/sysmacros.h', 'major') + config_h.set('MAJOR_IN_SYSMACROS', 1) +elif cc.has_header_symbol('sys/mkdev.h', 'major') + config_h.set('MAJOR_IN_MKDEV', 1) +endif + +optional_libc_funcs = [ + 'mkostemp', 'strchrnul', 'initgroups', 'posix_fallocate' +] +foreach func : optional_libc_funcs + if cc.has_function(func) + config_h.set('HAVE_' + func.to_upper(), 1) + endif +endforeach + +optional_system_headers = [ + 'linux/sync_file.h' +] +foreach hdr : optional_system_headers + if cc.has_header(hdr) + config_h.set('HAVE_' + hdr.underscorify().to_upper(), 1) + endif +endforeach + +env_modmap = '' + +config_h.set('_GNU_SOURCE', '1') +config_h.set('_ALL_SOURCE', '1') + +config_h.set_quoted('PACKAGE_STRING', 'weston @0@'.format(version_weston)) +config_h.set_quoted('PACKAGE_VERSION', version_weston) +config_h.set_quoted('VERSION', version_weston) +config_h.set_quoted('PACKAGE_URL', 'https://wayland.freedesktop.org') +config_h.set_quoted('PACKAGE_BUGREPORT', 'https://gitlab.freedesktop.org/wayland/weston/issues/') + +config_h.set_quoted('BINDIR', dir_bin) +config_h.set_quoted('DATADIR', dir_data) +config_h.set_quoted('LIBEXECDIR', dir_libexec) +config_h.set_quoted('MODULEDIR', dir_module_weston) +config_h.set_quoted('LIBWESTON_MODULEDIR', dir_module_libweston) + +backend_default = get_option('backend-default') +if backend_default == 'auto' + foreach b : [ 'headless', 'fbdev', 'x11', 'wayland', 'drm' ] + if get_option('backend-' + b) + backend_default = b + endif + endforeach +endif +opt_backend_native = backend_default + '-backend.so' +config_h.set_quoted('WESTON_NATIVE_BACKEND', opt_backend_native) +message('The default backend is ' + backend_default) +if not get_option('backend-' + backend_default) + error('Backend @0@ was chosen as native but is not being built.'.format(backend_default)) +endif + +dep_xkbcommon = dependency('xkbcommon', version: '>= 0.3.0') +if dep_xkbcommon.version().version_compare('>= 0.5.0') + config_h.set('HAVE_XKBCOMMON_COMPOSE', '1') +endif + +dep_wayland_server = dependency('wayland-server', version: '>= 1.12.0') +dep_wayland_client = dependency('wayland-client', version: '>= 1.12.0') +dep_pixman = dependency('pixman-1', version: '>= 0.25.2') +dep_libinput = dependency('libinput', version: '>= 0.8.0') +dep_libm = cc.find_library('m') +dep_libdl = cc.find_library('dl') +dep_libdrm = dependency('libdrm', version: '>= 2.4.68') +dep_libdrm_headers = dep_libdrm.partial_dependency(compile_args: true) +dep_egl = dependency('egl', required: false) +dep_wl_egl = dependency('wayland-egl') +dep_glesv2 = dependency('glesv2', required: false) +dep_threads = dependency('threads') + +subdir('protocol') +subdir('shared') +subdir('libweston') +subdir('libweston-desktop') +subdir('xwayland') +subdir('compositor') +subdir('desktop-shell') +subdir('fullscreen-shell') +subdir('ivi-shell') +subdir('remoting') +subdir('clients') +subdir('wcap') +subdir('tests') +subdir('data') +subdir('man') + +configure_file(output: 'config.h', install: false, configuration: config_h) + +# TODO: process doc/doxygen/*.doxygen.in diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 000000000..31362ba4d --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,206 @@ +# This option is not implemented: +# --with-cairo=[image|gl|glesv2] Which Cairo renderer to use for the clients +# It is hardcoded to cairo-image for now. + +option( + 'backend-drm', + type: 'boolean', + value: true, + description: 'Weston backend: DRM/KMS' +) +option( + 'backend-drm-screencast-vaapi', + type: 'boolean', + value: true, + description: 'DRM/KMS backend support for VA-API screencasting' +) +option( + 'backend-headless', + type: 'boolean', + value: true, + description: 'Weston backend: headless (testing)' +) +option( + 'backend-rdp', + type: 'boolean', + value: true, + description: 'Weston backend: RDP remote screensharing' +) +option( + 'screenshare', + type: 'boolean', + value: true, + description: 'Compositor: RDP screen-sharing support' +) +option( + 'backend-wayland', + type: 'boolean', + value: true, + description: 'Weston backend: Wayland (nested)' +) +option( + 'backend-x11', + type: 'boolean', + value: true, + description: 'Weston backend: X11 (nested)' +) +option( + 'backend-fbdev', + type: 'boolean', + value: true, + description: 'Weston backend: fbdev' +) +option( + 'backend-default', + type: 'combo', + choices: [ 'auto', 'drm', 'wayland', 'x11', 'fbdev', 'headless' ], + value: 'drm', + description: 'Default backend when no parent display server detected' +) + +option( + 'renderer-gl', + type: 'boolean', + value: true, + description: 'Weston renderer: EGL / OpenGL ES 2.x' +) + +option( + 'weston-launch', + type: 'boolean', + value: true, + description: 'Weston launcher for systems without logind' +) + +option( + 'xwayland', + type: 'boolean', + value: true, + description: 'Xwayland: support for X11 clients inside Weston' +) +option( + 'xwayland-path', + type: 'string', + value: '/usr/bin/Xwayland', + description: 'Xwayland: path to installed Xwayland binary' +) + +option( + 'systemd', + type: 'boolean', + value: true, + description: 'systemd service plugin: state notify, watchdog, socket activation' +) + +option( + 'remoting', + type: 'boolean', + value: true, + description: 'Virtual remote output with GStreamer on DRM backend' +) + +option( + 'shell-desktop', + type: 'boolean', + value: true, + description: 'Weston shell UI: traditional desktop' +) +option( + 'shell-fullscreen', + type: 'boolean', + value: true, + description: 'Weston shell UI: fullscreen/kiosk' +) +option( + 'shell-ivi', + type: 'boolean', + value: true, + description: 'Weston shell UI: IVI (automotive)' +) + +option( + 'desktop-shell-client-default', + type: 'string', + value: 'weston-desktop-shell', + description: 'Weston desktop shell: default helper client selection' +) + +option( + 'color-management-lcms', + type: 'boolean', + value: true, + description: 'Compositor color management: lcms' +) +option( + 'color-management-colord', + type: 'boolean', + value: true, + description: 'Compositor color management: colord (requires lcms)' +) + +option( + 'launcher-logind', + type: 'boolean', + value: true, + description: 'Compositor: support systemd-logind D-Bus protocol' +) + +option( + 'image-jpeg', + type: 'boolean', + value: true, + description: 'JPEG loading support' +) +option( + 'image-webp', + type: 'boolean', + value: true, + description: 'WebP loading support' +) + +option( + 'tools', + type: 'array', + choices: [ 'calibrator', 'debug', 'info', 'terminal', 'touch-calibrator' ], + description: 'List of accessory clients to build and install' +) +option( + 'simple-dmabuf-drm', + type: 'array', + choices: [ 'auto', 'intel', 'freedreno', 'etnaviv' ], + value: [ 'intel', 'freedreno', 'etnaviv' ], + description: 'List of DRM drivers to be supported by weston-simple-dmabuf-drm' +) +option( + 'demo-clients', + type: 'boolean', + value: true, + description: 'Sample clients: toytoolkit demo programs' +) +option( + 'simple-clients', + type: 'array', + choices: [ 'all', 'damage', 'im', 'egl', 'shm', 'touch', 'dmabuf-v4l' ], + value: [ 'all' ], + description: 'Sample clients: simple test programs' +) + +option( + 'resize-pool', + type: 'boolean', + value: true, + description: 'Sample clients: optimize window resize performance' +) +option( + 'wcap-decode', + type: 'boolean', + value: true, + description: 'Tools: screen recording decoder tool' +) + +option( + 'test-junit-xml', + type: 'boolean', + value: true, + description: 'Tests: output JUnit XML results' +) diff --git a/protocol/meson.build b/protocol/meson.build new file mode 100644 index 000000000..a947eeab8 --- /dev/null +++ b/protocol/meson.build @@ -0,0 +1,72 @@ +dep_scanner = dependency('wayland-scanner', native: true) +prog_scanner = find_program(dep_scanner.get_pkgconfig_variable('wayland_scanner')) + +dep_wp = dependency('wayland-protocols', version: '>= 1.14') +dir_wp_base = dep_wp.get_pkgconfig_variable('pkgdatadir') + +install_data( + [ + 'weston-debug.xml', + ], + install_dir: join_paths(dir_data, dir_protocol_libweston) +) + +generated_protocols = [ + [ 'input-method', 'v1' ], + [ 'input-timestamps', 'v1' ], + [ 'ivi-application', 'internal' ], + [ 'ivi-hmi-controller', 'internal' ], + [ 'fullscreen-shell', 'v1' ], + [ 'linux-dmabuf', 'v1' ], + [ 'presentation-time', 'stable' ], + [ 'pointer-constraints', 'v1' ], + [ 'relative-pointer', 'v1' ], + [ 'tablet', 'v2' ], + [ 'text-cursor-position', 'internal' ], + [ 'text-input', 'v1' ], + [ 'viewporter', 'stable' ], + [ 'weston-debug', 'internal' ], + [ 'weston-desktop-shell', 'internal' ], + [ 'weston-screenshooter', 'internal' ], + [ 'weston-test', 'internal' ], + [ 'weston-touch-calibration', 'internal' ], + [ 'xdg-output', 'v1' ], + [ 'xdg-shell', 'v6' ], +] + +foreach proto: generated_protocols + proto_name = proto[0] + if proto[1] == 'internal' + base_file = proto_name + xml_path = '@0@.xml'.format(proto_name) + elif proto[1] == 'stable' + base_file = proto_name + xml_path = '@0@/stable/@1@/@1@.xml'.format(dir_wp_base, base_file) + else + base_file = '@0@-unstable-@1@'.format(proto_name, proto[1]) + xml_path = '@0@/unstable/@1@/@2@.xml'.format(dir_wp_base, proto_name, base_file) + endif + + foreach output_type: [ 'client-header', 'server-header', 'private-code' ] + if output_type == 'client-header' + output_file = '@0@-client-protocol.h'.format(base_file) + elif output_type == 'server-header' + output_file = '@0@-server-protocol.h'.format(base_file) + else + output_file = '@0@-protocol.c'.format(base_file) + if dep_scanner.version().version_compare('< 1.14.91') + output_type = 'code' + endif + endif + + var_name = output_file.underscorify() + target = custom_target( + '@0@ @1@'.format(base_file, output_type), + command: [ prog_scanner, output_type, '@INPUT@', '@OUTPUT@' ], + input: xml_path, + output: output_file, + ) + + set_variable(var_name, target) + endforeach +endforeach diff --git a/remoting/meson.build b/remoting/meson.build new file mode 100644 index 000000000..224e8cd96 --- /dev/null +++ b/remoting/meson.build @@ -0,0 +1,31 @@ +if get_option('remoting') + user_hint = 'If you rather not build this, set "remoting=false".' + + if not get_option('backend-drm') + error('Attempting to build the remoting plugin without the required DRM backend. ' + user_hint) + endif + + depnames = [ + 'gstreamer-1.0', 'gstreamer-allocators-1.0', + 'gstreamer-app-1.0', 'gstreamer-video-1.0' + ] + deps_remoting = [ dep_libweston ] + foreach depname : depnames + dep = dependency(depname, required: false) + if not dep.found() + error('Remoting plugin requires @0@ which was not found. '.format(depname) + user_hint) + endif + deps_remoting += dep + endforeach + + plugin_remoting = shared_library( + 'remoting-plugin', + 'remoting-plugin.c', + include_directories: include_directories('..', '../shared'), + dependencies: deps_remoting, + name_prefix: '', + install: true, + install_dir: dir_module_libweston + ) + env_modmap += 'remoting-plugin.so=@0@;'.format(plugin_remoting.full_path()) +endif diff --git a/shared/meson.build b/shared/meson.build new file mode 100644 index 000000000..979debb70 --- /dev/null +++ b/shared/meson.build @@ -0,0 +1,65 @@ +srcs_libshared = [ + 'config-parser.c', + 'option-parser.c', + 'file-util.c', + 'os-compatibility.c', + 'xalloc.c', +] +deps_libshared = dep_wayland_client + +lib_libshared = static_library( + 'shared', + srcs_libshared, + include_directories: include_directories('..'), + dependencies: deps_libshared, + install: false +) +dep_libshared = declare_dependency( + link_with: lib_libshared, + dependencies: deps_libshared +) + +srcs_cairo_shared = [ + 'image-loader.c', + 'cairo-util.c', + 'frame.c', +] + +deps_cairo_shared = [ + dep_libshared, + dependency('cairo'), + dependency('libpng'), + dep_pixman, + dep_libm, +] + +dep_pango = dependency('pango', required: false) +dep_pangocairo = dependency('pangocairo', required: false) +dep_glib = dependency('glib-2.0', version: '>= 2.36', required: false) + +if dep_pango.found() and dep_pangocairo.found() and dep_glib.found() + deps_cairo_shared += [ dep_pango, dep_pangocairo, dep_glib ] + config_h.set('HAVE_PANGO', '1') +endif + +if get_option('image-jpeg') + deps_cairo_shared += cc.find_library('jpeg') + config_h.set('HAVE_JPEG', '1') +endif + +if get_option('image-webp') + deps_cairo_shared += dependency('libwebp') + config_h.set('HAVE_WEBP', '1') +endif + +lib_cairo_shared = static_library( + 'cairo-shared', + srcs_cairo_shared, + include_directories: include_directories('..'), + dependencies: deps_cairo_shared, + install: false +) +dep_lib_cairo_shared = declare_dependency( + link_with: lib_cairo_shared, + dependencies: deps_cairo_shared +) diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 000000000..2d6819099 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,371 @@ +plugin_test_shell_desktop = shared_library( + 'weston-test-desktop-shell', + 'weston-test-desktop-shell.c', + include_directories: include_directories('..', '../shared'), + dependencies: [ dep_lib_desktop, dep_libweston ], + name_prefix: '', + install: false +) +env_modmap += 'weston-test-desktop-shell.so=@0@;'.format(plugin_test_shell_desktop.full_path()) + +lib_test_runner = static_library( + 'test-runner', + 'weston-test-runner.c', + include_directories: include_directories('..', '../shared'), + install: false, +) +dep_test_runner = declare_dependency(link_with: lib_test_runner) + +lib_test_client = static_library( + 'test-client', + 'weston-test-client-helper.c', + weston_test_client_protocol_h, + weston_test_protocol_c, + include_directories: include_directories('..', '../shared'), + dependencies: [ + dep_libshared, + dep_wayland_client, + dep_pixman, + dependency('cairo'), + ], + install: false, +) +dep_test_client = declare_dependency( + link_with: lib_test_client, + dependencies: [ + dep_test_runner, + dep_pixman, + ] +) + +exe_plugin_test = shared_library( + 'test-plugin', + 'weston-test.c', + weston_test_server_protocol_h, + weston_test_protocol_c, + include_directories: include_directories('..', '../shared'), + dependencies: [ + dep_libweston, + dependency('egl'), + ], + name_prefix: '', + install: false, +) + +deps_zuc = [ dep_libshared ] +if get_option('test-junit-xml') + deps_zuc += [ dependency('libxml-2.0', version: '>= 2.6') ] + config_h.set('ENABLE_JUNIT_XML', '1') +endif + +lib_zuc = static_library( + 'zunitc', + '../tools/zunitc/inc/zunitc/zunitc.h', + '../tools/zunitc/inc/zunitc/zunitc_impl.h', + '../tools/zunitc/src/zuc_base_logger.c', + '../tools/zunitc/src/zuc_base_logger.h', + '../tools/zunitc/src/zuc_collector.c', + '../tools/zunitc/src/zuc_collector.h', + '../tools/zunitc/src/zuc_context.h', + '../tools/zunitc/src/zuc_event.h', + '../tools/zunitc/src/zuc_event_listener.h', + '../tools/zunitc/src/zuc_junit_reporter.c', + '../tools/zunitc/src/zuc_junit_reporter.h', + '../tools/zunitc/src/zuc_types.h', + '../tools/zunitc/src/zunitc_impl.c', + include_directories: include_directories('..', '../shared', '../tools/zunitc/inc'), + dependencies: deps_zuc, +) +dep_zuc = declare_dependency( + link_with: lib_zuc, + dependencies: dep_libshared, + include_directories: include_directories('../tools/zunitc/inc') +) + +lib_zucmain = static_library( + 'zunitcmain', + '../tools/zunitc/src/main.c', + include_directories: include_directories('..', '../shared', '../tools/zunitc/inc'), + dependencies: [ dep_libshared, dep_zuc ], +) +dep_zucmain = declare_dependency( + link_with: lib_zucmain, + dependencies: dep_zuc +) + +tests_standalone = [ + ['config-parser', [], [ dep_zucmain ]], + ['matrix', [ '../shared/matrix.c' ], [ dep_libm ]], + ['string'], + [ + 'vertex-clip', + [ + '../libweston/vertex-clipping.c' + ] + ], + ['timespec', [], [ dep_zucmain ]], + ['zuc', + [ + '../tools/zunitc/test/fixtures_test.c', + '../tools/zunitc/test/zunitc_test.c' + ], + [ dep_zucmain ] + ], +] + +tests_weston = [ + ['bad-buffer'], + ['devices'], + ['event'], + [ + 'keyboard', + [ + 'input-timestamps-helper.c', + input_timestamps_unstable_v1_client_protocol_h, + input_timestamps_unstable_v1_protocol_c, + ] + ], + ['internal-screenshot'], + [ + 'presentation', + [ + presentation_time_client_protocol_h, + presentation_time_protocol_c, + ] + ], + [ + 'pointer', + [ + 'input-timestamps-helper.c', + input_timestamps_unstable_v1_client_protocol_h, + input_timestamps_unstable_v1_protocol_c, + ] + ], + ['roles'], + ['subsurface'], + ['subsurface-shot'], + [ + 'text', + [ + text_input_unstable_v1_client_protocol_h, + text_input_unstable_v1_protocol_c, + ] + ], + [ + 'touch', + [ + 'input-timestamps-helper.c', + input_timestamps_unstable_v1_client_protocol_h, + input_timestamps_unstable_v1_protocol_c, + ] + ], + [ + 'viewporter', + [ + viewporter_client_protocol_h, + viewporter_protocol_c, + ] + ], +] + +if get_option('xwayland') + tests_weston += [ [ 'xwayland', [], dependency('x11') ] ] +endif + +tests_weston_plugin = [ + ['plugin-registry'], + ['surface'], + ['surface-global'], + ['surface-screenshot'], +] + +if get_option('shell-ivi') + tests_weston += [ + [ + 'ivi-shell-app', + [ + ivi_application_client_protocol_h, + ivi_application_protocol_c, + ] + ] + ] + tests_weston_plugin += [ + ['ivi-layout-internal'], + [ + 'ivi-layout', + [ + 'ivi-layout-test-plugin.c', + weston_test_server_protocol_h, + weston_test_protocol_c, + ] + ], + ] + + exe_ivi_layout_client = executable( + 'ivi-layout-test-client', + 'ivi-layout-test-client.c', + weston_test_client_protocol_h, + weston_test_protocol_c, + ivi_application_client_protocol_h, + ivi_application_protocol_c, + include_directories: include_directories('..', '../shared'), + dependencies: dep_test_client, + install: false + ) + + env_modmap += 'ivi-layout-test-client.ivi=@0@;'.format(exe_ivi_layout_client.full_path()) +endif + +env_test_weston = [ + 'WESTON_TEST_REFERENCE_PATH=@0@/reference'.format(meson.current_source_dir()), + 'WESTON_MODULE_MAP=@0@'.format(env_modmap), +] + +# FIXME: the multiple loops is lame. rethink this. +foreach t : tests_standalone + if t[0] != 'zuc' + srcs_t = [ + '@0@-test.c'.format(t.get(0)), + weston_test_client_protocol_h, + ] + else + srcs_t = [] + endif + + if t.length() > 1 + srcs_t += t.get(1) + endif + + if t.length() > 2 + deps_t = t[2] + else + deps_t = [ dep_test_client ] + endif + + exe_t = executable( + 'test-@0@'.format(t.get(0)), + srcs_t, + c_args: [ '-DUNIT_TEST' ], + build_by_default: true, + include_directories: include_directories('..', '../shared', '../libweston'), + dependencies: deps_t, + install: false, + ) + + # matrix-test is a manual test + if t[0] != 'matrix' + test(t.get(0), exe_t) + endif +endforeach + +foreach t : tests_weston + srcs_t = [ + '@0@-test.c'.format(t.get(0)), + weston_test_client_protocol_h, + ] + if t.length() > 1 + srcs_t += t.get(1) + endif + + deps_t = [ + dep_test_client + ] + if t.length() > 2 + deps_t += t.get(2) + endif + + args_t = [ + '--backend=headless-backend.so', + '--socket=test-@0@'.format(t.get(0)), + '--modules=@0@'.format(exe_plugin_test.full_path()), + '--width=320', + '--height=240', + ] + + if t.get(0) == 'xwayland' + args_t += '--xwayland' + endif + + # FIXME: Get this from the array ... ? + if t.get(0) == 'internal-screenshot' + args_t += [ '--config=@0@/internal-screenshot.ini'.format(meson.current_source_dir()) ] + args_t += [ '--use-pixman' ] + args_t += [ '--shell=desktop-shell.so' ] + elif t[0] == 'subsurface-shot' + args_t += [ '--no-config' ] + args_t += [ '--use-pixman' ] + args_t += [ '--width=320' ] + args_t += [ '--height=240' ] + args_t += [ '--shell=weston-test-desktop-shell.so' ] + elif t.get(0).startswith('ivi-') + args_t += [ '--config=@0@/../ivi-shell/weston-ivi-test.ini'.format(meson.current_build_dir()) ] + args_t += [ '--shell=ivi-shell.so' ] + else + args_t += [ '--no-config' ] + args_t += [ '--shell=desktop-shell.so' ] + endif + + exe_t = executable( + 'test-@0@'.format(t.get(0)), + srcs_t, + c_args: [ '-DUNIT_TEST' ], + include_directories: + include_directories('..', '../shared'), + dependencies: deps_t, + install: false, + ) + + env_t = [ + 'WESTON_TEST_CLIENT_PATH=@0@'.format(exe_t.full_path()) + ] + env_t += env_test_weston + + test(t.get(0), exe_weston, env: env_t, args: args_t) +endforeach + +foreach t : tests_weston_plugin + srcs_t = [] + + if t.length() > 1 + srcs_t += t.get(1) + else + srcs_t += '@0@-test.c'.format(t.get(0)) + endif + + deps_t = [ + dep_libweston, + ] + if t.length() > 2 + deps_t += t.get(2) + endif + + exe_t = shared_library( + 'test-@0@'.format(t.get(0)), + srcs_t, + include_directories: include_directories('..', '../shared'), + dependencies: deps_t, + name_prefix: '', + install: false, + ) + + args_t = [ + '--backend=headless-backend.so', + '--socket=test-@0@'.format(t.get(0)), + ] + + # FIXME: Get this from the array ... ? + if t.get(0).startswith('ivi-') + args_t += [ '--config=@0@/../ivi-shell/weston-ivi-test.ini'.format(meson.current_build_dir()) ] + args_t += [ '--modules=@1@,@0@'.format(exe_plugin_test.full_path(),exe_t.full_path()) ] + args_t += [ '--shell=ivi-shell.so' ] + else + args_t += [ '--no-config' ] + args_t += [ '--shell=desktop-shell.so' ] + args_t += [ '--modules=@0@'.format(exe_t.full_path()) ] + endif + + # surface-screenshot is a manual test + if t[0] != 'surface-screenshot' + test(t.get(0), exe_weston, env: env_test_weston, args: args_t) + endif +endforeach diff --git a/wcap/meson.build b/wcap/meson.build new file mode 100644 index 000000000..9a47cdb52 --- /dev/null +++ b/wcap/meson.build @@ -0,0 +1,21 @@ +if not get_option('wcap-decode') + subdir_done() +endif + +srcs_wcap = [ + 'main.c', + 'wcap-decode.c', +] + +deps_wcap = [ + dep_libm, + dependency('cairo'), +] + +executable( + 'wcap-decode', + srcs_wcap, + include_directories: include_directories('..'), + dependencies: deps_wcap, + install: true +) diff --git a/xwayland/meson.build b/xwayland/meson.build new file mode 100644 index 000000000..6a4064529 --- /dev/null +++ b/xwayland/meson.build @@ -0,0 +1,31 @@ +if get_option('xwayland') + srcs_xwayland = [ + 'launcher.c', + 'window-manager.c', + 'selection.c', + 'dnd.c', + 'hash.c', + ] + deps_xwayland = [ + dependency('xcb'), + dependency('xcb-composite'), + dependency('xcb-shape'), + dependency('xcb-xfixes'), + dependency('xcursor'), + dependency('cairo-xcb'), + dep_libweston, + ] + plugin_xwayland = shared_library( + 'xwayland', + srcs_xwayland, + link_with: lib_cairo_shared, + include_directories: include_directories('..', '../shared'), + dependencies: deps_xwayland, + name_prefix: '', + install: true, + install_dir: dir_module_libweston + ) + env_modmap += 'xwayland.so=@0@;'.format(plugin_xwayland.full_path()) + + install_headers('xwayland-api.h', subdir: dir_include_libweston) +endif From 577683f09bb4a5eb35d0032372083d0d8f64c445 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Sat, 24 Nov 2018 14:13:02 +0200 Subject: [PATCH 0675/1642] build: add meson to autotools dist tar-ball This should let people using the dist tarballs to build with meson. Signed-off-by: Pekka Paalanen --- Makefile.am | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Makefile.am b/Makefile.am index 321c24135..359f7e992 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1712,3 +1712,23 @@ protocol/%-server-protocol.h : $(top_srcdir)/protocol/%.xml protocol/%-client-protocol.h : $(top_srcdir)/protocol/%.xml $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(wayland_scanner) client-header < $< > $@ + +EXTRA_DIST += \ + clients/meson.build \ + compositor/meson.build \ + data/meson.build \ + desktop-shell/meson.build \ + fullscreen-shell/meson.build \ + ivi-shell/meson.build \ + libweston-desktop/meson.build \ + libweston/git-version.h.meson \ + libweston/meson.build \ + man/meson.build \ + meson.build \ + meson_options.txt \ + protocol/meson.build \ + remoting/meson.build \ + shared/meson.build \ + tests/meson.build \ + wcap/meson.build \ + xwayland/meson.build From 54705d752b899c21d8f293e4f5a306c99e1d7155 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Sat, 24 Nov 2018 13:51:04 +0200 Subject: [PATCH 0676/1642] CI: build remoting-plugin Extends CI build coverage to catch more issues. Signed-off-by: Pekka Paalanen --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 80b99278e..0b837372f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,7 @@ before_script: - echo 'exit 101' >> /usr/sbin/policy-rc.d - chmod +x /usr/sbin/policy-rc.d - apt-get update - - apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git + - apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev - mkdir -p /tmp/.X11-unix - chmod 777 /tmp/.X11-unix @@ -34,7 +34,7 @@ build-native: - export BUILDDIR="$(pwd)/build-$BUILD_ID" - mkdir "$BUILDDIR" "$PREFIX" - cd "$BUILDDIR" - - ../autogen.sh --prefix="$PREFIX" --disable-setuid-install --enable-xwayland --enable-x11-compositor --enable-drm-compositor --enable-wayland-compositor --enable-headless-compositor --enable-fbdev-compositor --disable-rdp-compositor --enable-screen-sharing --enable-vaapi-recorder --enable-simple-clients --enable-simple-egl-clients --enable-simple-dmabuf-drm-client --enable-simple-dmabuf-v4l-client --enable-clients --enable-resize-optimization --enable-weston-launch --enable-fullscreen-shell --enable-colord --enable-dbus --enable-systemd-login --enable-junit-xml --enable-ivi-shell --enable-wcap-tools --disable-libunwind --enable-demo-clients-install --enable-lcms --with-cairo=image + - ../autogen.sh --prefix="$PREFIX" --disable-setuid-install --enable-xwayland --enable-x11-compositor --enable-drm-compositor --enable-wayland-compositor --enable-headless-compositor --enable-fbdev-compositor --disable-rdp-compositor --enable-screen-sharing --enable-vaapi-recorder --enable-simple-clients --enable-simple-egl-clients --enable-simple-dmabuf-drm-client --enable-simple-dmabuf-v4l-client --enable-clients --enable-resize-optimization --enable-weston-launch --enable-fullscreen-shell --enable-colord --enable-dbus --enable-systemd-login --enable-junit-xml --enable-ivi-shell --enable-wcap-tools --disable-libunwind --enable-demo-clients-install --enable-lcms --with-cairo=image --enable-remoting - make all - make check - make install From dfac945c23501889d90cd0467e91810410aa41f9 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Sat, 24 Nov 2018 13:52:49 +0200 Subject: [PATCH 0677/1642] CI: build rdp-backend Extends CI build coverage to catch more issues. freerdp2-dev needs stretch-backports. Signed-off-by: Pekka Paalanen --- .gitlab-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0b837372f..439c1ffd8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,8 +9,9 @@ before_script: - echo '#!/bin/sh' > /usr/sbin/policy-rc.d - echo 'exit 101' >> /usr/sbin/policy-rc.d - chmod +x /usr/sbin/policy-rc.d + - echo 'deb http://deb.debian.org/debian stretch-backports main' >> /etc/apt/sources.list - apt-get update - - apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev + - apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev freerdp2-dev - mkdir -p /tmp/.X11-unix - chmod 777 /tmp/.X11-unix @@ -34,7 +35,7 @@ build-native: - export BUILDDIR="$(pwd)/build-$BUILD_ID" - mkdir "$BUILDDIR" "$PREFIX" - cd "$BUILDDIR" - - ../autogen.sh --prefix="$PREFIX" --disable-setuid-install --enable-xwayland --enable-x11-compositor --enable-drm-compositor --enable-wayland-compositor --enable-headless-compositor --enable-fbdev-compositor --disable-rdp-compositor --enable-screen-sharing --enable-vaapi-recorder --enable-simple-clients --enable-simple-egl-clients --enable-simple-dmabuf-drm-client --enable-simple-dmabuf-v4l-client --enable-clients --enable-resize-optimization --enable-weston-launch --enable-fullscreen-shell --enable-colord --enable-dbus --enable-systemd-login --enable-junit-xml --enable-ivi-shell --enable-wcap-tools --disable-libunwind --enable-demo-clients-install --enable-lcms --with-cairo=image --enable-remoting + - ../autogen.sh --prefix="$PREFIX" --disable-setuid-install --enable-xwayland --enable-x11-compositor --enable-drm-compositor --enable-wayland-compositor --enable-headless-compositor --enable-fbdev-compositor --enable-rdp-compositor --enable-screen-sharing --enable-vaapi-recorder --enable-simple-clients --enable-simple-egl-clients --enable-simple-dmabuf-drm-client --enable-simple-dmabuf-v4l-client --enable-clients --enable-resize-optimization --enable-weston-launch --enable-fullscreen-shell --enable-colord --enable-dbus --enable-systemd-login --enable-junit-xml --enable-ivi-shell --enable-wcap-tools --disable-libunwind --enable-demo-clients-install --enable-lcms --with-cairo=image --enable-remoting - make all - make check - make install From 0f9f86f4a41a6a7a82fc5c1099d778e5d697abdf Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Sun, 22 Jul 2018 15:38:54 +0100 Subject: [PATCH 0678/1642] CI: Add Meson build This uses pip to install Meson in order to get a sufficiently new version. Signed-off-by: Daniel Stone --- .gitlab-ci.yml | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 439c1ffd8..b23b8f286 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,11 +11,12 @@ before_script: - chmod +x /usr/sbin/policy-rc.d - echo 'deb http://deb.debian.org/debian stretch-backports main' >> /etc/apt/sources.list - apt-get update - - apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev freerdp2-dev + - apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev freerdp2-dev curl python3-pip python3-setuptools ninja-build + - pip3 install --user meson - mkdir -p /tmp/.X11-unix - chmod 777 /tmp/.X11-unix -build-native: +build-native-autotools: stage: build script: - git clone --depth=1 git://anongit.freedesktop.org/git/wayland/wayland-protocols @@ -48,3 +49,36 @@ build-native: - build-*/*.log - build-*/logs - prefix-* + +build-native-meson: + stage: build + script: + - git clone --depth=1 git://anongit.freedesktop.org/git/wayland/wayland-protocols + - export WAYLAND_PROTOCOLS_DIR="$(pwd)/prefix-wayland-protocols" + - export PKG_CONFIG_PATH="$WAYLAND_PROTOCOLS_DIR/share/pkgconfig:$PKG_CONFIG_PATH" + - export MAKEFLAGS="-j4" + - cd wayland-protocols + - git show -s HEAD + - mkdir build + - cd build + - ../autogen.sh --prefix="$WAYLAND_PROTOCOLS_DIR" + - make install + - cd ../../ + - export XDG_RUNTIME_DIR="$(mktemp -p $(pwd) -d xdg-runtime-XXXXXX)" + - export BUILD_ID="weston-$CI_JOB_NAME_$CI_COMMIT_SHA-$CI_JOB_ID" + - export PREFIX="$(pwd)/prefix-$BUILD_ID" + - export BUILDDIR="$(pwd)/build-$BUILD_ID" + - export PATH=~/.local/bin:$PATH + - mkdir "$BUILDDIR" "$PREFIX" + - cd "$BUILDDIR" + - meson --prefix="$PREFIX" -Dsimple-dmabuf-drm=intel .. + - ninja -k0 + - ninja install + - ninja test + - ninja clean + artifacts: + name: weston-$CI_COMMIT_SHA-$CI_JOB_ID + when: always + paths: + - build-meson/meson-logs + - prefix-* From bc315aa2881949d3961c03599e8c20e0273ec817 Mon Sep 17 00:00:00 2001 From: Eric Engestrom Date: Thu, 13 Dec 2018 17:05:31 +0000 Subject: [PATCH 0679/1642] meson: fix -Wno-foo argument testing gcc and clang ignore `-Wno-foo` arguments nowadays, so we need to test the positive variant instead. --- meson.build | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index a5dcb7662..3eaf2c0a0 100644 --- a/meson.build +++ b/meson.build @@ -66,17 +66,24 @@ cc = meson.get_compiler('c') global_args = [] global_args_maybe = [ - '-Wno-unused-parameter', - '-Wno-shift-negative-value', # required due to Pixman - '-Wno-missing-field-initializers', '-fvisibility=hidden', '-DIN_WESTON', ] +global_wnoargs_maybe = [ + 'unused-parameter', + 'shift-negative-value', # required due to Pixman + 'missing-field-initializers', +] foreach a : global_args_maybe if cc.has_argument(a) global_args += a endif endforeach +foreach a : global_wnoargs_maybe + if cc.has_argument('-W' + a) + global_args += '-Wno-' + a + endif +endforeach add_global_arguments(global_args, language: 'c') if cc.has_header_symbol('sys/sysmacros.h', 'major') From 7d070ca0bad5dd9607dd7a3f92b4ea4dfe3c3162 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 23 Nov 2018 14:02:07 +0200 Subject: [PATCH 0680/1642] Fix compiler warnings generated by older toolchains/compiler This fixes warnings like ``may be used uninitialized'' libweston/compositor-drm.c: In function 'drm_device_is_kms': libweston/compositor-drm.c:6374:12: warning: 'id' may be used uninitialized in this function [-Wmaybe-uninitialized] b->drm.id = id; Signed-off-by: Marius Vlad --- clients/terminal.c | 2 +- libweston/compositor-drm.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/terminal.c b/clients/terminal.c index f792badcd..ebc5bbe29 100644 --- a/clients/terminal.c +++ b/clients/terminal.c @@ -2610,7 +2610,7 @@ recompute_selection(struct terminal *terminal) int side_margin, top_margin; int start_x, end_x; int cw, ch; - union utf8_char *data; + union utf8_char *data = NULL; cw = terminal->average_width; ch = terminal->extents.height; diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index b1be49220..91031c8c9 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -6680,7 +6680,7 @@ drm_device_is_kms(struct drm_backend *b, struct udev_device *device) const char *filename = udev_device_get_devnode(device); const char *sysnum = udev_device_get_sysnum(device); drmModeRes *res; - int id, fd; + int id = -1, fd; if (!filename) return false; From 7a8a3a35471f6357e403456783c4514c4f62dfa3 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 23 Nov 2018 14:04:46 +0200 Subject: [PATCH 0681/1642] Fix compiler warnings: clobber variables This patchs fixes warnings generated by older toolchains: shared/image-loader.c: In function 'load_png': shared/image-loader.c:211:12: warning: variable 'data' might be clobbered by 'longjmp' or 'vfork' [-Wclobbered] png_byte *data = NULL; See https://linux.die.net/man/3/longjmp why is this needed. Signed-off-by: Marius Vlad --- shared/image-loader.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/image-loader.c b/shared/image-loader.c index 140968162..8686e02a3 100644 --- a/shared/image-loader.c +++ b/shared/image-loader.c @@ -208,8 +208,8 @@ load_png(FILE *fp) { png_struct *png; png_info *info; - png_byte *data = NULL; - png_byte **row_pointers = NULL; + png_byte *volatile data = NULL; + png_byte **volatile row_pointers = NULL; png_uint_32 width, height; int depth, color_type, interlace, stride; unsigned int i; From acec383be06b13594fb3dd4f0d479d8a975c0e5a Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 23 Nov 2018 14:07:33 +0200 Subject: [PATCH 0682/1642] Fix compiler warnings: invalid type format This patch fixes the following warnings: clients/weston-info.c: In function 'print_tablet_tool_info': clients/weston-info.c:569:3: warning: format '%lx' expects argument of type 'long unsigned int', but argument 2 has type 'uint64_t' [-Wformat=] printf("\t\t\thardware serial: %lx\n", info->hardware_serial); ^ clients/weston-info.c:572:3: warning: format '%lx' expects argument of type 'long unsigned int', but argument 2 has type 'uint64_t' [-Wformat=] printf("\t\t\thardware wacom: %lx\n", info->hardware_id_wacom); Signed-off-by: Marius Vlad --- clients/weston-info.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/weston-info.c b/clients/weston-info.c index 3cfeed6d1..8d7d79887 100644 --- a/clients/weston-info.c +++ b/clients/weston-info.c @@ -594,10 +594,10 @@ print_tablet_tool_info(const struct tablet_tool_info *info) { printf("\t\ttablet_tool: %s\n", tablet_tool_type_to_str(info->type)); if (info->hardware_serial) { - printf("\t\t\thardware serial: %lx\n", info->hardware_serial); + printf("\t\t\thardware serial: %" PRIx64 "\n", info->hardware_serial); } if (info->hardware_id_wacom) { - printf("\t\t\thardware wacom: %lx\n", info->hardware_id_wacom); + printf("\t\t\thardware wacom: %" PRIx64 "\n", info->hardware_id_wacom); } printf("\t\t\tcapabilities:"); From 0161802aa025db55cb31af0320468770edfa4eaa Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 23 Nov 2018 14:10:16 +0200 Subject: [PATCH 0683/1642] Fix compiler warning: unused variable when building with DEBUG clients/keyboard.c: In function 'dbg': clients/keyboard.c:276:6: warning: variable 'l' set but not used [-Wunused-but-set-variable] int l; Signed-off-by: Marius Vlad --- clients/keyboard.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clients/keyboard.c b/clients/keyboard.c index 9af3f419b..c9f6f28e7 100644 --- a/clients/keyboard.c +++ b/clients/keyboard.c @@ -273,11 +273,10 @@ static void __attribute__ ((format (printf, 1, 2))) dbg(const char *fmt, ...) { #ifdef DEBUG - int l; va_list argp; va_start(argp, fmt); - l = vfprintf(stderr, fmt, argp); + vfprintf(stderr, fmt, argp); va_end(argp); #endif } From 5d767416c1847fce96b0c1d9e23a9379746c9a0b Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 14 Dec 2018 11:56:10 +0200 Subject: [PATCH 0684/1642] libweston/compositor-drm: Print composition mode in weston-debug This fixes the situation when using only plane-state mode for compositing there's no obvious debug message stating that. This patch makes it slightly better/easier to dermine what mode the compositor is using currently. Signed-off-by: Marius Vlad --- libweston/compositor-drm.c | 45 ++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 91031c8c9..c097d6be8 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -409,6 +409,12 @@ struct drm_pending_state { struct wl_list output_list; }; +enum drm_output_propose_state_mode { + DRM_OUTPUT_PROPOSE_STATE_MIXED, /**< mix renderer & planes */ + DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY, /**< only assign to renderer & cursor */ + DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY, /**< no renderer use, only planes */ +}; + /* * Output state holds the dynamic state for one Weston output, i.e. a KMS CRTC, * plus >= 1 each of encoder/connector/plane. Since everything but the planes @@ -568,6 +574,12 @@ static const char *const aspect_ratio_as_string[] = { [WESTON_MODE_PIC_AR_256_135] = " 256:135", }; +static const char *const drm_output_propose_state_mode_as_string[] = { + [DRM_OUTPUT_PROPOSE_STATE_MIXED] = "mixed state", + [DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY] = "render-only state", + [DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY] = "plane-only state" +}; + static struct gl_renderer_interface *gl_renderer; static const char default_seat[] = "seat0"; @@ -1972,12 +1984,6 @@ drm_output_assign_state(struct drm_output_state *state, } } -enum drm_output_propose_state_mode { - DRM_OUTPUT_PROPOSE_STATE_MIXED, /**< mix renderer & planes */ - DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY, /**< only assign to renderer & cursor */ - DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY, /**< no renderer use, only planes */ -}; - static struct drm_plane_state * drm_output_prepare_scanout_view(struct drm_output_state *output_state, struct weston_view *ev, @@ -3736,7 +3742,6 @@ drm_output_propose_state(struct weston_output *output_base, scanout_state->fb->type == BUFFER_PIXMAN_DUMB); drm_plane_state_put_back(scanout_state); } - return state; err_region: @@ -3747,6 +3752,15 @@ drm_output_propose_state(struct weston_output *output_base, return NULL; } +static const char * +drm_propose_state_mode_to_string(enum drm_output_propose_state_mode mode) +{ + if (mode < 0 || mode >= ARRAY_LENGTH(drm_output_propose_state_mode_as_string)) + return " unknown compositing mode"; + + return drm_output_propose_state_mode_as_string[mode]; +} + static void drm_assign_planes(struct weston_output *output_base, void *repaint_data) { @@ -3757,18 +3771,21 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) struct drm_plane_state *plane_state; struct weston_view *ev; struct weston_plane *primary = &output_base->compositor->primary_plane; + enum drm_output_propose_state_mode mode = DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY; drm_debug(b, "\t[repaint] preparing state for output %s (%lu)\n", output_base->name, (unsigned long) output_base->id); if (!b->sprites_are_broken && !output->virtual) { - state = drm_output_propose_state(output_base, pending_state, - DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); + drm_debug(b, "\t[repaint] trying planes-only build state\n"); + state = drm_output_propose_state(output_base, pending_state, mode); if (!state) { drm_debug(b, "\t[repaint] could not build planes-only " "state, trying mixed\n"); - state = drm_output_propose_state(output_base, pending_state, - DRM_OUTPUT_PROPOSE_STATE_MIXED); + mode = DRM_OUTPUT_PROPOSE_STATE_MIXED; + state = drm_output_propose_state(output_base, + pending_state, + mode); } if (!state) { drm_debug(b, "\t[repaint] could not build mixed-mode " @@ -3778,11 +3795,15 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) drm_debug(b, "\t[state] no overlay plane support\n"); } - if (!state) + if (!state) { + mode = DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY; state = drm_output_propose_state(output_base, pending_state, DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY); + } assert(state); + drm_debug(b, "\t[repaint] Using %s composition\n", + drm_propose_state_mode_to_string(mode)); wl_list_for_each(ev, &output_base->compositor->view_list, link) { struct drm_plane *target_plane = NULL; From 94698d2a8341c10a607aff22fdf43fd838389f73 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Tue, 18 Dec 2018 10:27:31 +0200 Subject: [PATCH 0685/1642] libweston/compositor-drm: No need to test for invalid alpha for the view This is redundant and is already being checked drm_fb_get_from_view() Signed-off-by: Marius Vlad --- libweston/compositor-drm.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c097d6be8..c81ee4c76 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2008,9 +2008,6 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, extents->y2 != output->base.y + output->base.height) return NULL; - if (ev->alpha != 1.0f) - return NULL; - fb = drm_fb_get_from_view(output_state, ev); if (!fb) return NULL; From 748f09efe50d27d1d979cecc421121e8572713e3 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Tue, 18 Dec 2018 10:29:20 +0200 Subject: [PATCH 0686/1642] libweston/compositor-drm: Add missing debug message for scanout_view Print debug message when the fb coudn't be retrieved for the primary plane. Signed-off-by: Marius Vlad --- libweston/compositor-drm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c81ee4c76..a86880421 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2009,8 +2009,11 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, return NULL; fb = drm_fb_get_from_view(output_state, ev); - if (!fb) + if (!fb) { + drm_debug(b, "\t\t\t\t[scanout] not placing view %p on scanout: " + " couldn't get fb\n", ev); return NULL; + } /* Can't change formats with just a pageflip */ if (!b->atomic_modeset && fb->format->format != output->gbm_format) { From 3c7cfbbf6268c5e91ae54676a4b365a6953e5153 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Thu, 13 Dec 2018 15:24:08 +0200 Subject: [PATCH 0687/1642] clients/screenshot: Avoid using global variables to pass down data between functions This is just cosmetic and doesn't fixes anything. Signed-off-by: Marius Vlad --- clients/screenshot.c | 113 ++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 44 deletions(-) diff --git a/clients/screenshot.c b/clients/screenshot.c index 78a5d4242..b74482a2b 100644 --- a/clients/screenshot.c +++ b/clients/screenshot.c @@ -45,11 +45,6 @@ * the compositor and serves as a test bed for implementing client * side marshalling outside libwayland.so */ -static struct wl_shm *shm; -static struct weston_screenshooter *screenshooter; -static struct wl_list output_list; -int min_x, min_y, max_x, max_y; -int buffer_copy_done; struct screenshooter_output { struct wl_output *output; @@ -59,6 +54,22 @@ struct screenshooter_output { struct wl_list link; }; +struct buffer_size { + int width, height; + + int min_x, min_y; + int max_x, max_y; +}; + +struct screenshooter_data { + struct wl_shm *shm; + struct wl_list output_list; + + struct weston_screenshooter *screenshooter; + int buffer_copy_done; +}; + + static void display_handle_geometry(void *data, struct wl_output *wl_output, @@ -107,7 +118,8 @@ static const struct wl_output_listener output_listener = { static void screenshot_done(void *data, struct weston_screenshooter *screenshooter) { - buffer_copy_done = 1; + struct screenshooter_data *sh_data = data; + sh_data->buffer_copy_done = 1; } static const struct weston_screenshooter_listener screenshooter_listener = { @@ -119,19 +131,20 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { static struct screenshooter_output *output; + struct screenshooter_data *sh_data = data; if (strcmp(interface, "wl_output") == 0) { output = xmalloc(sizeof *output); output->output = wl_registry_bind(registry, name, &wl_output_interface, 1); - wl_list_insert(&output_list, &output->link); + wl_list_insert(&sh_data->output_list, &output->link); wl_output_add_listener(output->output, &output_listener, output); } else if (strcmp(interface, "wl_shm") == 0) { - shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + sh_data->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (strcmp(interface, "weston_screenshooter") == 0) { - screenshooter = wl_registry_bind(registry, name, - &weston_screenshooter_interface, - 1); + sh_data->screenshooter = wl_registry_bind(registry, name, + &weston_screenshooter_interface, + 1); } } @@ -147,7 +160,8 @@ static const struct wl_registry_listener registry_listener = { }; static struct wl_buffer * -create_shm_buffer(int width, int height, void **data_out) +screenshot_create_shm_buffer(int width, int height, void **data_out, + struct wl_shm *shm) { struct wl_shm_pool *pool; struct wl_buffer *buffer; @@ -183,7 +197,8 @@ create_shm_buffer(int width, int height, void **data_out) } static void -write_png(int width, int height) +screenshot_write_png(const struct buffer_size *buff_size, + struct wl_list *output_list) { int output_stride, buffer_stride, i; cairo_surface_t *surface; @@ -192,17 +207,17 @@ write_png(int width, int height) FILE *fp; char filepath[PATH_MAX]; - buffer_stride = width * 4; + buffer_stride = buff_size->width * 4; - data = xmalloc(buffer_stride * height); + data = xmalloc(buffer_stride * buff_size->height); if (!data) return; - wl_list_for_each_safe(output, next, &output_list, link) { + wl_list_for_each_safe(output, next, output_list, link) { output_stride = output->width * 4; s = output->data; - d = data + (output->offset_y - min_y) * buffer_stride + - (output->offset_x - min_x) * 4; + d = data + (output->offset_y - buff_size->min_y) * buffer_stride + + (output->offset_x - buff_size->min_x) * 4; for (i = 0; i < output->height; i++) { memcpy(d, s, output_stride); @@ -215,7 +230,9 @@ write_png(int width, int height) surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, - width, height, buffer_stride); + buff_size->width, + buff_size->height, + buffer_stride); fp = file_create_dated(getenv("XDG_PICTURES_DIR"), "wayland-screenshot-", ".png", filepath, sizeof(filepath)); @@ -228,30 +245,33 @@ write_png(int width, int height) } static int -set_buffer_size(int *width, int *height) +screenshot_set_buffer_size(struct buffer_size *buff_size, struct wl_list *output_list) { struct screenshooter_output *output; - min_x = min_y = INT_MAX; - max_x = max_y = INT_MIN; + buff_size->min_x = buff_size->min_y = INT_MAX; + buff_size->max_x = buff_size->max_y = INT_MIN; int position = 0; - wl_list_for_each_reverse(output, &output_list, link) { + wl_list_for_each_reverse(output, output_list, link) { output->offset_x = position; position += output->width; } - wl_list_for_each(output, &output_list, link) { - min_x = MIN(min_x, output->offset_x); - min_y = MIN(min_y, output->offset_y); - max_x = MAX(max_x, output->offset_x + output->width); - max_y = MAX(max_y, output->offset_y + output->height); + wl_list_for_each(output, output_list, link) { + buff_size->min_x = MIN(buff_size->min_x, output->offset_x); + buff_size->min_y = MIN(buff_size->min_y, output->offset_y); + buff_size->max_x = + MAX(buff_size->max_x, output->offset_x + output->width); + buff_size->max_y = + MAX(buff_size->max_y, output->offset_y + output->height); } - if (max_x <= min_x || max_y <= min_y) + if (buff_size->max_x <= buff_size->min_x || + buff_size->max_y <= buff_size->min_y) return -1; - *width = max_x - min_x; - *height = max_y - min_y; + buff_size->width = buff_size->max_x - buff_size->min_x; + buff_size->height = buff_size->max_y - buff_size->min_y; return 0; } @@ -261,7 +281,8 @@ int main(int argc, char *argv[]) struct wl_display *display; struct wl_registry *registry; struct screenshooter_output *output; - int width, height; + struct buffer_size buff_size = {}; + struct screenshooter_data sh_data = {}; if (getenv("WAYLAND_SOCKET") == NULL) { fprintf(stderr, "%s must be launched by weston.\n" @@ -276,35 +297,39 @@ int main(int argc, char *argv[]) return -1; } - wl_list_init(&output_list); + wl_list_init(&sh_data.output_list); registry = wl_display_get_registry(display); - wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_registry_add_listener(registry, ®istry_listener, &sh_data); wl_display_dispatch(display); wl_display_roundtrip(display); - if (screenshooter == NULL) { + if (sh_data.screenshooter == NULL) { fprintf(stderr, "display doesn't support screenshooter\n"); return -1; } - weston_screenshooter_add_listener(screenshooter, + weston_screenshooter_add_listener(sh_data.screenshooter, &screenshooter_listener, - screenshooter); + &sh_data); - if (set_buffer_size(&width, &height)) + if (screenshot_set_buffer_size(&buff_size, &sh_data.output_list)) return -1; - wl_list_for_each(output, &output_list, link) { - output->buffer = create_shm_buffer(output->width, output->height, &output->data); - weston_screenshooter_shoot(screenshooter, + wl_list_for_each(output, &sh_data.output_list, link) { + output->buffer = + screenshot_create_shm_buffer(output->width, + output->height, + &output->data, + sh_data.shm); + weston_screenshooter_shoot(sh_data.screenshooter, output->output, output->buffer); - buffer_copy_done = 0; - while (!buffer_copy_done) + sh_data.buffer_copy_done = 0; + while (!sh_data.buffer_copy_done) wl_display_roundtrip(display); } - write_png(width, height); + screenshot_write_png(&buff_size, &sh_data.output_list); return 0; } From f7843a50abb8d4bca3d97039d5929076b8867827 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Wed, 5 Dec 2018 14:40:31 +0200 Subject: [PATCH 0688/1642] clients/screenshot: Allow weston-screenshooter to be called directly As is stands now, libexec/weston-screenshooter can only be called from within weston server. This will allow weston-screenshooter to be called on the command line. The final scope here is to allow taking screenshots without the need of a keyboard. Signed-off-by: Marius Vlad --- clients/screenshot.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/clients/screenshot.c b/clients/screenshot.c index b74482a2b..96fb16828 100644 --- a/clients/screenshot.c +++ b/clients/screenshot.c @@ -284,13 +284,6 @@ int main(int argc, char *argv[]) struct buffer_size buff_size = {}; struct screenshooter_data sh_data = {}; - if (getenv("WAYLAND_SOCKET") == NULL) { - fprintf(stderr, "%s must be launched by weston.\n" - "Use the MOD+S shortcut to take a screenshot.\n", - program_invocation_short_name); - return -1; - } - display = wl_display_connect(NULL); if (display == NULL) { fprintf(stderr, "failed to create display: %m\n"); From d9bcc0b17106be0e8f4e1a0234b14f4d726fb532 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Thu, 13 Dec 2018 23:03:30 +0200 Subject: [PATCH 0689/1642] libweston/weston-debug: Add a easy way to determine if the debug protocol has been enabled This allows additional debug features to depend on the debug protocol being enabled Signed-off-by: Marius Vlad --- libweston/weston-debug.c | 10 ++++++++++ libweston/weston-debug.h | 3 +++ 2 files changed, 13 insertions(+) diff --git a/libweston/weston-debug.c b/libweston/weston-debug.c index 04895ad53..2776c8816 100644 --- a/libweston/weston-debug.c +++ b/libweston/weston-debug.c @@ -350,6 +350,16 @@ weston_compositor_enable_debug_protocol(struct weston_compositor *compositor) "information leak.\n"); } +/** Determine if the debug protocol has been enabled + * + * \param wc The libweston compositor to verify if debug protocol has been enabled + */ +WL_EXPORT bool +weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) +{ + return wc->weston_debug->global != NULL; +} + /** Register a new debug stream name, creating a debug scope * * \param compositor The libweston compositor where to add. diff --git a/libweston/weston-debug.h b/libweston/weston-debug.h index c76cec852..157752509 100644 --- a/libweston/weston-debug.h +++ b/libweston/weston-debug.h @@ -39,6 +39,9 @@ struct weston_compositor; void weston_compositor_enable_debug_protocol(struct weston_compositor *); +bool +weston_compositor_is_debug_protocol_enabled(struct weston_compositor *); + struct weston_debug_scope; struct weston_debug_stream; From 4e1d9bc72fe46840767077f27c1a0a576fd8e56c Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Thu, 13 Dec 2018 23:04:52 +0200 Subject: [PATCH 0690/1642] libweston: Allow taking screenshots when debug protocol is enabled Screenshots of the outputs can only be taken by having a keyboard plug-ed in, as to avoid potential malicious intent. On the other hand, this is problematic as there are cases where a keyboard cannot be used as a input device. A particular use-case is that for multiple devices it can be cumbersome to connect a keyboard such that using ssh can be much easier and can be further automated. This patch allows taking screenshots without the need of having a keyboard connected when debug protocol is enabled. Add also a few words about the fact that this is a serious issue and can lead to silently leaking the output contents. Signed-off-by: Marius Vlad --- compositor/weston-screenshooter.c | 12 ++++++++++-- man/weston.man | 7 +++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c index 981aff86a..dfe5cc857 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -32,6 +32,7 @@ #include "weston.h" #include "weston-screenshooter-server-protocol.h" #include "shared/helpers.h" +#include "weston-debug.h" struct screenshooter { struct weston_compositor *ec; @@ -88,13 +89,20 @@ bind_shooter(struct wl_client *client, { struct screenshooter *shooter = data; struct wl_resource *resource; + bool debug_enabled = + weston_compositor_is_debug_protocol_enabled(shooter->ec); resource = wl_resource_create(client, &weston_screenshooter_interface, 1, id); - if (client != shooter->client) { + if (!debug_enabled && !shooter->client) { wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, - "screenshooter failed: permission denied"); + "screenshooter failed: permission denied. "\ + "Debug protocol must be enabled"); + return; + } else if (!debug_enabled && client != shooter->client) { + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "screenshooter failed: permission denied."); return; } diff --git a/man/weston.man b/man/weston.man index c1aa64766..a60023dfc 100644 --- a/man/weston.man +++ b/man/weston.man @@ -141,8 +141,11 @@ which any client can use to receive debugging messages from the compositor. .B WARNING: This is risky for two reasons. First, a client may cause a denial-of-service blocking the compositor by providing an unsuitable file descriptor, and -second, the debug messages may expose sensitive information. This option -should not be used in production. +second, the debug messages may expose sensitive information. +Additionally this will expose weston-screenshooter interface allowing the user +to take screenshots of the outputs using weston-screenshooter application, +which can lead to silently leaking the output contents. This option should +not be used in production. .TP .BR \-\-version Print the program version. From 6bc37259e648157334845c3f94ba0c83421680a7 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Thu, 13 Dec 2018 16:31:14 +0200 Subject: [PATCH 0691/1642] ivi-shell/hmi-controller: Include config.h as to not break ivi-shell build on meson We're going to eventually pass bindir to weston-screenshooter and we will need config.h info. Signed-off-by: Marius Vlad --- ivi-shell/hmi-controller.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index a0e49ba0d..773f2dd57 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -49,6 +49,8 @@ * TODO: support fade-in when UI is ready */ +#include "config.h" + #include #include #include From 64fbd0f41f4eb175b772a2eddba8bfccb66e016a Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Sat, 15 Dec 2018 15:51:57 +0200 Subject: [PATCH 0692/1642] compositor: Add some handy wrappers for wet_get_binary_path() This allows to possibility to specify where to look for the executable but also simplifies the need of having to pass either BINDIR/LIBEXECDIR for retrieving full-path of the executable. Signed-off-by: Marius Vlad --- compositor/main.c | 18 +++++++++++++++--- compositor/text-backend.c | 2 +- compositor/weston-screenshooter.c | 2 +- compositor/weston.h | 5 ++++- desktop-shell/shell.c | 2 +- ivi-shell/hmi-controller.c | 2 +- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 383a88b9f..3400ffa41 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -903,8 +903,8 @@ wet_load_shell(struct weston_compositor *compositor, return 0; } -WL_EXPORT char * -wet_get_binary_path(const char *name) +static char * +wet_get_binary_path(const char *name, const char *dir) { char path[PATH_MAX]; size_t len; @@ -913,13 +913,25 @@ wet_get_binary_path(const char *name) if (len > 0) return strdup(path); - len = snprintf(path, sizeof path, "%s/%s", LIBEXECDIR, name); + len = snprintf(path, sizeof path, "%s/%s", dir, name); if (len >= sizeof path) return NULL; return strdup(path); } +WL_EXPORT char * +wet_get_libexec_path(const char *name) +{ + return wet_get_binary_path(name, LIBEXECDIR); +} + +WL_EXPORT char * +wet_get_bindir_path(const char *name) +{ + return wet_get_binary_path(name, BINDIR); +} + static int load_modules(struct weston_compositor *ec, const char *modules, int *argc, char *argv[], int32_t *xwayland) diff --git a/compositor/text-backend.c b/compositor/text-backend.c index 664c36f7a..030195845 100644 --- a/compositor/text-backend.c +++ b/compositor/text-backend.c @@ -1049,7 +1049,7 @@ text_backend_configuration(struct text_backend *text_backend) section = weston_config_get_section(config, "input-method", NULL, NULL); - client = wet_get_binary_path("weston-keyboard"); + client = wet_get_libexec_path("weston-keyboard"); weston_config_section_get_string(section, "path", &text_backend->input_method.path, client); diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c index dfe5cc857..c23a69440 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -127,7 +127,7 @@ screenshooter_binding(struct weston_keyboard *keyboard, char *screenshooter_exe; - screenshooter_exe = wet_get_binary_path("weston-screenshooter"); + screenshooter_exe = wet_get_libexec_path("weston-screenshooter"); if (!screenshooter_exe) { weston_log("Could not construct screenshooter path.\n"); return; diff --git a/compositor/weston.h b/compositor/weston.h index 8e1d2602c..5a2225d18 100644 --- a/compositor/weston.h +++ b/compositor/weston.h @@ -78,7 +78,10 @@ module_init(struct weston_compositor *compositor, int *argc, char *argv[]); char * -wet_get_binary_path(const char *name); +wet_get_libexec_path(const char *name); + +char * +wet_get_bindir_path(const char *name); int wet_load_xwayland(struct weston_compositor *comp); diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 9a447159a..a72f7391f 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -468,7 +468,7 @@ shell_configuration(struct desktop_shell *shell) section = weston_config_get_section(wet_get_config(shell->compositor), "shell", NULL, NULL); - client = wet_get_binary_path(WESTON_SHELL_CLIENT); + client = wet_get_libexec_path(WESTON_SHELL_CLIENT); weston_config_section_get_string(section, "client", &s, client); free(client); shell->client = s; diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index 773f2dd57..412844136 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -709,7 +709,7 @@ hmi_server_setting_create(struct weston_compositor *ec) "ivi-shell-user-interface", &ivi_ui_config, NULL); if (ivi_ui_config && ivi_ui_config[0] != '/') { - setting->ivi_homescreen = wet_get_binary_path(ivi_ui_config); + setting->ivi_homescreen = wet_get_libexec_path(ivi_ui_config); if (setting->ivi_homescreen) free(ivi_ui_config); else From d85fe29c1f4fbe33801ed1361a3a283f8d3aee9c Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Sat, 15 Dec 2018 15:57:19 +0200 Subject: [PATCH 0693/1642] compositor: Install weston-screenshooter in BINDIR Previously weston-screenshooter was installed in LIBEXECDIR, but given that now it can be invoked by the user whenever debug protocol is enabled, let's intall it into BINDIR. This way, it can be invoked without the need to modify PATH. Signed-off-by: Marius Vlad --- Makefile.am | 3 +-- clients/meson.build | 2 +- compositor/weston-screenshooter.c | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index 359f7e992..3c2fea509 100644 --- a/Makefile.am +++ b/Makefile.am @@ -548,11 +548,10 @@ spring_tool_SOURCES = \ if BUILD_CLIENTS -bin_PROGRAMS += weston-terminal weston-info weston-debug +bin_PROGRAMS += weston-terminal weston-info weston-debug weston-screenshooter libexec_PROGRAMS += \ weston-desktop-shell \ - weston-screenshooter \ weston-keyboard \ weston-simple-im diff --git a/clients/meson.build b/clients/meson.build index 42590e1e5..70b763716 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -331,7 +331,7 @@ if get_option('shell-desktop') weston_screenshooter_protocol_c, include_directories: include_directories('..'), dependencies: dep_toytoolkit, - install_dir: get_option('libexecdir'), + install_dir: get_option('bindir'), install: true ) env_modmap += 'weston-screenshooter=@0@;'.format(exe_shooter.full_path()) diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c index c23a69440..13dea3f18 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -127,7 +127,7 @@ screenshooter_binding(struct weston_keyboard *keyboard, char *screenshooter_exe; - screenshooter_exe = wet_get_libexec_path("weston-screenshooter"); + screenshooter_exe = wet_get_bindir_path("weston-screenshooter"); if (!screenshooter_exe) { weston_log("Could not construct screenshooter path.\n"); return; From a0d9cc64f66f5d2e7a70db4624bcdc52e67cedfc Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 23 Nov 2018 12:04:27 +0200 Subject: [PATCH 0694/1642] libweston/screnshooter: Fix weston screenshot event done if there's no client running The 'done' event sent back to client with the weston screenshot interface is not being sent if there is no damage on the plane. This patch (re-uses just like recording part) weston_output_damage() to achieve that. Otherwise the client will have to wait (and be blocked) until some damage on the plane is being done. Signed-off-by: Marius Vlad --- libweston/screenshooter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/screenshooter.c b/libweston/screenshooter.c index f4e3f4def..2bde4f1df 100644 --- a/libweston/screenshooter.c +++ b/libweston/screenshooter.c @@ -204,7 +204,7 @@ weston_screenshooter_shoot(struct weston_output *output, l->listener.notify = screenshooter_frame_notify; wl_signal_add(&output->frame_signal, &l->listener); output->disable_planes++; - weston_output_schedule_repaint(output); + weston_output_damage(output); return 0; } From b10066842680c996f45295b3b1130e7b08735a7f Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 25 Oct 2018 17:32:36 +0300 Subject: [PATCH 0695/1642] clients: Add simple-dmabuf-egl Add a client that uses EGL/GLESv2 to draw to dmabuf buffers, utilizing EGLImages and FBOs. The client uses GBM to create the dmabufs buffers. The simple-dmabuf-egl client is partly based on patch [1] that changes dmabuf clients to use GBM instead of libdrm code, but has been greatly simplified since in this case we don't require direct pixel access or non-RGBA formats. [1] https://patchwork.freedesktop.org/patch/239796/ Signed-off-by: Alexandros Frantzis --- Makefile.am | 14 + clients/meson.build | 19 + clients/simple-dmabuf-egl.c | 846 ++++++++++++++++++++++++++++++++++++ configure.ac | 15 + 4 files changed, 894 insertions(+) create mode 100644 clients/simple-dmabuf-egl.c diff --git a/Makefile.am b/Makefile.am index 3c2fea509..1493f6976 100644 --- a/Makefile.am +++ b/Makefile.am @@ -680,6 +680,20 @@ weston_simple_dmabuf_v4l_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_V4L_CLIENT_CFLAGS weston_simple_dmabuf_v4l_LDADD = $(SIMPLE_DMABUF_V4L_CLIENT_LIBS) libshared.la endif +if BUILD_SIMPLE_DMABUF_EGL_CLIENT +demo_clients += weston-simple-dmabuf-egl +weston_simple_dmabuf_egl_SOURCES = clients/simple-dmabuf-egl.c +nodist_weston_simple_dmabuf_egl_SOURCES = \ + protocol/xdg-shell-unstable-v6-protocol.c \ + protocol/xdg-shell-unstable-v6-client-protocol.h \ + protocol/fullscreen-shell-unstable-v1-protocol.c \ + protocol/fullscreen-shell-unstable-v1-client-protocol.h \ + protocol/linux-dmabuf-unstable-v1-protocol.c \ + protocol/linux-dmabuf-unstable-v1-client-protocol.h +weston_simple_dmabuf_egl_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_EGL_CLIENT_CFLAGS) +weston_simple_dmabuf_egl_LDADD = $(SIMPLE_DMABUF_EGL_CLIENT_LIBS) libshared.la +endif + noinst_LTLIBRARIES += libtoytoolkit.la libtoytoolkit_la_SOURCES = \ diff --git a/clients/meson.build b/clients/meson.build index 70b763716..062050aec 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -48,6 +48,25 @@ simple_clients = [ ], 'dep_objs': [ dep_wayland_client, dep_libshared ] }, + { + 'name': 'dmabuf-egl', + 'sources': [ + 'simple-dmabuf-egl.c', + linux_dmabuf_unstable_v1_client_protocol_h, + linux_dmabuf_unstable_v1_protocol_c, + xdg_shell_unstable_v6_client_protocol_h, + xdg_shell_unstable_v6_protocol_c, + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + ], + 'dep_objs': [ + dep_wayland_client, + dep_libdrm_headers, + dep_gbm, + dep_egl, + dep_glesv2 + ] + }, { 'name': 'dmabuf-v4l', 'sources': [ diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c new file mode 100644 index 000000000..b78d6b7b9 --- /dev/null +++ b/clients/simple-dmabuf-egl.c @@ -0,0 +1,846 @@ +/* + * Copyright © 2011 Benjamin Franzke + * Copyright © 2010 Intel Corporation + * Copyright © 2014,2018 Collabora Ltd. + * + * 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 +#include + +#include +#include "shared/platform.h" +#include "shared/zalloc.h" +#include "xdg-shell-unstable-v6-client-protocol.h" +#include "fullscreen-shell-unstable-v1-client-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" + +#include +#include +#include +#include + +#include "shared/weston-egl-ext.h" + +#ifndef DRM_FORMAT_MOD_INVALID +#define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1) +#endif + +/* Possible options that affect the displayed image */ +#define OPT_IMMEDIATE 1 /* create wl_buffer immediately */ + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct zxdg_shell_v6 *shell; + struct zwp_fullscreen_shell_v1 *fshell; + struct zwp_linux_dmabuf_v1 *dmabuf; + int xrgb8888_format_found; + int req_dmabuf_immediate; + struct { + EGLDisplay display; + EGLContext context; + PFNEGLCREATEIMAGEKHRPROC create_image; + PFNEGLDESTROYIMAGEKHRPROC destroy_image; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; + } egl; + struct { + int drm_fd; + struct gbm_device *device; + } gbm; +}; + +struct buffer { + struct display *display; + struct wl_buffer *buffer; + int busy; + + struct gbm_bo *bo; + + int dmabuf_fd; + + int width; + int height; + uint32_t stride; + int format; + + EGLImageKHR egl_image; + GLuint gl_texture; + GLuint gl_fbo; +}; + +#define NUM_BUFFERS 3 + +struct window { + struct display *display; + int width, height; + struct wl_surface *surface; + struct zxdg_surface_v6 *xdg_surface; + struct zxdg_toplevel_v6 *xdg_toplevel; + struct buffer buffers[NUM_BUFFERS]; + struct wl_callback *callback; + bool initialized; + bool wait_for_configure; +}; + +static sig_atomic_t running = 1; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time); + +static void +buffer_release(void *data, struct wl_buffer *buffer) +{ + struct buffer *mybuf = data; + + mybuf->busy = 0; +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static void +buffer_free(struct buffer *buf) +{ + if (buf->gl_fbo) + glDeleteFramebuffers(1, &buf->gl_fbo); + + if (buf->gl_texture) + glDeleteTextures(1, &buf->gl_texture); + + if (buf->egl_image) { + buf->display->egl.destroy_image(buf->display->egl.display, + buf->egl_image); + } + + if (buf->buffer) + wl_buffer_destroy(buf->buffer); + + if (buf->bo) + gbm_bo_destroy(buf->bo); + + if (buf->dmabuf_fd >= 0) + close(buf->dmabuf_fd); +} + +static void +create_succeeded(void *data, + struct zwp_linux_buffer_params_v1 *params, + struct wl_buffer *new_buffer) +{ + struct buffer *buffer = data; + + buffer->buffer = new_buffer; + wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); + + zwp_linux_buffer_params_v1_destroy(params); +} + +static void +create_failed(void *data, struct zwp_linux_buffer_params_v1 *params) +{ + struct buffer *buffer = data; + + buffer->buffer = NULL; + running = 0; + + zwp_linux_buffer_params_v1_destroy(params); + + fprintf(stderr, "Error: zwp_linux_buffer_params.create failed.\n"); +} + +static const struct zwp_linux_buffer_params_v1_listener params_listener = { + create_succeeded, + create_failed +}; + +static bool +create_fbo_for_buffer(struct display *display, struct buffer *buffer) +{ + EGLint attribs[] = { + EGL_WIDTH, buffer->width, + EGL_HEIGHT, buffer->height, + EGL_LINUX_DRM_FOURCC_EXT, buffer->format, + EGL_DMA_BUF_PLANE0_FD_EXT, buffer->dmabuf_fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, + EGL_DMA_BUF_PLANE0_PITCH_EXT, (int) buffer->stride, + EGL_NONE + }; + + buffer->egl_image = display->egl.create_image(display->egl.display, + EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, + NULL, attribs); + if (buffer->egl_image == EGL_NO_IMAGE) { + fprintf(stderr, "EGLImageKHR creation failed\n"); + return false; + } + + eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, + display->egl.context); + + glGenTextures(1, &buffer->gl_texture); + glBindTexture(GL_TEXTURE_2D, buffer->gl_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + display->egl.image_target_texture_2d(GL_TEXTURE_2D, buffer->egl_image); + + glGenFramebuffers(1, &buffer->gl_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, buffer->gl_texture, 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + fprintf(stderr, "FBO creation failed\n"); + return false; + } + + return true; +} + + +static int +create_dmabuf_buffer(struct display *display, struct buffer *buffer, + int width, int height, int format) +{ + static const uint32_t flags = 0; + static const uint64_t modifier = DRM_FORMAT_MOD_INVALID; + struct zwp_linux_buffer_params_v1 *params; + + buffer->display = display; + buffer->width = width; + buffer->height = height; + buffer->format = format; + + buffer->bo = gbm_bo_create(display->gbm.device, + buffer->width, buffer->height, + format, + GBM_BO_USE_RENDERING); + if (!buffer->bo) { + fprintf(stderr, "create_bo failed\n"); + goto error; + } + + buffer->stride = gbm_bo_get_stride(buffer->bo); + buffer->dmabuf_fd = gbm_bo_get_fd(buffer->bo); + + if (buffer->dmabuf_fd < 0) { + fprintf(stderr, "error: dmabuf_fd < 0\n"); + goto error; + } + + params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); + zwp_linux_buffer_params_v1_add(params, + buffer->dmabuf_fd, + 0, /* plane_idx */ + 0, /* offset */ + buffer->stride, + modifier >> 32, + modifier & 0xffffffff); + + zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buffer); + if (display->req_dmabuf_immediate) { + buffer->buffer = + zwp_linux_buffer_params_v1_create_immed(params, + buffer->width, + buffer->height, + format, + flags); + wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); + } + else { + zwp_linux_buffer_params_v1_create(params, + buffer->width, + buffer->height, + format, + flags); + } + + if (!create_fbo_for_buffer(display, buffer)) + goto error; + + return 0; + +error: + buffer_free(buffer); + return -1; +} + +static void +xdg_surface_handle_configure(void *data, struct zxdg_surface_v6 *surface, + uint32_t serial) +{ + struct window *window = data; + + zxdg_surface_v6_ack_configure(surface, serial); + + if (window->initialized && window->wait_for_configure) + redraw(window, NULL, 0); + window->wait_for_configure = false; +} + +static const struct zxdg_surface_v6_listener xdg_surface_listener = { + xdg_surface_handle_configure, +}; + +static void +xdg_toplevel_handle_configure(void *data, struct zxdg_toplevel_v6 *toplevel, + int32_t width, int32_t height, + struct wl_array *states) +{ +} + +static void +xdg_toplevel_handle_close(void *data, struct zxdg_toplevel_v6 *xdg_toplevel) +{ + running = 0; +} + +static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { + xdg_toplevel_handle_configure, + xdg_toplevel_handle_close, +}; + +static struct window * +create_window(struct display *display, int width, int height) +{ + struct window *window; + int i; + int ret; + + window = zalloc(sizeof *window); + if (!window) + return NULL; + + window->callback = NULL; + window->display = display; + window->width = width; + window->height = height; + window->surface = wl_compositor_create_surface(display->compositor); + + if (display->shell) { + window->xdg_surface = + zxdg_shell_v6_get_xdg_surface(display->shell, + window->surface); + + assert(window->xdg_surface); + + zxdg_surface_v6_add_listener(window->xdg_surface, + &xdg_surface_listener, window); + + window->xdg_toplevel = + zxdg_surface_v6_get_toplevel(window->xdg_surface); + + assert(window->xdg_toplevel); + + zxdg_toplevel_v6_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); + + zxdg_toplevel_v6_set_title(window->xdg_toplevel, "simple-dmabuf-egl"); + + window->wait_for_configure = true; + wl_surface_commit(window->surface); + } else if (display->fshell) { + zwp_fullscreen_shell_v1_present_surface(display->fshell, + window->surface, + ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, + NULL); + } else { + assert(0); + } + + for (i = 0; i < NUM_BUFFERS; ++i) { + ret = create_dmabuf_buffer(display, &window->buffers[i], + width, height, DRM_FORMAT_XRGB8888); + + if (ret < 0) + return NULL; + } + + return window; +} + +static void +destroy_window(struct window *window) +{ + int i; + + if (window->callback) + wl_callback_destroy(window->callback); + + for (i = 0; i < NUM_BUFFERS; i++) { + if (window->buffers[i].buffer) + buffer_free(&window->buffers[i]); + } + + if (window->xdg_toplevel) + zxdg_toplevel_v6_destroy(window->xdg_toplevel); + if (window->xdg_surface) + zxdg_surface_v6_destroy(window->xdg_surface); + wl_surface_destroy(window->surface); + free(window); +} + +static struct buffer * +window_next_buffer(struct window *window) +{ + int i; + + for (i = 0; i < NUM_BUFFERS; i++) + if (!window->buffers[i].busy) + return &window->buffers[i]; + + return NULL; +} + +static const struct wl_callback_listener frame_listener; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time) +{ + /* With a 60Hz redraw rate this completes a cycle in 3 seconds */ + static const int MAX_STEP = 180; + static int step = 0; + static int step_dir = 1; + struct window *window = data; + struct buffer *buffer; + + buffer = window_next_buffer(window); + if (!buffer) { + fprintf(stderr, + !callback ? "Failed to create the first buffer.\n" : + "All buffers busy at redraw(). Server bug?\n"); + abort(); + } + + /* Direct all GL draws to the buffer through the FBO */ + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + + /* Cycle between 0 and MAX_STEP */ + step += step_dir; + if (step == 0 || step == MAX_STEP) + step_dir = -step_dir; + + glClearColor(0.0, + (float) step / MAX_STEP, + 1.0 - (float) step / MAX_STEP, + 1.0); + glClear(GL_COLOR_BUFFER_BIT); + glFinish(); + + wl_surface_attach(window->surface, buffer->buffer, 0, 0); + wl_surface_damage(window->surface, 0, 0, window->width, window->height); + + if (callback) + wl_callback_destroy(callback); + + window->callback = wl_surface_frame(window->surface); + wl_callback_add_listener(window->callback, &frame_listener, window); + wl_surface_commit(window->surface); + buffer->busy = 1; +} + +static const struct wl_callback_listener frame_listener = { + redraw +}; + +static void +dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, + uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) +{ + struct display *d = data; + + switch (format) { + case DRM_FORMAT_XRGB8888: + d->xrgb8888_format_found = 1; + break; + default: + break; + } +} + +static void +dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) +{ + /* XXX: deprecated */ +} + +static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { + dmabuf_format, + dmabuf_modifiers +}; + +static void +xdg_shell_ping(void *data, struct zxdg_shell_v6 *shell, uint32_t serial) +{ + zxdg_shell_v6_pong(shell, serial); +} + +static const struct zxdg_shell_v6_listener xdg_shell_listener = { + xdg_shell_ping, +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, uint32_t version) +{ + struct display *d = data; + + if (strcmp(interface, "wl_compositor") == 0) { + d->compositor = + wl_registry_bind(registry, + id, &wl_compositor_interface, 1); + } else if (strcmp(interface, "zxdg_shell_v6") == 0) { + d->shell = wl_registry_bind(registry, + id, &zxdg_shell_v6_interface, 1); + zxdg_shell_v6_add_listener(d->shell, &xdg_shell_listener, d); + } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { + d->fshell = wl_registry_bind(registry, + id, &zwp_fullscreen_shell_v1_interface, 1); + } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { + if (version < 3) + return; + d->dmabuf = wl_registry_bind(registry, + id, &zwp_linux_dmabuf_v1_interface, 3); + zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static void +destroy_display(struct display *display) +{ + if (display->gbm.device) + gbm_device_destroy(display->gbm.device); + + if (display->gbm.drm_fd >= 0) + close(display->gbm.drm_fd); + + if (display->egl.context != EGL_NO_CONTEXT) + eglDestroyContext(display->egl.display, display->egl.context); + + if (display->egl.display != EGL_NO_DISPLAY) + eglTerminate(display->egl.display); + + if (display->dmabuf) + zwp_linux_dmabuf_v1_destroy(display->dmabuf); + + if (display->shell) + zxdg_shell_v6_destroy(display->shell); + + if (display->fshell) + zwp_fullscreen_shell_v1_release(display->fshell); + + if (display->compositor) + wl_compositor_destroy(display->compositor); + + if (display->registry) + wl_registry_destroy(display->registry); + + if (display->display) { + wl_display_flush(display->display); + wl_display_disconnect(display->display); + } + + free(display); +} + +static bool +display_set_up_egl(struct display *display) +{ + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + EGLint major, minor; + const char *egl_extensions = NULL; + const char *gl_extensions = NULL; + + display->egl.display = + weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, + display->display, NULL); + if (display->egl.display == EGL_NO_DISPLAY) { + fprintf(stderr, "Failed to create EGLDisplay\n"); + goto error; + } + + if (eglInitialize(display->egl.display, &major, &minor) == EGL_FALSE) { + fprintf(stderr, "Failed to initialize EGLDisplay\n"); + goto error; + } + + if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { + fprintf(stderr, "Failed to bind OpenGL ES API\n"); + goto error; + } + + egl_extensions = eglQueryString(display->egl.display, EGL_EXTENSIONS); + assert(egl_extensions != NULL); + + if (!weston_check_egl_extension(egl_extensions, + "EGL_EXT_image_dma_buf_import")) { + fprintf(stderr, "EGL_EXT_image_dma_buf_import not supported\n"); + goto error; + } + + if (!weston_check_egl_extension(egl_extensions, + "EGL_KHR_surfaceless_context")) { + fprintf(stderr, "EGL_KHR_surfaceless_context not supported\n"); + goto error; + } + + if (!weston_check_egl_extension(egl_extensions, + "EGL_KHR_no_config_context")) { + fprintf(stderr, "EGL_KHR_no_config_context not supported\n"); + goto error; + } + + display->egl.context = eglCreateContext(display->egl.display, + EGL_NO_CONFIG_KHR, + EGL_NO_CONTEXT, + context_attribs); + if (display->egl.context == EGL_NO_CONTEXT) { + fprintf(stderr, "Failed to create EGLContext\n"); + goto error; + } + + eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, + display->egl.context); + + gl_extensions = (const char *) glGetString(GL_EXTENSIONS); + assert(gl_extensions != NULL); + + if (!weston_check_egl_extension(gl_extensions, + "GL_OES_EGL_image")) { + fprintf(stderr, "GL_OES_EGL_image not suported\n"); + goto error; + } + + display->egl.create_image = + (void *) eglGetProcAddress("eglCreateImageKHR"); + assert(display->egl.create_image); + + display->egl.destroy_image = + (void *) eglGetProcAddress("eglDestroyImageKHR"); + assert(display->egl.destroy_image); + + display->egl.image_target_texture_2d = + (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); + assert(display->egl.image_target_texture_2d); + + return true; + +error: + return false; +} + +static bool +display_set_up_gbm(struct display *display, char const* drm_render_node) +{ + display->gbm.drm_fd = open(drm_render_node, O_RDWR); + if (display->gbm.drm_fd < 0) { + fprintf(stderr, "Failed to open drm render node %s\n", + drm_render_node); + return false; + } + + display->gbm.device = gbm_create_device(display->gbm.drm_fd); + if (display->gbm.device == NULL) { + fprintf(stderr, "Failed to create gbm device\n"); + return false; + } + + return true; +} + +static struct display * +create_display(char const *drm_render_node, int opts) +{ + struct display *display = NULL; + + display = zalloc(sizeof *display); + if (display == NULL) { + fprintf(stderr, "out of memory\n"); + goto error; + } + + display->gbm.drm_fd = -1; + + display->display = wl_display_connect(NULL); + assert(display->display); + + display->req_dmabuf_immediate = opts & OPT_IMMEDIATE; + + display->registry = wl_display_get_registry(display->display); + wl_registry_add_listener(display->registry, + ®istry_listener, display); + wl_display_roundtrip(display->display); + if (display->dmabuf == NULL) { + fprintf(stderr, "No zwp_linux_dmabuf global\n"); + goto error; + } + + wl_display_roundtrip(display->display); + + if (!display->xrgb8888_format_found) { + fprintf(stderr, "format XRGB8888 is not available\n"); + goto error; + } + + if (!display_set_up_egl(display)) + goto error; + + if (!display_set_up_gbm(display, drm_render_node)) + goto error; + + return display; + +error: + if (display != NULL) + destroy_display(display); + return NULL; +} + +static void +signal_int(int signum) +{ + running = 0; +} + +static void +print_usage_and_exit(void) +{ + printf("usage flags:\n" + "\t'-i,--import-immediate=<>'" + "\n\t\t0 to import dmabuf via roundtrip, " + "\n\t\t1 to enable import without roundtrip\n" + "\t'-d,--drm-render-node=<>'" + "\n\t\tthe full path to the drm render node to use\n"); + exit(0); +} + +static int +is_true(const char* c) +{ + if (!strcmp(c, "1")) + return 1; + else if (!strcmp(c, "0")) + return 0; + else + print_usage_and_exit(); + + return 0; +} + +int +main(int argc, char **argv) +{ + struct sigaction sigint; + struct display *display; + struct window *window; + int opts = 0; + char const *drm_render_node = "/dev/dri/renderD128"; + int c, option_index, ret = 0; + + static struct option long_options[] = { + {"import-immediate", required_argument, 0, 'i' }, + {"drm-render-node", required_argument, 0, 'd' }, + {"help", no_argument , 0, 'h' }, + {0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, "hi:d:", + long_options, &option_index)) != -1) { + switch (c) { + case 'i': + if (is_true(optarg)) + opts |= OPT_IMMEDIATE; + break; + case 'd': + drm_render_node = optarg; + break; + default: + print_usage_and_exit(); + } + } + + display = create_display(drm_render_node, opts); + if (!display) + return 1; + window = create_window(display, 256, 256); + if (!window) + return 1; + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sigint, NULL); + + /* Here we retrieve the linux-dmabuf objects if executed without immed, + * or error */ + wl_display_roundtrip(display->display); + + if (!running) + return 1; + + window->initialized = true; + + if (!window->wait_for_configure) + redraw(window, NULL, 0); + + while (running && ret != -1) + ret = wl_display_dispatch(display->display); + + fprintf(stderr, "simple-dmabuf-egl exiting\n"); + destroy_window(window); + destroy_display(display); + + return 0; +} diff --git a/configure.ac b/configure.ac index 132a3e937..8ebe73ee2 100644 --- a/configure.ac +++ b/configure.ac @@ -451,6 +451,21 @@ if ! test "x$enable_simple_dmabuf_v4l_client" = "xno"; then fi AM_CONDITIONAL(BUILD_SIMPLE_DMABUF_V4L_CLIENT, test "x$enable_simple_dmabuf_v4l_client" = "xyes") +AC_ARG_ENABLE(simple-dmabuf-egl-client, + AS_HELP_STRING([--disable-simple-dmabuf-egl-client], + [do not build the simple dmabuf egl client]),, + enable_simple_dmabuf_egl_client="auto") +if ! test "x$enable_simple_dmabuf_egl_client" = "xno"; then + PKG_CHECK_MODULES(SIMPLE_DMABUF_EGL_CLIENT, [wayland-client libdrm gbm egl glesv2], + [have_simple_dmabuf_egl_client=yes], [have_simple_dmabuf_egl_client=no]) + + if test "x$have_simple_dmabuf_egl_client" = "xno" -a "x$enable_simple_dmabuf_egl_client" = "xyes"; then + AC_MSG_ERROR([EGL dmabuf client explicitly enabled, but libdrm/egl/glev2 couldn't be found]) + fi + enable_simple_dmabuf_egl_client="$have_simple_dmabuf_egl_client" +fi +AM_CONDITIONAL(BUILD_SIMPLE_DMABUF_EGL_CLIENT, test "x$enable_simple_dmabuf_egl_client" = "xyes") + AC_ARG_ENABLE(clients, [ --enable-clients],, enable_clients=yes) AM_CONDITIONAL(BUILD_CLIENTS, test x$enable_clients = xyes) have_cairo_egl=no From f0ba93c1945be5dda160be1bc81c41255282a101 Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Wed, 28 Nov 2018 08:45:46 +0100 Subject: [PATCH 0696/1642] compositor-fbdev: add support for ABGR Make fbdev work with some Android downstream kernels, like the asus-grouper (Google Nexus 7 2012). Signed-off-by: Oliver Smith --- libweston/compositor-fbdev.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index a71b7bdcf..44d150777 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -234,8 +234,8 @@ calculate_pixman_format(struct fb_var_screeninfo *vinfo, vinfo->blue.msb_right != 0) return 0; - /* Work out the format type from the offsets. We only support RGBA and - * ARGB at the moment. */ + /* Work out the format type from the offsets. We only support RGBA, ARGB + * and ABGR at the moment. */ type = PIXMAN_TYPE_OTHER; if ((vinfo->transp.offset >= vinfo->red.offset || @@ -247,6 +247,10 @@ calculate_pixman_format(struct fb_var_screeninfo *vinfo, vinfo->green.offset >= vinfo->blue.offset && vinfo->blue.offset >= vinfo->transp.offset) type = PIXMAN_TYPE_RGBA; + else if (vinfo->transp.offset >= vinfo->blue.offset && + vinfo->blue.offset >= vinfo->green.offset && + vinfo->green.offset >= vinfo->red.offset) + type = PIXMAN_TYPE_ABGR; if (type == PIXMAN_TYPE_OTHER) return 0; From 3eaa57a73d5a5054cddc2e66c4e34876fc1219b8 Mon Sep 17 00:00:00 2001 From: Greg V Date: Thu, 13 Dec 2018 23:13:25 +0300 Subject: [PATCH 0697/1642] build: use pkg-config to find libjpeg in meson Signed-off-by: Greg V --- shared/meson.build | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shared/meson.build b/shared/meson.build index 979debb70..f24b73b6a 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -43,7 +43,11 @@ if dep_pango.found() and dep_pangocairo.found() and dep_glib.found() endif if get_option('image-jpeg') - deps_cairo_shared += cc.find_library('jpeg') + dep_libjpeg = dependency('libjpeg', required: false) + if not dep_libjpeg.found() + dep_libjpeg = cc.find_library('jpeg') + endif + deps_cairo_shared += dep_libjpeg config_h.set('HAVE_JPEG', '1') endif From 8a8558dd59d5a511be76e94fcc94dad315831f64 Mon Sep 17 00:00:00 2001 From: Greg V Date: Thu, 13 Dec 2018 23:17:48 +0300 Subject: [PATCH 0698/1642] build: use '-Wl,' wrapper for the -export-dynamic linker flag Meson links with the C compiler, not the raw linker. With clang+LLD, the bare flag would be ignored. Signed-off-by: Greg V --- compositor/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compositor/meson.build b/compositor/meson.build index 0cacae521..c2b6432d4 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -27,7 +27,7 @@ exe_weston = executable( 'weston', srcs_weston, include_directories: include_directories('..', '../shared'), - link_args: [ '-export-dynamic' ], + link_args: [ '-Wl,-export-dynamic' ], dependencies: deps_weston, install: true ) From 0f0409835d49f9fa9fb50808de0e26eb2777d554 Mon Sep 17 00:00:00 2001 From: Greg V Date: Thu, 13 Dec 2018 23:20:16 +0300 Subject: [PATCH 0699/1642] build: add missing wayland-client dep in meson Tests library requires Wayland headers to build. Signed-off-by: Greg V --- tests/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/meson.build b/tests/meson.build index 2d6819099..f1bfe74a3 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -33,6 +33,7 @@ lib_test_client = static_library( dep_test_client = declare_dependency( link_with: lib_test_client, dependencies: [ + dep_wayland_client, dep_test_runner, dep_pixman, ] From a23ce2950643ffdd59b4b81ec9c957d879a3663a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 20 Dec 2018 11:53:21 +0200 Subject: [PATCH 0700/1642] build: replace IN_WESTON with UNIT_TEST Remove IN_WESTON in favour of the already defined UNIT_TEST which is used to modify a compilation unit to expose more functions for unit tests to prod at. Originally IN_WESTON meant that compilation unit was being compiled for use in the Weston compositor, but it probably never really did anything more than change what WL_EXPORT means in matrix.c. This patch not only simplifies the logic, but it fixes the Meson build of test-matrix: IN_WESTON was defined there even when matrix.c was being built outside of Weston, which caused it to depend on libwayland headers, which were not included in the Meson build of test-matrix. Test-matrix has no reason to depend in libwayland in any way, so this patch fixes that. Reported-by: Greg V Signed-off-by: Pekka Paalanen --- Makefile.am | 13 +++++-------- meson.build | 1 - shared/matrix.c | 6 +++--- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Makefile.am b/Makefile.am index 1493f6976..24e754e36 100644 --- a/Makefile.am +++ b/Makefile.am @@ -57,7 +57,7 @@ CLEANFILES = weston.ini \ install-libweston_moduleLTLIBRARIES install-moduleLTLIBRARIES: install-libLTLIBRARIES lib_LTLIBRARIES = libweston-@LIBWESTON_MAJOR@.la -libweston_@LIBWESTON_MAJOR@_la_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON +libweston_@LIBWESTON_MAJOR@_la_CPPFLAGS = $(AM_CPPFLAGS) libweston_@LIBWESTON_MAJOR@_la_CFLAGS = $(AM_CFLAGS) \ $(COMPOSITOR_CFLAGS) $(EGL_CFLAGS) $(LIBDRM_CFLAGS) libweston_@LIBWESTON_MAJOR@_la_LIBADD = $(COMPOSITOR_LIBS) \ @@ -111,7 +111,7 @@ dist_libweston_@LIBWESTON_MAJOR@_data_DATA = \ protocol/weston-debug.xml lib_LTLIBRARIES += libweston-desktop-@LIBWESTON_MAJOR@.la -libweston_desktop_@LIBWESTON_MAJOR@_la_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON +libweston_desktop_@LIBWESTON_MAJOR@_la_CPPFLAGS = $(AM_CPPFLAGS) libweston_desktop_@LIBWESTON_MAJOR@_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) libweston_desktop_@LIBWESTON_MAJOR@_la_LIBADD = \ libweston-@LIBWESTON_MAJOR@.la \ @@ -184,8 +184,7 @@ BUILT_SOURCES += $(nodist_libweston_@LIBWESTON_MAJOR@_la_SOURCES) bin_PROGRAMS += weston weston_LDFLAGS = -export-dynamic -weston_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON \ - -DMODULEDIR='"$(moduledir)"' \ +weston_CPPFLAGS = $(AM_CPPFLAGS) -DMODULEDIR='"$(moduledir)"' \ -DXSERVER_PATH='"@XSERVER_PATH@"' weston_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBINPUT_BACKEND_CFLAGS) \ $(PTHREAD_CFLAGS) @@ -1026,8 +1025,7 @@ desktop_shell_la_CPPFLAGS = \ -I$(top_srcdir)/libweston \ -I$(top_builddir)/desktop-shell \ -DMODULEDIR='"$(moduledir)"' \ - -DLIBEXECDIR='"$(libexecdir)"' \ - -DIN_WESTON + -DLIBEXECDIR='"$(libexecdir)"' desktop_shell_la_LDFLAGS = -module -avoid-version desktop_shell_la_LIBADD = libshared.la libweston-desktop-@LIBWESTON_MAJOR@.la $(COMPOSITOR_LIBS) @@ -1053,8 +1051,7 @@ fullscreen_shell_la_CPPFLAGS = \ -I$(top_builddir)/protocol \ -I$(top_srcdir)/shared \ -I$(top_builddir)/libweston \ - -I$(top_srcdir)/libweston \ - -DIN_WESTON + -I$(top_srcdir)/libweston fullscreen_shell_la_LDFLAGS = -module -avoid-version fullscreen_shell_la_LIBADD = \ diff --git a/meson.build b/meson.build index 3eaf2c0a0..dc7c1bc7b 100644 --- a/meson.build +++ b/meson.build @@ -67,7 +67,6 @@ cc = meson.get_compiler('c') global_args = [] global_args_maybe = [ '-fvisibility=hidden', - '-DIN_WESTON', ] global_wnoargs_maybe = [ 'unused-parameter', diff --git a/shared/matrix.c b/shared/matrix.c index 7feea4310..605db83c9 100644 --- a/shared/matrix.c +++ b/shared/matrix.c @@ -31,10 +31,10 @@ #include #include -#ifdef IN_WESTON -#include -#else +#ifdef UNIT_TEST #define WL_EXPORT +#else +#include #endif #include "matrix.h" From 182d3771dde2ac59ae49881a08e0419c1e2bf6ca Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 14 Dec 2018 19:11:51 +0200 Subject: [PATCH 0701/1642] meson: fix pangocairo being optional Cannot use dependency() directly in the structure, because it will execute regardless of the option. Instead, let's store the dependency name in the structure and use the same logic as with simple_clients to conditionally look for the dependencies. As a bonus, this brings friendly error messages to demo-clients dependencies. subsurfaces' dependencies are also converted to maintain consistency with simple_clients. Signed-off-by: Pekka Paalanen --- clients/meson.build | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/clients/meson.build b/clients/meson.build index 062050aec..ad4eac387 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -235,7 +235,7 @@ demo_clients = [ text_input_unstable_v1_client_protocol_h, text_input_unstable_v1_protocol_c, ], - 'add_deps': [ dependency('pangocairo') ] + 'deps': [ 'pangocairo' ] }, { 'basename': 'eventdemo' }, { 'basename': 'flower' }, @@ -267,7 +267,7 @@ demo_clients = [ { 'basename': 'stacking' }, { 'basename': 'subsurfaces', - 'add_deps': [ dep_egl, dep_glesv2, dep_wl_egl ] + 'deps': [ 'egl', 'glesv2', 'wayland-egl' ] }, { 'basename': 'transformed' }, ] @@ -276,7 +276,14 @@ if get_option('demo-clients') foreach t : demo_clients t_name = 'weston-' + t.get('basename') t_srcs = [ t.get('basename') + '.c' ] + t.get('add_sources', []) - t_deps = [ dep_toytoolkit ] + t.get('add_deps', []) + t_deps = [ dep_toytoolkit ] + foreach depname : t.get('deps', []) + dep = dependency(depname, required: false) + if not dep.found() + error('@0@ requires \'@1@\' which was not found. If you rather not build this, set \'-Ddemo-clients=false\'.'.format(t_name, depname)) + endif + t_deps += dep + endforeach executable( t_name, t_srcs, From b423edecbb513c36d6d1c6a26b1edbb4d72c0c41 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 12:09:24 +0200 Subject: [PATCH 0702/1642] meson: better error for wcap dep cairo Add human-friendly error message. Cairo is a hard dependency on the whole at least because tests seem to require it, but this will help if someone adds an option to disable building tests to get rid of Cairo. Signed-off-by: Pekka Paalanen --- wcap/meson.build | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wcap/meson.build b/wcap/meson.build index 9a47cdb52..02281c98d 100644 --- a/wcap/meson.build +++ b/wcap/meson.build @@ -7,15 +7,15 @@ srcs_wcap = [ 'wcap-decode.c', ] -deps_wcap = [ - dep_libm, - dependency('cairo'), -] +wcap_dep_cairo = dependency('cairo', required: false) +if not wcap_dep_cairo.found() + error('wcap requires cairo which was not found. Or, you can use \'-Dwcap-decode=false\'.') +endif executable( 'wcap-decode', srcs_wcap, include_directories: include_directories('..'), - dependencies: deps_wcap, + dependencies: [ dep_libm, wcap_dep_cairo ], install: true ) From 4cf7db6d885bc5c8a5a0fc033ed69876acead65c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 12:13:39 +0200 Subject: [PATCH 0703/1642] meson: drop indent level from xwayland Use the pattern to avoid identing everything, when everything in the file is under the one conditional. Helps readability. Signed-off-by: Pekka Paalanen --- xwayland/meson.build | 62 +++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/xwayland/meson.build b/xwayland/meson.build index 6a4064529..08f8970c4 100644 --- a/xwayland/meson.build +++ b/xwayland/meson.build @@ -1,31 +1,33 @@ -if get_option('xwayland') - srcs_xwayland = [ - 'launcher.c', - 'window-manager.c', - 'selection.c', - 'dnd.c', - 'hash.c', - ] - deps_xwayland = [ - dependency('xcb'), - dependency('xcb-composite'), - dependency('xcb-shape'), - dependency('xcb-xfixes'), - dependency('xcursor'), - dependency('cairo-xcb'), - dep_libweston, - ] - plugin_xwayland = shared_library( - 'xwayland', - srcs_xwayland, - link_with: lib_cairo_shared, - include_directories: include_directories('..', '../shared'), - dependencies: deps_xwayland, - name_prefix: '', - install: true, - install_dir: dir_module_libweston - ) - env_modmap += 'xwayland.so=@0@;'.format(plugin_xwayland.full_path()) - - install_headers('xwayland-api.h', subdir: dir_include_libweston) +if not get_option('xwayland') + subdir_done() endif + +srcs_xwayland = [ + 'launcher.c', + 'window-manager.c', + 'selection.c', + 'dnd.c', + 'hash.c', +] +deps_xwayland = [ + dependency('xcb'), + dependency('xcb-composite'), + dependency('xcb-shape'), + dependency('xcb-xfixes'), + dependency('xcursor'), + dependency('cairo-xcb'), + dep_libweston, +] +plugin_xwayland = shared_library( + 'xwayland', + srcs_xwayland, + link_with: lib_cairo_shared, + include_directories: include_directories('..', '../shared'), + dependencies: deps_xwayland, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'xwayland.so=@0@;'.format(plugin_xwayland.full_path()) + +install_headers('xwayland-api.h', subdir: dir_include_libweston) From 2d7a2901b626b7d728d4bdf5f6a5a181e8db7ca3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 12:20:22 +0200 Subject: [PATCH 0704/1642] meson: better errors for xwayland deps Helps people to disable xwayland if they don't want to install the dependencies. Signed-off-by: Pekka Paalanen --- xwayland/meson.build | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/xwayland/meson.build b/xwayland/meson.build index 08f8970c4..6eb7e20c2 100644 --- a/xwayland/meson.build +++ b/xwayland/meson.build @@ -9,15 +9,26 @@ srcs_xwayland = [ 'dnd.c', 'hash.c', ] -deps_xwayland = [ - dependency('xcb'), - dependency('xcb-composite'), - dependency('xcb-shape'), - dependency('xcb-xfixes'), - dependency('xcursor'), - dependency('cairo-xcb'), - dep_libweston, + +dep_names_xwayland = [ + 'xcb', + 'xcb-composite', + 'xcb-shape', + 'xcb-xfixes', + 'xcursor', + 'cairo-xcb', ] + +deps_xwayland = [ dep_libweston ] + +foreach name : dep_names_xwayland + d = dependency(name, required: false) + if not d.found() + error('xwayland requires @0@ which was not found. Or, you can use \'-Dxwayland=false\'.'.format(name)) + endif + deps_xwayland += d +endforeach + plugin_xwayland = shared_library( 'xwayland', srcs_xwayland, From 9adcf440776b2aa1749ea9b25066e12c68f534fc Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 12:29:12 +0200 Subject: [PATCH 0705/1642] meson: better error for logind deps Helps people to disable logind support if they do not want the dependencies. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/libweston/meson.build b/libweston/meson.build index 6c87552e9..d4d0ef7ad 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -122,8 +122,17 @@ if get_option('launcher-logind') if systemd_dep.found() config_h.set('HAVE_SYSTEMD_LOGIN_209', '1') else - systemd_dep = dependency('libsystemd-login', version: '>= 198') + systemd_dep = dependency('libsystemd-login', version: '>= 198', required: false) + if not systemd_dep.found() + error('logind support requires libsystemd or libsystemd-login but neither was found. Or, you can use \'-Dlauncher-logind=false\'') + endif + endif + + dbus_dep = dependency('dbus-1', version: '>= 1.6', required: false) + if not dbus_dep.found() + error('logind support requires dbus-1 >= 1.6 which was not found. Or, you can use \'-Dlauncher-logind=false\'') endif + config_h.set('HAVE_DBUS', '1') config_h.set('HAVE_SYSTEMD_LOGIN', '1') @@ -132,7 +141,7 @@ if get_option('launcher-logind') 'launcher-logind.c', ] deps_session_helper += [ - dependency('dbus-1', version: '>= 1.6'), + dbus_dep, systemd_dep, ] endif From c529d0b91973e232e76737d7a08b0cc07b2fa4b2 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 13:02:32 +0200 Subject: [PATCH 0706/1642] meson: better error for freerdp Helps people to avoid freerdp if they don't want it. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libweston/meson.build b/libweston/meson.build index d4d0ef7ad..d7dfa3fb7 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -251,7 +251,10 @@ if get_option('backend-rdp') dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) if not dep_frdp.found() - dep_frdp = dependency('freerdp', version: '>= 1.1.0') + dep_frdp = dependency('freerdp', version: '>= 1.1.0', required: false) + endif + if not dep_frdp.found() + error('RDP-backend requires freerdp which was not found. Or, you can use \'-Dbackend-rdp=false\'.') endif if cc.has_header('freerdp/version.h', dependencies: dep_frdp) From 16487ebc0639103221f875b226210e7561641fd0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 13:13:53 +0200 Subject: [PATCH 0707/1642] meson: better errors for x11-backend deps Helps people avoid X11 related dependencies if they don't want them. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/libweston/meson.build b/libweston/meson.build index d7dfa3fb7..e51480ed7 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -334,15 +334,25 @@ if get_option('backend-x11') presentation_time_server_protocol_h, ] + dep_x11_xcb = dependency('xcb', version: '>= 1.8', required: false) + if not dep_x11_xcb.found() + error('x11-backend requires xcb >= 1.8 which was not found. Or, you can use \'-Dbackend-x11=false\'.') + endif + deps_x11 = [ - dependency('xcb', version: '>= 1.8'), - dependency('xcb-shm'), - dependency('x11'), - dependency('x11-xcb'), + dep_x11_xcb, dep_lib_cairo_shared, dep_pixman, ] + foreach name : [ 'xcb-shm', 'x11', 'x11-xcb' ] + d = dependency(name, required: false) + if not d.found() + error('x11-backend requires @0@ which was not found. Or, you can use \'-Dbackend-x11=false\'.'.format(name)) + endif + deps_x11 += d + endforeach + dep_xcb_xkb = dependency('xcb-xkb', version: '>= 1.9', required: false) if dep_xcb_xkb.found() deps_x11 += dep_xcb_xkb From a18bd3432d4df63dcd6509fa2ba1a0a6dda1a72f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 13:28:23 +0200 Subject: [PATCH 0708/1642] meson: better errors for gl-renderer egl and glesv2 Helps people to avoid EGL and GLESv2 if they do not want them. Stops using dep_egl and dep_glesv2 so that the human friendly error message is alongside the dependency() statement, so that the message and the statement can later be merged together once Meson offers the custom error messages feature or something even more sophisticated. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/libweston/meson.build b/libweston/meson.build index e51480ed7..5cde4a939 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -413,10 +413,6 @@ endif if get_option('renderer-gl') config_h.set('ENABLE_EGL', '1') - if not dep_egl.found() or not dep_glesv2.found() - error('gl-renderer enabled but EGL/GLESv2 not found') - endif - srcs_renderer_gl = [ 'gl-renderer.c', 'vertex-clipping.c', @@ -424,13 +420,21 @@ if get_option('renderer-gl') linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_server_protocol_h, ] + deps_renderer_gl = [ - dep_egl, - dep_glesv2, dep_pixman, dep_libweston, dep_libdrm_headers, ] + + foreach name : [ 'egl', 'glesv2' ] + d = dependency(name, required: false) + if not d.found() + error('gl-renderer requires @0@ which was not found. Or, you can use \'-Drenderer-gl=false\'.'.format(name)) + endif + deps_renderer_gl += d + endforeach + plugin_gl = shared_library( 'gl-renderer', srcs_renderer_gl, From 4b29ffddaa729dbb0dedf578ae6f4b3c0f9b2ab3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 13:36:40 +0200 Subject: [PATCH 0709/1642] meson: contain and check simple-dmabuf-egl deps Check the egl, glesv2 and gbm dependencies locally instead of relying on the dep_* variables from the top level meson.build or libweston/meson.build (dep_gbm). This should make these dependencies now explicitly checked when the app is built, rather than relying on other components doing the checks. If the drm-backend was disabled, this would have probably hit an error using the undeclared variable dep_gbm. Signed-off-by: Pekka Paalanen --- clients/meson.build | 8 +++----- meson.build | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/clients/meson.build b/clients/meson.build index ad4eac387..e4da973b1 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -61,11 +61,9 @@ simple_clients = [ ], 'dep_objs': [ dep_wayland_client, - dep_libdrm_headers, - dep_gbm, - dep_egl, - dep_glesv2 - ] + dep_libdrm_headers + ], + 'deps': [ 'egl', 'glesv2', 'gbm' ] }, { 'name': 'dmabuf-v4l', diff --git a/meson.build b/meson.build index dc7c1bc7b..2a37355ec 100644 --- a/meson.build +++ b/meson.build @@ -154,9 +154,7 @@ dep_libm = cc.find_library('m') dep_libdl = cc.find_library('dl') dep_libdrm = dependency('libdrm', version: '>= 2.4.68') dep_libdrm_headers = dep_libdrm.partial_dependency(compile_args: true) -dep_egl = dependency('egl', required: false) dep_wl_egl = dependency('wayland-egl') -dep_glesv2 = dependency('glesv2', required: false) dep_threads = dependency('threads') subdir('protocol') From 37dabe5f3ffa140bf053575741ca6ddaa2e8037f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 14:17:23 +0200 Subject: [PATCH 0710/1642] meson: better error cms-static deps Helps people to avoid lcms2 if they don't want it. Signed-off-by: Pekka Paalanen --- compositor/meson.build | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/compositor/meson.build b/compositor/meson.build index c2b6432d4..dd345492d 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -80,15 +80,17 @@ if get_option('color-management-lcms') 'cms-static.c', 'cms-helper.c', ] - deps_lcms = [ - dep_libweston, - dependency('lcms2'), - ] + + dep_lcms2 = dependency('lcms2', required: false) + if not dep_lcms2.found() + error('cms-static requires lcms2 which was not found. Or, you can use \'-Dcolor-management-lcms=false\'.') + endif + plugin_lcms = shared_library( 'cms-static', srcs_lcms, include_directories: include_directories('..', '../shared'), - dependencies: deps_lcms, + dependencies: [ dep_libweston, dep_lcms2 ], name_prefix: '', install: true, install_dir: dir_module_weston From 217a15c1b2bdfb88f80b6edca108251267ee720a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 14:20:40 +0200 Subject: [PATCH 0711/1642] meson: better error for cms-colord deps Helps people avoid colord if they don't want it. Signed-off-by: Pekka Paalanen --- compositor/meson.build | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/compositor/meson.build b/compositor/meson.build index dd345492d..9ef895d20 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -107,15 +107,17 @@ if get_option('color-management-colord') 'cms-colord.c', 'cms-helper.c', ] - deps_colord = [ - dep_libweston, - dependency('colord', version: '>= 0.1.27') - ] + + dep_colord = dependency('colord', version: '>= 0.1.27', required: false) + if not dep_colord.found() + error('cms-colord requires colord >= 0.1.27 which was not found. Or, you can use \'-Dcolor-management-colord=false\'.') + endif + plugin_colord = shared_library( 'cms-colord', srcs_colord, include_directories: include_directories('..', '../shared'), - dependencies: deps_colord, + dependencies: [ dep_libweston, dep_colord ], name_prefix: '', install: true, install_dir: dir_module_weston From d64649b56b64ff642124b77308c9ce9661d353d4 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 14:23:59 +0200 Subject: [PATCH 0712/1642] meson: better errors for systemd-notify deps Helps people avoid libsystemd if they don't want it. Signed-off-by: Pekka Paalanen --- compositor/meson.build | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compositor/meson.build b/compositor/meson.build index 9ef895d20..88bea8d84 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -126,11 +126,16 @@ if get_option('color-management-colord') endif if get_option('systemd') + dep_libsystemd = dependency('libsystemd', required: false) + if not dep_libsystemd.found() + error('systemd-notify requires libsystemd which was not found. Or, you can use \'-Dsystemd=false\'.') + endif + plugin_systemd_notify = shared_library( 'systemd-notify', 'systemd-notify.c', include_directories: include_directories('..', '../shared'), - dependencies: [ dep_libweston, dependency('libsystemd') ], + dependencies: [ dep_libweston, dep_libsystemd ], name_prefix: '', install: true, install_dir: dir_module_weston From 4da8141255099dc0d3ee682e1e9396137f187dae Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 14:40:01 +0200 Subject: [PATCH 0713/1642] meson: better errors for image loader deps Helps people avoid libjpeg or libwebp if they don't want them. Signed-off-by: Pekka Paalanen --- shared/meson.build | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/shared/meson.build b/shared/meson.build index f24b73b6a..09020430c 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -45,14 +45,21 @@ endif if get_option('image-jpeg') dep_libjpeg = dependency('libjpeg', required: false) if not dep_libjpeg.found() - dep_libjpeg = cc.find_library('jpeg') + dep_libjpeg = cc.find_library('jpeg', required: false) + endif + if not dep_libjpeg.found() + error('JPEG image loading requires libjpeg or jpeg, neither was found. Or, you can use \'-Dimage-jpeg=false\'.') endif deps_cairo_shared += dep_libjpeg config_h.set('HAVE_JPEG', '1') endif if get_option('image-webp') - deps_cairo_shared += dependency('libwebp') + dep_webp = dependency('libwebp', required: false) + if not dep_webp.found() + error('WEBP image loading requires libwebp which was not found. Or, you can use \'-Dimage-webp=false\'.') + endif + deps_cairo_shared += dep_webp config_h.set('HAVE_WEBP', '1') endif From 43a42920cf31022a9e8334620359b9eaf88b2ecf Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 14:53:04 +0200 Subject: [PATCH 0714/1642] meson: better error for wayland-backend wl-egl dep Helps people avoid wayland-egl if they don't want it. Makes the check for wayland-egl explicit on the site instead of relying on gl-renderer checking for it. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 6 +++++- meson.build | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libweston/meson.build b/libweston/meson.build index 5cde4a939..b9beb4db1 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -309,7 +309,11 @@ if get_option('backend-wayland') ] if get_option('renderer-gl') - deps_wlwl += dep_wl_egl + d = dependency('wayland-egl', required: false) + if not d.found() + error('wayland-backend + gl-renderer requires wayland-egl which was not found. Or, you can use \'-Dbackend-wayland=false\' or \'-Drenderer-gl=false\'.') + endif + deps_wlwl += d endif plugin_wlwl = shared_library( diff --git a/meson.build b/meson.build index 2a37355ec..e39353d3c 100644 --- a/meson.build +++ b/meson.build @@ -154,7 +154,6 @@ dep_libm = cc.find_library('m') dep_libdl = cc.find_library('dl') dep_libdrm = dependency('libdrm', version: '>= 2.4.68') dep_libdrm_headers = dep_libdrm.partial_dependency(compile_args: true) -dep_wl_egl = dependency('wayland-egl') dep_threads = dependency('threads') subdir('protocol') From 83a46ca98032bf6933122c551fb96da909cb0bef Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 15:00:21 +0200 Subject: [PATCH 0715/1642] meson: better error for x11+gl deps Helps people avoid egl in the rare case they don't have it. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libweston/meson.build b/libweston/meson.build index b9beb4db1..81ae6ca5a 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -364,7 +364,11 @@ if get_option('backend-x11') endif if get_option('renderer-gl') - deps_x11 += dependency('egl') + d = dependency('egl', required: false) + if not d.found() + error('x11-backend + gl-renderer requires egl which was not found. Or, you can use \'-Dbackend-x11=false\' or \'-Drenderer-gl=false\'.') + endif + deps_x11 += d endif plugin_x11 = shared_library( From 3b6b1e91281a47978e1216070f51b333754a93a4 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 15:01:48 +0200 Subject: [PATCH 0716/1642] meson: better error for drm+gl deps Helps people avoid GBM if they don't have it. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libweston/meson.build b/libweston/meson.build index 81ae6ca5a..492d59bfa 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -178,7 +178,10 @@ if get_option('backend-drm') ] if get_option('renderer-gl') - dep_gbm = dependency('gbm') + dep_gbm = dependency('gbm', required: false) + if not dep_gbm.found() + error('drm-backend + gl-renderer requires gbm which was not found. Or, you can use \'-Dbackend-drm=false\' or \'-Drenderer-gl=false\'.') + endif if dep_gbm.version().version_compare('>= 17.1') config_h.set('HAVE_GBM_MODIFIERS', '1') endif From 13dda10f1c5e20578d225f6020a10c4e6f8bf7a4 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 31 Dec 2018 15:08:25 +0200 Subject: [PATCH 0717/1642] meson: better errors for VA-API deps Helps people avoid libva if they don't want it. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/libweston/meson.build b/libweston/meson.build index 492d59bfa..bfb5defaa 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -192,12 +192,16 @@ if get_option('backend-drm') endif if get_option('backend-drm-screencast-vaapi') + foreach name : [ 'libva', 'libva-drm' ] + d = dependency(name, version: '>= 0.34.0', required: false) + if not d.found() + error('VA-API recorder requires @0@ >= 0.34.0 which was not found. Or, you can use \'-Dbackend-drm-screencast-vaapi=false\'.'.format(name)) + endif + deps_drm += d + endforeach + srcs_drm += 'vaapi-recorder.c' - deps_drm += [ - dependency('libva', version: '>= 0.34.0'), - dependency('libva-drm', version: '>= 0.34.0'), - dependency('threads'), - ] + deps_drm += dependency('threads') config_h.set('BUILD_VAAPI_RECORDER', '1') endif From 91bf16be7da6241ca5f8e74075673d2ab7d2f154 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 7 Jan 2019 15:04:21 +0200 Subject: [PATCH 0718/1642] tests: remove remnants of buffer-count test The buffer-count test was added in 40c0c3f91eeb747b86df64579e3b3706cc5450af and removed in 4938f93f578c0d73d63068e4a777250fce7db430, but the removal left around the dependency to EGL headers in weston-test.c. Removal of those unneeded includes allows us to drop the EGL dependency completely from weston-test.c build. For the Meson build this means that there are no dependency('egl') directives anymore without the user friendly error message. Signed-off-by: Pekka Paalanen --- Makefile.am | 5 ----- configure.ac | 1 - tests/meson.build | 5 +---- tests/weston-test.c | 6 ------ 4 files changed, 1 insertion(+), 16 deletions(-) diff --git a/Makefile.am b/Makefile.am index 24e754e36..bf36c306d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1376,11 +1376,6 @@ weston_test_desktop_shell_la_SOURCES = \ tests/weston-test-desktop-shell.c \ shared/helpers.h -if ENABLE_EGL -weston_test_la_CFLAGS += $(EGL_TESTS_CFLAGS) -weston_test_la_LDFLAGS += $(EGL_TESTS_LIBS) -endif - libtest_runner_la_SOURCES = \ tests/weston-test-runner.c \ tests/weston-test-runner.h diff --git a/configure.ac b/configure.ac index 8ebe73ee2..225d6692d 100644 --- a/configure.ac +++ b/configure.ac @@ -148,7 +148,6 @@ AM_CONDITIONAL(ENABLE_EGL, test x$enable_egl = xyes) if test x$enable_egl = xyes; then AC_DEFINE([ENABLE_EGL], [1], [Build Weston with EGL support]) PKG_CHECK_MODULES(EGL, [egl glesv2]) - PKG_CHECK_MODULES([EGL_TESTS], [egl glesv2 wayland-client wayland-egl]) AC_CHECK_HEADERS([linux/sync_file.h]) fi diff --git a/tests/meson.build b/tests/meson.build index f1bfe74a3..048a5398e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -45,10 +45,7 @@ exe_plugin_test = shared_library( weston_test_server_protocol_h, weston_test_protocol_c, include_directories: include_directories('..', '../shared'), - dependencies: [ - dep_libweston, - dependency('egl'), - ], + dependencies: [ dep_libweston ], name_prefix: '', install: false, ) diff --git a/tests/weston-test.c b/tests/weston-test.c index 412eb243c..e38aff4c5 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -36,12 +36,6 @@ #include "compositor/weston.h" #include "weston-test-server-protocol.h" -#ifdef ENABLE_EGL -#include -#include -#include "weston-egl-ext.h" -#endif /* ENABLE_EGL */ - #include "shared/helpers.h" #include "shared/timespec-util.h" From 5605b72e00c75ce13554b1622307bd1760787ea9 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 7 Jan 2019 21:33:08 +0200 Subject: [PATCH 0719/1642] meson: better error for tests missing libX11 Helps people avoid libX11 at the cost of disabling Xwayland support. Signed-off-by: Pekka Paalanen --- tests/meson.build | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/meson.build b/tests/meson.build index 048a5398e..980062c04 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -167,7 +167,11 @@ tests_weston = [ ] if get_option('xwayland') - tests_weston += [ [ 'xwayland', [], dependency('x11') ] ] + d = dependency('x11', required: false) + if not d.found() + error('Xwayland tests require libX11 which was not found. Or, you can use \'-Dxwayland=false\'.') + endif + tests_weston += [ [ 'xwayland', [], d ] ] endif tests_weston_plugin = [ From 917ecfcb04a5b43b295dd903ad0c3424b37b751d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 8 Jan 2019 20:05:09 +0200 Subject: [PATCH 0720/1642] meson: better error for test-junit-xml dep Helps people avoid libxml if they... want to? Signed-off-by: Pekka Paalanen --- tests/meson.build | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/meson.build b/tests/meson.build index 980062c04..ebd3872aa 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -52,7 +52,11 @@ exe_plugin_test = shared_library( deps_zuc = [ dep_libshared ] if get_option('test-junit-xml') - deps_zuc += [ dependency('libxml-2.0', version: '>= 2.6') ] + d = dependency('libxml-2.0', version: '>= 2.6', required: false) + if not d.found() + error('JUnit XML support requires libxml-2.0 >= 2.6 which was not found. Or, you can use \'-Dtest-junit-xml=false\'.') + endif + deps_zuc += d config_h.set('ENABLE_JUNIT_XML', '1') endif From 29a6803817345d8bc5a692afa52f4b7c8ae65cfd Mon Sep 17 00:00:00 2001 From: Eric Engestrom Date: Thu, 10 Jan 2019 14:57:05 +0000 Subject: [PATCH 0721/1642] Revert "meson: fix -Wno-foo argument testing" This reverts commit bc315aa2881949d3961c03599e8c20e0273ec817 Turns out since meson 0.46 it knows about this quirk of compilers and checks the right thing internally. See https://github.com/mesonbuild/meson/pull/2284 --- meson.build | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/meson.build b/meson.build index e39353d3c..7826dbb03 100644 --- a/meson.build +++ b/meson.build @@ -66,23 +66,16 @@ cc = meson.get_compiler('c') global_args = [] global_args_maybe = [ + '-Wno-unused-parameter', + '-Wno-shift-negative-value', # required due to Pixman + '-Wno-missing-field-initializers', '-fvisibility=hidden', ] -global_wnoargs_maybe = [ - 'unused-parameter', - 'shift-negative-value', # required due to Pixman - 'missing-field-initializers', -] foreach a : global_args_maybe if cc.has_argument(a) global_args += a endif endforeach -foreach a : global_wnoargs_maybe - if cc.has_argument('-W' + a) - global_args += '-Wno-' + a - endif -endforeach add_global_arguments(global_args, language: 'c') if cc.has_header_symbol('sys/sysmacros.h', 'major') From e9700f62af4003f6a6c3fd64309b0e119a670ab6 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Tue, 8 Jan 2019 12:32:12 +0200 Subject: [PATCH 0722/1642] clients/simple-dmabuf-egl: Support dmabuf format modifiers Take into account format modifiers advertised by the compositor and the EGL implementation and supported by the buffer creation mechanism, to select the optimal buffer modifier. Signed-off-by: Alexandros Frantzis --- clients/meson.build | 2 +- clients/simple-dmabuf-egl.c | 264 ++++++++++++++++++++++++++++++------ 2 files changed, 224 insertions(+), 42 deletions(-) diff --git a/clients/meson.build b/clients/meson.build index e4da973b1..3c4d86ab5 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -61,7 +61,7 @@ simple_clients = [ ], 'dep_objs': [ dep_wayland_client, - dep_libdrm_headers + dep_libdrm ], 'deps': [ 'egl', 'glesv2', 'gbm' ] }, diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c index b78d6b7b9..f7d41f9b0 100644 --- a/clients/simple-dmabuf-egl.c +++ b/clients/simple-dmabuf-egl.c @@ -39,9 +39,11 @@ #include #include +#include #include #include +#include "shared/helpers.h" #include "shared/platform.h" #include "shared/zalloc.h" #include "xdg-shell-unstable-v6-client-protocol.h" @@ -62,6 +64,9 @@ /* Possible options that affect the displayed image */ #define OPT_IMMEDIATE 1 /* create wl_buffer immediately */ +#define BUFFER_FORMAT DRM_FORMAT_XRGB8888 +#define MAX_BUFFER_PLANES 4 + struct display { struct wl_display *display; struct wl_registry *registry; @@ -69,11 +74,14 @@ struct display { struct zxdg_shell_v6 *shell; struct zwp_fullscreen_shell_v1 *fshell; struct zwp_linux_dmabuf_v1 *dmabuf; - int xrgb8888_format_found; + uint64_t *modifiers; + int modifiers_count; int req_dmabuf_immediate; struct { EGLDisplay display; EGLContext context; + bool has_dma_buf_import_modifiers; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dma_buf_modifiers; PFNEGLCREATEIMAGEKHRPROC create_image; PFNEGLDESTROYIMAGEKHRPROC destroy_image; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; @@ -91,12 +99,14 @@ struct buffer { struct gbm_bo *bo; - int dmabuf_fd; - int width; int height; - uint32_t stride; int format; + uint64_t modifier; + int plane_count; + int dmabuf_fds[MAX_BUFFER_PLANES]; + uint32_t strides[MAX_BUFFER_PLANES]; + uint32_t offsets[MAX_BUFFER_PLANES]; EGLImageKHR egl_image; GLuint gl_texture; @@ -137,6 +147,8 @@ static const struct wl_buffer_listener buffer_listener = { static void buffer_free(struct buffer *buf) { + int i; + if (buf->gl_fbo) glDeleteFramebuffers(1, &buf->gl_fbo); @@ -154,8 +166,10 @@ buffer_free(struct buffer *buf) if (buf->bo) gbm_bo_destroy(buf->bo); - if (buf->dmabuf_fd >= 0) - close(buf->dmabuf_fd); + for (i = 0; i < buf->plane_count; ++i) { + if (buf->dmabuf_fds[i] >= 0) + close(buf->dmabuf_fds[i]); + } } static void @@ -192,15 +206,52 @@ static const struct zwp_linux_buffer_params_v1_listener params_listener = { static bool create_fbo_for_buffer(struct display *display, struct buffer *buffer) { - EGLint attribs[] = { - EGL_WIDTH, buffer->width, - EGL_HEIGHT, buffer->height, - EGL_LINUX_DRM_FOURCC_EXT, buffer->format, - EGL_DMA_BUF_PLANE0_FD_EXT, buffer->dmabuf_fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, - EGL_DMA_BUF_PLANE0_PITCH_EXT, (int) buffer->stride, - EGL_NONE - }; + static const int general_attribs = 3; + static const int plane_attribs = 5; + static const int entries_per_attrib = 2; + EGLint attribs[(general_attribs + plane_attribs * MAX_BUFFER_PLANES) * + entries_per_attrib + 1]; + unsigned int atti = 0; + + attribs[atti++] = EGL_WIDTH; + attribs[atti++] = buffer->width; + attribs[atti++] = EGL_HEIGHT; + attribs[atti++] = buffer->height; + attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[atti++] = buffer->format; + +#define ADD_PLANE_ATTRIBS(plane_idx) { \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _FD_EXT; \ + attribs[atti++] = buffer->dmabuf_fds[plane_idx]; \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _OFFSET_EXT; \ + attribs[atti++] = (int) buffer->offsets[plane_idx]; \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _PITCH_EXT; \ + attribs[atti++] = (int) buffer->strides[plane_idx]; \ + if (display->egl.has_dma_buf_import_modifiers) { \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_LO_EXT; \ + attribs[atti++] = buffer->modifier & 0xFFFFFFFF; \ + attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_HI_EXT; \ + attribs[atti++] = buffer->modifier >> 32; \ + } \ + } + + if (buffer->plane_count > 0) + ADD_PLANE_ATTRIBS(0); + + if (buffer->plane_count > 1) + ADD_PLANE_ATTRIBS(1); + + if (buffer->plane_count > 2) + ADD_PLANE_ATTRIBS(2); + + if (buffer->plane_count > 3) + ADD_PLANE_ATTRIBS(3); + +#undef ADD_PLANE_ATTRIBS + + attribs[atti] = EGL_NONE; + + assert(atti < ARRAY_LENGTH(attribs)); buffer->egl_image = display->egl.create_image(display->egl.display, EGL_NO_CONTEXT, @@ -238,42 +289,79 @@ create_fbo_for_buffer(struct display *display, struct buffer *buffer) static int create_dmabuf_buffer(struct display *display, struct buffer *buffer, - int width, int height, int format) + int width, int height) { - static const uint32_t flags = 0; - static const uint64_t modifier = DRM_FORMAT_MOD_INVALID; + /* Y-Invert the buffer image, since we are going to renderer to the + * buffer through a FBO. */ + static const uint32_t flags = ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; struct zwp_linux_buffer_params_v1 *params; + int i; buffer->display = display; buffer->width = width; buffer->height = height; - buffer->format = format; + buffer->format = BUFFER_FORMAT; + +#ifdef HAVE_GBM_MODIFIERS + if (display->modifiers_count > 0) { + buffer->bo = gbm_bo_create_with_modifiers(display->gbm.device, + buffer->width, + buffer->height, + buffer->format, + display->modifiers, + display->modifiers_count); + if (buffer->bo) + buffer->modifier = gbm_bo_get_modifier(buffer->bo); + } +#endif + + if (!buffer->bo) { + buffer->bo = gbm_bo_create(display->gbm.device, + buffer->width, + buffer->height, + buffer->format, + GBM_BO_USE_RENDERING); + buffer->modifier = DRM_FORMAT_MOD_INVALID; + } - buffer->bo = gbm_bo_create(display->gbm.device, - buffer->width, buffer->height, - format, - GBM_BO_USE_RENDERING); if (!buffer->bo) { fprintf(stderr, "create_bo failed\n"); goto error; } - buffer->stride = gbm_bo_get_stride(buffer->bo); - buffer->dmabuf_fd = gbm_bo_get_fd(buffer->bo); - - if (buffer->dmabuf_fd < 0) { - fprintf(stderr, "error: dmabuf_fd < 0\n"); +#ifdef HAVE_GBM_MODIFIERS + buffer->plane_count = gbm_bo_get_plane_count(buffer->bo); + for (i = 0; i < buffer->plane_count; ++i) { + uint32_t handle = gbm_bo_get_handle_for_plane(buffer->bo, i).u32; + int ret = drmPrimeHandleToFD(display->gbm.drm_fd, handle, 0, + &buffer->dmabuf_fds[i]); + if (ret < 0 || buffer->dmabuf_fds[i] < 0) { + fprintf(stderr, "error: failed to get dmabuf_fd\n"); + goto error; + } + buffer->strides[i] = gbm_bo_get_stride_for_plane(buffer->bo, i); + buffer->offsets[i] = gbm_bo_get_offset(buffer->bo, i); + } +#else + buffer->plane_count = 1; + buffer->strides[0] = gbm_bo_get_stride(buffer->bo); + buffer->dmabuf_fds[0] = gbm_bo_get_fd(buffer->bo); + if (buffer->dmabuf_fds[0] < 0) { + fprintf(stderr, "error: failed to get dmabuf_fd\n"); goto error; } +#endif params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); - zwp_linux_buffer_params_v1_add(params, - buffer->dmabuf_fd, - 0, /* plane_idx */ - 0, /* offset */ - buffer->stride, - modifier >> 32, - modifier & 0xffffffff); + for (i = 0; i < buffer->plane_count; ++i) { + zwp_linux_buffer_params_v1_add(params, + buffer->dmabuf_fds[i], + i, + buffer->offsets[i], + buffer->strides[i], + buffer->modifier >> 32, + buffer->modifier & 0xffffffff); + } zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buffer); if (display->req_dmabuf_immediate) { @@ -281,7 +369,7 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, zwp_linux_buffer_params_v1_create_immed(params, buffer->width, buffer->height, - format, + buffer->format, flags); wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); } @@ -289,7 +377,7 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, zwp_linux_buffer_params_v1_create(params, buffer->width, buffer->height, - format, + buffer->format, flags); } @@ -386,9 +474,16 @@ create_window(struct display *display, int width, int height) assert(0); } + for (i = 0; i < NUM_BUFFERS; ++i) { + int j; + for (j = 0; j < MAX_BUFFER_PLANES; ++j) + window->buffers[i].dmabuf_fds[j] = -1; + + } + for (i = 0; i < NUM_BUFFERS; ++i) { ret = create_dmabuf_buffer(display, &window->buffers[i], - width, height, DRM_FORMAT_XRGB8888); + width, height); if (ret < 0) return NULL; @@ -488,8 +583,12 @@ dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, struct display *d = data; switch (format) { - case DRM_FORMAT_XRGB8888: - d->xrgb8888_format_found = 1; + case BUFFER_FORMAT: + ++d->modifiers_count; + d->modifiers = realloc(d->modifiers, + d->modifiers_count * sizeof(*d->modifiers)); + d->modifiers[d->modifiers_count - 1] = + ((uint64_t)modifier_hi << 32) | modifier_lo; break; default: break; @@ -569,6 +668,8 @@ destroy_display(struct display *display) if (display->egl.display != EGL_NO_DISPLAY) eglTerminate(display->egl.display); + free(display->modifiers); + if (display->dmabuf) zwp_linux_dmabuf_v1_destroy(display->dmabuf); @@ -663,6 +764,14 @@ display_set_up_egl(struct display *display) goto error; } + if (weston_check_egl_extension(egl_extensions, + "EGL_EXT_image_dma_buf_import_modifiers")) { + display->egl.has_dma_buf_import_modifiers = true; + display->egl.query_dma_buf_modifiers = + (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + assert(display->egl.query_dma_buf_modifiers); + } + display->egl.create_image = (void *) eglGetProcAddress("eglCreateImageKHR"); assert(display->egl.create_image); @@ -681,6 +790,76 @@ display_set_up_egl(struct display *display) return false; } +static bool +display_update_supported_modifiers_for_egl(struct display *d) +{ + uint64_t *egl_modifiers = NULL; + int num_egl_modifiers = 0; + EGLBoolean ret; + int i; + + /* If EGL doesn't support modifiers, don't use them at all. */ + if (!d->egl.has_dma_buf_import_modifiers) { + d->modifiers_count = 0; + free(d->modifiers); + d->modifiers = NULL; + return true; + } + + ret = d->egl.query_dma_buf_modifiers(d->egl.display, + BUFFER_FORMAT, + 0, /* max_modifiers */ + NULL, /* modifiers */ + NULL, /* external_only */ + &num_egl_modifiers); + if (ret == EGL_FALSE || num_egl_modifiers == 0) { + fprintf(stderr, "Failed to query num EGL modifiers for format\n"); + goto error; + } + + egl_modifiers = zalloc(num_egl_modifiers * sizeof(*egl_modifiers)); + + ret = d->egl.query_dma_buf_modifiers(d->egl.display, + BUFFER_FORMAT, + num_egl_modifiers, + egl_modifiers, + NULL, /* external_only */ + &num_egl_modifiers); + if (ret == EGL_FALSE) { + fprintf(stderr, "Failed to query EGL modifiers for format\n"); + goto error; + } + + /* Poor person's set intersection: d->modifiers INTERSECT + * egl_modifiers. If a modifier is not supported, replace it with + * DRM_FORMAT_MOD_INVALID in the d->modifiers array. + */ + for (i = 0; i < d->modifiers_count; ++i) { + uint64_t mod = d->modifiers[i]; + bool egl_supported = false; + int j; + + for (j = 0; j < num_egl_modifiers; ++j) { + if (egl_modifiers[j] == mod) { + egl_supported = true; + break; + } + } + + if (!egl_supported) + d->modifiers[i] = DRM_FORMAT_MOD_INVALID; + } + + free(egl_modifiers); + + return true; + +error: + free(egl_modifiers); + + return false; +} + static bool display_set_up_gbm(struct display *display, char const* drm_render_node) { @@ -729,7 +908,7 @@ create_display(char const *drm_render_node, int opts) wl_display_roundtrip(display->display); - if (!display->xrgb8888_format_found) { + if (!display->modifiers_count) { fprintf(stderr, "format XRGB8888 is not available\n"); goto error; } @@ -737,6 +916,9 @@ create_display(char const *drm_render_node, int opts) if (!display_set_up_egl(display)) goto error; + if (!display_update_supported_modifiers_for_egl(display)) + goto error; + if (!display_set_up_gbm(display, drm_render_node)) goto error; From 9985c534b8bf2f0a4dfc8788afd3c5ce787ddffe Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 9 Jan 2019 13:38:16 +0200 Subject: [PATCH 0723/1642] clients/simple-dmabuf-egl: Render a moving square Render a moving square instead of just clearing the buffer, to help uncover rendering issues (e.g. modifier-related issues) which may not be visible with a simple glClear. Signed-off-by: Alexandros Frantzis --- Makefile.am | 2 +- clients/meson.build | 3 +- clients/simple-dmabuf-egl.c | 222 ++++++++++++++++++++++++++++++------ 3 files changed, 188 insertions(+), 39 deletions(-) diff --git a/Makefile.am b/Makefile.am index bf36c306d..6036dbf51 100644 --- a/Makefile.am +++ b/Makefile.am @@ -690,7 +690,7 @@ nodist_weston_simple_dmabuf_egl_SOURCES = \ protocol/linux-dmabuf-unstable-v1-protocol.c \ protocol/linux-dmabuf-unstable-v1-client-protocol.h weston_simple_dmabuf_egl_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_EGL_CLIENT_CFLAGS) -weston_simple_dmabuf_egl_LDADD = $(SIMPLE_DMABUF_EGL_CLIENT_LIBS) libshared.la +weston_simple_dmabuf_egl_LDADD = $(SIMPLE_DMABUF_EGL_CLIENT_LIBS) libshared.la -lm endif noinst_LTLIBRARIES += libtoytoolkit.la diff --git a/clients/meson.build b/clients/meson.build index 3c4d86ab5..175e5f6da 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -61,7 +61,8 @@ simple_clients = [ ], 'dep_objs': [ dep_wayland_client, - dep_libdrm + dep_libdrm, + dep_libm ], 'deps': [ 'egl', 'glesv2', 'gbm' ] }, diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c index f7d41f9b0..01c16e287 100644 --- a/clients/simple-dmabuf-egl.c +++ b/clients/simple-dmabuf-egl.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -125,6 +126,12 @@ struct window { struct wl_callback *callback; bool initialized; bool wait_for_configure; + struct { + GLuint program; + GLuint pos; + GLuint color; + GLuint offset_uniform; + } gl; }; static sig_atomic_t running = 1; @@ -426,6 +433,117 @@ static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { xdg_toplevel_handle_close, }; +static const char *vert_shader_text = + "uniform float offset;\n" + "attribute vec4 pos;\n" + "attribute vec4 color;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\n" + " v_color = color;\n" + "}\n"; + +static const char *frag_shader_text = + "precision mediump float;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_FragColor = v_color;\n" + "}\n"; + +static GLuint +create_shader(const char *source, GLenum shader_type) +{ + GLuint shader; + GLint status; + + shader = glCreateShader(shader_type); + assert(shader != 0); + + glShaderSource(shader, 1, (const char **) &source, NULL); + glCompileShader(shader); + + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetShaderInfoLog(shader, 1000, &len, log); + fprintf(stderr, "Error: compiling %s: %*s\n", + shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", + len, log); + return 0; + } + + return shader; +} + +static GLuint +create_and_link_program(GLuint vert, GLuint frag) +{ + GLint status; + GLuint program = glCreateProgram(); + + glAttachShader(program, vert); + glAttachShader(program, frag); + glLinkProgram(program); + + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetProgramInfoLog(program, 1000, &len, log); + fprintf(stderr, "Error: linking:\n%*s\n", len, log); + return 0; + } + + return program; +} + +static bool +window_set_up_gl(struct window *window) +{ + GLuint vert = create_shader(vert_shader_text, GL_VERTEX_SHADER); + GLuint frag = create_shader(frag_shader_text, GL_FRAGMENT_SHADER); + + window->gl.program = create_and_link_program(vert, frag); + + glDeleteShader(vert); + glDeleteShader(frag); + + window->gl.pos = glGetAttribLocation(window->gl.program, "pos"); + window->gl.color = glGetAttribLocation(window->gl.program, "color"); + + glUseProgram(window->gl.program); + + window->gl.offset_uniform = + glGetUniformLocation(window->gl.program, "offset"); + + return window->gl.program != 0; +} + +static void +destroy_window(struct window *window) +{ + int i; + + if (window->gl.program) + glDeleteProgram(window->gl.program); + + if (window->callback) + wl_callback_destroy(window->callback); + + for (i = 0; i < NUM_BUFFERS; i++) { + if (window->buffers[i].buffer) + buffer_free(&window->buffers[i]); + } + + if (window->xdg_toplevel) + zxdg_toplevel_v6_destroy(window->xdg_toplevel); + if (window->xdg_surface) + zxdg_surface_v6_destroy(window->xdg_surface); + wl_surface_destroy(window->surface); + free(window); +} + static struct window * create_window(struct display *display, int width, int height) { @@ -486,31 +604,19 @@ create_window(struct display *display, int width, int height) width, height); if (ret < 0) - return NULL; + goto error; } - return window; -} - -static void -destroy_window(struct window *window) -{ - int i; + if (!window_set_up_gl(window)) + goto error; - if (window->callback) - wl_callback_destroy(window->callback); + return window; - for (i = 0; i < NUM_BUFFERS; i++) { - if (window->buffers[i].buffer) - buffer_free(&window->buffers[i]); - } +error: + if (window) + destroy_window(window); - if (window->xdg_toplevel) - zxdg_toplevel_v6_destroy(window->xdg_toplevel); - if (window->xdg_surface) - zxdg_surface_v6_destroy(window->xdg_surface); - wl_surface_destroy(window->surface); - free(window); + return NULL; } static struct buffer * @@ -527,13 +633,67 @@ window_next_buffer(struct window *window) static const struct wl_callback_listener frame_listener; +/* Renders a square moving from the lower left corner to the + * upper right corner of the window. The square's vertices have + * the following colors: + * + * green +-----+ yellow + * | | + * | | + * red +-----+ blue + */ +static void +render(struct window *window, struct buffer *buffer) +{ + /* Complete a movement iteration in 5000 ms. */ + static const uint64_t iteration_ms = 5000; + static const GLfloat verts[4][2] = { + { -0.5, -0.5 }, + { -0.5, 0.5 }, + { 0.5, -0.5 }, + { 0.5, 0.5 } + }; + static const GLfloat colors[4][3] = { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 }, + { 1, 1, 0 } + }; + GLfloat offset; + struct timeval tv; + uint64_t time_ms; + + gettimeofday(&tv, NULL); + time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + /* Split time_ms in repeating windows of [0, iteration_ms) and map them + * to offsets in the [-0.5, 0.5) range. */ + offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; + + /* Direct all GL draws to the buffer through the FBO */ + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + + glViewport(0, 0, window->width, window->height); + + glUniform1f(window->gl.offset_uniform, offset); + + glClearColor(0.0,0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(window->gl.color, 3, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(window->gl.pos); + glEnableVertexAttribArray(window->gl.color); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(window->gl.pos); + glDisableVertexAttribArray(window->gl.color); +} + static void redraw(void *data, struct wl_callback *callback, uint32_t time) { - /* With a 60Hz redraw rate this completes a cycle in 3 seconds */ - static const int MAX_STEP = 180; - static int step = 0; - static int step_dir = 1; struct window *window = data; struct buffer *buffer; @@ -545,19 +705,7 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) abort(); } - /* Direct all GL draws to the buffer through the FBO */ - glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); - - /* Cycle between 0 and MAX_STEP */ - step += step_dir; - if (step == 0 || step == MAX_STEP) - step_dir = -step_dir; - - glClearColor(0.0, - (float) step / MAX_STEP, - 1.0 - (float) step / MAX_STEP, - 1.0); - glClear(GL_COLOR_BUFFER_BIT); + render(window, buffer); glFinish(); wl_surface_attach(window->surface, buffer->buffer, 0, 0); From fb61e45de2d31fd590059ad16025af5dba30f1b7 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 17 Jan 2019 15:49:10 +0200 Subject: [PATCH 0724/1642] meson: friendly error for simple-dmabuf-drm This is probably the most annoying ones. Some distributions do not even package a libdrm_${driver} if the driver's hardware does not occur on the CPU architecture, e.g. Debian x86_64 does not have libdrm_etnaviv. Helps people to disable those. Signed-off-by: Pekka Paalanen --- clients/meson.build | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clients/meson.build b/clients/meson.build index 175e5f6da..60c48714c 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -307,10 +307,12 @@ foreach driver : [ 'etnaviv', 'intel', 'freedreno' ] endif if enabled - dep = dependency('libdrm_' + driver, required: required) + dep = dependency('libdrm_' + driver, required: false) if dep.found() simple_dmabuf_drm_deps += dep config_h.set('HAVE_LIBDRM_' + driver.to_upper(), 1) + elif required + error('simple-dmabuf-drm is configured to use @0@ but it was not found. Or, you can remove @1@ from \'-Dsimple-dmabuf-drm\' list.'.format('libdrm_' + driver, driver)) endif endif endforeach From a9b6470cdff7168389562e031c0b7dc683bf0c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Tue, 22 Jan 2019 11:05:19 +0100 Subject: [PATCH 0725/1642] gitlab-ci.yml: Install meson from 0.49 branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise CI might fail due to https://github.com/mesonbuild/meson/issues/4718 but the fix isn't included in any release yet, so install meson from the 0.49 branch. Signed-off-by: Jonas Ådahl --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b23b8f286..dcdc60d24 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,7 +12,7 @@ before_script: - echo 'deb http://deb.debian.org/debian stretch-backports main' >> /etc/apt/sources.list - apt-get update - apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev freerdp2-dev curl python3-pip python3-setuptools ninja-build - - pip3 install --user meson + - pip3 install --user git+https://github.com/mesonbuild/meson.git@0.49 - mkdir -p /tmp/.X11-unix - chmod 777 /tmp/.X11-unix From f5ca2f1424068c1a98111058e05bdde7297bdad3 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Tue, 22 Jan 2019 17:56:36 +0200 Subject: [PATCH 0726/1642] compositor-drm: Print DRM FB pixel format of the view in human-friendly form Signed-off-by: Marius Vlad --- libweston/compositor-drm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index a86880421..8eaa92e76 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1640,6 +1640,8 @@ drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) } } + drm_debug(b, "\t\t\t[view] view %p format: %s\n", + ev, fb->format->drm_format_name); drm_fb_set_buffer(fb, buffer); return fb; } From 2ce2339045688dccbae98bd8aa8f4647e880ed04 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Tue, 22 Jan 2019 17:56:58 +0200 Subject: [PATCH 0727/1642] pixel-formats: Document pixel format human-friendly conversion methods Signed-off-by: Marius Vlad --- libweston/pixel-formats.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libweston/pixel-formats.c b/libweston/pixel-formats.c index b96f3b21b..875e65261 100644 --- a/libweston/pixel-formats.c +++ b/libweston/pixel-formats.c @@ -387,6 +387,10 @@ pixel_format_get_info_shm(uint32_t format) return pixel_format_get_info(format); } +/** Retrive a pixel format information structure from a DRM FOURCC format + * + * \param format a DRM FOURCC format + */ WL_EXPORT const struct pixel_format_info * pixel_format_get_info(uint32_t format) { @@ -427,6 +431,15 @@ pixel_format_is_opaque(const struct pixel_format_info *info) return !info->opaque_substitute; } +/** Retrieve the opaque substitute for a pixel format + * + * If the given pixel format contains an alpha channel, look up an identical + * pixel format except where the alpha channel is ignored, if such format + * exists. Otherwise returns the passed in format as is. + * + * \param info a pixel_format_info already retrieved using pixel_format_get_info() + * + */ WL_EXPORT const struct pixel_format_info * pixel_format_get_opaque_substitute(const struct pixel_format_info *info) { From 1ca025cc5d76a3cc53bcd73bfdc4f18fc7e46df1 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Wed, 9 Jan 2019 12:26:07 +0200 Subject: [PATCH 0728/1642] compositor-drm: Display the pixel format of the framebuffer used by the plane With this patch we also display the format in usage by the HW-plane. This touches both legacy and atomic paths. --- libweston/compositor-drm.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 8eaa92e76..5102bb143 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2219,6 +2219,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) struct drm_plane_state *ps; struct drm_mode *mode; struct drm_head *head; + const struct pixel_format_info *pinfo = NULL; uint32_t connectors[MAX_CLONED_CONNECTORS]; int n_conn = 0; struct timespec now; @@ -2298,6 +2299,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) !scanout_plane->state_cur->fb || scanout_plane->state_cur->fb->strides[0] != scanout_state->fb->strides[0]) { + ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, scanout_state->fb->fb_id, 0, 0, @@ -2309,6 +2311,11 @@ drm_output_apply_state_legacy(struct drm_output_state *state) } } + pinfo = scanout_state->fb->format; + drm_debug(backend, "\t[CRTC:%u, PLANE:%u] FORMAT: %s\n", + output->crtc_id, scanout_state->plane->plane_id, + pinfo ? pinfo->drm_format_name : "UNKNOWN"); + if (drmModePageFlip(backend->drm.fd, output->crtc_id, scanout_state->fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { @@ -2531,6 +2538,7 @@ drm_output_apply_state_atomic(struct drm_output_state *state, wl_list_for_each(plane_state, &state->plane_list, link) { struct drm_plane *plane = plane_state->plane; + const struct pixel_format_info *pinfo = NULL; ret |= plane_add_prop(req, plane, WDRM_PLANE_FB_ID, plane_state->fb ? plane_state->fb->fb_id : 0); @@ -2553,6 +2561,13 @@ drm_output_apply_state_atomic(struct drm_output_state *state, ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_H, plane_state->dest_h); + if (plane_state->fb && plane_state->fb->format) + pinfo = plane_state->fb->format; + + drm_debug(plane->backend, "\t\t\t[PLANE:%lu] FORMAT: %s\n", + (unsigned long) plane->plane_id, + pinfo ? pinfo->drm_format_name : "UNKNOWN"); + if (ret != 0) { weston_log("couldn't set plane state\n"); return ret; From 619958e382d018aee2312e9d0f94e7e925a1cbce Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Tue, 22 Jan 2019 11:28:46 +0100 Subject: [PATCH 0729/1642] compositor-drm: fix drm_output_prepare_overlay_view for non-matching format Add missing drm_plane_state_put_back in case the view's pixel format does not match any of the tested plane's supported formats. Signed-off-by: Philipp Zabel --- libweston/compositor-drm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 5102bb143..d001711d8 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3234,8 +3234,11 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, if (j != p->formats[i].count_modifiers) break; } - if (i == p->count_formats) + if (i == p->count_formats) { + drm_plane_state_put_back(state); + state = NULL; continue; + } if (availability == NO_PLANES_WITH_FORMAT) availability = NO_PLANES_ACCEPTED; From c91cf30154041b44357872787ce5e2a62fa342c1 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 21 Dec 2018 12:48:22 +0200 Subject: [PATCH 0730/1642] compositor-drm: Forgot to use mode variable when using render only mode of composition In patch 5d767416c1847 we simplified a bit the way in which the compositing mode was being printed with the purpose to improve weston-debug. It seems we forgot to use the mode when RENDER-only mode is being used, so this patch fixes that. Signed-off-by: Marius Vlad --- libweston/compositor-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index d001711d8..c23a6913b 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3818,7 +3818,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) if (!state) { mode = DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY; state = drm_output_propose_state(output_base, pending_state, - DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY); + mode); } assert(state); From be57857af656171706f05a03f56db239d0b590d9 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 25 Jan 2019 12:56:24 +0200 Subject: [PATCH 0731/1642] compositor-drm: Add an environmental variable to force RENDER-only mode of compositing Signed-off-by: Marius Vlad --- libweston/compositor-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c23a6913b..9ded52657 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4127,7 +4127,7 @@ init_kms_caps(struct drm_backend *b) * to a fraction. For cursors, it's not so bad, so they are * enabled. */ - if (!b->atomic_modeset) + if (!b->atomic_modeset || getenv("WESTON_FORCE_RENDERER")) b->sprites_are_broken = 1; ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ASPECT_RATIO, 1); From 99553750db20e014a8b2322307eeb25cf2fdb83e Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Mon, 28 Jan 2019 15:40:55 +1300 Subject: [PATCH 0732/1642] compositor-drm: Don't set linear modifier when not supported This will cause gbm_surface_create_with_modifiers to fail on drivers where modifiers are not yet supported (e.g. amdgpu). We need to make sure we only end up using gbm_surface_create in this case. This fixes the remoting plugin on these drivers. Signed-off-by: Scott Anderson --- libweston/compositor-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 9ded52657..90e9fb5c3 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4555,7 +4555,7 @@ drm_virtual_plane_create(struct drm_backend *b, struct drm_output *output) plane->state_cur->complete = true; plane->formats[0].format = output->gbm_format; plane->count_formats = 1; - if (output->gbm_bo_flags & GBM_BO_USE_LINEAR) { + if ((output->gbm_bo_flags & GBM_BO_USE_LINEAR) && b->fb_modifiers) { uint64_t *modifiers = zalloc(sizeof *modifiers); if (modifiers) { *modifiers = DRM_FORMAT_MOD_LINEAR; From 336ce67da79049bda0ac002c90215fb469346bde Mon Sep 17 00:00:00 2001 From: Dima Ryazanov Date: Wed, 14 Nov 2018 21:03:35 -0800 Subject: [PATCH 0733/1642] Revert "Fix a crash when unlocking or unconfining a pointer" This reverts commit e0dc5d47cb5f29deec495efd958fcd5f6f833389. Signed-off-by: Dima Ryazanov --- clients/window.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/clients/window.c b/clients/window.c index 12939cb76..1ab33545b 100644 --- a/clients/window.c +++ b/clients/window.c @@ -286,7 +286,6 @@ struct window { confined_pointer_unconfined_handler_t pointer_unconfined_handler; struct zwp_confined_pointer_v1 *confined_pointer; - struct input *confined_input; struct widget *confined_widget; bool confined; @@ -4793,8 +4792,8 @@ static void locked_pointer_locked(void *data, struct zwp_locked_pointer_v1 *locked_pointer) { - struct window *window = data; - struct input *input = window->locked_input; + struct input *input = data; + struct window *window = input->pointer_focus; window->pointer_locked = true; @@ -4809,8 +4808,8 @@ static void locked_pointer_unlocked(void *data, struct zwp_locked_pointer_v1 *locked_pointer) { - struct window *window = data; - struct input *input = window->locked_input; + struct input *input = data; + struct window *window = input->pointer_focus; window_unlock_pointer(window); @@ -4865,7 +4864,7 @@ window_lock_pointer(struct window *window, struct input *input) ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); zwp_locked_pointer_v1_add_listener(locked_pointer, &locked_pointer_listener, - window); + input); window->locked_input = input; window->locked_pointer = locked_pointer; @@ -4907,8 +4906,8 @@ static void confined_pointer_confined(void *data, struct zwp_confined_pointer_v1 *confined_pointer) { - struct window *window = data; - struct input *input = window->confined_input; + struct input *input = data; + struct window *window = input->pointer_focus; window->confined = true; @@ -4923,8 +4922,8 @@ static void confined_pointer_unconfined(void *data, struct zwp_confined_pointer_v1 *confined_pointer) { - struct window *window = data; - struct input *input = window->confined_input; + struct input *input = data; + struct window *window = input->pointer_focus; window_unconfine_pointer(window); @@ -4989,9 +4988,8 @@ window_confine_pointer_to_rectangles(struct window *window, zwp_confined_pointer_v1_add_listener(confined_pointer, &confined_pointer_listener, - window); + input); - window->confined_input = input; window->confined_pointer = confined_pointer; window->confined_widget = NULL; @@ -5052,7 +5050,6 @@ window_unconfine_pointer(struct window *window) zwp_confined_pointer_v1_destroy(window->confined_pointer); window->confined_pointer = NULL; window->confined = false; - window->confined_input = NULL; } static void From 44dd7f2738e2b2a5c4fe4793f99e1fa049101c67 Mon Sep 17 00:00:00 2001 From: Dima Ryazanov Date: Wed, 14 Nov 2018 21:15:16 -0800 Subject: [PATCH 0734/1642] clients: Delete an unused variable Signed-off-by: Dima Ryazanov --- clients/window.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/clients/window.c b/clients/window.c index 1ab33545b..470ac090e 100644 --- a/clients/window.c +++ b/clients/window.c @@ -278,7 +278,6 @@ struct window { struct zwp_relative_pointer_v1 *relative_pointer; struct zwp_locked_pointer_v1 *locked_pointer; - struct input *locked_input; bool pointer_locked; locked_pointer_locked_handler_t pointer_locked_handler; locked_pointer_unlocked_handler_t pointer_unlocked_handler; @@ -4866,7 +4865,6 @@ window_lock_pointer(struct window *window, struct input *input) &locked_pointer_listener, input); - window->locked_input = input; window->locked_pointer = locked_pointer; window->relative_pointer = relative_pointer; @@ -4884,7 +4882,6 @@ window_unlock_pointer(struct window *window) window->locked_pointer = NULL; window->relative_pointer = NULL; window->pointer_locked = false; - window->locked_input = NULL; } void From 13bdf25270a9f69c71296d7f83fb5cbe608c068d Mon Sep 17 00:00:00 2001 From: Dima Ryazanov Date: Wed, 14 Nov 2018 22:17:42 -0800 Subject: [PATCH 0735/1642] clients: A better fix for a crash when unlocking or unconfining a pointer This is a rewrite of the fix in: https://lists.freedesktop.org/archives/wayland-devel/2018-May/038140.html It addresses Pekka's concerns about window getting destroyed before the unlock/unconfine event is triggered. Signed-off-by: Dima Ryazanov --- clients/window.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/clients/window.c b/clients/window.c index 470ac090e..d55d27eb3 100644 --- a/clients/window.c +++ b/clients/window.c @@ -343,6 +343,8 @@ struct input { struct window *pointer_focus; struct window *keyboard_focus; struct window *touch_focus; + struct window *locked_window; + struct window *confined_window; int current_cursor; uint32_t cursor_anim_start; struct wl_callback *cursor_frame_cb; @@ -1584,6 +1586,10 @@ window_destroy(struct window *window) input->pointer_focus = NULL; if (input->keyboard_focus == window) input->keyboard_focus = NULL; + if (input->locked_window == window) + input->locked_window = NULL; + if (input->confined_window == window) + input->confined_window = NULL; if (input->focus_widget && input->focus_widget->window == window) input->focus_widget = NULL; @@ -4792,7 +4798,10 @@ locked_pointer_locked(void *data, struct zwp_locked_pointer_v1 *locked_pointer) { struct input *input = data; - struct window *window = input->pointer_focus; + struct window *window = input->locked_window; + + if (!window) + return; window->pointer_locked = true; @@ -4808,10 +4817,15 @@ locked_pointer_unlocked(void *data, struct zwp_locked_pointer_v1 *locked_pointer) { struct input *input = data; - struct window *window = input->pointer_focus; + struct window *window = input->locked_window; + + if (!window) + return; window_unlock_pointer(window); + input->locked_window = NULL; + if (window->pointer_unlocked_handler) { window->pointer_unlocked_handler(window, input, @@ -4867,6 +4881,7 @@ window_lock_pointer(struct window *window, struct input *input) window->locked_pointer = locked_pointer; window->relative_pointer = relative_pointer; + input->locked_window = window; return 0; } @@ -4904,7 +4919,10 @@ confined_pointer_confined(void *data, struct zwp_confined_pointer_v1 *confined_pointer) { struct input *input = data; - struct window *window = input->pointer_focus; + struct window *window = input->confined_window; + + if (!window) + return; window->confined = true; @@ -4920,11 +4938,15 @@ confined_pointer_unconfined(void *data, struct zwp_confined_pointer_v1 *confined_pointer) { struct input *input = data; - struct window *window = input->pointer_focus; + struct window *window = input->confined_window; + + if (!window) + return; window_unconfine_pointer(window); window->confined = false; + input->confined_window = NULL; if (window->pointer_unconfined_handler) { window->pointer_unconfined_handler(window, @@ -4989,6 +5011,7 @@ window_confine_pointer_to_rectangles(struct window *window, window->confined_pointer = confined_pointer; window->confined_widget = NULL; + input->confined_window = window; return 0; } From f57774e1125dc261b276e00f717146d549c483c9 Mon Sep 17 00:00:00 2001 From: Greg V Date: Tue, 24 Jul 2018 23:21:55 +0300 Subject: [PATCH 0736/1642] desktop-shell: extract view_get_transform, make it reliable Avoid crashes related to get_shell_surface returning NULL. Surfaces are already allowed to be neither focus nor shell in e.g. focus_state_surface_destroy. --- desktop-shell/shell.c | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index a72f7391f..2d6d7c203 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -961,19 +961,32 @@ get_output_height(struct weston_output *output) return abs(output->region.extents.y1 - output->region.extents.y2); } -static void -view_translate(struct workspace *ws, struct weston_view *view, double d) +static struct weston_transform * +view_get_transform(struct weston_view *view) { - struct weston_transform *transform; + struct focus_surface *fsurf = NULL; + struct shell_surface *shsurf = NULL; if (is_focus_view(view)) { - struct focus_surface *fsurf = get_focus_surface(view->surface); - transform = &fsurf->workspace_transform; - } else { - struct shell_surface *shsurf = get_shell_surface(view->surface); - transform = &shsurf->workspace_transform; + fsurf = get_focus_surface(view->surface); + return &fsurf->workspace_transform; } + shsurf = get_shell_surface(view->surface); + if (shsurf) + return &shsurf->workspace_transform; + + return NULL; +} + +static void +view_translate(struct workspace *ws, struct weston_view *view, double d) +{ + struct weston_transform *transform = view_get_transform(view); + + if (!transform) + return; + if (wl_list_empty(&transform->link)) wl_list_insert(view->geometry.transformation_list.prev, &transform->link); @@ -1044,13 +1057,9 @@ workspace_deactivate_transforms(struct workspace *ws) struct weston_transform *transform; wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { - if (is_focus_view(view)) { - struct focus_surface *fsurf = get_focus_surface(view->surface); - transform = &fsurf->workspace_transform; - } else { - struct shell_surface *shsurf = get_shell_surface(view->surface); - transform = &shsurf->workspace_transform; - } + transform = view_get_transform(view); + if (!transform) + continue; if (!wl_list_empty(&transform->link)) { wl_list_remove(&transform->link); From ea54c2fda65aacd87c6385461dc7e7f7fb0d425d Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 21 Sep 2018 14:44:57 +0200 Subject: [PATCH 0737/1642] weston: Store use_pixman as `bool` type Signed-off-by: Thomas Zimmermann --- libweston/compositor-drm.c | 2 +- libweston/compositor-headless.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 90e9fb5c3..f19006f30 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -333,7 +333,7 @@ struct drm_backend { bool universal_planes; bool atomic_modeset; - int use_pixman; + bool use_pixman; bool use_pixman_shadow; struct udev_input input; diff --git a/libweston/compositor-headless.h b/libweston/compositor-headless.h index c84ed9fc4..039b50e3d 100644 --- a/libweston/compositor-headless.h +++ b/libweston/compositor-headless.h @@ -40,7 +40,7 @@ struct weston_headless_backend_config { struct weston_backend_config base; /** Whether to use the pixman renderer instead of the OpenGL ES renderer. */ - int use_pixman; + bool use_pixman; }; #ifdef __cplusplus From 22dd67ccea41714a4832d2eba2676f0bc2e12475 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 21 Sep 2018 11:50:32 +0200 Subject: [PATCH 0738/1642] weston: Add config option to enable pixman-based rendering Pixman can be used for rendering if the default GLESv2 rendering is broken or cannot be used. Pixman-based rendering is already available with the command-line switch '--use-pixman'. This patch adds support for this option to the configuration file. Putting [core] use-pixman=true into 'weston.ini' enables pixman-based rendering for all backends that support it. With this change, pixman has to be enabled only once. Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/27 Signed-off-by: Thomas Zimmermann --- compositor/main.c | 39 +++++++++++++++++++++++++++++++++++---- man/weston.ini.man | 6 ++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 3400ffa41..b4fdbfe9d 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2143,18 +2143,26 @@ load_drm_backend(struct weston_compositor *c, struct wet_compositor *wet = to_wet_compositor(c); int use_shadow; int ret = 0; + int use_pixman_config_ = 0; + int32_t use_pixman_ = 0; wet->drm_use_current_mode = false; + section = weston_config_get_section(wc, "core", NULL, NULL); + weston_config_section_get_bool(section, "use-pixman", &use_pixman_config_, + use_pixman_config_); + use_pixman_ = use_pixman_config_; + const struct weston_option options[] = { { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, { WESTON_OPTION_STRING, "drm-device", 0, &config.specific_device }, { WESTON_OPTION_BOOLEAN, "current-mode", 0, &wet->drm_use_current_mode }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &use_pixman_ }, }; parse_options(options, ARRAY_LENGTH(options), argc, argv); + config.use_pixman = use_pixman_; section = weston_config_get_section(wc, "core", NULL, NULL); weston_config_section_get_string(section, @@ -2204,23 +2212,32 @@ load_headless_backend(struct weston_compositor *c, { const struct weston_windowed_output_api *api; struct weston_headless_backend_config config = {{ 0, }}; + struct weston_config_section *section; int no_outputs = 0; int ret = 0; char *transform = NULL; + int32_t use_pixman_config_ = 0; + int use_pixman_ = 0; struct wet_output_config *parsed_options = wet_init_parsed_options(c); if (!parsed_options) return -1; + section = weston_config_get_section(wc, "core", NULL, NULL); + weston_config_section_get_bool(section, "use-pixman", &use_pixman_config_, + use_pixman_config_); + use_pixman_ = use_pixman_config_; + const struct weston_option options[] = { { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &use_pixman_ }, { WESTON_OPTION_STRING, "transform", 0, &transform }, { WESTON_OPTION_BOOLEAN, "no-outputs", 0, &no_outputs }, }; parse_options(options, ARRAY_LENGTH(options), argc, argv); + config.use_pixman = use_pixman_; if (transform) { if (weston_parse_transform(transform, &parsed_options->transform) < 0) { @@ -2415,11 +2432,18 @@ load_x11_backend(struct weston_compositor *c, int output_count = 0; char const *section_name; int i; + int32_t use_pixman_config_ = 0; + int use_pixman_ = 0; struct wet_output_config *parsed_options = wet_init_parsed_options(c); if (!parsed_options) return -1; + section = weston_config_get_section(wc, "core", NULL, NULL); + weston_config_section_get_bool(section, "use-pixman", &use_pixman_config_, + use_pixman_config_); + use_pixman_ = use_pixman_config_; + const struct weston_option options[] = { { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, @@ -2427,10 +2451,11 @@ load_x11_backend(struct weston_compositor *c, { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &config.fullscreen }, { WESTON_OPTION_INTEGER, "output-count", 0, &option_count }, { WESTON_OPTION_BOOLEAN, "no-input", 0, &config.no_input }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &use_pixman_ }, }; parse_options(options, ARRAY_LENGTH(options), argc, argv); + config.use_pixman = use_pixman_; config.base.struct_version = WESTON_X11_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_x11_backend_config); @@ -2522,6 +2547,7 @@ load_wayland_backend(struct weston_compositor *c, int32_t use_pixman_ = 0; int32_t sprawl_ = 0; int32_t fullscreen_ = 0; + int use_pixman_config_ = 0; struct wet_output_config *parsed_options = wet_init_parsed_options(c); if (!parsed_options) @@ -2531,6 +2557,11 @@ load_wayland_backend(struct weston_compositor *c, config.cursor_theme = NULL; config.display_name = NULL; + section = weston_config_get_section(wc, "core", NULL, NULL); + weston_config_section_get_bool(section, "use-pixman", &use_pixman_config_, + use_pixman_config_); + use_pixman_ = use_pixman_config_; + const struct weston_option wayland_options[] = { { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, @@ -2544,8 +2575,8 @@ load_wayland_backend(struct weston_compositor *c, parse_options(wayland_options, ARRAY_LENGTH(wayland_options), argc, argv); config.sprawl = sprawl_; - config.use_pixman = use_pixman_; config.fullscreen = fullscreen_; + config.use_pixman = use_pixman_config_; section = weston_config_get_section(wc, "shell", NULL, NULL); weston_config_section_get_string(section, "cursor-theme", diff --git a/man/weston.ini.man b/man/weston.ini.man index 2171b960d..7df6b1190 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -201,6 +201,12 @@ directroy are: .BR remoting-plugin.so .fi .RE +.TP 7 +.BI "use-pixman=" true +Enables pixman-based rendering for all outputs on backends that support it. +Boolean, defaults to +.BR false . +There is also a command line option to do the same. .SH "LIBINPUT SECTION" The From ac71ee5d6a6cfd5e1bb21cdce7875929b207162e Mon Sep 17 00:00:00 2001 From: emersion Date: Sun, 18 Nov 2018 21:42:10 +0100 Subject: [PATCH 0739/1642] clients: sanitize XCURSOR_SIZE Fixes https://gitlab.freedesktop.org/wayland/weston/issues/164 --- clients/window.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/clients/window.c b/clients/window.c index d55d27eb3..8d2881536 100644 --- a/clients/window.c +++ b/clients/window.c @@ -79,6 +79,7 @@ typedef void *EGLContext; #include "pointer-constraints-unstable-v1-client-protocol.h" #include "relative-pointer-unstable-v1-client-protocol.h" #include "shared/os-compatibility.h" +#include "shared/string-helpers.h" #include "window.h" @@ -89,6 +90,8 @@ typedef void *EGLContext; #define ZWP_RELATIVE_POINTER_MANAGER_V1_VERSION 1 #define ZWP_POINTER_CONSTRAINTS_V1_VERSION 1 +#define DEFAULT_XCURSOR_SIZE 32 + struct shm_pool; struct global { @@ -1340,14 +1343,19 @@ create_cursors(struct display *display) const char *config_file; struct weston_config *config; struct weston_config_section *s; - int size = 32; - char *theme = NULL; + int size = DEFAULT_XCURSOR_SIZE; + char *theme = NULL, *size_str; unsigned int i, j; struct wl_cursor *cursor; theme = getenv("XCURSOR_THEME"); - if (getenv("XCURSOR_SIZE")) - size = atoi(getenv("XCURSOR_SIZE")); + + size_str = getenv("XCURSOR_SIZE"); + if (size_str) { + safe_strtoint(size_str, &size); + if (size <= 0) + size = DEFAULT_XCURSOR_SIZE; + } config_file = weston_config_get_name_from_env(); config = weston_config_parse(config_file); From f3190a45affd0acdd1a8834f73a8c8cbee80cdf6 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 31 Jan 2019 02:06:22 +0000 Subject: [PATCH 0740/1642] gitlab-ci: Actually capture Meson logs Correct the path to the build directory so we can capture Meson logs; especially useful when tests fail like in #184. An example of this change having been run with a deliberately-failing test, capturing the Meson logs, can be found at: https://gitlab.freedesktop.org/daniels/weston/-/jobs/94623 Signed-off-by: Daniel Stone --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dcdc60d24..c6007d1d8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -80,5 +80,5 @@ build-native-meson: name: weston-$CI_COMMIT_SHA-$CI_JOB_ID when: always paths: - - build-meson/meson-logs + - build-*/meson-logs - prefix-* From fa2742b3808fc06ccea9cbe9b9fd470fdc4dfec8 Mon Sep 17 00:00:00 2001 From: n3rdopolis Date: Wed, 9 Jan 2019 01:59:58 +0000 Subject: [PATCH 0741/1642] libweston: fbdev: Force the Framebuffer devices to be activated. This attempts to wake up secondary framebuffer devices (/dev/fb1 and up) as usually these devices start powered off, and the FBIOPUT_VSCREENINFO ioctl turns it on. This was tested on qemu with two virtual QXL cards. This is a more precise way to activate framebuffer devices with the ioctl --- libweston/compositor-fbdev.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 44d150777..6031be522 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -362,6 +362,27 @@ fbdev_set_screen_info(int fd, struct fbdev_screeninfo *info) return 1; } +static int +fbdev_wakeup_screen(int fd, struct fbdev_screeninfo *info) +{ + struct fb_var_screeninfo varinfo; + + /* Grab the current screen information. */ + if (ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { + return -1; + } + + /* force the framebuffer to wake up */ + varinfo.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; + + /* Set the device's screen information. */ + if (ioctl(fd, FBIOPUT_VSCREENINFO, &varinfo) < 0) { + return -1; + } + + return 1; +} + /* Returns an FD for the frame buffer device. */ static int fbdev_frame_buffer_open(const char *fb_dev, @@ -390,8 +411,8 @@ fbdev_frame_buffer_open(const char *fb_dev, /* Attempt to wake up the framebuffer device, needed for secondary * framebuffer devices */ - if (fbdev_set_screen_info(fd, screen_info) < 0) { - weston_log("Failed to set mode settings. " + if (fbdev_wakeup_screen(fd, screen_info) < 0) { + weston_log("Failed to activate framebuffer display. " "Attempting to open output anyway.\n"); } From f0d3197fa52cf127ffb73e1f956577395c6d0084 Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Fri, 1 Feb 2019 12:11:31 +1300 Subject: [PATCH 0742/1642] meson: Fix deprecation warning for pkgconfig Meson 0.49 now issues a warning for libraries being passed into the 'libraries' keyword argument. Now they should be passed as a positional argument. See https://mesonbuild.com/Release-notes-for-0-49-0.html#deprecation-warning-in-pkgconfig-generator Signed-off-by: Scott Anderson --- libweston-desktop/meson.build | 2 +- libweston/meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libweston-desktop/meson.build b/libweston-desktop/meson.build index 81bd64e2e..d6be7a847 100644 --- a/libweston-desktop/meson.build +++ b/libweston-desktop/meson.build @@ -25,11 +25,11 @@ dep_lib_desktop = declare_dependency( install_headers('libweston-desktop.h', subdir: dir_include_libweston) pkgconfig.generate( + lib_desktop, filebase: 'libweston-desktop-@0@'.format(libweston_major), name: 'libweston-desktop', version: version_weston, description: 'Desktop shells abstraction library for libweston compositors', - libraries: lib_desktop, requires_private: [ lib_weston, dep_wayland_server ], subdirs: dir_include_libweston ) diff --git a/libweston/meson.build b/libweston/meson.build index bfb5defaa..1c94433c0 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -81,11 +81,11 @@ dep_libweston = declare_dependency( ) pkgconfig.generate( + lib_weston, filebase: 'libweston-@0@'.format(libweston_major), name: 'libweston API', version: version_weston, description: 'Header files for libweston compositors development', - libraries: lib_weston, requires_private: [ dep_wayland_server, dep_pixman, dep_xkbcommon ], subdirs: dir_include_libweston ) From 91d0f08bcecb1b0676c856922f67df86c7785a63 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 14 Jan 2019 19:44:45 +0200 Subject: [PATCH 0743/1642] meson: remind about weston-launch suid Since the Meson install step is not written to try to set the suid bit automatically, remind the user that weston-launch needs to be setuid-root to work. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libweston/meson.build b/libweston/meson.build index 1c94433c0..0e43a3fb9 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -476,4 +476,6 @@ if get_option('weston-launch') include_directories: include_directories('..'), install: true ) + + meson.add_install_script('echo', 'REMINDER: You are installing weston-launch, please make it setuid-root.') endif From b6dae6caa3f37b6fd37aaf8d57b79afc6392f17b Mon Sep 17 00:00:00 2001 From: Eric Toombs <3672-ewtoombs@users.noreply.gitlab.freedesktop.org> Date: Thu, 31 Jan 2019 17:43:17 -0500 Subject: [PATCH 0744/1642] man: fix small typo: directroy Signed-off-by: Eric Toombs <3672-ewtoombs@users.noreply.gitlab.freedesktop.org> --- man/weston.ini.man | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/weston.ini.man b/man/weston.ini.man index 7df6b1190..c81032ce4 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -194,7 +194,7 @@ specifies a plugin for remote output to load (string). This can be used to load your own implemented remoting plugin or one with Weston as default. Available remoting plugins in the .IR "__libweston_modules_dir__" -directroy are: +directory are: .PP .RS 10 .nf From 9d2220380a3bce0cb19372c94332d8f35969f86c Mon Sep 17 00:00:00 2001 From: Eric Toombs <3672-ewtoombs@users.noreply.gitlab.freedesktop.org> Date: Thu, 31 Jan 2019 18:24:04 -0500 Subject: [PATCH 0745/1642] weston: deprecate enable_tap in favour of enable-tap This is to increase consistency in config option naming in weston.ini. (Prefer hyphens over underscores to separate words.) If enable_tap is present in weston.ini, an obnoxious error message is logged with weston_log(). In terms of configuration, if enable-tap is present, enable_tap is ignored. Signed-off-by: Eric Toombs <3672-ewtoombs@users.noreply.gitlab.freedesktop.org> --- compositor/main.c | 24 +++++++++++++++--------- man/weston.ini.man | 2 +- weston.ini.in | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index b4fdbfe9d..6fdb6bae1 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1464,21 +1464,27 @@ configure_input_device(struct weston_compositor *compositor, { struct weston_config_section *s; struct weston_config *config = wet_get_config(compositor); + int has_enable_tap = 0; int enable_tap; - int enable_tap_default; s = weston_config_get_section(config, "libinput", NULL, NULL); if (libinput_device_config_tap_get_finger_count(device) > 0) { - enable_tap_default = - libinput_device_config_tap_get_default_enabled( - device); - weston_config_section_get_bool(s, "enable_tap", - &enable_tap, - enable_tap_default); - libinput_device_config_tap_set_enabled(device, - enable_tap); + if (weston_config_section_get_bool(s, "enable_tap", + &enable_tap, 0) == 0) { + weston_log("!!DEPRECATION WARNING!!: In weston.ini, " + "enable_tap is deprecated in favour of " + "enable-tap. Support for it may be removed " + "at any time!"); + has_enable_tap = 1; + } + if (weston_config_section_get_bool(s, "enable-tap", + &enable_tap, 0) == 0) + has_enable_tap = 1; + if (has_enable_tap) + libinput_device_config_tap_set_enabled(device, + enable_tap); } } diff --git a/man/weston.ini.man b/man/weston.ini.man index c81032ce4..dea80ca07 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -216,7 +216,7 @@ backend. .PP Available configuration are: .TP 7 -.BI "enable_tap=" true +.BI "enable-tap=" true enables tap to click on touchpad devices .TP 7 .BI "touchscreen_calibrator=" true diff --git a/weston.ini.in b/weston.ini.in index 257c4ec4c..1a9acee41 100644 --- a/weston.ini.in +++ b/weston.ini.in @@ -60,7 +60,7 @@ path=@libexecdir@/weston-keyboard #transform=flipped-90 #[libinput] -#enable_tap=true +#enable-tap=true #[touchpad] #constant_accel_factor = 50 From 6e229ca26381bc8191fd9af1e439c311da709aff Mon Sep 17 00:00:00 2001 From: Eric Toombs <3672-ewtoombs@users.noreply.gitlab.freedesktop.org> Date: Thu, 31 Jan 2019 21:53:25 -0500 Subject: [PATCH 0746/1642] weston: add more libinput config options This is so that, for instance, people using weston as their main Wayland compositor can invert the sense of two finger scrolling or change pointer acceleration using weston.ini, rather than having to edit C code. All of the options that libinput itself exposes through its API are now exposed in weston.ini. The new options are called `tap-and-drag`, `tap-and-drag-lock`, `disable-while-typing`, `middle-emulation`, `left-handed`, `rotation`, `accel-profile`, `accel-speed`, `scroll-method`, `natural-scroll`, and `scroll-button`. I have successfully tested everything except for `rotation`, out of a lack of hardware support. weston now depends directly on libevdev for turning button name strings into kernel input codes. This was needed for the `scroll-button` config option. (weston already depends indirectly on libevdev through libinput, so I figured people would be OK with this.) As a practical matter for debian-style packagers, weston now has a build dependency on libevdev-dev. Right now, the code applies the same options to all attached devices that a given option is relevant for. There are plans for multiple [libinput] sections, each with different device filters, for users who need more control here. Signed-off-by: Eric Toombs <3672-ewtoombs@users.noreply.gitlab.freedesktop.org> --- .gitlab-ci.yml | 2 +- Makefile.am | 3 +- compositor/main.c | 176 ++++++++++++++++++++++++++++++++++++++++- compositor/meson.build | 1 + configure.ac | 1 + man/weston.ini.man | 74 ++++++++++++++++- meson.build | 1 + weston.ini.in | 13 +++ 8 files changed, 265 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c6007d1d8..f4b56d483 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,7 +11,7 @@ before_script: - chmod +x /usr/sbin/policy-rc.d - echo 'deb http://deb.debian.org/debian stretch-backports main' >> /etc/apt/sources.list - apt-get update - - apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev freerdp2-dev curl python3-pip python3-setuptools ninja-build + - apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libevdev-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev freerdp2-dev curl python3-pip python3-setuptools ninja-build - pip3 install --user git+https://github.com/mesonbuild/meson.git@0.49 - mkdir -p /tmp/.X11-unix - chmod 777 /tmp/.X11-unix diff --git a/Makefile.am b/Makefile.am index 6036dbf51..24561a58b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -187,12 +187,13 @@ weston_LDFLAGS = -export-dynamic weston_CPPFLAGS = $(AM_CPPFLAGS) -DMODULEDIR='"$(moduledir)"' \ -DXSERVER_PATH='"@XSERVER_PATH@"' weston_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBINPUT_BACKEND_CFLAGS) \ - $(PTHREAD_CFLAGS) + $(PTHREAD_CFLAGS) $(LIBEVDEV_CFLAGS) weston_LDADD = libshared.la libweston-@LIBWESTON_MAJOR@.la \ $(COMPOSITOR_LIBS) \ $(DL_LIBS) $(LIBINPUT_BACKEND_LIBS) \ $(CLOCK_GETRES_LIBS) \ $(PTHREAD_LIBS) \ + $(LIBEVDEV_LIBS) \ -lm weston_SOURCES = \ diff --git a/compositor/main.c b/compositor/main.c index 6fdb6bae1..34d2e7153 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include #include @@ -1458,6 +1460,114 @@ wet_set_simple_head_configurator(struct weston_compositor *compositor, &wet->heads_changed_listener); } +static void +configure_input_device_accel(struct weston_config_section *s, + struct libinput_device *device) +{ + char *profile_string = NULL; + int is_a_profile = 1; + uint32_t profiles; + enum libinput_config_accel_profile profile; + double speed; + + if (weston_config_section_get_string(s, "accel-profile", + &profile_string, NULL) == 0) { + if (strcmp(profile_string, "flat") == 0) + profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; + else if (strcmp(profile_string, "adaptive") == 0) + profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + else { + weston_log("warning: no such accel-profile: %s\n", + profile_string); + is_a_profile = 0; + } + + profiles = libinput_device_config_accel_get_profiles(device); + if (is_a_profile && (profile & profiles) != 0) { + weston_log(" accel-profile=%s\n", + profile_string); + libinput_device_config_accel_set_profile(device, + profile); + } + } + + if (weston_config_section_get_double(s, "accel-speed", + &speed, 0) == 0 && + speed >= -1. && speed <= 1.) { + weston_log(" accel-speed=%.3f\n", speed); + libinput_device_config_accel_set_speed(device, speed); + } + + free(profile_string); +} + +static void +configure_input_device_scroll(struct weston_config_section *s, + struct libinput_device *device) +{ + int natural; + char *method_string = NULL; + uint32_t methods; + enum libinput_config_scroll_method method; + char *button_string = NULL; + int button; + + if (libinput_device_config_scroll_has_natural_scroll(device) && + weston_config_section_get_bool(s, "natural-scroll", + &natural, 0) == 0) { + weston_log(" natural-scroll=%s\n", + natural ? "true" : "false"); + libinput_device_config_scroll_set_natural_scroll_enabled( + device, natural); + } + + if (weston_config_section_get_string(s, "scroll-method", + &method_string, NULL) != 0) + goto done; + if (strcmp(method_string, "two-finger") == 0) + method = LIBINPUT_CONFIG_SCROLL_2FG; + else if (strcmp(method_string, "edge") == 0) + method = LIBINPUT_CONFIG_SCROLL_EDGE; + else if (strcmp(method_string, "button") == 0) + method = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; + else if (strcmp(method_string, "none") == 0) + method = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + else { + weston_log("warning: no such scroll-method: %s\n", + method_string); + goto done; + } + + methods = libinput_device_config_scroll_get_methods(device); + if (method != LIBINPUT_CONFIG_SCROLL_NO_SCROLL && + (method & methods) == 0) + goto done; + + weston_log(" scroll-method=%s\n", method_string); + libinput_device_config_scroll_set_method(device, method); + + if (method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) { + if (weston_config_section_get_string(s, "scroll-button", + &button_string, + NULL) != 0) + goto done; + + button = libevdev_event_code_from_name(EV_KEY, button_string); + if (button == -1) { + weston_log(" Bad scroll-button: %s\n", + button_string); + goto done; + } + + weston_log(" scroll-button=%s\n", button_string); + libinput_device_config_scroll_set_button(device, button); + } + +done: + free(method_string); + free(button_string); +} + static void configure_input_device(struct weston_compositor *compositor, struct libinput_device *device) @@ -1466,6 +1576,15 @@ configure_input_device(struct weston_compositor *compositor, struct weston_config *config = wet_get_config(compositor); int has_enable_tap = 0; int enable_tap; + int disable_while_typing; + int middle_emulation; + int tap_and_drag; + int tap_and_drag_lock; + int left_handed; + unsigned int rotation; + + weston_log("libinput: configuring device \"%s\".\n", + libinput_device_get_name(device)); s = weston_config_get_section(config, "libinput", NULL, NULL); @@ -1482,10 +1601,65 @@ configure_input_device(struct weston_compositor *compositor, if (weston_config_section_get_bool(s, "enable-tap", &enable_tap, 0) == 0) has_enable_tap = 1; - if (has_enable_tap) + if (has_enable_tap) { + weston_log(" enable-tap=%s.\n", + enable_tap ? "true" : "false"); libinput_device_config_tap_set_enabled(device, enable_tap); + } + if (weston_config_section_get_bool(s, "tap-and-drag", + &tap_and_drag, 0) == 0) { + weston_log(" tap-and-drag=%s.\n", + tap_and_drag ? "true" : "false"); + libinput_device_config_tap_set_drag_enabled(device, + tap_and_drag); + } + if (weston_config_section_get_bool(s, "tap-and-drag-lock", + &tap_and_drag_lock, 0) == 0) { + weston_log(" tap-and-drag-lock=%s.\n", + tap_and_drag_lock ? "true" : "false"); + libinput_device_config_tap_set_drag_lock_enabled( + device, tap_and_drag_lock); + } + } + + if (libinput_device_config_dwt_is_available(device) && + weston_config_section_get_bool(s, "disable-while-typing", + &disable_while_typing, 0) == 0) { + weston_log(" disable-while-typing=%s.\n", + disable_while_typing ? "true" : "false"); + libinput_device_config_dwt_set_enabled(device, + disable_while_typing); } + + if (libinput_device_config_middle_emulation_is_available(device) && + weston_config_section_get_bool(s, "middle-button-emulation", + &middle_emulation, 0) == 0) { + weston_log(" middle-button-emulation=%s\n", + middle_emulation ? "true" : "false"); + libinput_device_config_middle_emulation_set_enabled( + device, middle_emulation); + } + + if (libinput_device_config_left_handed_is_available(device) && + weston_config_section_get_bool(s, "left-handed", + &left_handed, 0) == 0) { + weston_log(" left-handed=%s\n", + left_handed ? "true" : "false"); + libinput_device_config_left_handed_set(device, left_handed); + } + + if (libinput_device_config_rotation_is_available(device) && + weston_config_section_get_uint(s, "rotation", + &rotation, 0) == 0) { + weston_log(" rotation=%u\n", rotation); + libinput_device_config_rotation_set_angle(device, rotation); + } + + if (libinput_device_config_accel_is_available(device)) + configure_input_device_accel(s, device); + + configure_input_device_scroll(s, device); } static int diff --git a/compositor/meson.build b/compositor/meson.build index 88bea8d84..994b5877f 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -14,6 +14,7 @@ deps_weston = [ dep_libshared, dep_libweston, dep_libinput, + dep_libevdev, dep_libdl, dep_threads, ] diff --git a/configure.ac b/configure.ac index 225d6692d..00066eaec 100644 --- a/configure.ac +++ b/configure.ac @@ -235,6 +235,7 @@ if test x$enable_remoting = xyes; then fi +PKG_CHECK_MODULES(LIBEVDEV, [libevdev]) PKG_CHECK_MODULES(LIBINPUT_BACKEND, [libinput >= 0.8.0]) PKG_CHECK_MODULES(COMPOSITOR, [$COMPOSITOR_MODULES]) diff --git a/man/weston.ini.man b/man/weston.ini.man index dea80ca07..5c0f1097e 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -212,12 +212,80 @@ There is also a command line option to do the same. The .B libinput section is used to configure input devices when using the libinput input device -backend. +backend. The defaults are determined by libinput and vary according to what is +most sensible for any given device. .PP Available configuration are: .TP 7 -.BI "enable-tap=" true -enables tap to click on touchpad devices +.BI "enable-tap=" false +Enables tap to click on touchpad devices. +.TP 7 +.BI "tap-and-drag=" false +For touchpad devices with \fBenable-tap\fR enabled. If the user taps, then +taps a second time, this time holding, the virtual mouse button stays down for +as long as the user keeps their finger on the touchpad, allowing the user to +click and drag with taps alone. +.TP 7 +.BI "tap-and-drag-lock=" false +For touchpad devices with \fBenable-tap\fR and \fBtap-and-drag\fR enabled. +In the middle of a tap-and-drag, if the user releases the touchpad for less +than a certain number of milliseconds, then touches it again, the virtual mouse +button will remain pressed and the drag can continue. +.TP 7 +.BI "disable-while-typing=" true +For devices that may be accidentally triggered while typing on the keyboard, +causing a disruption of the typing. Disables them while the keyboard is in +use. +.TP 7 +.BI "middle-button-emulation=" false +For pointer devices with left and right buttons, but no middle button. When +enabled, a middle button event is emitted when the left and right buttons are +pressed simultaneously. +.TP 7 +.BI "left-handed=" false +Configures the device for use by left-handed people. Exactly what this option +does depends on the device. For pointers with left and right buttons, the +buttons are swapped. On tablets, the tablet is logically turned upside down, +because it will be physically turned upside down. +.TP 7 +.BI "rotation=" n +Changes the direction of the logical north, rotating it \fIn\fR degrees +clockwise away from the default orientation, where \fIn\fR is a whole +number between 0 and 359 inclusive. Needed for trackballs, mainly. Allows the +user to orient the trackball sideways, for example. +.TP 7 +.BI "accel-profile=" "{flat,adaptive}" +Set the pointer acceleration profile. The pointer's screen speed is +proportional to the physical speed with a certain constant of proportionality. +Call that constant alpha. \fIflat\fR keeps alpha fixed. See \fBaccel-speed\fR. +\fIadaptive\fR causes alpha to increase with physical speed, giving the user +more control when the speed is slow, and more reach when the speed is high. +\fIadaptive\fR is the default. +.TP 7 +.BI "accel-speed=" v +If \fBaccel-profile\fR is set to \fIflat\fR, it simply sets the value of alpha. +If \fBaccel-profile\fR is set to \fIadaptive\fR, the effect is more +complicated, but generally speaking, it will change the pointer's speed. +\fIv\fR is normalised and must lie in the range [-1, 1]. The exact mapping +between \fIv\fR and alpha is hardware-dependent, but higher values cause higher +cursor speeds. +.TP 7 +.BI "natural-scroll=" false +Enables natural scrolling, mimicking the behaviour of touchscreen scrolling. +That is, if the wheel, finger, or fingers are moved down, the surface is +scrolled up instead of down, as if the finger, or fingers were in contact with +the surface being scrolled. +.TP 7 +.BI "scroll-method=" {two-finger,edge,button,none} +Sets the scroll method. \fItwo-finger\fR scrolls with two fingers on a +touchpad. \fIedge\fR scrolls with one finger on the right edge of a touchpad. +\fIbutton\fR scrolls when the pointer is moved while a certain button is +pressed. See \fBscroll-button\fR. \fInone\fR disables scrolling altogether. +.TP 7 +.BI "scroll-button=" {BTN_LEFT,BTN_RIGHT,BTN_MIDDLE,...} +For devices with \fBscroll-method\fR set to \fIbutton\fR. Specifies the +button that will trigger scrolling. See /usr/include/linux/input-event-codes.h +for the complete list of possible values. .TP 7 .BI "touchscreen_calibrator=" true Advertise the touchscreen calibrator interface to all clients. This is a diff --git a/meson.build b/meson.build index 7826dbb03..12fa7433a 100644 --- a/meson.build +++ b/meson.build @@ -143,6 +143,7 @@ dep_wayland_server = dependency('wayland-server', version: '>= 1.12.0') dep_wayland_client = dependency('wayland-client', version: '>= 1.12.0') dep_pixman = dependency('pixman-1', version: '>= 0.25.2') dep_libinput = dependency('libinput', version: '>= 0.8.0') +dep_libevdev = dependency('libevdev') dep_libm = cc.find_library('m') dep_libdl = cc.find_library('dl') dep_libdrm = dependency('libdrm', version: '>= 2.4.68') diff --git a/weston.ini.in b/weston.ini.in index 1a9acee41..846ef746b 100644 --- a/weston.ini.in +++ b/weston.ini.in @@ -61,6 +61,19 @@ path=@libexecdir@/weston-keyboard #[libinput] #enable-tap=true +#tap-and-drag=true +#tap-and-drag-lock=true +#disable-while-typing=false +#middle-button-emulation=true +#left-handed=true +#rotation=90 +#accel-profile=flat +#accel-speed=.9 +#natural-scroll=true +#scroll-method=edge +# For button-triggered scrolling: +#scroll-method=button +#scroll-button=BTN_RIGHT #[touchpad] #constant_accel_factor = 50 From 052032d7306ca745670547870dfe0bdce65647af Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 6 Feb 2019 10:44:37 +0200 Subject: [PATCH 0747/1642] desktop-shell: use weston_compositor_exit Use the proper weston_compositor_exit API instead of wl_display_terminate() to allow the compositor main to prepare for exit, and most importantly to set the exit error code as appropriate. I have some brokenness in my test suite running, and weston-desktop-shell was crashing at start, yet the tests did not notice. With this patch, the tests where the helper crashes are properly marked as failed. Signed-off-by: Pekka Paalanen --- desktop-shell/shell.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 2d6d7c203..809f31c8c 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -3536,7 +3536,7 @@ terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, { struct weston_compositor *compositor = data; - wl_display_terminate(compositor->wl_display); + weston_compositor_exit(compositor); } static void @@ -4289,7 +4289,8 @@ check_desktop_shell_crash_too_early(struct desktop_shell *shell) weston_log("Error: %s apparently cannot run at all.\n", shell->client); weston_log_continue(STAMP_SPACE "Quitting..."); - wl_display_terminate(shell->compositor->wl_display); + weston_compositor_exit_with_code(shell->compositor, + EXIT_FAILURE); return true; } From 818c20e78bc1ab7d3a5cd58f14a0a66498fde7ba Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 6 Feb 2019 10:48:51 +0200 Subject: [PATCH 0748/1642] ivi-shell: use weston_compositor_exit Use the proper function to exit instead of the libwayland one, to allow main handle_exit() to be called. This is just to unify the exit paths. Signed-off-by: Pekka Paalanen --- ivi-shell/ivi-shell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 54f35ac93..54ab04193 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -369,7 +369,7 @@ terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, { struct weston_compositor *compositor = data; - wl_display_terminate(compositor->wl_display); + weston_compositor_exit(compositor); } static void From a37920e77c1ca8bb5252da38e165cc0da2acadf1 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 6 Feb 2019 10:53:35 +0200 Subject: [PATCH 0749/1642] tests: use weston_compositor_exit Use the proper function to exit instead of the libwayland one, to allow main handle_exit() to be called. This is just to unify the exit paths. Signed-off-by: Pekka Paalanen --- tests/plugin-registry-test.c | 2 +- tests/surface-global-test.c | 2 +- tests/surface-test.c | 2 +- tests/weston-test.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/plugin-registry-test.c b/tests/plugin-registry-test.c index 81e26cd8b..738ccd315 100644 --- a/tests/plugin-registry-test.c +++ b/tests/plugin-registry-test.c @@ -85,7 +85,7 @@ runtime_tests(void *data) api = weston_plugin_api_get(compositor, MY_API_NAME, sz); assert(api && api->func2 == dummy_func); - wl_display_terminate(compositor->wl_display); + weston_compositor_exit(compositor); } WL_EXPORT int diff --git a/tests/surface-global-test.c b/tests/surface-global-test.c index 4604d19dc..59d4152db 100644 --- a/tests/surface-global-test.c +++ b/tests/surface-global-test.c @@ -76,7 +76,7 @@ surface_to_from_global(void *data) weston_view_from_global(view, 5, 10, &ix, &iy); assert(ix == 0 && iy == 0); - wl_display_terminate(compositor->wl_display); + weston_compositor_exit(compositor); } WL_EXPORT int diff --git a/tests/surface-test.c b/tests/surface-test.c index 28520aaa2..0661cc950 100644 --- a/tests/surface-test.c +++ b/tests/surface-test.c @@ -57,7 +57,7 @@ surface_transform(void *data) weston_view_to_global_float(view, 50, 40, &x, &y); assert(x == 200 && y == 340); - wl_display_terminate(compositor->wl_display); + weston_compositor_exit(compositor); } WL_EXPORT int diff --git a/tests/weston-test.c b/tests/weston-test.c index e38aff4c5..12ef54ee5 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -72,7 +72,7 @@ test_client_sigchld(struct weston_process *process, int status) /* In case the child aborted or segfaulted... */ assert(status == 0); - wl_display_terminate(test->compositor->wl_display); + weston_compositor_exit(test->compositor); } static void From 27d7c395c7600e9bf6b13dd8b3d06ee19aab1aba Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 19 Oct 2018 12:14:11 +0300 Subject: [PATCH 0750/1642] libweston: Introduce zwp_linux_explicit_synchronization_v1 Introduce support for the zwp_linux_explicit_synchronization_unstable_v1 protocol with an implementation of the zwp_linux_explicit_synchronization_v1 interface. Explicit synchronization provides a more versatile notification mechanism for buffer readiness and availability, and can be used to improve efficiency by integrating with related functionality in display and graphics APIs. In addition, the per-commit nature of the release events provided by this protocol potentially offers a solution to a deficiency of the wl_buffer.release event (see https://gitlab.freedesktop.org/wayland/wayland/issues/46). Support for this protocol depends on the capabilities of the backend, so we don't register it by default but provide a function which each backend will need to call. In this commit only the headless backend when using the noop renderer supports this to enable testing. Note that the zwp_surface_synchronization_v1 interface, which contains the core functionality of the protocol, is not implemented in this commit. Support for it will be added in future commits. Changes in v7: - Added some information in the commit message about the benefits of the explicit sync protocol. Changes in v6: - Fall back to advertising minor version 1 of the explicit sync protocol, although we support minor version 2 features, until the new wayland-protocols version is released. Changes in v5: - Meson support. - Advertise minor version 2 of the explicit sync protocol. Changes in v4: - Enable explicit sync support in the headless backend for all renderers. Changes in v3: - Use wl_resource_get_version() instead of hardcoding version 1. - Use updated protocol interface names. - Use correct format specifier for resource id. - Change test name to 'linux-explicit-synchronization.weston' (s/_/-/g). Changes in v2: - Move implementation to separate file so protocol can be registered on demand by backends. - Register protocol in headless+noop backend for testing purposes. Signed-off-by: Alexandros Frantzis --- Makefile.am | 21 ++- configure.ac | 5 +- libweston/compositor-headless.c | 6 + libweston/compositor.c | 5 + libweston/compositor.h | 3 + libweston/linux-explicit-synchronization.c | 164 ++++++++++++++++++++ libweston/linux-explicit-synchronization.h | 34 ++++ libweston/meson.build | 3 + protocol/meson.build | 6 +- tests/linux-explicit-synchronization-test.c | 96 ++++++++++++ tests/meson.build | 7 + 11 files changed, 345 insertions(+), 5 deletions(-) create mode 100644 libweston/linux-explicit-synchronization.c create mode 100644 libweston/linux-explicit-synchronization.h create mode 100644 tests/linux-explicit-synchronization-test.c diff --git a/Makefile.am b/Makefile.am index 24561a58b..358dd7bb0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -94,6 +94,8 @@ libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ libweston/timeline-object.h \ libweston/linux-dmabuf.c \ libweston/linux-dmabuf.h \ + libweston/linux-explicit-synchronization.c \ + libweston/linux-explicit-synchronization.h \ libweston/pixel-formats.c \ libweston/pixel-formats.h \ libweston/weston-debug.c \ @@ -177,7 +179,9 @@ nodist_libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ protocol/input-timestamps-unstable-v1-protocol.c \ protocol/input-timestamps-unstable-v1-server-protocol.h \ protocol/weston-touch-calibration-protocol.c \ - protocol/weston-touch-calibration-server-protocol.h + protocol/weston-touch-calibration-server-protocol.h \ + protocol/linux-explicit-synchronization-unstable-v1-protocol.c \ + protocol/linux-explicit-synchronization-unstable-v1-server-protocol.h BUILT_SOURCES += $(nodist_libweston_@LIBWESTON_MAJOR@_la_SOURCES) @@ -968,7 +972,9 @@ BUILT_SOURCES += \ protocol/input-timestamps-unstable-v1-protocol.c \ protocol/input-timestamps-unstable-v1-client-protocol.h \ protocol/xdg-output-unstable-v1-protocol.c \ - protocol/xdg-output-unstable-v1-client-protocol.h + protocol/xdg-output-unstable-v1-client-protocol.h \ + protocol/linux-explicit-synchronization-unstable-v1-protocol.c \ + protocol/linux-explicit-synchronization-unstable-v1-client-protocol.h westondatadir = $(datadir)/weston dist_westondata_DATA = \ @@ -1308,7 +1314,8 @@ weston_tests = \ subsurface.weston \ subsurface-shot.weston \ devices.weston \ - touch.weston + touch.weston \ + linux-explicit-synchronization.weston AM_TESTS_ENVIRONMENT = \ abs_builddir='$(abs_builddir)'; export abs_builddir; \ @@ -1502,6 +1509,14 @@ touch_weston_SOURCES = tests/touch-test.c touch_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) touch_weston_LDADD = libtest-client.la +linux_explicit_synchronization_weston_SOURCES = \ + tests/linux-explicit-synchronization-test.c +nodist_linux_explicit_synchronization_weston_SOURCES = \ + protocol/linux-explicit-synchronization-unstable-v1-protocol.c \ + protocol/linux-explicit-synchronization-unstable-v1-client-protocol.h +linux_explicit_synchronization_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) +linux_explicit_synchronization_weston_LDADD = libtest-client.la + if ENABLE_XWAYLAND_TEST weston_tests += xwayland-test.weston xwayland_test_weston_SOURCES = tests/xwayland-test.c diff --git a/configure.ac b/configure.ac index 00066eaec..cb8b0a5b3 100644 --- a/configure.ac +++ b/configure.ac @@ -239,7 +239,10 @@ PKG_CHECK_MODULES(LIBEVDEV, [libevdev]) PKG_CHECK_MODULES(LIBINPUT_BACKEND, [libinput >= 0.8.0]) PKG_CHECK_MODULES(COMPOSITOR, [$COMPOSITOR_MODULES]) -PKG_CHECK_MODULES(WAYLAND_PROTOCOLS, [wayland-protocols >= 1.14], +# XXX: For minor version 2 of zwp_linux_explicit_synchronization_v1, we +# actually need a development version after 1.17, but there is no way to +# express such a requirement at the moment. +PKG_CHECK_MODULES(WAYLAND_PROTOCOLS, [wayland-protocols >= 1.17], [ac_wayland_protocols_pkgdatadir=`$PKG_CONFIG --variable=pkgdatadir wayland-protocols`]) AC_SUBST(WAYLAND_PROTOCOLS_DATADIR, $ac_wayland_protocols_pkgdatadir) diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index 61a5bd93e..5a0e46c8d 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -36,6 +36,7 @@ #include "compositor.h" #include "compositor-headless.h" #include "shared/helpers.h" +#include "linux-explicit-synchronization.h" #include "pixman-renderer.h" #include "presentation-time-server-protocol.h" #include "windowed-output-api.h" @@ -339,6 +340,11 @@ headless_backend_create(struct weston_compositor *compositor, if (!b->use_pixman && noop_renderer_init(compositor) < 0) goto err_input; + /* Support zwp_linux_explicit_synchronization_unstable_v1 to enable + * testing. */ + if (linux_explicit_synchronization_setup(compositor) < 0) + goto err_input; + ret = weston_plugin_api_register(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, &api, sizeof(api)); diff --git a/libweston/compositor.c b/libweston/compositor.c index 3ef472e3b..14033cbd8 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2035,6 +2035,11 @@ destroy_surface(struct wl_resource *resource) if (surface->viewport_resource) wl_resource_set_user_data(surface->viewport_resource, NULL); + if (surface->synchronization_resource) { + wl_resource_set_user_data(surface->synchronization_resource, + NULL); + } + weston_surface_destroy(surface); } diff --git a/libweston/compositor.h b/libweston/compositor.h index b4b751977..3f2ad01e1 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1486,6 +1486,9 @@ struct weston_surface { /* An list of per seat pointer constraints. */ struct wl_list pointer_constraints; + + /* zwp_surface_synchronization_v1 resource for this surface */ + struct wl_resource *synchronization_resource; }; struct weston_subsurface { diff --git a/libweston/linux-explicit-synchronization.c b/libweston/linux-explicit-synchronization.c new file mode 100644 index 000000000..c42b8aa35 --- /dev/null +++ b/libweston/linux-explicit-synchronization.c @@ -0,0 +1,164 @@ +/* + * Copyright © 2018 Collabora, Ltd. + * + * 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 "compositor.h" +#include "linux-explicit-synchronization.h" +#include "linux-explicit-synchronization-unstable-v1-server-protocol.h" + +static void +destroy_linux_surface_synchronization(struct wl_resource *resource) +{ + struct weston_surface *surface = + wl_resource_get_user_data(resource); + + if (surface) + surface->synchronization_resource = NULL; +} + +static void +linux_surface_synchronization_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +linux_surface_synchronization_set_acquire_fence(struct wl_client *client, + struct wl_resource *resource, + int32_t fd) +{ + wl_client_post_no_memory(client); +} + +static void +linux_surface_synchronization_get_release(struct wl_client *client, + struct wl_resource *resource, + uint32_t id) +{ + wl_client_post_no_memory(client); +} + +const struct zwp_linux_surface_synchronization_v1_interface +linux_surface_synchronization_implementation = { + linux_surface_synchronization_destroy, + linux_surface_synchronization_set_acquire_fence, + linux_surface_synchronization_get_release, +}; + +static void +linux_explicit_synchronization_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +linux_explicit_synchronization_get_synchronization(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource) +{ + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + + if (surface->synchronization_resource) { + wl_resource_post_error( + resource, + ZWP_LINUX_EXPLICIT_SYNCHRONIZATION_V1_ERROR_SYNCHRONIZATION_EXISTS, + "wl_surface@%"PRIu32" already has a synchronization object", + wl_resource_get_id(surface_resource)); + return; + } + + surface->synchronization_resource = + wl_resource_create(client, + &zwp_linux_surface_synchronization_v1_interface, + wl_resource_get_version(resource), id); + if (!surface->synchronization_resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(surface->synchronization_resource, + &linux_surface_synchronization_implementation, + surface, + destroy_linux_surface_synchronization); +} + +static const struct zwp_linux_explicit_synchronization_v1_interface +linux_explicit_synchronization_implementation = { + linux_explicit_synchronization_destroy, + linux_explicit_synchronization_get_synchronization +}; + +static void +bind_linux_explicit_synchronization(struct wl_client *client, + void *data, uint32_t version, + uint32_t id) +{ + struct weston_compositor *compositor = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, + &zwp_linux_explicit_synchronization_v1_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, + &linux_explicit_synchronization_implementation, + compositor, NULL); +} + +/** Advertise linux_explicit_synchronization support + * + * Calling this initializes the zwp_linux_explicit_synchronization_v1 + * protocol support, so that the interface will be advertised to clients. + * Essentially it creates a global. Do not call this function multiple times + * in the compositor's lifetime. There is no way to deinit explicitly, globals + * will be reaped when the wl_display gets destroyed. + * + * \param compositor The compositor to init for. + * \return Zero on success, -1 on failure. + */ +WL_EXPORT int +linux_explicit_synchronization_setup(struct weston_compositor *compositor) +{ + /* TODO: Update to minor version 2 when the next version of + * wayland-protocols that contains it is released. */ + if (!wl_global_create(compositor->wl_display, + &zwp_linux_explicit_synchronization_v1_interface, + 1, compositor, + bind_linux_explicit_synchronization)) + return -1; + + return 0; +} diff --git a/libweston/linux-explicit-synchronization.h b/libweston/linux-explicit-synchronization.h new file mode 100644 index 000000000..96edbbb63 --- /dev/null +++ b/libweston/linux-explicit-synchronization.h @@ -0,0 +1,34 @@ +/* + * Copyright © 2018 Collabora, Ltd. + * + * 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. + */ + +#ifndef WESTON_LINUX_EXPLICIT_SYNCHRONIZATION_H +#define WESTON_LINUX_EXPLICIT_SYNCHRONIZATION_H + +struct weston_compositor; + +int +linux_explicit_synchronization_setup(struct weston_compositor *compositor); + +#endif /* WESTON_LINUX_EXPLICIT_SYNCHRONIZATION */ diff --git a/libweston/meson.build b/libweston/meson.build index 0e43a3fb9..64efdd95a 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -16,6 +16,7 @@ srcs_libweston = [ 'data-device.c', 'input.c', 'linux-dmabuf.c', + 'linux-explicit-synchronization.c', 'log.c', 'noop-renderer.c', 'pixel-formats.c', @@ -29,6 +30,8 @@ srcs_libweston = [ '../shared/matrix.c', linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_server_protocol_h, + linux_explicit_synchronization_unstable_v1_protocol_c, + linux_explicit_synchronization_unstable_v1_server_protocol_h, input_method_unstable_v1_protocol_c, input_method_unstable_v1_server_protocol_h, input_timestamps_unstable_v1_protocol_c, diff --git a/protocol/meson.build b/protocol/meson.build index a947eeab8..b3ea5b214 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,7 +1,10 @@ dep_scanner = dependency('wayland-scanner', native: true) prog_scanner = find_program(dep_scanner.get_pkgconfig_variable('wayland_scanner')) -dep_wp = dependency('wayland-protocols', version: '>= 1.14') +# XXX: For minor version 2 of zwp_linux_explicit_synchronization_v1, we +# actually need a development version after 1.17, but there is no way to +# express such a requirement at the moment. +dep_wp = dependency('wayland-protocols', version: '>= 1.17') dir_wp_base = dep_wp.get_pkgconfig_variable('pkgdatadir') install_data( @@ -18,6 +21,7 @@ generated_protocols = [ [ 'ivi-hmi-controller', 'internal' ], [ 'fullscreen-shell', 'v1' ], [ 'linux-dmabuf', 'v1' ], + [ 'linux-explicit-synchronization', 'v1' ], [ 'presentation-time', 'stable' ], [ 'pointer-constraints', 'v1' ], [ 'relative-pointer', 'v1' ], diff --git a/tests/linux-explicit-synchronization-test.c b/tests/linux-explicit-synchronization-test.c new file mode 100644 index 000000000..62a54e0d4 --- /dev/null +++ b/tests/linux-explicit-synchronization-test.c @@ -0,0 +1,96 @@ +/* + * Copyright © 2018 Collabora, Ltd. + * + * 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 "linux-explicit-synchronization-unstable-v1-client-protocol.h" +#include "weston-test-client-helper.h" +#include "wayland-server-protocol.h" + +static struct zwp_linux_explicit_synchronization_v1 * +get_linux_explicit_synchronization(struct client *client) +{ + struct global *g; + struct global *global_sync = NULL; + struct zwp_linux_explicit_synchronization_v1 *sync = NULL; + + wl_list_for_each(g, &client->global_list, link) { + if (strcmp(g->interface, + zwp_linux_explicit_synchronization_v1_interface.name)) + continue; + + if (global_sync) + assert(!"Multiple linux explicit sync objects"); + + global_sync = g; + } + + assert(global_sync); + assert(global_sync->version == 1); + + sync = wl_registry_bind( + client->wl_registry, global_sync->name, + &zwp_linux_explicit_synchronization_v1_interface, 1); + assert(sync); + + return sync; +} + +static struct client * +create_test_client(void) +{ + struct client *cl = create_client_and_test_surface(0, 0, 100, 100); + assert(cl); + return cl; +} + +TEST(second_surface_synchronization_on_surface_raises_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync1; + struct zwp_linux_surface_synchronization_v1 *surface_sync2; + + surface_sync1 = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + client_roundtrip(client); + + /* Second surface_synchronization creation should fail */ + surface_sync2 = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + expect_protocol_error( + client, + &zwp_linux_explicit_synchronization_v1_interface, + ZWP_LINUX_EXPLICIT_SYNCHRONIZATION_V1_ERROR_SYNCHRONIZATION_EXISTS); + + zwp_linux_surface_synchronization_v1_destroy(surface_sync2); + zwp_linux_surface_synchronization_v1_destroy(surface_sync1); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} diff --git a/tests/meson.build b/tests/meson.build index ebd3872aa..7baee5122 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -127,6 +127,13 @@ tests_weston = [ input_timestamps_unstable_v1_protocol_c, ] ], + [ + 'linux-explicit-synchronization', + [ + linux_explicit_synchronization_unstable_v1_client_protocol_h, + linux_explicit_synchronization_unstable_v1_protocol_c, + ] + ], ['internal-screenshot'], [ 'presentation', From c0e2f9261ff4653ed8f530f38e71c62043f4c1d9 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 19 Oct 2018 12:14:11 +0300 Subject: [PATCH 0751/1642] libweston: Introduce an internal linux sync file API Introduce an internal API for dealing with linux sync files, and use it in the codebase to replace ad-hoc sync file management. The linux_sync_file_is_valid function is not currently used, but will be utilized in upcoming commits to implement the zwp_linux_explicit_synchronization_unstable_v1 protocol. Changes in v5: - Meson support. Changes in v3: - Use parameter name in function documentation. - Move kernel UAPI to separate header file. Changes in v2: - Add function documentation - Remove linux_sync_file_wait() Signed-off-by: Alexandros Frantzis --- Makefile.am | 4 +- libweston/gl-renderer.c | 34 +------- ...ton-sync-file.h => linux-sync-file-uapi.h} | 6 +- libweston/linux-sync-file.c | 82 +++++++++++++++++++ libweston/linux-sync-file.h | 38 +++++++++ libweston/meson.build | 1 + 6 files changed, 130 insertions(+), 35 deletions(-) rename libweston/{weston-sync-file.h => linux-sync-file-uapi.h} (78%) create mode 100644 libweston/linux-sync-file.c create mode 100644 libweston/linux-sync-file.h diff --git a/Makefile.am b/Makefile.am index 358dd7bb0..00e298d6d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -340,7 +340,9 @@ gl_renderer_la_SOURCES = \ libweston/gl-renderer.c \ libweston/vertex-clipping.c \ libweston/vertex-clipping.h \ - libweston/weston-sync-file.h \ + libweston/linux-sync-file.c \ + libweston/linux-sync-file.h \ + libweston/linux-sync-file-uapi.h \ shared/helpers.h endif diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index c9e5a8fc7..1a7577e34 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -40,13 +40,8 @@ #include #include #include -#include -#ifdef HAVE_LINUX_SYNC_FILE_H -#include -#else -#include "weston-sync-file.h" -#endif +#include "linux-sync-file.h" #include "timeline.h" @@ -315,25 +310,6 @@ get_renderer(struct weston_compositor *ec) return (struct gl_renderer *)ec->renderer; } -static int -linux_sync_file_read_timestamp(int fd, uint64_t *ts) -{ - struct sync_file_info file_info = { { 0 } }; - struct sync_fence_info fence_info = { { 0 } }; - - assert(ts != NULL); - - file_info.sync_fence_info = (uint64_t)(uintptr_t)&fence_info; - file_info.num_fences = 1; - - if (ioctl(fd, SYNC_IOC_FILE_INFO, &file_info) < 0) - return -1; - - *ts = fence_info.timestamp_ns; - - return 0; -} - static void timeline_render_point_destroy(struct timeline_render_point *trp) { @@ -351,13 +327,9 @@ timeline_render_point_handler(int fd, uint32_t mask, void *data) "renderer_gpu_begin" : "renderer_gpu_end"; if (mask & WL_EVENT_READABLE) { - uint64_t ts; - - if (linux_sync_file_read_timestamp(trp->fd, &ts) == 0) { - struct timespec tspec = { 0 }; - - timespec_add_nsec(&tspec, &tspec, ts); + struct timespec tspec = { 0 }; + if (linux_sync_file_read_timestamp(trp->fd, &tspec) == 0) { TL_POINT(tp_name, TLP_GPU(&tspec), TLP_OUTPUT(trp->output), TLP_END); } diff --git a/libweston/weston-sync-file.h b/libweston/linux-sync-file-uapi.h similarity index 78% rename from libweston/weston-sync-file.h rename to libweston/linux-sync-file-uapi.h index 114e0b6eb..cd30665f9 100644 --- a/libweston/weston-sync-file.h +++ b/libweston/linux-sync-file-uapi.h @@ -1,7 +1,7 @@ /* Sync file Linux kernel UAPI */ -#ifndef WESTON_SYNC_FILE_H -#define WESTON_SYNC_FILE_H +#ifndef WESTON_LINUX_SYNC_FILE_UAPI_H +#define WESTON_LINUX_SYNC_FILE_UAPI_H #include #include @@ -27,4 +27,4 @@ struct sync_file_info { #define SYNC_IOC_MAGIC '>' #define SYNC_IOC_FILE_INFO _IOWR(SYNC_IOC_MAGIC, 4, struct sync_file_info) -#endif +#endif /* WESTON_LINUX_SYNC_FILE_UAPI_H */ diff --git a/libweston/linux-sync-file.c b/libweston/linux-sync-file.c new file mode 100644 index 000000000..913fb8da8 --- /dev/null +++ b/libweston/linux-sync-file.c @@ -0,0 +1,82 @@ +/* + * Copyright © 2018 Collabora, Ltd + * + * 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 + +#ifdef HAVE_LINUX_SYNC_FILE_H +#include +#else +#include "linux-sync-file-uapi.h" +#endif + +#include "linux-sync-file.h" +#include "shared/timespec-util.h" + +/* Check that a file descriptor represents a valid sync file + * + * \param fd[in] a file descriptor + * \return true if fd is a valid sync file, false otherwise + */ +bool +linux_sync_file_is_valid(int fd) +{ + struct sync_file_info file_info = { { 0 } }; + + if (ioctl(fd, SYNC_IOC_FILE_INFO, &file_info) < 0) + return false; + + return file_info.num_fences > 0; +} + +/* Read the timestamp stored in a sync file + * + * \param fd[in] fd a file descriptor for a sync file + * \param ts[out] the timespec struct to fill with the timestamp + * \return 0 if a timestamp was read, -1 on error + */ +int +linux_sync_file_read_timestamp(int fd, struct timespec *ts) +{ + struct sync_file_info file_info = { { 0 } }; + struct sync_fence_info fence_info = { { 0 } }; + + assert(ts != NULL); + + file_info.sync_fence_info = (uint64_t)(uintptr_t)&fence_info; + file_info.num_fences = 1; + + if (ioctl(fd, SYNC_IOC_FILE_INFO, &file_info) < 0) + return -1; + + timespec_from_nsec(ts, fence_info.timestamp_ns); + + return 0; +} diff --git a/libweston/linux-sync-file.h b/libweston/linux-sync-file.h new file mode 100644 index 000000000..b831fa1ea --- /dev/null +++ b/libweston/linux-sync-file.h @@ -0,0 +1,38 @@ +/* + * Copyright © 2018 Collabora, Ltd + * + * 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. + */ + +#ifndef WESTON_LINUX_SYNC_FILE_H +#define WESTON_LINUX_SYNC_FILE_H + +#include +#include + +bool +linux_sync_file_is_valid(int fd); + +int +linux_sync_file_read_timestamp(int fd, struct timespec *ts); + +#endif /* WESTON_LINUX_SYNC_FILE_H */ diff --git a/libweston/meson.build b/libweston/meson.build index 64efdd95a..63e75ea61 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -433,6 +433,7 @@ if get_option('renderer-gl') srcs_renderer_gl = [ 'gl-renderer.c', + 'linux-sync-file.c', 'vertex-clipping.c', '../shared/matrix.c', linux_dmabuf_unstable_v1_protocol_c, From acff29b3b3c3ab804db54fcc8524df53b6b9e1fb Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 19 Oct 2018 12:14:11 +0300 Subject: [PATCH 0752/1642] libweston: Support zwp_surface_synchronization_v1.set_acquire_fence Implement the set_acquire_fence request of the zwp_surface_synchronization_v1 interface. The implementation uses the acquire fence in two ways: 1. If the associated buffer is used as GL render source, an EGLSyncKHR is created from the fence and used to synchronize access. 2. If the associated buffer is used as a plane framebuffer, the acquire fence is treated as an in-fence for the atomic commit operation. If in-fences are not supported and the buffer has an acquire fence, we don't consider it for plane placement. If the used compositor/renderer doesn't support explicit synchronization, we don't advertise the protocol at all. Currently only the DRM and X11 backends when using the GL renderer advertise the protocol for production use. Issues for discussion --------------------- a. Currently, a server-side wait of EGLSyncKHR is performed before using the EGLImage/texture during rendering. Unfortunately, it's not clear from the specs whether this is generally safe to do, or we need to sync before glEGLImageTargetTexture2DOES. The exception is TEXTURE_EXTERNAL_OES where the spec mentions it's enough to sync and then glBindTexture for any changes to take effect. Changes in v5: - Meson support. - Make explicit sync server error reporting more generic, supporting all explicit sync related interfaces not just wp_linux_surface_synchronization. - Fix typo in warning for missing EGL_KHR_wait_sync extension. - Support minor version 2 of the explicit sync protocol (i.e., support fences for opaque EGL buffers). Changes in v4: - Introduce and use fd_clear and and fd_move helpers. - Don't check for a valid buffer when updating surface acquire fence fd from state. - Assert that pending state acquire fence fd is always clear after a commit. - Clarify that WESTON_CAP_EXPLICIT_SYNC applies to just the renderer. - Check for EGL_KHR_wait_sync before using eglWaitSyncKHR. - Dup the acquire fence before passing to EGL. Changes in v3: - Keep acquire_fence_fd in surface instead of buffer. - Clarify that WESTON_CAP_EXPLICIT_SYNC applies to both backend and renderer. - Move comment about non-ownership of in_fence_fd to struct drm_plane_state definition. - Assert that we don't try to use planes with in-fences when using the legacy KMS API. - Remove unnecessary info from wayland error messages. - Handle acquire fence for subsurface commits. - Guard against self-update in fd_update. - Disconnect the client if acquire fence EGLSyncKHR creation or wait fails. - Use updated protocol interface names. - User correct format specifier for resource ids. - Advertise protocol for X11 backend with GL renderer. Changes in v2: - Remove sync file wait fallbacks. - Raise UNSUPPORTED_BUFFER error at commit if we have an acquire fence, but the committed buffer is not a valid linux_dmabuf. - Don't put buffers with in-fences on planes that don't support in-fences. - Don't advertise explicit sync protocol if backend does not support explicit sync. Signed-off-by: Alexandros Frantzis --- Makefile.am | 3 + libweston/compositor-drm.c | 58 +++++++++++++ libweston/compositor-x11.c | 7 ++ libweston/compositor.c | 52 ++++++++++- libweston/compositor.h | 7 ++ libweston/gl-renderer.c | 95 +++++++++++++++++++-- libweston/linux-explicit-synchronization.c | 72 +++++++++++++++- libweston/linux-explicit-synchronization.h | 5 ++ libweston/meson.build | 1 + shared/fd-util.h | 56 ++++++++++++ tests/linux-explicit-synchronization-test.c | 52 +++++++++++ 11 files changed, 400 insertions(+), 8 deletions(-) create mode 100644 shared/fd-util.h diff --git a/Makefile.am b/Makefile.am index 00e298d6d..3e95631a3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -96,10 +96,13 @@ libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ libweston/linux-dmabuf.h \ libweston/linux-explicit-synchronization.c \ libweston/linux-explicit-synchronization.h \ + libweston/linux-sync-file.c \ + libweston/linux-sync-file.h \ libweston/pixel-formats.c \ libweston/pixel-formats.h \ libweston/weston-debug.c \ libweston/weston-debug.h \ + shared/fd-util.h \ shared/helpers.h \ shared/matrix.c \ shared/matrix.h \ diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index f19006f30..aa2b0bbd1 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -66,6 +66,7 @@ #include "presentation-time-server-protocol.h" #include "linux-dmabuf.h" #include "linux-dmabuf-unstable-v1-server-protocol.h" +#include "linux-explicit-synchronization.h" #ifndef DRM_CLIENT_CAP_ASPECT_RATIO #define DRM_CLIENT_CAP_ASPECT_RATIO 4 @@ -171,6 +172,7 @@ enum wdrm_plane_property { WDRM_PLANE_FB_ID, WDRM_PLANE_CRTC_ID, WDRM_PLANE_IN_FORMATS, + WDRM_PLANE_IN_FENCE_FD, WDRM_PLANE__COUNT }; @@ -213,6 +215,7 @@ static const struct drm_property_info plane_props[] = { [WDRM_PLANE_FB_ID] = { .name = "FB_ID", }, [WDRM_PLANE_CRTC_ID] = { .name = "CRTC_ID", }, [WDRM_PLANE_IN_FORMATS] = { .name = "IN_FORMATS" }, + [WDRM_PLANE_IN_FENCE_FD] = { .name = "IN_FENCE_FD" }, }; /** @@ -456,6 +459,9 @@ struct drm_plane_state { bool complete; + /* We don't own the fd, so we shouldn't close it */ + int in_fence_fd; + struct wl_list link; /* drm_output_state::plane_list */ }; @@ -1381,6 +1387,7 @@ drm_plane_state_alloc(struct drm_output_state *state_output, assert(state); state->output_state = state_output; state->plane = plane; + state->in_fence_fd = -1; /* Here we only add the plane state to the desired link, and not * set the member. Having an output pointer set means that the @@ -1411,6 +1418,7 @@ drm_plane_state_free(struct drm_plane_state *state, bool force) wl_list_remove(&state->link); wl_list_init(&state->link); state->output_state = NULL; + state->in_fence_fd = -1; if (force || state != state->plane->state_cur) { drm_fb_unref(state->fb); @@ -2010,6 +2018,13 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, extents->y2 != output->base.y + output->base.height) return NULL; + /* If the surface buffer has an in-fence fd, but the plane doesn't + * support fences, we can't place the buffer on this plane. */ + if (ev->surface->acquire_fence_fd >= 0 && + (!b->atomic_modeset || + scanout_plane->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0)) + return NULL; + fb = drm_fb_get_from_view(output_state, ev); if (!fb) { drm_debug(b, "\t\t\t\t[scanout] not placing view %p on scanout: " @@ -2049,6 +2064,8 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, state->src_h != state->dest_h << 16)) goto err; + state->in_fence_fd = ev->surface->acquire_fence_fd; + /* In plane-only mode, we don't need to test the state now, as we * will only test it once at the end. */ return state; @@ -2293,6 +2310,8 @@ drm_output_apply_state_legacy(struct drm_output_state *state) assert(scanout_state->dest_y == 0); assert(scanout_state->dest_w == scanout_state->src_w >> 16); assert(scanout_state->dest_h == scanout_state->src_h >> 16); + /* The legacy SetCrtc API doesn't support fences */ + assert(scanout_state->in_fence_fd == -1); mode = to_drm_mode(output->base.current_mode); if (backend->state_invalid || @@ -2351,6 +2370,8 @@ drm_output_apply_state_legacy(struct drm_output_state *state) assert(!ps->complete); assert(!ps->output || ps->output == output); assert(!!ps->output == !!ps->fb); + /* The legacy SetPlane API doesn't support fences */ + assert(ps->in_fence_fd == -1); if (ps->fb && !backend->sprites_hidden) fb_id = ps->fb->fb_id; @@ -2568,6 +2589,12 @@ drm_output_apply_state_atomic(struct drm_output_state *state, (unsigned long) plane->plane_id, pinfo ? pinfo->drm_format_name : "UNKNOWN"); + if (plane_state->in_fence_fd >= 0) { + ret |= plane_add_prop(req, plane, + WDRM_PLANE_IN_FENCE_FD, + plane_state->in_fence_fd); + } + if (ret != 0) { weston_log("couldn't set plane state\n"); return ret; @@ -3262,12 +3289,27 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, continue; } + /* If the surface buffer has an in-fence fd, but the plane + * doesn't support fences, we can't place the buffer on this + * plane. */ + if (ev->surface->acquire_fence_fd >= 0 && + (!b->atomic_modeset || + p->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0)) { + drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " + "no in-fence support\n", ev); + drm_plane_state_put_back(state); + state = NULL; + continue; + } + /* We hold one reference for the lifetime of this function; * from calling drm_fb_get_from_view, to the out label where * we unconditionally drop the reference. So, we take another * reference here to live within the state. */ state->fb = drm_fb_ref(fb); + state->in_fence_fd = ev->surface->acquire_fence_fd; + /* In planes-only mode, we don't have an incremental state to * test against, so we just hope it'll work. */ if (mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) { @@ -7014,11 +7056,14 @@ switch_to_gl_renderer(struct drm_backend *b) { struct drm_output *output; bool dmabuf_support_inited; + bool linux_explicit_sync_inited; if (!b->use_pixman) return; dmabuf_support_inited = !!b->compositor->renderer->import_dmabuf; + linux_explicit_sync_inited = + b->compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC; weston_log("Switching to GL renderer\n"); @@ -7051,6 +7096,13 @@ switch_to_gl_renderer(struct drm_backend *b) weston_log("Error: initializing dmabuf " "support failed.\n"); } + + if (!linux_explicit_sync_inited && + (b->compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC)) { + if (linux_explicit_synchronization_setup(b->compositor) < 0) + weston_log("Error: initializing explicit " + " synchronization support failed.\n"); + } } static void @@ -7482,6 +7534,12 @@ drm_backend_create(struct weston_compositor *compositor, "support failed.\n"); } + if (compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC) { + if (linux_explicit_synchronization_setup(compositor) < 0) + weston_log("Error: initializing explicit " + " synchronization support failed.\n"); + } + ret = weston_plugin_api_register(compositor, WESTON_DRM_OUTPUT_API_NAME, &api, sizeof(api)); diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 4a9d068f6..922e3c80b 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -62,6 +62,7 @@ #include "pixman-renderer.h" #include "presentation-time-server-protocol.h" #include "linux-dmabuf.h" +#include "linux-explicit-synchronization.h" #include "windowed-output-api.h" #define DEFAULT_AXIS_STEP_DISTANCE 10 @@ -1895,6 +1896,12 @@ x11_backend_create(struct weston_compositor *compositor, "support failed.\n"); } + if (compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC) { + if (linux_explicit_synchronization_setup(compositor) < 0) + weston_log("Error: initializing explicit " + " synchronization support failed.\n"); + } + ret = weston_plugin_api_register(compositor, WESTON_WINDOWED_OUTPUT_API_NAME, &api, sizeof(api)); diff --git a/libweston/compositor.c b/libweston/compositor.c index 14033cbd8..ce4f51ea8 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -51,6 +51,7 @@ #include #include #include +#include #include "timeline.h" @@ -59,6 +60,8 @@ #include "linux-dmabuf.h" #include "viewporter-server-protocol.h" #include "presentation-time-server-protocol.h" +#include "linux-explicit-synchronization-unstable-v1-server-protocol.h" +#include "shared/fd-util.h" #include "shared/helpers.h" #include "shared/os-compatibility.h" #include "shared/string-helpers.h" @@ -448,6 +451,8 @@ weston_surface_state_init(struct weston_surface_state *state) state->buffer_viewport.buffer.src_width = wl_fixed_from_int(-1); state->buffer_viewport.surface.width = -1; state->buffer_viewport.changed = 0; + + state->acquire_fence_fd = -1; } static void @@ -469,6 +474,8 @@ weston_surface_state_fini(struct weston_surface_state *state) if (state->buffer) wl_list_remove(&state->buffer_destroy_listener.link); state->buffer = NULL; + + fd_clear(&state->acquire_fence_fd); } static void @@ -525,6 +532,8 @@ weston_surface_create(struct weston_compositor *compositor) wl_list_init(&surface->pointer_constraints); + surface->acquire_fence_fd = -1; + return surface; } @@ -2017,6 +2026,8 @@ weston_surface_destroy(struct weston_surface *surface) link) weston_pointer_constraint_destroy(constraint); + fd_clear(&surface->acquire_fence_fd); + free(surface); } @@ -3216,9 +3227,15 @@ weston_surface_commit_state(struct weston_surface *surface, surface->buffer_viewport = state->buffer_viewport; /* wl_surface.attach */ - if (state->newly_attached) + if (state->newly_attached) { + /* zwp_surface_synchronization_v1.set_acquire_fence */ + fd_move(&surface->acquire_fence_fd, + &state->acquire_fence_fd); + weston_surface_attach(surface, state->buffer); + } weston_surface_state_set_buffer(state, NULL); + assert(state->acquire_fence_fd == -1); weston_surface_build_buffer_matrix(surface, &surface->surface_to_buffer_matrix); @@ -3329,6 +3346,35 @@ surface_commit(struct wl_client *client, struct wl_resource *resource) return; } + if (surface->pending.acquire_fence_fd >= 0) { + assert(surface->synchronization_resource); + + if (!surface->pending.buffer) { + fd_clear(&surface->pending.acquire_fence_fd); + wl_resource_post_error(surface->synchronization_resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER, + "wl_surface@%"PRIu32" no buffer for synchronization", + wl_resource_get_id(resource)); + return; + } + + /* We support fences for both wp_linux_dmabuf and opaque EGL + * buffers, as mandated by minor version 2 of the + * zwp_linux_explicit_synchronization_v1 protocol. Since + * renderers that support fences currently only support these + * two buffer types plus SHM buffers, we can just check for the + * SHM buffer case here. + */ + if (wl_shm_buffer_get(surface->pending.buffer->resource)) { + fd_clear(&surface->pending.acquire_fence_fd); + wl_resource_post_error(surface->synchronization_resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_UNSUPPORTED_BUFFER, + "wl_surface@%"PRIu32" unsupported buffer for synchronization", + wl_resource_get_id(resource)); + return; + } + } + if (sub) { weston_subsurface_commit(sub); return; @@ -3535,7 +3581,11 @@ weston_subsurface_commit_to_cache(struct weston_subsurface *sub) surface->pending.buffer); weston_presentation_feedback_discard_list( &sub->cached.feedback_list); + /* zwp_surface_synchronization_v1.set_acquire_fence */ + fd_move(&sub->cached.acquire_fence_fd, + &surface->pending.acquire_fence_fd); } + assert(surface->pending.acquire_fence_fd == -1); sub->cached.sx += surface->pending.sx; sub->cached.sy += surface->pending.sy; diff --git a/libweston/compositor.h b/libweston/compositor.h index 3f2ad01e1..c58620fa9 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -942,6 +942,9 @@ enum weston_capability { /* renderer supports weston_view_set_mask() clipping */ WESTON_CAP_VIEW_CLIP_MASK = 0x0010, + + /* renderer supports explicit synchronization */ + WESTON_CAP_EXPLICIT_SYNC = 0x0020, }; /* Configuration struct for a backend. @@ -1362,6 +1365,9 @@ struct weston_surface_state { /* wp_viewport.set_source */ /* wp_viewport.set_destination */ struct weston_buffer_viewport buffer_viewport; + + /* zwp_surface_synchronization_v1.set_acquire_fence */ + int acquire_fence_fd; }; struct weston_surface_activation_data { @@ -1489,6 +1495,7 @@ struct weston_surface { /* zwp_surface_synchronization_v1 resource for this surface */ struct wl_resource *synchronization_resource; + int acquire_fence_fd; }; struct weston_subsurface { diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 1a7577e34..4f65f956f 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -49,6 +49,7 @@ #include "vertex-clipping.h" #include "linux-dmabuf.h" #include "linux-dmabuf-unstable-v1-server-protocol.h" +#include "linux-explicit-synchronization.h" #include "pixel-formats.h" #include "shared/helpers.h" @@ -258,6 +259,9 @@ struct gl_renderer { PFNEGLCREATESYNCKHRPROC create_sync; PFNEGLDESTROYSYNCKHRPROC destroy_sync; PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd; + + int has_wait_sync; + PFNEGLWAITSYNCKHRPROC wait_sync; }; enum timeline_render_point_type { @@ -861,6 +865,72 @@ shader_uniforms(struct gl_shader *shader, glUniform1i(shader->tex_uniforms[i], i); } +static int +ensure_surface_buffer_is_ready(struct gl_renderer *gr, + struct gl_surface_state *gs) +{ + EGLint attribs[] = { + EGL_SYNC_NATIVE_FENCE_FD_ANDROID, + -1, + EGL_NONE + }; + struct weston_surface *surface = gs->surface; + struct weston_buffer *buffer = gs->buffer_ref.buffer; + EGLSyncKHR sync; + EGLint wait_ret; + EGLint destroy_ret; + + if (!buffer) + return 0; + + if (surface->acquire_fence_fd < 0) + return 0; + + /* We should only get a fence if we support EGLSyncKHR, since + * we don't advertise the explicit sync protocol otherwise. */ + assert(gr->has_native_fence_sync); + /* We should only get a fence for non-SHM buffers, since surface + * commit would have failed otherwise. */ + assert(wl_shm_buffer_get(buffer->resource) == NULL); + + attribs[1] = dup(surface->acquire_fence_fd); + if (attribs[1] == -1) { + linux_explicit_synchronization_send_server_error( + gs->surface->synchronization_resource, + "Failed to dup acquire fence"); + return -1; + } + + sync = gr->create_sync(gr->egl_display, + EGL_SYNC_NATIVE_FENCE_ANDROID, + attribs); + if (sync == EGL_NO_SYNC_KHR) { + linux_explicit_synchronization_send_server_error( + gs->surface->synchronization_resource, + "Failed to create EGLSyncKHR object"); + close(attribs[1]); + return -1; + } + + wait_ret = gr->wait_sync(gr->egl_display, sync, 0); + if (wait_ret == EGL_FALSE) { + linux_explicit_synchronization_send_server_error( + gs->surface->synchronization_resource, + "Failed to wait on EGLSyncKHR object"); + /* Continue to try to destroy the sync object. */ + } + + + destroy_ret = gr->destroy_sync(gr->egl_display, sync); + if (destroy_ret == EGL_FALSE) { + linux_explicit_synchronization_send_server_error( + gs->surface->synchronization_resource, + "Failed to destroy on EGLSyncKHR object"); + } + + return (wait_ret == EGL_TRUE && destroy_ret == EGL_TRUE) ? 0 : -1; +} + static void draw_view(struct weston_view *ev, struct weston_output *output, pixman_region32_t *damage) /* in global coordinates */ @@ -891,6 +961,9 @@ draw_view(struct weston_view *ev, struct weston_output *output, if (!pixman_region32_not_empty(&repaint)) goto out; + if (ensure_surface_buffer_is_ready(gr, gs) < 0) + goto out; + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); if (gr->fan_debug) { @@ -3288,8 +3361,17 @@ gl_renderer_setup_egl_extensions(struct weston_compositor *ec) (void *) eglGetProcAddress("eglDupNativeFenceFDANDROID"); gr->has_native_fence_sync = 1; } else { - weston_log("warning: Disabling render GPU timeline due to " - "missing EGL_ANDROID_native_fence_sync extension\n"); + weston_log("warning: Disabling render GPU timeline and explicit " + "synchronization due to missing " + "EGL_ANDROID_native_fence_sync extension\n"); + } + + if (weston_check_egl_extension(extensions, "EGL_KHR_wait_sync")) { + gr->wait_sync = (void *) eglGetProcAddress("eglWaitSyncKHR"); + gr->has_wait_sync = 1; + } else { + weston_log("warning: Disabling explicit synchronization due" + "to missing EGL_KHR_wait_sync extension\n"); } renderer_setup_egl_client_extensions(gr); @@ -3521,13 +3603,16 @@ gl_renderer_display_create(struct weston_compositor *ec, EGLenum platform, } ec->renderer = &gr->base; - ec->capabilities |= WESTON_CAP_ROTATION_ANY; - ec->capabilities |= WESTON_CAP_CAPTURE_YFLIP; - ec->capabilities |= WESTON_CAP_VIEW_CLIP_MASK; if (gl_renderer_setup_egl_extensions(ec) < 0) goto fail_with_error; + ec->capabilities |= WESTON_CAP_ROTATION_ANY; + ec->capabilities |= WESTON_CAP_CAPTURE_YFLIP; + ec->capabilities |= WESTON_CAP_VIEW_CLIP_MASK; + if (gr->has_native_fence_sync && gr->has_wait_sync) + ec->capabilities |= WESTON_CAP_EXPLICIT_SYNC; + wl_list_init(&gr->dmabuf_images); if (gr->has_dmabuf_import) { gr->base.import_dmabuf = gl_renderer_import_dmabuf; diff --git a/libweston/linux-explicit-synchronization.c b/libweston/linux-explicit-synchronization.c index c42b8aa35..f577a1894 100644 --- a/libweston/linux-explicit-synchronization.c +++ b/libweston/linux-explicit-synchronization.c @@ -25,11 +25,14 @@ #include "config.h" +#include #include #include "compositor.h" #include "linux-explicit-synchronization.h" #include "linux-explicit-synchronization-unstable-v1-server-protocol.h" +#include "linux-sync-file.h" +#include "shared/fd-util.h" static void destroy_linux_surface_synchronization(struct wl_resource *resource) @@ -37,8 +40,10 @@ destroy_linux_surface_synchronization(struct wl_resource *resource) struct weston_surface *surface = wl_resource_get_user_data(resource); - if (surface) + if (surface) { + fd_clear(&surface->pending.acquire_fence_fd); surface->synchronization_resource = NULL; + } } static void @@ -53,7 +58,38 @@ linux_surface_synchronization_set_acquire_fence(struct wl_client *client, struct wl_resource *resource, int32_t fd) { - wl_client_post_no_memory(client); + struct weston_surface *surface = wl_resource_get_user_data(resource); + + if (!surface) { + wl_resource_post_error( + resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE, + "surface no longer exists"); + goto err; + } + + if (!linux_sync_file_is_valid(fd)) { + wl_resource_post_error( + resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_INVALID_FENCE, + "invalid fence fd"); + goto err; + } + + if (surface->pending.acquire_fence_fd != -1) { + wl_resource_post_error( + resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_FENCE, + "already have a fence fd"); + goto err; + } + + fd_update(&surface->pending.acquire_fence_fd, fd); + + return; + +err: + close(fd); } static void @@ -162,3 +198,35 @@ linux_explicit_synchronization_setup(struct weston_compositor *compositor) return 0; } + +/** Resolve an internal compositor error by disconnecting the client. + * + * This function is used in cases when explicit synchronization + * turns out to be unusable and there is no fallback path. + * + * It is possible the fault is caused by a compositor bug, the underlying + * graphics stack bug or normal behaviour, or perhaps a client mistake. + * In any case, the options are to either composite garbage or nothing, + * or disconnect the client. This is a helper function for the latter. + * + * The error is sent as an INVALID_OBJECT error on the client's wl_display. + * + * \param sync The explicit synchronization related resource that is unusable. + * \param msg A custom error message attached to the protocol error. + */ +WL_EXPORT void +linux_explicit_synchronization_send_server_error(struct wl_resource *resource, + const char *msg) +{ + uint32_t id = wl_resource_get_id(resource); + const char *class = wl_resource_get_class(resource); + struct wl_client *client = wl_resource_get_client(resource); + struct wl_resource *display_resource = wl_client_get_object(client, 1); + + assert(display_resource); + wl_resource_post_error(display_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "linux_explicit_synchronization server error " + "with %s@%"PRIu32": %s", + class, id, msg); +} diff --git a/libweston/linux-explicit-synchronization.h b/libweston/linux-explicit-synchronization.h index 96edbbb63..550228793 100644 --- a/libweston/linux-explicit-synchronization.h +++ b/libweston/linux-explicit-synchronization.h @@ -27,8 +27,13 @@ #define WESTON_LINUX_EXPLICIT_SYNCHRONIZATION_H struct weston_compositor; +struct wl_resource; int linux_explicit_synchronization_setup(struct weston_compositor *compositor); +void +linux_explicit_synchronization_send_server_error(struct wl_resource *resource, + const char *msg); + #endif /* WESTON_LINUX_EXPLICIT_SYNCHRONIZATION */ diff --git a/libweston/meson.build b/libweston/meson.build index 63e75ea61..f6e2a0fd7 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -17,6 +17,7 @@ srcs_libweston = [ 'input.c', 'linux-dmabuf.c', 'linux-explicit-synchronization.c', + 'linux-sync-file.c', 'log.c', 'noop-renderer.c', 'pixel-formats.c', diff --git a/shared/fd-util.h b/shared/fd-util.h new file mode 100644 index 000000000..da4ef6fd2 --- /dev/null +++ b/shared/fd-util.h @@ -0,0 +1,56 @@ +/* + * Copyright © 2018 Collabora, Ltd. + * + * 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. + */ + +#ifndef FD_UTIL_H +#define FD_UTIL_H + +#include + +static inline void +fd_update(int *fd, int new_fd) +{ + if (*fd == new_fd) + return; + if (*fd >= 0) + close(*fd); + *fd = new_fd; +} + +static inline void +fd_move(int *dest, int *src) +{ + if (dest == src) + return; + fd_update(dest, *src); + *src = -1; +} + +static inline void +fd_clear(int *fd) +{ + fd_update(fd, -1); +} + +#endif /* FD_UTIL_H */ diff --git a/tests/linux-explicit-synchronization-test.c b/tests/linux-explicit-synchronization-test.c index 62a54e0d4..9b74564cf 100644 --- a/tests/linux-explicit-synchronization-test.c +++ b/tests/linux-explicit-synchronization-test.c @@ -26,6 +26,7 @@ #include "config.h" #include +#include #include "linux-explicit-synchronization-unstable-v1-client-protocol.h" #include "weston-test-client-helper.h" @@ -94,3 +95,54 @@ TEST(second_surface_synchronization_on_surface_raises_error) zwp_linux_surface_synchronization_v1_destroy(surface_sync1); zwp_linux_explicit_synchronization_v1_destroy(sync); } + +TEST(set_acquire_fence_with_invalid_fence_raises_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + int pipefd[2] = { -1, -1 }; + + assert(pipe(pipefd) == 0); + + zwp_linux_surface_synchronization_v1_set_acquire_fence(surface_sync, + pipefd[0]); + expect_protocol_error( + client, + &zwp_linux_surface_synchronization_v1_interface, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_INVALID_FENCE); + + close(pipefd[0]); + close(pipefd[1]); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(set_acquire_fence_on_destroyed_surface_raises_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + int pipefd[2] = { -1, -1 }; + + assert(pipe(pipefd) == 0); + + wl_surface_destroy(client->surface->wl_surface); + zwp_linux_surface_synchronization_v1_set_acquire_fence(surface_sync, + pipefd[0]); + expect_protocol_error( + client, + &zwp_linux_surface_synchronization_v1_interface, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE); + + close(pipefd[0]); + close(pipefd[1]); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} From 676296749a5ddb82d2f378829f419398602e5ce4 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Fri, 19 Oct 2018 12:14:11 +0300 Subject: [PATCH 0753/1642] libweston: Support zwp_surface_synchronization_v1.get_release Implement the get_release request of the zwp_surface_synchronization_v1 interface. This commit implements the zwp_buffer_release_v1 interface. It supports the zwp_buffer_release_v1.fenced_release event for surfaces rendered by the GL renderer, and the zwp_buffer_release_v1.immediate_release event for other cases. Note that the immediate_release event is safe to be used for surface buffers used as planes in the DRM backend, since the backend releases them only after the next page flip that doesn't use the buffers has finished. Changes in v7: - Remove "partial" from commit title and description. - Fix inverted check when clearing used_in_output_repaint flag. Changes in v5: - Use the new, generic explicit sync server error reporting function. - Introduce and use weston_buffer_release_move. - Introduce internally and use weston_buffer_release_destroy. Changes in v4: - Support the zwp_buffer_release_v1.fenced_release event. - Support release fences in the GL renderer. - Assert that pending state buffer_release is always NULL after a commit. - Simplify weston_buffer_release_reference. - Move removal of destroy listener before resource destruction to avoid concerns about use-after-free in weston_buffer_release_reference - Rename weston_buffer_release_reference.busy_count to ref_count. - Add documentation for weston_buffer_release and ..._reference. Changes in v3: - Raise NO_BUFFER for get_release if no buffer has been committed, don't raise UNSUPPORTED_BUFFER for non-dmabuf buffers, so get_release works for all valid buffers. - Destroy the buffer_release object after sending an event. - Track lifetime of buffer_release objects per commit, independently of any buffers. - Use updated protocol interface names. - Use correct format specifier for resource ids. Changes in v2: - Raise UNSUPPORTED_BUFFER at commit if client has requested a buffer_release, but the committed buffer is not a valid linux_dmabuf. - Remove tests that are not viable anymore due to our inability to create dmabuf buffers and fences in a unit-test environment. Signed-off-by: Alexandros Frantzis --- libweston/compositor-drm.c | 10 +- libweston/compositor.c | 89 +++++++++- libweston/compositor.h | 30 ++++ libweston/gl-renderer.c | 93 ++++++++++ libweston/linux-explicit-synchronization.c | 56 ++++++ libweston/pixman-renderer.c | 6 + tests/linux-explicit-synchronization-test.c | 181 ++++++++++++++++++++ 7 files changed, 462 insertions(+), 3 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index aa2b0bbd1..54230eccc 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -385,6 +385,7 @@ struct drm_fb { int width, height; int fd; struct weston_buffer_reference buffer_ref; + struct weston_buffer_release_reference buffer_release_ref; /* Used by gbm fbs */ struct gbm_bo *bo; @@ -951,6 +952,7 @@ drm_fb_destroy(struct drm_fb *fb) if (fb->fb_id != 0) drmModeRmFB(fb->fd, fb->fb_id); weston_buffer_reference(&fb->buffer_ref, NULL); + weston_buffer_release_reference(&fb->buffer_release_ref, NULL); free(fb); } @@ -1338,11 +1340,14 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, } static void -drm_fb_set_buffer(struct drm_fb *fb, struct weston_buffer *buffer) +drm_fb_set_buffer(struct drm_fb *fb, struct weston_buffer *buffer, + struct weston_buffer_release *buffer_release) { assert(fb->buffer_ref.buffer == NULL); assert(fb->type == BUFFER_CLIENT || fb->type == BUFFER_DMABUF); weston_buffer_reference(&fb->buffer_ref, buffer); + weston_buffer_release_reference(&fb->buffer_release_ref, + buffer_release); } static void @@ -1650,7 +1655,8 @@ drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev) drm_debug(b, "\t\t\t[view] view %p format: %s\n", ev, fb->format->drm_format_name); - drm_fb_set_buffer(fb, buffer); + drm_fb_set_buffer(fb, buffer, + ev->surface->buffer_release_ref.buffer_release); return fb; } diff --git a/libweston/compositor.c b/libweston/compositor.c index ce4f51ea8..0f1f01fb5 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -61,6 +61,7 @@ #include "viewporter-server-protocol.h" #include "presentation-time-server-protocol.h" #include "linux-explicit-synchronization-unstable-v1-server-protocol.h" +#include "linux-explicit-synchronization.h" #include "shared/fd-util.h" #include "shared/helpers.h" #include "shared/os-compatibility.h" @@ -476,6 +477,7 @@ weston_surface_state_fini(struct weston_surface_state *state) state->buffer = NULL; fd_clear(&state->acquire_fence_fd); + weston_buffer_release_reference(&state->buffer_release_ref, NULL); } static void @@ -2011,6 +2013,7 @@ weston_surface_destroy(struct weston_surface *surface) weston_surface_state_fini(&surface->pending); weston_buffer_reference(&surface->buffer_ref, NULL); + weston_buffer_release_reference(&surface->buffer_release_ref, NULL); pixman_region32_fini(&surface->damage); pixman_region32_fini(&surface->opaque); @@ -2125,6 +2128,68 @@ weston_buffer_reference(struct weston_buffer_reference *ref, ref->destroy_listener.notify = weston_buffer_reference_handle_destroy; } +static void +weston_buffer_release_reference_handle_destroy(struct wl_listener *listener, + void *data) +{ + struct weston_buffer_release_reference *ref = + container_of(listener, struct weston_buffer_release_reference, + destroy_listener); + + assert((struct wl_resource *)data == ref->buffer_release->resource); + ref->buffer_release = NULL; +} + +static void +weston_buffer_release_destroy(struct weston_buffer_release *buffer_release) +{ + struct wl_resource *resource = buffer_release->resource; + int release_fence_fd = buffer_release->fence_fd; + + if (release_fence_fd >= 0) { + zwp_linux_buffer_release_v1_send_fenced_release( + resource, release_fence_fd); + } else { + zwp_linux_buffer_release_v1_send_immediate_release( + resource); + } + + wl_resource_destroy(resource); +} + +WL_EXPORT void +weston_buffer_release_reference(struct weston_buffer_release_reference *ref, + struct weston_buffer_release *buffer_release) +{ + if (buffer_release == ref->buffer_release) + return; + + if (ref->buffer_release) { + ref->buffer_release->ref_count--; + wl_list_remove(&ref->destroy_listener.link); + if (ref->buffer_release->ref_count == 0) + weston_buffer_release_destroy(ref->buffer_release); + } + + if (buffer_release) { + buffer_release->ref_count++; + wl_resource_add_destroy_listener(buffer_release->resource, + &ref->destroy_listener); + } + + ref->buffer_release = buffer_release; + ref->destroy_listener.notify = + weston_buffer_release_reference_handle_destroy; +} + +WL_EXPORT void +weston_buffer_release_move(struct weston_buffer_release_reference *dest, + struct weston_buffer_release_reference *src) +{ + weston_buffer_release_reference(dest, src->buffer_release); + weston_buffer_release_reference(src, NULL); +} + static void weston_surface_attach(struct weston_surface *surface, struct weston_buffer *buffer) @@ -2250,8 +2315,11 @@ compositor_accumulate_damage(struct weston_compositor *ec) * reference now, and allow early buffer release. This enables * clients to use single-buffering. */ - if (!ev->surface->keep_buffer) + if (!ev->surface->keep_buffer) { weston_buffer_reference(&ev->surface->buffer_ref, NULL); + weston_buffer_release_reference( + &ev->surface->buffer_release_ref, NULL); + } } } @@ -3231,11 +3299,15 @@ weston_surface_commit_state(struct weston_surface *surface, /* zwp_surface_synchronization_v1.set_acquire_fence */ fd_move(&surface->acquire_fence_fd, &state->acquire_fence_fd); + /* zwp_surface_synchronization_v1.get_release */ + weston_buffer_release_move(&surface->buffer_release_ref, + &state->buffer_release_ref); weston_surface_attach(surface, state->buffer); } weston_surface_state_set_buffer(state, NULL); assert(state->acquire_fence_fd == -1); + assert(state->buffer_release_ref.buffer_release == NULL); weston_surface_build_buffer_matrix(surface, &surface->surface_to_buffer_matrix); @@ -3375,6 +3447,17 @@ surface_commit(struct wl_client *client, struct wl_resource *resource) } } + if (surface->pending.buffer_release_ref.buffer_release && + !surface->pending.buffer) { + assert(surface->synchronization_resource); + + wl_resource_post_error(surface->synchronization_resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER, + "wl_surface@%"PRIu32" no buffer for synchronization", + wl_resource_get_id(resource)); + return; + } + if (sub) { weston_subsurface_commit(sub); return; @@ -3584,8 +3667,12 @@ weston_subsurface_commit_to_cache(struct weston_subsurface *sub) /* zwp_surface_synchronization_v1.set_acquire_fence */ fd_move(&sub->cached.acquire_fence_fd, &surface->pending.acquire_fence_fd); + /* zwp_surface_synchronization_v1.get_release */ + weston_buffer_release_move(&sub->cached.buffer_release_ref, + &surface->pending.buffer_release_ref); } assert(surface->pending.acquire_fence_fd == -1); + assert(surface->pending.buffer_release_ref.buffer_release == NULL); sub->cached.sx += surface->pending.sx; sub->cached.sy += surface->pending.sy; diff --git a/libweston/compositor.h b/libweston/compositor.h index c58620fa9..a5223c285 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -1215,6 +1215,24 @@ struct weston_buffer_viewport { int changed; }; +struct weston_buffer_release { + /* The associated zwp_linux_buffer_release_v1 resource. */ + struct wl_resource *resource; + /* How many weston_buffer_release_reference objects point to this + * object. */ + uint32_t ref_count; + /* The fence fd, if any, associated with this release. If the fence fd + * is -1 then this is considered an immediate release. */ + int fence_fd; +}; + +struct weston_buffer_release_reference { + struct weston_buffer_release *buffer_release; + /* Listener for the destruction of the wl_resource associated with the + * referenced buffer_release object. */ + struct wl_listener destroy_listener; +}; + struct weston_region { struct wl_resource *resource; pixman_region32_t region; @@ -1368,6 +1386,9 @@ struct weston_surface_state { /* zwp_surface_synchronization_v1.set_acquire_fence */ int acquire_fence_fd; + + /* zwp_surface_synchronization_v1.get_release */ + struct weston_buffer_release_reference buffer_release_ref; }; struct weston_surface_activation_data { @@ -1496,6 +1517,7 @@ struct weston_surface { /* zwp_surface_synchronization_v1 resource for this surface */ struct wl_resource *synchronization_resource; int acquire_fence_fd; + struct weston_buffer_release_reference buffer_release_ref; }; struct weston_subsurface { @@ -1946,6 +1968,14 @@ void weston_buffer_reference(struct weston_buffer_reference *ref, struct weston_buffer *buffer); +void +weston_buffer_release_reference(struct weston_buffer_release_reference *ref, + struct weston_buffer_release *buf_release); + +void +weston_buffer_release_move(struct weston_buffer_release_reference *dest, + struct weston_buffer_release_reference *src); + void weston_compositor_get_time(struct timespec *time); diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index 4f65f956f..a03e45eaa 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -52,6 +52,7 @@ #include "linux-explicit-synchronization.h" #include "pixel-formats.h" +#include "shared/fd-util.h" #include "shared/helpers.h" #include "shared/platform.h" #include "shared/timespec-util.h" @@ -175,6 +176,7 @@ struct gl_surface_state { int num_images; struct weston_buffer_reference buffer_ref; + struct weston_buffer_release_reference buffer_release_ref; enum buffer_type buffer_type; int pitch; /* in pixels */ int height; /* in pixels */ @@ -187,6 +189,10 @@ struct gl_surface_state { struct weston_surface *surface; + /* Whether this surface was used in the current output repaint. + Used only in the context of a gl_renderer_repaint_output call. */ + bool used_in_output_repaint; + struct wl_listener surface_destroy_listener; struct wl_listener renderer_destroy_listener; }; @@ -1022,12 +1028,14 @@ draw_view(struct weston_view *ev, struct weston_output *output, glDisable(GL_BLEND); repaint_region(ev, &repaint, &surface_opaque); + gs->used_in_output_repaint = true; } if (pixman_region32_not_empty(&surface_blend)) { use_shader(gr, gs->shader); glEnable(GL_BLEND); repaint_region(ev, &repaint, &surface_blend); + gs->used_in_output_repaint = true; } pixman_region32_fini(&surface_blend); @@ -1048,6 +1056,73 @@ repaint_views(struct weston_output *output, pixman_region32_t *damage) draw_view(view, output, damage); } +static int +gl_renderer_create_fence_fd(struct weston_output *output); + +/* Updates the release fences of surfaces that were used in the current output + * repaint. Should only be used from gl_renderer_repaint_output, so that the + * information in gl_surface_state.used_in_output_repaint is accurate. + */ +static void +update_buffer_release_fences(struct weston_compositor *compositor, + struct weston_output *output) +{ + struct weston_view *view; + + wl_list_for_each_reverse(view, &compositor->view_list, link) { + struct gl_surface_state *gs; + struct weston_buffer_release *buffer_release; + int fence_fd; + + if (view->plane != &compositor->primary_plane) + continue; + + gs = get_surface_state(view->surface); + buffer_release = gs->buffer_release_ref.buffer_release; + + if (!gs->used_in_output_repaint || !buffer_release) + continue; + + fence_fd = gl_renderer_create_fence_fd(output); + + /* If we have a buffer_release then it means we support fences, + * and we should be able to create the release fence. If we + * can't, something has gone horribly wrong, so disconnect the + * client. + */ + if (fence_fd == -1) { + linux_explicit_synchronization_send_server_error( + buffer_release->resource, + "Failed to create release fence"); + fd_clear(&buffer_release->fence_fd); + continue; + } + + /* At the moment it is safe to just replace the fence_fd, + * discarding the previous one: + * + * 1. If the previous fence fd represents a sync fence from + * a previous repaint cycle, that fence fd is now not + * sufficient to provide the release guarantee and should + * be replaced. + * + * 2. If the fence fd represents a sync fence from another + * output in the same repaint cycle, it's fine to replace + * it since we are rendering to all outputs using the same + * EGL context, so a fence issued for a later output rendering + * is guaranteed to signal after fences for previous output + * renderings. + * + * Note that the above is only valid if the buffer_release + * fences only originate from the GL renderer, which guarantees + * a total order of operations and fences. If we introduce + * fences from other sources (e.g., plane out-fences), we will + * need to merge fences instead. + */ + fd_update(&buffer_release->fence_fd, fence_fd); + } +} + static void draw_output_border_texture(struct gl_output_state *go, enum gl_renderer_border_side side, @@ -1298,10 +1373,21 @@ gl_renderer_repaint_output(struct weston_output *output, pixman_box32_t *rects; pixman_region32_t buffer_damage, total_damage; enum gl_border_status border_damage = BORDER_STATUS_CLEAN; + struct weston_view *view; if (use_output(output) < 0) return; + /* Clear the used_in_output_repaint flag, so that we can properly track + * which surfaces were used in this output repaint. */ + wl_list_for_each_reverse(view, &compositor->view_list, link) { + if (view->plane == &compositor->primary_plane) { + struct gl_surface_state *gs = + get_surface_state(view->surface); + gs->used_in_output_repaint = false; + } + } + if (go->begin_render_sync != EGL_NO_SYNC_KHR) gr->destroy_sync(gr->egl_display, go->begin_render_sync); if (go->end_render_sync != EGL_NO_SYNC_KHR) @@ -1413,6 +1499,8 @@ gl_renderer_repaint_output(struct weston_output *output, TIMELINE_RENDER_POINT_TYPE_BEGIN); timeline_submit_render_sync(gr, compositor, output, go->end_render_sync, TIMELINE_RENDER_POINT_TYPE_END); + + update_buffer_release_fences(compositor, output); } static int @@ -1571,6 +1659,7 @@ gl_renderer_flush_damage(struct weston_surface *surface) gs->needs_full_upload = false; weston_buffer_reference(&gs->buffer_ref, NULL); + weston_buffer_release_reference(&gs->buffer_release_ref, NULL); } static void @@ -2395,6 +2484,8 @@ gl_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) int i; weston_buffer_reference(&gs->buffer_ref, buffer); + weston_buffer_release_reference(&gs->buffer_release_ref, + es->buffer_release_ref.buffer_release); if (!buffer) { for (i = 0; i < gs->num_images; i++) { @@ -2427,6 +2518,7 @@ gl_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) gl_renderer_print_egl_error_state(); } weston_buffer_reference(&gs->buffer_ref, NULL); + weston_buffer_release_reference(&gs->buffer_release_ref, NULL); gs->buffer_type = BUFFER_TYPE_NULL; gs->y_inverted = 1; es->is_opaque = false; @@ -2614,6 +2706,7 @@ surface_state_destroy(struct gl_surface_state *gs, struct gl_renderer *gr) egl_image_unref(gs->images[i]); weston_buffer_reference(&gs->buffer_ref, NULL); + weston_buffer_release_reference(&gs->buffer_release_ref, NULL); pixman_region32_fini(&gs->texture_damage); free(gs); } diff --git a/libweston/linux-explicit-synchronization.c b/libweston/linux-explicit-synchronization.c index f577a1894..99e30a1fc 100644 --- a/libweston/linux-explicit-synchronization.c +++ b/libweston/linux-explicit-synchronization.c @@ -34,6 +34,16 @@ #include "linux-sync-file.h" #include "shared/fd-util.h" +static void +destroy_linux_buffer_release(struct wl_resource *resource) +{ + struct weston_buffer_release *buffer_release = + wl_resource_get_user_data(resource); + + fd_clear(&buffer_release->fence_fd); + free(buffer_release); +} + static void destroy_linux_surface_synchronization(struct wl_resource *resource) { @@ -97,7 +107,53 @@ linux_surface_synchronization_get_release(struct wl_client *client, struct wl_resource *resource, uint32_t id) { + struct weston_surface *surface = + wl_resource_get_user_data(resource); + struct weston_buffer_release *buffer_release; + + if (!surface) { + wl_resource_post_error( + resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE, + "surface no longer exists"); + return; + } + + if (surface->pending.buffer_release_ref.buffer_release) { + wl_resource_post_error( + resource, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_RELEASE, + "already has a buffer release"); + return; + } + + buffer_release = zalloc(sizeof *buffer_release); + if (buffer_release == NULL) + goto err_alloc; + + buffer_release->fence_fd = -1; + buffer_release->resource = + wl_resource_create(client, + &zwp_linux_buffer_release_v1_interface, + wl_resource_get_version(resource), id); + if (!buffer_release->resource) + goto err_create; + + wl_resource_set_implementation(buffer_release->resource, NULL, + buffer_release, + destroy_linux_buffer_release); + + weston_buffer_release_reference(&surface->pending.buffer_release_ref, + buffer_release); + + return; + +err_create: + free(buffer_release); + +err_alloc: wl_client_post_no_memory(client); + } const struct zwp_linux_surface_synchronization_v1_interface diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c index 9640b6168..8677023cf 100644 --- a/libweston/pixman-renderer.c +++ b/libweston/pixman-renderer.c @@ -49,6 +49,7 @@ struct pixman_surface_state { pixman_image_t *image; struct weston_buffer_reference buffer_ref; + struct weston_buffer_release_reference buffer_release_ref; struct wl_listener buffer_destroy_listener; struct wl_listener surface_destroy_listener; @@ -625,6 +626,8 @@ pixman_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) pixman_format_code_t pixman_format; weston_buffer_reference(&ps->buffer_ref, buffer); + weston_buffer_release_reference(&ps->buffer_release_ref, + es->buffer_release_ref.buffer_release); if (ps->buffer_destroy_listener.notify) { wl_list_remove(&ps->buffer_destroy_listener.link); @@ -644,6 +647,7 @@ pixman_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) if (! shm_buffer) { weston_log("Pixman renderer supports only SHM buffers\n"); weston_buffer_reference(&ps->buffer_ref, NULL); + weston_buffer_release_reference(&ps->buffer_release_ref, NULL); return; } @@ -664,6 +668,7 @@ pixman_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) weston_log("Unsupported SHM buffer format 0x%x\n", wl_shm_buffer_get_format(shm_buffer)); weston_buffer_reference(&ps->buffer_ref, NULL); + weston_buffer_release_reference(&ps->buffer_release_ref, NULL); weston_buffer_send_server_error(buffer, "disconnecting due to unhandled buffer type"); return; @@ -702,6 +707,7 @@ pixman_renderer_surface_state_destroy(struct pixman_surface_state *ps) ps->image = NULL; } weston_buffer_reference(&ps->buffer_ref, NULL); + weston_buffer_release_reference(&ps->buffer_release_ref, NULL); free(ps); } diff --git a/tests/linux-explicit-synchronization-test.c b/tests/linux-explicit-synchronization-test.c index 9b74564cf..73b1177cb 100644 --- a/tests/linux-explicit-synchronization-test.c +++ b/tests/linux-explicit-synchronization-test.c @@ -146,3 +146,184 @@ TEST(set_acquire_fence_on_destroyed_surface_raises_error) zwp_linux_surface_synchronization_v1_destroy(surface_sync); zwp_linux_explicit_synchronization_v1_destroy(sync); } + +TEST(second_buffer_release_in_commit_raises_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + struct zwp_linux_buffer_release_v1 *buffer_release1; + struct zwp_linux_buffer_release_v1 *buffer_release2; + + buffer_release1 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + client_roundtrip(client); + + /* Second buffer_release creation should fail */ + buffer_release2 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + expect_protocol_error( + client, + &zwp_linux_surface_synchronization_v1_interface, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_RELEASE); + + zwp_linux_buffer_release_v1_destroy(buffer_release2); + zwp_linux_buffer_release_v1_destroy(buffer_release1); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(get_release_without_buffer_raises_commit_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + struct wl_surface *surface = client->surface->wl_surface; + struct zwp_linux_buffer_release_v1 *buffer_release; + + buffer_release = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + wl_surface_commit(surface); + expect_protocol_error( + client, + &zwp_linux_surface_synchronization_v1_interface, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER); + + zwp_linux_buffer_release_v1_destroy(buffer_release); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(get_release_on_destroyed_surface_raises_error) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + struct zwp_linux_buffer_release_v1 *buffer_release; + + wl_surface_destroy(client->surface->wl_surface); + buffer_release = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + expect_protocol_error( + client, + &zwp_linux_surface_synchronization_v1_interface, + ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE); + + zwp_linux_buffer_release_v1_destroy(buffer_release); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(get_release_after_commit_succeeds) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct wl_surface *surface = client->surface->wl_surface; + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, surface); + struct buffer *buf1 = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct zwp_linux_buffer_release_v1 *buffer_release1; + struct zwp_linux_buffer_release_v1 *buffer_release2; + + buffer_release1 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + client_roundtrip(client); + + wl_surface_attach(surface, buf1->proxy, 0, 0); + wl_surface_commit(surface); + + buffer_release2 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + client_roundtrip(client); + + buffer_destroy(buf1); + zwp_linux_buffer_release_v1_destroy(buffer_release2); + zwp_linux_buffer_release_v1_destroy(buffer_release1); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +static void +buffer_release_fenced_handler(void *data, + struct zwp_linux_buffer_release_v1 *buffer_release, + int32_t fence) +{ + assert(!"Fenced release not supported yet"); +} + +static void +buffer_release_immediate_handler(void *data, + struct zwp_linux_buffer_release_v1 *buffer_release) +{ + int *released = data; + + *released += 1; +} + +struct zwp_linux_buffer_release_v1_listener buffer_release_listener = { + buffer_release_fenced_handler, + buffer_release_immediate_handler +}; + +TEST(get_release_events_are_emitted) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + struct buffer *buf1 = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct buffer *buf2 = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct wl_surface *surface = client->surface->wl_surface; + struct zwp_linux_buffer_release_v1 *buffer_release1; + struct zwp_linux_buffer_release_v1 *buffer_release2; + int buf_released1 = 0; + int buf_released2 = 0; + int frame; + + buffer_release1 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + zwp_linux_buffer_release_v1_add_listener(buffer_release1, + &buffer_release_listener, + &buf_released1); + wl_surface_attach(surface, buf1->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* Check that exactly one buffer_release event was emitted. */ + assert(buf_released1 == 1); + + buffer_release2 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + zwp_linux_buffer_release_v1_add_listener(buffer_release2, + &buffer_release_listener, + &buf_released2); + wl_surface_attach(surface, buf2->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* Check that we didn't get any new events on the inactive + * buffer_release. */ + assert(buf_released1 == 1); + /* Check that exactly one buffer_release event was emitted. */ + assert(buf_released2 == 1); + + buffer_destroy(buf2); + buffer_destroy(buf1); + zwp_linux_buffer_release_v1_destroy(buffer_release2); + zwp_linux_buffer_release_v1_destroy(buffer_release1); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} From c715c750f641c8efc85865694845c8070b93128d Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Mon, 12 Nov 2018 18:33:54 +0200 Subject: [PATCH 0754/1642] tests: Add tests for per commit zwp_buffer_release_v1 behavior Add tests to check that the zwp_buffer_release_v1 events are emitted per surface commit. To be able to test this we need to use a renderer that holds the buffer until the next buffer is committed, hence we use the pixman renderer. Changes in v7: - Remove references to obsolete noop-hold renderer. Changes in v5: - Meson support. Changes in v4: - Use the pixman renderer instead of the (now gone) noop-hold renderer. Signed-off-by: Alexandros Frantzis --- tests/linux-explicit-synchronization-test.c | 169 +++++++++++++++++++- tests/meson.build | 2 + 2 files changed, 165 insertions(+), 6 deletions(-) diff --git a/tests/linux-explicit-synchronization-test.c b/tests/linux-explicit-synchronization-test.c index 73b1177cb..a89b252b1 100644 --- a/tests/linux-explicit-synchronization-test.c +++ b/tests/linux-explicit-synchronization-test.c @@ -32,6 +32,11 @@ #include "weston-test-client-helper.h" #include "wayland-server-protocol.h" +/* We need to use the pixman renderer, since a few of the tests depend + * on the renderer holding onto a surface buffer until the next one + * is committed, which the noop renderer doesn't do. */ +char *server_parameters = "--use-pixman"; + static struct zwp_linux_explicit_synchronization_v1 * get_linux_explicit_synchronization(struct client *client) { @@ -276,7 +281,14 @@ struct zwp_linux_buffer_release_v1_listener buffer_release_listener = { buffer_release_immediate_handler }; -TEST(get_release_events_are_emitted) +/* The following release event tests depend on the behavior of the used + * backend, in this case the pixman backend. This doesn't limit their + * usefulness, though, since it allows them to check if, given a typical + * backend implementation, weston core supports the per commit nature of the + * release events. + */ + +TEST(get_release_events_are_emitted_for_different_buffers) { struct client *client = create_test_client(); struct zwp_linux_explicit_synchronization_v1 *sync = @@ -302,8 +314,9 @@ TEST(get_release_events_are_emitted) frame_callback_set(surface, &frame); wl_surface_commit(surface); frame_callback_wait(client, &frame); - /* Check that exactly one buffer_release event was emitted. */ - assert(buf_released1 == 1); + /* No release event should have been emitted yet (we are using the + * pixman renderer, which holds buffers until they are replaced). */ + assert(buf_released1 == 0); buffer_release2 = zwp_linux_surface_synchronization_v1_get_release(surface_sync); @@ -314,10 +327,18 @@ TEST(get_release_events_are_emitted) frame_callback_set(surface, &frame); wl_surface_commit(surface); frame_callback_wait(client, &frame); - /* Check that we didn't get any new events on the inactive - * buffer_release. */ + /* Check that exactly one buffer_release event was emitted for the + * previous commit (buf1). */ + assert(buf_released1 == 1); + assert(buf_released2 == 0); + + wl_surface_attach(surface, buf1->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* Check that exactly one buffer_release event was emitted for the + * previous commit (buf2). */ assert(buf_released1 == 1); - /* Check that exactly one buffer_release event was emitted. */ assert(buf_released2 == 1); buffer_destroy(buf2); @@ -327,3 +348,139 @@ TEST(get_release_events_are_emitted) zwp_linux_surface_synchronization_v1_destroy(surface_sync); zwp_linux_explicit_synchronization_v1_destroy(sync); } + +TEST(get_release_events_are_emitted_for_same_buffer_on_surface) +{ + struct client *client = create_test_client(); + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, client->surface->wl_surface); + struct buffer *buf = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct wl_surface *surface = client->surface->wl_surface; + struct zwp_linux_buffer_release_v1 *buffer_release1; + struct zwp_linux_buffer_release_v1 *buffer_release2; + int buf_released1 = 0; + int buf_released2 = 0; + int frame; + + buffer_release1 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + zwp_linux_buffer_release_v1_add_listener(buffer_release1, + &buffer_release_listener, + &buf_released1); + wl_surface_attach(surface, buf->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* No release event should have been emitted yet (we are using the + * pixman renderer, which holds buffers until they are replaced). */ + assert(buf_released1 == 0); + + buffer_release2 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync); + zwp_linux_buffer_release_v1_add_listener(buffer_release2, + &buffer_release_listener, + &buf_released2); + wl_surface_attach(surface, buf->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* Check that exactly one buffer_release event was emitted for the + * previous commit (buf). */ + assert(buf_released1 == 1); + assert(buf_released2 == 0); + + wl_surface_attach(surface, buf->proxy, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + /* Check that exactly one buffer_release event was emitted for the + * previous commit (buf again). */ + assert(buf_released1 == 1); + assert(buf_released2 == 1); + + buffer_destroy(buf); + zwp_linux_buffer_release_v1_destroy(buffer_release2); + zwp_linux_buffer_release_v1_destroy(buffer_release1); + zwp_linux_surface_synchronization_v1_destroy(surface_sync); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} + +TEST(get_release_events_are_emitted_for_same_buffer_on_different_surfaces) +{ + struct client *client = create_test_client(); + struct surface *other_surface = create_test_surface(client); + struct wl_surface *surface1 = client->surface->wl_surface; + struct wl_surface *surface2 = other_surface->wl_surface; + struct zwp_linux_explicit_synchronization_v1 *sync = + get_linux_explicit_synchronization(client); + struct zwp_linux_surface_synchronization_v1 *surface_sync1 = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, surface1); + struct zwp_linux_surface_synchronization_v1 *surface_sync2 = + zwp_linux_explicit_synchronization_v1_get_synchronization( + sync, surface2); + struct buffer *buf1 = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct buffer *buf2 = create_shm_buffer_a8r8g8b8(client, 100, 100); + struct zwp_linux_buffer_release_v1 *buffer_release1; + struct zwp_linux_buffer_release_v1 *buffer_release2; + int buf_released1 = 0; + int buf_released2 = 0; + int frame; + + weston_test_move_surface(client->test->weston_test, surface2, 0, 0); + + /* Attach buf1 to both surface1 and surface2. */ + buffer_release1 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync1); + zwp_linux_buffer_release_v1_add_listener(buffer_release1, + &buffer_release_listener, + &buf_released1); + wl_surface_attach(surface1, buf1->proxy, 0, 0); + frame_callback_set(surface1, &frame); + wl_surface_commit(surface1); + frame_callback_wait(client, &frame); + + buffer_release2 = + zwp_linux_surface_synchronization_v1_get_release(surface_sync2); + zwp_linux_buffer_release_v1_add_listener(buffer_release2, + &buffer_release_listener, + &buf_released2); + wl_surface_attach(surface2, buf1->proxy, 0, 0); + frame_callback_set(surface2, &frame); + wl_surface_commit(surface2); + frame_callback_wait(client, &frame); + + assert(buf_released1 == 0); + assert(buf_released2 == 0); + + /* Attach buf2 to surface1, and check that a buffer_release event for + * the previous commit (buf1) for that surface is emitted. */ + wl_surface_attach(surface1, buf2->proxy, 0, 0); + frame_callback_set(surface1, &frame); + wl_surface_commit(surface1); + frame_callback_wait(client, &frame); + + assert(buf_released1 == 1); + assert(buf_released2 == 0); + + /* Attach buf2 to surface2, and check that a buffer_release event for + * the previous commit (buf1) for that surface is emitted. */ + wl_surface_attach(surface2, buf2->proxy, 0, 0); + frame_callback_set(surface2, &frame); + wl_surface_commit(surface2); + frame_callback_wait(client, &frame); + + assert(buf_released1 == 1); + assert(buf_released2 == 1); + + buffer_destroy(buf2); + buffer_destroy(buf1); + zwp_linux_buffer_release_v1_destroy(buffer_release2); + zwp_linux_buffer_release_v1_destroy(buffer_release1); + zwp_linux_surface_synchronization_v1_destroy(surface_sync2); + zwp_linux_surface_synchronization_v1_destroy(surface_sync1); + zwp_linux_explicit_synchronization_v1_destroy(sync); +} diff --git a/tests/meson.build b/tests/meson.build index 7baee5122..ee24562b1 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -310,6 +310,8 @@ foreach t : tests_weston args_t += [ '--width=320' ] args_t += [ '--height=240' ] args_t += [ '--shell=weston-test-desktop-shell.so' ] + elif t.get(0) == 'linux-explicit-synchronization' + args_t += [ '--use-pixman' ] elif t.get(0).startswith('ivi-') args_t += [ '--config=@0@/../ivi-shell/weston-ivi-test.ini'.format(meson.current_build_dir()) ] args_t += [ '--shell=ivi-shell.so' ] From a95bb6f7e554c1a7ae52e87391f86944a3ba9461 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Tue, 30 Oct 2018 14:26:47 +0200 Subject: [PATCH 0755/1642] clients: Support explicit synchronization in simple-dmabuf-egl Signed-off-by: Alexandros Frantzis Changes in v7: - Merge acquire fence and release fence code blocks in redraw(). - Use 1 << n to define option bitflags. - Remove redundant statement involving OPT_IMPLICIT_SYNC. Changes in v6: - Add option for window size. - Add option for enabling/disabling explicit sync. Changes in v5: - Meson support. --- Makefile.am | 4 +- clients/meson.build | 2 + clients/simple-dmabuf-egl.c | 227 ++++++++++++++++++++++++++++++++++-- 3 files changed, 225 insertions(+), 8 deletions(-) diff --git a/Makefile.am b/Makefile.am index 3e95631a3..a72f05164 100644 --- a/Makefile.am +++ b/Makefile.am @@ -698,7 +698,9 @@ nodist_weston_simple_dmabuf_egl_SOURCES = \ protocol/fullscreen-shell-unstable-v1-protocol.c \ protocol/fullscreen-shell-unstable-v1-client-protocol.h \ protocol/linux-dmabuf-unstable-v1-protocol.c \ - protocol/linux-dmabuf-unstable-v1-client-protocol.h + protocol/linux-dmabuf-unstable-v1-client-protocol.h \ + protocol/linux-explicit-synchronization-unstable-v1-protocol.c \ + protocol/linux-explicit-synchronization-v1-client-protocol.h weston_simple_dmabuf_egl_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_EGL_CLIENT_CFLAGS) weston_simple_dmabuf_egl_LDADD = $(SIMPLE_DMABUF_EGL_CLIENT_LIBS) libshared.la -lm endif diff --git a/clients/meson.build b/clients/meson.build index 60c48714c..4d682e3b9 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -54,6 +54,8 @@ simple_clients = [ 'simple-dmabuf-egl.c', linux_dmabuf_unstable_v1_client_protocol_h, linux_dmabuf_unstable_v1_protocol_c, + linux_explicit_synchronization_unstable_v1_client_protocol_h, + linux_explicit_synchronization_unstable_v1_protocol_c, xdg_shell_unstable_v6_client_protocol_h, xdg_shell_unstable_v6_protocol_c, fullscreen_shell_unstable_v1_client_protocol_h, diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c index 01c16e287..3d0cf7a10 100644 --- a/clients/simple-dmabuf-egl.c +++ b/clients/simple-dmabuf-egl.c @@ -50,6 +50,7 @@ #include "xdg-shell-unstable-v6-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "linux-explicit-synchronization-unstable-v1-client-protocol.h" #include #include @@ -63,7 +64,8 @@ #endif /* Possible options that affect the displayed image */ -#define OPT_IMMEDIATE 1 /* create wl_buffer immediately */ +#define OPT_IMMEDIATE (1 << 0) /* create wl_buffer immediately */ +#define OPT_IMPLICIT_SYNC (1 << 1) /* force implicit sync */ #define BUFFER_FORMAT DRM_FORMAT_XRGB8888 #define MAX_BUFFER_PLANES 4 @@ -75,9 +77,11 @@ struct display { struct zxdg_shell_v6 *shell; struct zwp_fullscreen_shell_v1 *fshell; struct zwp_linux_dmabuf_v1 *dmabuf; + struct zwp_linux_explicit_synchronization_v1 *explicit_sync; uint64_t *modifiers; int modifiers_count; int req_dmabuf_immediate; + bool use_explicit_sync; struct { EGLDisplay display; EGLContext context; @@ -86,6 +90,11 @@ struct display { PFNEGLCREATEIMAGEKHRPROC create_image; PFNEGLDESTROYIMAGEKHRPROC destroy_image; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; + PFNEGLCREATESYNCKHRPROC create_sync; + PFNEGLDESTROYSYNCKHRPROC destroy_sync; + PFNEGLCLIENTWAITSYNCKHRPROC client_wait_sync; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd; + PFNEGLWAITSYNCKHRPROC wait_sync; } egl; struct { int drm_fd; @@ -112,6 +121,11 @@ struct buffer { EGLImageKHR egl_image; GLuint gl_texture; GLuint gl_fbo; + + struct zwp_linux_buffer_release_v1 *buffer_release; + /* The buffer owns the release_fence_fd, until it passes ownership + * to it to EGL (see wait_for_buffer_release_fence). */ + int release_fence_fd; }; #define NUM_BUFFERS 3 @@ -122,6 +136,7 @@ struct window { struct wl_surface *surface; struct zxdg_surface_v6 *xdg_surface; struct zxdg_toplevel_v6 *xdg_toplevel; + struct zwp_linux_surface_synchronization_v1 *surface_sync; struct buffer buffers[NUM_BUFFERS]; struct wl_callback *callback; bool initialized; @@ -156,6 +171,12 @@ buffer_free(struct buffer *buf) { int i; + if (buf->release_fence_fd >= 0) + close(buf->release_fence_fd); + + if (buf->buffer_release) + zwp_linux_buffer_release_v1_destroy(buf->buffer_release); + if (buf->gl_fbo) glDeleteFramebuffers(1, &buf->gl_fbo); @@ -187,7 +208,13 @@ create_succeeded(void *data, struct buffer *buffer = data; buffer->buffer = new_buffer; - wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); + /* When not using explicit synchronization listen to wl_buffer.release + * for release notifications, otherwise we are going to use + * zwp_linux_buffer_release_v1. */ + if (!buffer->display->use_explicit_sync) { + wl_buffer_add_listener(buffer->buffer, &buffer_listener, + buffer); + } zwp_linux_buffer_params_v1_destroy(params); } @@ -308,6 +335,7 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, buffer->width = width; buffer->height = height; buffer->format = BUFFER_FORMAT; + buffer->release_fence_fd = -1; #ifdef HAVE_GBM_MODIFIERS if (display->modifiers_count > 0) { @@ -378,7 +406,14 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, buffer->height, buffer->format, flags); - wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); + /* When not using explicit synchronization listen to + * wl_buffer.release for release notifications, otherwise we + * are going to use zwp_linux_buffer_release_v1. */ + if (!buffer->display->use_explicit_sync) { + wl_buffer_add_listener(buffer->buffer, + &buffer_listener, + buffer); + } } else { zwp_linux_buffer_params_v1_create(params, @@ -540,6 +575,8 @@ destroy_window(struct window *window) zxdg_toplevel_v6_destroy(window->xdg_toplevel); if (window->xdg_surface) zxdg_surface_v6_destroy(window->xdg_surface); + if (window->surface_sync) + zwp_linux_surface_synchronization_v1_destroy(window->surface_sync); wl_surface_destroy(window->surface); free(window); } @@ -592,6 +629,13 @@ create_window(struct display *display, int width, int height) assert(0); } + if (display->explicit_sync) { + window->surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + display->explicit_sync, window->surface); + assert(window->surface_sync); + } + for (i = 0; i < NUM_BUFFERS; ++i) { int j; for (j = 0; j < MAX_BUFFER_PLANES; ++j) @@ -619,6 +663,26 @@ create_window(struct display *display, int width, int height) return NULL; } +static int +create_egl_fence_fd(struct window *window) +{ + struct display *d = window->display; + EGLSyncKHR sync = d->egl.create_sync(d->egl.display, + EGL_SYNC_NATIVE_FENCE_ANDROID, + NULL); + int fd; + + assert(sync != EGL_NO_SYNC_KHR); + /* We need to flush before we can get the fence fd. */ + glFlush(); + fd = d->egl.dup_native_fence_fd(d->egl.display, sync); + assert(fd >= 0); + + d->egl.destroy_sync(d->egl.display, sync); + + return fd; +} + static struct buffer * window_next_buffer(struct window *window) { @@ -691,6 +755,70 @@ render(struct window *window, struct buffer *buffer) glDisableVertexAttribArray(window->gl.color); } +static void +buffer_fenced_release(void *data, + struct zwp_linux_buffer_release_v1 *release, + int32_t fence) +{ + struct buffer *buffer = data; + + assert(release == buffer->buffer_release); + assert(buffer->release_fence_fd == -1); + + buffer->busy = 0; + buffer->release_fence_fd = fence; + zwp_linux_buffer_release_v1_destroy(buffer->buffer_release); + buffer->buffer_release = NULL; +} + +static void +buffer_immediate_release(void *data, + struct zwp_linux_buffer_release_v1 *release) +{ + struct buffer *buffer = data; + + assert(release == buffer->buffer_release); + assert(buffer->release_fence_fd == -1); + + buffer->busy = 0; + zwp_linux_buffer_release_v1_destroy(buffer->buffer_release); + buffer->buffer_release = NULL; +} + +static const struct zwp_linux_buffer_release_v1_listener buffer_release_listener = { + buffer_fenced_release, + buffer_immediate_release, +}; + +static void +wait_for_buffer_release_fence(struct buffer *buffer) +{ + struct display *d = buffer->display; + EGLint attrib_list[] = { + EGL_SYNC_NATIVE_FENCE_FD_ANDROID, buffer->release_fence_fd, + EGL_NONE, + }; + EGLSyncKHR sync = d->egl.create_sync(d->egl.display, + EGL_SYNC_NATIVE_FENCE_ANDROID, + attrib_list); + int ret; + + assert(sync); + + /* EGLSyncKHR takes ownership of the fence fd. */ + buffer->release_fence_fd = -1; + + if (d->egl.wait_sync) + ret = d->egl.wait_sync(d->egl.display, sync, 0); + else + ret = d->egl.client_wait_sync(d->egl.display, sync, 0, + EGL_FOREVER_KHR); + assert(ret == EGL_TRUE); + + ret = d->egl.destroy_sync(d->egl.display, sync); + assert(ret == EGL_TRUE); +} + static void redraw(void *data, struct wl_callback *callback, uint32_t time) { @@ -705,8 +833,24 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) abort(); } + if (buffer->release_fence_fd >= 0) + wait_for_buffer_release_fence(buffer); + render(window, buffer); - glFinish(); + + if (window->display->use_explicit_sync) { + int fence_fd = create_egl_fence_fd(window); + zwp_linux_surface_synchronization_v1_set_acquire_fence( + window->surface_sync, fence_fd); + close(fence_fd); + + buffer->buffer_release = + zwp_linux_surface_synchronization_v1_get_release(window->surface_sync); + zwp_linux_buffer_release_v1_add_listener( + buffer->buffer_release, &buffer_release_listener, buffer); + } else { + glFinish(); + } wl_surface_attach(window->surface, buffer->buffer, 0, 0); wl_surface_damage(window->surface, 0, 0, window->width, window->height); @@ -787,6 +931,10 @@ registry_handle_global(void *data, struct wl_registry *registry, d->dmabuf = wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, 3); zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); + } else if (strcmp(interface, "zwp_linux_explicit_synchronization_v1") == 0) { + d->explicit_sync = wl_registry_bind( + registry, id, + &zwp_linux_explicit_synchronization_v1_interface, 1); } } @@ -932,6 +1080,33 @@ display_set_up_egl(struct display *display) (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); assert(display->egl.image_target_texture_2d); + if (weston_check_egl_extension(egl_extensions, "EGL_KHR_fence_sync") && + weston_check_egl_extension(egl_extensions, + "EGL_ANDROID_native_fence_sync")) { + display->egl.create_sync = + (void *) eglGetProcAddress("eglCreateSyncKHR"); + assert(display->egl.create_sync); + + display->egl.destroy_sync = + (void *) eglGetProcAddress("eglDestroySyncKHR"); + assert(display->egl.destroy_sync); + + display->egl.client_wait_sync = + (void *) eglGetProcAddress("eglClientWaitSyncKHR"); + assert(display->egl.client_wait_sync); + + display->egl.dup_native_fence_fd = + (void *) eglGetProcAddress("eglDupNativeFenceFDANDROID"); + assert(display->egl.dup_native_fence_fd); + } + + if (weston_check_egl_extension(egl_extensions, + "EGL_KHR_wait_sync")) { + display->egl.wait_sync = + (void *) eglGetProcAddress("eglWaitSyncKHR"); + assert(display->egl.wait_sync); + } + return true; error: @@ -1070,6 +1245,29 @@ create_display(char const *drm_render_node, int opts) if (!display_set_up_gbm(display, drm_render_node)) goto error; + /* We use explicit synchronization only if the user hasn't disabled it, + * the compositor supports it, we can handle fence fds. */ + display->use_explicit_sync = + !(opts & OPT_IMPLICIT_SYNC) && + display->explicit_sync && + display->egl.dup_native_fence_fd; + + if (opts & OPT_IMPLICIT_SYNC) { + fprintf(stderr, "Warning: Not using explicit sync, disabled by user\n"); + } else if (!display->explicit_sync) { + fprintf(stderr, + "Warning: zwp_linux_explicit_synchronization_v1 not supported,\n" + " will not use explicit synchronization\n"); + } else if (!display->egl.dup_native_fence_fd) { + fprintf(stderr, + "Warning: EGL_ANDROID_native_fence_sync not supported,\n" + " will not use explicit synchronization\n"); + } else if (!display->egl.wait_sync) { + fprintf(stderr, + "Warning: EGL_KHR_wait_sync not supported,\n" + " will not use server-side wait\n"); + } + return display; error: @@ -1092,7 +1290,12 @@ print_usage_and_exit(void) "\n\t\t0 to import dmabuf via roundtrip, " "\n\t\t1 to enable import without roundtrip\n" "\t'-d,--drm-render-node=<>'" - "\n\t\tthe full path to the drm render node to use\n"); + "\n\t\tthe full path to the drm render node to use\n" + "\t'-s,--size=<>'" + "\n\t\tthe window size in pixels (default: 256)\n" + "\t'-e,--explicit-sync=<>'" + "\n\t\t0 to disable explicit sync, " + "\n\t\t1 to enable explicit sync (default: 1)\n"); exit(0); } @@ -1118,15 +1321,18 @@ main(int argc, char **argv) int opts = 0; char const *drm_render_node = "/dev/dri/renderD128"; int c, option_index, ret = 0; + int window_size = 256; static struct option long_options[] = { {"import-immediate", required_argument, 0, 'i' }, {"drm-render-node", required_argument, 0, 'd' }, + {"size", required_argument, 0, 's' }, + {"explicit-sync", required_argument, 0, 'e' }, {"help", no_argument , 0, 'h' }, {0, 0, 0, 0} }; - while ((c = getopt_long(argc, argv, "hi:d:", + while ((c = getopt_long(argc, argv, "hi:d:s:e:", long_options, &option_index)) != -1) { switch (c) { case 'i': @@ -1136,6 +1342,13 @@ main(int argc, char **argv) case 'd': drm_render_node = optarg; break; + case 's': + window_size = strtol(optarg, NULL, 10); + break; + case 'e': + if (!is_true(optarg)) + opts |= OPT_IMPLICIT_SYNC; + break; default: print_usage_and_exit(); } @@ -1144,7 +1357,7 @@ main(int argc, char **argv) display = create_display(drm_render_node, opts); if (!display) return 1; - window = create_window(display, 256, 256); + window = create_window(display, window_size, window_size); if (!window) return 1; From 7261edfe363482e140162cc18534a8e46f01915c Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Tue, 29 Jan 2019 16:22:45 +0200 Subject: [PATCH 0756/1642] clients: Add a mandelbrot set shader to simple-dmabuf-egl Support drawing a mandelbrot set in the fragment shader, rendering it with separate draw calls, one for each cell in a virtual 4x4 grid. This more complex and heavy drawing will potentially help us to visually discover any present or future explicit synchronization issues. The mandelbrot set rendering is enabled with the -m/--mandelbrot command-line switch. Signed-off-by: Alexandros Frantzis --- clients/simple-dmabuf-egl.c | 126 +++++++++++++++++++++++++++++++++--- 1 file changed, 118 insertions(+), 8 deletions(-) diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c index 3d0cf7a10..e81f1629c 100644 --- a/clients/simple-dmabuf-egl.c +++ b/clients/simple-dmabuf-egl.c @@ -66,6 +66,7 @@ /* Possible options that affect the displayed image */ #define OPT_IMMEDIATE (1 << 0) /* create wl_buffer immediately */ #define OPT_IMPLICIT_SYNC (1 << 1) /* force implicit sync */ +#define OPT_MANDELBROT (1 << 2) /* render mandelbrot */ #define BUFFER_FORMAT DRM_FORMAT_XRGB8888 #define MAX_BUFFER_PLANES 4 @@ -147,6 +148,7 @@ struct window { GLuint color; GLuint offset_uniform; } gl; + bool render_mandelbrot; }; static sig_atomic_t running = 1; @@ -485,6 +487,40 @@ static const char *frag_shader_text = " gl_FragColor = v_color;\n" "}\n"; +static const char *vert_shader_mandelbrot_text = + "uniform float offset;\n" + "attribute vec4 pos;\n" + "varying vec2 v_pos;\n" + "void main() {\n" + " v_pos = pos.xy;\n" + " gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\n" + "}\n"; + + +/* Mandelbrot set shader using the escape time algorithm. */ +static const char *frag_shader_mandelbrot_text = + "precision mediump float;\n" + "varying vec2 v_pos;\n" + "void main() {\n" + " const int max_iteration = 500;\n" + " // Scale and translate position to get a nice mandelbrot drawing for\n" + " // the used v_pos x and y range (-0.5 to 0.5).\n" + " float x0 = 3.0 * v_pos.x - 0.5;\n" + " float y0 = 3.0 * v_pos.y;\n" + " float x = 0.0;\n" + " float y = 0.0;\n" + " int iteration = 0;\n" + " while (x * x + y * y <= 4.0 && iteration < max_iteration) {\n" + " float xtemp = x * x - y * y + x0;\n" + " y = 2.0 * x * y + y0;\n" + " x = xtemp;\n" + " ++iteration;\n" + " }\n" + " float red = iteration == max_iteration ?\n" + " 0.0 : 1.0 - fract(float(iteration) / 20.0);\n" + " gl_FragColor = vec4(red, 0.0, 0.0, 1.0);\n" + "}\n"; + static GLuint create_shader(const char *source, GLenum shader_type) { @@ -536,8 +572,14 @@ create_and_link_program(GLuint vert, GLuint frag) static bool window_set_up_gl(struct window *window) { - GLuint vert = create_shader(vert_shader_text, GL_VERTEX_SHADER); - GLuint frag = create_shader(frag_shader_text, GL_FRAGMENT_SHADER); + GLuint vert = create_shader( + window->render_mandelbrot ? vert_shader_mandelbrot_text : + vert_shader_text, + GL_VERTEX_SHADER); + GLuint frag = create_shader( + window->render_mandelbrot ? frag_shader_mandelbrot_text : + frag_shader_text, + GL_FRAGMENT_SHADER); window->gl.program = create_and_link_program(vert, frag); @@ -582,7 +624,7 @@ destroy_window(struct window *window) } static struct window * -create_window(struct display *display, int width, int height) +create_window(struct display *display, int width, int height, int opts) { struct window *window; int i; @@ -651,6 +693,8 @@ create_window(struct display *display, int width, int height) goto error; } + window->render_mandelbrot = opts & OPT_MANDELBROT; + if (!window_set_up_gl(window)) goto error; @@ -755,6 +799,63 @@ render(struct window *window, struct buffer *buffer) glDisableVertexAttribArray(window->gl.color); } +static void +render_mandelbrot(struct window *window, struct buffer *buffer) +{ + /* Complete a movement iteration in 5000 ms. */ + static const uint64_t iteration_ms = 5000; + /* Split drawing in a square grid consisting of grid_side * grid_side + * cells. */ + static const int grid_side = 4; + GLfloat norm_cell_side = 1.0 / grid_side; + int num_cells = grid_side * grid_side; + GLfloat offset; + struct timeval tv; + uint64_t time_ms; + int i; + + gettimeofday(&tv, NULL); + time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + /* Split time_ms in repeating windows of [0, iteration_ms) and map them + * to offsets in the [-0.5, 0.5) range. */ + offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; + + /* Direct all GL draws to the buffer through the FBO */ + glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); + + glViewport(0, 0, window->width, window->height); + + glUniform1f(window->gl.offset_uniform, offset); + + glClearColor(0.6, 0.6, 0.6, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + for (i = 0; i < num_cells; ++i) { + /* Calculate the vertex coordinates of the current grid cell. */ + int row = i / grid_side; + int col = i % grid_side; + GLfloat left = -0.5 + norm_cell_side * col; + GLfloat right = left + norm_cell_side; + GLfloat top = 0.5 - norm_cell_side * row; + GLfloat bottom = top - norm_cell_side; + GLfloat verts[4][2] = { + { left, bottom }, + { left, top }, + { right, bottom }, + { right, top } + }; + + /* ... and draw it. */ + glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glEnableVertexAttribArray(window->gl.pos); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(window->gl.pos); + } +} + static void buffer_fenced_release(void *data, struct zwp_linux_buffer_release_v1 *release, @@ -836,7 +937,10 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) if (buffer->release_fence_fd >= 0) wait_for_buffer_release_fence(buffer); - render(window, buffer); + if (window->render_mandelbrot) + render_mandelbrot(window, buffer); + else + render(window, buffer); if (window->display->use_explicit_sync) { int fence_fd = create_egl_fence_fd(window); @@ -1295,7 +1399,9 @@ print_usage_and_exit(void) "\n\t\tthe window size in pixels (default: 256)\n" "\t'-e,--explicit-sync=<>'" "\n\t\t0 to disable explicit sync, " - "\n\t\t1 to enable explicit sync (default: 1)\n"); + "\n\t\t1 to enable explicit sync (default: 1)\n" + "\t'-m,--mandelbrot'" + "\n\t\trender a mandelbrot set with multiple draw calls\n"); exit(0); } @@ -1328,12 +1434,13 @@ main(int argc, char **argv) {"drm-render-node", required_argument, 0, 'd' }, {"size", required_argument, 0, 's' }, {"explicit-sync", required_argument, 0, 'e' }, + {"mandelbrot", no_argument, 0, 'm' }, {"help", no_argument , 0, 'h' }, {0, 0, 0, 0} }; - while ((c = getopt_long(argc, argv, "hi:d:s:e:", - long_options, &option_index)) != -1) { + while ((c = getopt_long(argc, argv, "hi:d:s:e:m", + long_options, &option_index)) != -1) { switch (c) { case 'i': if (is_true(optarg)) @@ -1349,6 +1456,9 @@ main(int argc, char **argv) if (!is_true(optarg)) opts |= OPT_IMPLICIT_SYNC; break; + case 'm': + opts |= OPT_MANDELBROT; + break; default: print_usage_and_exit(); } @@ -1357,7 +1467,7 @@ main(int argc, char **argv) display = create_display(drm_render_node, opts); if (!display) return 1; - window = create_window(display, window_size, window_size); + window = create_window(display, window_size, window_size, opts); if (!window) return 1; From 1ed3506b70d988493b91861c5761e8a7ed88e772 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 6 Feb 2019 16:42:09 +0200 Subject: [PATCH 0757/1642] libweston: export weston_config API Make it official that libweston will export the weston_config API, as requested in https://gitlab.freedesktop.org/wayland/weston/merge_requests/29 . There is no other way third party helper clients could access the API. The autotools build has been accidentally exporting it all the time, but the Meson build needed fixing. Signed-off-by: Pekka Paalanen --- README.md | 7 +++++++ ivi-shell/meson.build | 2 +- libweston/meson.build | 2 +- shared/meson.build | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d61b9322a..d62da5172 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,13 @@ Libweston was first introduced in Weston 1.12, and is expected to continue evolving through many Weston releases before it achieves a stable API and feature completeness. +Libweston's primary purpose is exporting an API for creating Wayland +compositors. Libweston's secondary purpose is to export the weston_config API +so that third party plugins and helper programs can read `weston.ini` if they +want to. However, these two scopes are orthogonal and independent. At no point +will the compositor functionality use or depend on the weston_config +functionality. + API/ABI (in)stability and parallel installability ------------------------------------------------- diff --git a/ivi-shell/meson.build b/ivi-shell/meson.build index 057e39736..7a1769949 100644 --- a/ivi-shell/meson.build +++ b/ivi-shell/meson.build @@ -30,7 +30,7 @@ if get_option('shell-ivi') 'hmi-controller', srcs_ivi_hmi, include_directories: include_directories('..', '../shared'), - dependencies: dep_libweston, + dependencies: [ dep_libweston, dep_libshared ], name_prefix: '', install: true, install_dir: dir_module_weston diff --git a/libweston/meson.build b/libweston/meson.build index f6e2a0fd7..fa7950113 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -4,7 +4,6 @@ deps_libweston = [ dep_libm, dep_libdl, dep_libdrm_headers, - dep_libshared, dep_xkbcommon, ] srcs_libweston = [ @@ -75,6 +74,7 @@ lib_weston = shared_library( link_args: [ '-export-dynamic' ], install: true, version: '0.0.@0@'.format(libweston_revision), + link_whole: lib_libshared, dependencies: deps_libweston ) diff --git a/shared/meson.build b/shared/meson.build index 09020430c..5b0d8d130 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -12,6 +12,7 @@ lib_libshared = static_library( srcs_libshared, include_directories: include_directories('..'), dependencies: deps_libshared, + pic: true, install: false ) dep_libshared = declare_dependency( From f2042e132e589fb5915d1caa2072edbed21105f4 Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Tue, 26 Sep 2017 17:12:08 +0200 Subject: [PATCH 0758/1642] ivi-shell: rework goto labels to avoid memory leaks Signed-off-by: Michael Teyfel --- ivi-shell/ivi-shell.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 54ab04193..e2761b9ab 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -461,7 +461,6 @@ wet_shell_init(struct weston_compositor *compositor, int *argc, char *argv[]) { struct ivi_shell *shell; - int retval = -1; shell = zalloc(sizeof *shell); if (shell == NULL) @@ -478,13 +477,15 @@ wet_shell_init(struct weston_compositor *compositor, if (wl_global_create(compositor->wl_display, &ivi_application_interface, 1, shell, bind_ivi_application) == NULL) - goto out; + goto err_shell; ivi_layout_init_with_compositor(compositor); shell_add_bindings(compositor, shell); - retval = 0; + return IVI_SUCCEEDED; -out: - return retval; +err_shell: + free(shell); + + return IVI_FAILED; } From 8b6fb14871f066063e26938ec499085b03a8a58e Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Tue, 17 Oct 2017 10:29:21 +0200 Subject: [PATCH 0759/1642] ivi-shell: removed assert Removed assert, that checks if ivi-surface is not available, since this can now happen with xdg-shell support. Signed-off-by: Michael Teyfel --- ivi-shell/ivi-shell.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index e2761b9ab..ba628551e 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -108,7 +108,6 @@ shell_surface_send_configure(struct weston_surface *surface, struct ivi_shell_surface *shsurf; shsurf = get_ivi_shell_surface(surface); - assert(shsurf); if (!shsurf) return; From 7419dc26e0e81dfc7547b919f9663cd685af972c Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Tue, 17 Oct 2017 10:31:09 +0200 Subject: [PATCH 0760/1642] ivi-shell: introduction of IVI_INVALID_ID Introduction of IVI_INVALID_ID for xdg-shell applications. Signed-off-by: Michael Teyfel --- ivi-shell/ivi-layout-export.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index 016d8b5c7..02bfb2cbb 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -56,6 +56,7 @@ extern "C" { #endif /* __cplusplus */ #include +#include #include "stdbool.h" #include "compositor.h" @@ -63,6 +64,7 @@ extern "C" { #define IVI_SUCCEEDED (0) #define IVI_FAILED (-1) +#define IVI_INVALID_ID UINT_MAX struct ivi_layout_layer; struct ivi_layout_screen; From c9c247730b37a0101570f7a2596b1fb8b6ac66b5 Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Tue, 17 Oct 2017 10:35:47 +0200 Subject: [PATCH 0761/1642] layout-interface: added interface to change surface id This interface enables an id-agent to change the surface ids of an ivi-layout-surface once. Signed-off-by: Michael Teyfel --- ivi-shell/ivi-layout-export.h | 6 ++++++ ivi-shell/ivi-layout.c | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index 02bfb2cbb..a3ec645a0 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -314,6 +314,12 @@ struct ivi_layout_interface { struct ivi_layout_surface *ivisurf, uint32_t duration); + /** + * \brief set id of ivi_layout_surface + */ + int32_t (*surface_set_id)(struct ivi_layout_surface *ivisurf, + uint32_t id_surface); + /** * layer controller interface */ diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index b9bcfc804..95a7f6289 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -1811,6 +1811,44 @@ ivi_layout_surface_set_transition_duration(struct ivi_layout_surface *ivisurf, return 0; } +/* + * This interface enables e.g. an id agent to set the id of an ivi-layout + * surface, that has been created by a desktop application. This can only be + * done once as long as the initial surface id equals IVI_INVALID_ID. Afterwards + * two events are emitted, namely surface_created and surface_configured. + */ +static int32_t +ivi_layout_surface_set_id(struct ivi_layout_surface *ivisurf, + uint32_t id_surface) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_surface *search_ivisurf = NULL; + + if (!ivisurf) { + weston_log("%s: invalid argument\n", __func__); + return IVI_FAILED; + } + + if (ivisurf->id_surface != IVI_INVALID_ID) { + weston_log("surface id can only be set once\n"); + return IVI_FAILED; + } + + search_ivisurf = get_surface(&layout->surface_list, id_surface); + if (search_ivisurf) { + weston_log("id_surface(%d) is already created\n", id_surface); + return IVI_FAILED; + } + + ivisurf->id_surface = id_surface; + + wl_signal_emit(&layout->surface_notification.created, ivisurf); + wl_signal_emit(&layout->surface_notification.configure_changed, + ivisurf); + + return IVI_SUCCEEDED; +} + static int32_t ivi_layout_surface_set_transition(struct ivi_layout_surface *ivisurf, enum ivi_layout_transition_type type, @@ -1973,6 +2011,7 @@ static struct ivi_layout_interface ivi_layout_interface = { .surface_get_weston_surface = ivi_layout_surface_get_weston_surface, .surface_set_transition = ivi_layout_surface_set_transition, .surface_set_transition_duration = ivi_layout_surface_set_transition_duration, + .surface_set_id = ivi_layout_surface_set_id, /** * layer controller interfaces From 65e1be1234fcbb7455fc9b41ac9916d258cf39f8 Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Tue, 17 Oct 2017 11:03:38 +0200 Subject: [PATCH 0762/1642] ivi-layout: introduced configure_desktop_changed Introduced signal configure_desktop_changed with interface. This signal should be sent, when a desktop-surface is configured. Signed-off-by: Michael Teyfel --- ivi-shell/ivi-layout-export.h | 10 ++++++++++ ivi-shell/ivi-layout-private.h | 1 + ivi-shell/ivi-layout.c | 17 +++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index a3ec645a0..c65eb306c 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -190,6 +190,16 @@ struct ivi_layout_interface { */ int32_t (*add_listener_configure_surface)(struct wl_listener *listener); + /** + * \brief add a listener for notification when desktop_surface is configured + * + * When an desktop_surface is configured, a signal is emitted + * to the listening controller plugins. + * The pointer of the configured desktop_surface is sent as the void *data argument + * to the wl_listener::notify callback function of the listener. + */ + int32_t (*add_listener_configure_desktop_surface)(struct wl_listener *listener); + /** * \brief Get all ivi_surfaces which are currently registered and managed * by the services diff --git a/ivi-shell/ivi-layout-private.h b/ivi-shell/ivi-layout-private.h index 2b8bd472e..fe5be01a9 100644 --- a/ivi-shell/ivi-layout-private.h +++ b/ivi-shell/ivi-layout-private.h @@ -104,6 +104,7 @@ struct ivi_layout { struct wl_signal created; struct wl_signal removed; struct wl_signal configure_changed; + struct wl_signal configure_desktop_changed; } surface_notification; struct weston_layer layout_layer; diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 95a7f6289..9e4cd1f10 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -968,6 +968,21 @@ ivi_layout_add_listener_configure_surface(struct wl_listener *listener) return IVI_SUCCEEDED; } +static int32_t +ivi_layout_add_listener_configure_desktop_surface(struct wl_listener *listener) +{ + struct ivi_layout *layout = get_instance(); + + if (!listener) { + weston_log("ivi_layout_add_listener_configure_desktop_surface: invalid argument\n"); + return IVI_FAILED; + } + + wl_signal_add(&layout->surface_notification.configure_desktop_changed, listener); + + return IVI_SUCCEEDED; +} + uint32_t ivi_layout_get_id_of_surface(struct ivi_layout_surface *ivisurf) { @@ -1969,6 +1984,7 @@ ivi_layout_init_with_compositor(struct weston_compositor *ec) wl_signal_init(&layout->surface_notification.created); wl_signal_init(&layout->surface_notification.removed); wl_signal_init(&layout->surface_notification.configure_changed); + wl_signal_init(&layout->surface_notification.configure_desktop_changed); /* Add layout_layer at the last of weston_compositor.layer_list */ weston_layer_init(&layout->layout_layer, ec); @@ -1997,6 +2013,7 @@ static struct ivi_layout_interface ivi_layout_interface = { .add_listener_create_surface = ivi_layout_add_listener_create_surface, .add_listener_remove_surface = ivi_layout_add_listener_remove_surface, .add_listener_configure_surface = ivi_layout_add_listener_configure_surface, + .add_listener_configure_desktop_surface = ivi_layout_add_listener_configure_desktop_surface, .get_surface = shell_get_ivi_layout_surface, .get_surfaces = ivi_layout_get_surfaces, .get_id_of_surface = ivi_layout_get_id_of_surface, From 4d886d605828f35975af94e3b5889f08a1451ed5 Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Tue, 5 Feb 2019 15:15:22 +0100 Subject: [PATCH 0763/1642] ivi-layout: introduced surface create and configure Introduced surface create and configure function for xdg-apps. Signed-off-by: Michael Teyfel --- ivi-shell/ivi-layout-shell.h | 7 ++++ ivi-shell/ivi-layout.c | 73 ++++++++++++++++++++++++------------ 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/ivi-shell/ivi-layout-shell.h b/ivi-shell/ivi-layout-shell.h index 68ca68ba1..bbbb77da9 100644 --- a/ivi-shell/ivi-layout-shell.h +++ b/ivi-shell/ivi-layout-shell.h @@ -39,6 +39,13 @@ struct weston_view; struct weston_surface; struct ivi_layout_surface; +void +ivi_layout_desktop_surface_configure(struct ivi_layout_surface *ivisurf, + int32_t width, int32_t height); + +struct ivi_layout_surface* +ivi_layout_desktop_surface_create(struct weston_surface *wl_surface); + void ivi_layout_surface_configure(struct ivi_layout_surface *ivisurf, int32_t width, int32_t height); diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 9e4cd1f10..94b08be50 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -1905,20 +1905,8 @@ ivi_layout_surface_dump(struct weston_surface *surface, * methods of interaction between ivi-shell with ivi-layout */ -void -ivi_layout_surface_configure(struct ivi_layout_surface *ivisurf, - int32_t width, int32_t height) -{ - struct ivi_layout *layout = get_instance(); - - /* emit callback which is set by ivi-layout api user */ - wl_signal_emit(&layout->surface_notification.configure_changed, - ivisurf); -} - -struct ivi_layout_surface* -ivi_layout_surface_create(struct weston_surface *wl_surface, - uint32_t id_surface) +static struct ivi_layout_surface* +surface_create(struct weston_surface *wl_surface, uint32_t id_surface) { struct ivi_layout *layout = get_instance(); struct ivi_layout_surface *ivisurf = NULL; @@ -1928,14 +1916,6 @@ ivi_layout_surface_create(struct weston_surface *wl_surface, return NULL; } - ivisurf = get_surface(&layout->surface_list, id_surface); - if (ivisurf != NULL) { - if (ivisurf->surface != NULL) { - weston_log("id_surface(%d) is already created\n", id_surface); - return NULL; - } - } - ivisurf = calloc(1, sizeof *ivisurf); if (ivisurf == NULL) { weston_log("fails to allocate memory\n"); @@ -1959,7 +1939,54 @@ ivi_layout_surface_create(struct weston_surface *wl_surface, wl_list_insert(&layout->surface_list, &ivisurf->link); - wl_signal_emit(&layout->surface_notification.created, ivisurf); + return ivisurf; +} + +void +ivi_layout_desktop_surface_configure(struct ivi_layout_surface *ivisurf, + int32_t width, int32_t height) +{ + struct ivi_layout *layout = get_instance(); + + /* emit callback which is set by ivi-layout api user */ + wl_signal_emit(&layout->surface_notification.configure_desktop_changed, + ivisurf); +} + +struct ivi_layout_surface* +ivi_layout_desktop_surface_create(struct weston_surface *wl_surface) +{ + return surface_create(wl_surface, IVI_INVALID_ID); +} + +void +ivi_layout_surface_configure(struct ivi_layout_surface *ivisurf, + int32_t width, int32_t height) +{ + struct ivi_layout *layout = get_instance(); + + /* emit callback which is set by ivi-layout api user */ + wl_signal_emit(&layout->surface_notification.configure_changed, + ivisurf); +} + +struct ivi_layout_surface* +ivi_layout_surface_create(struct weston_surface *wl_surface, + uint32_t id_surface) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_surface *ivisurf = NULL; + + ivisurf = get_surface(&layout->surface_list, id_surface); + if (ivisurf) { + weston_log("id_surface(%d) is already created\n", id_surface); + return NULL; + } + + ivisurf = surface_create(wl_surface, id_surface); + + if (ivisurf) + wl_signal_emit(&layout->surface_notification.created, ivisurf); return ivisurf; } From 8f9e92e8e32d1a0d53a8891e102cd78dca07509e Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Fri, 1 Feb 2019 15:17:35 +0100 Subject: [PATCH 0764/1642] ivi-shell: linked libweston-desktop and added structs Signed-off-by: Michael Teyfel --- Makefile.am | 1 + ivi-shell/ivi-layout-private.h | 2 ++ ivi-shell/ivi-shell.c | 4 +++- ivi-shell/ivi-shell.h | 2 ++ ivi-shell/meson.build | 2 +- 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index a72f05164..ae96b8c53 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1093,6 +1093,7 @@ ivi_shell_la_LDFLAGS = -module -avoid-version ivi_shell_la_LIBADD = \ libshared.la \ libweston-@LIBWESTON_MAJOR@.la \ + libweston-desktop-@LIBWESTON_MAJOR@.la \ $(COMPOSITOR_LIBS) ivi_shell_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) ivi_shell_la_SOURCES = \ diff --git a/ivi-shell/ivi-layout-private.h b/ivi-shell/ivi-layout-private.h index fe5be01a9..c054130bc 100644 --- a/ivi-shell/ivi-layout-private.h +++ b/ivi-shell/ivi-layout-private.h @@ -30,6 +30,7 @@ #include "compositor.h" #include "ivi-layout-export.h" +#include "libweston-desktop/libweston-desktop.h" struct ivi_layout_view { struct wl_list link; /* ivi_layout::view_list */ @@ -52,6 +53,7 @@ struct ivi_layout_surface { struct ivi_layout *layout; struct weston_surface *surface; + struct weston_desktop_surface *weston_desktop_surface; struct ivi_layout_surface_properties prop; diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index ba628551e..30190d4e0 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -44,7 +44,7 @@ #include "ivi-shell.h" #include "ivi-application-server-protocol.h" -#include "ivi-layout-export.h" +#include "ivi-layout-private.h" #include "ivi-layout-shell.h" #include "shared/helpers.h" #include "compositor/weston.h" @@ -265,6 +265,8 @@ application_surface_create(struct wl_client *client, return; } + layout_surface->weston_desktop_surface = NULL; + ivisurf = zalloc(sizeof *ivisurf); if (ivisurf == NULL) { wl_resource_post_no_memory(resource); diff --git a/ivi-shell/ivi-shell.h b/ivi-shell/ivi-shell.h index 78bf57dc5..3aeddcab6 100644 --- a/ivi-shell/ivi-shell.h +++ b/ivi-shell/ivi-shell.h @@ -30,6 +30,7 @@ #include #include "compositor.h" +#include "libweston-desktop/libweston-desktop.h" struct ivi_shell { @@ -38,6 +39,7 @@ struct ivi_shell struct weston_compositor *compositor; + struct weston_desktop *desktop; struct wl_list ivi_surface_list; /* struct ivi_shell_surface::link */ }; diff --git a/ivi-shell/meson.build b/ivi-shell/meson.build index 7a1769949..fceaf8041 100644 --- a/ivi-shell/meson.build +++ b/ivi-shell/meson.build @@ -12,7 +12,7 @@ if get_option('shell-ivi') 'ivi-shell', srcs_shell_ivi, include_directories: include_directories('..', '../shared'), - dependencies: dep_libweston, + dependencies: [ dep_lib_desktop, dep_libweston ], name_prefix: '', install: true, install_dir: dir_module_weston From 831e58f8e69d41cfcb78342f028275015ad60166 Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Mon, 6 Nov 2017 12:55:07 +0100 Subject: [PATCH 0765/1642] ivi-layout: use libweston-desktop api for views Signed-off-by: Michael Teyfel --- ivi-shell/ivi-layout.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 94b08be50..bb9f9f9f0 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -153,7 +153,10 @@ ivi_view_destroy(struct ivi_layout_view *ivi_view) wl_list_remove(&ivi_view->pending_link); wl_list_remove(&ivi_view->order_link); - weston_view_destroy(ivi_view->view); + if (weston_surface_is_desktop_surface(ivi_view->ivisurf->surface)) + weston_desktop_surface_unlink_view(ivi_view->view); + else + weston_view_destroy(ivi_view->view); free(ivi_view); } @@ -170,7 +173,13 @@ ivi_view_create(struct ivi_layout_layer *ivilayer, return NULL; } - ivi_view->view = weston_view_create(ivisurf->surface); + if (weston_surface_is_desktop_surface(ivisurf->surface)) { + ivi_view->view = weston_desktop_surface_create_view( + ivisurf->weston_desktop_surface); + } else { + ivi_view->view = weston_view_create(ivisurf->surface); + } + if (ivi_view->view == NULL) { weston_log("fails to allocate memory\n"); free(ivi_view); From c04188e5461280e666a89876c43a15b43c6868a3 Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Wed, 6 Feb 2019 16:10:09 +0100 Subject: [PATCH 0766/1642] ivi-layout: use libweston-desktop api for configure event The libweston-desktop api is used to send configure event to application. Signed-off-by: Michael Teyfel --- ivi-shell/ivi-layout.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index bb9f9f9f0..ccb78dc37 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -699,9 +699,15 @@ commit_surface_list(struct ivi_layout *layout) ivisurf->pending.prop.transition_type = IVI_LAYOUT_TRANSITION_NONE; if (configured && !is_surface_transition(ivisurf)) { - shell_surface_send_configure(ivisurf->surface, - ivisurf->prop.dest_width, - ivisurf->prop.dest_height); + if (weston_surface_is_desktop_surface(ivisurf->surface)) { + weston_desktop_surface_set_size(ivisurf->weston_desktop_surface, + ivisurf->prop.dest_width, + ivisurf->prop.dest_height); + } else { + shell_surface_send_configure(ivisurf->surface, + ivisurf->prop.dest_width, + ivisurf->prop.dest_height); + } } } else { configured = 0; From 62d6d56a83793f8f9d2f2594815d96c45735dcf8 Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Tue, 5 Feb 2019 15:25:56 +0100 Subject: [PATCH 0767/1642] ivi-shell: added libweston-desktop-api implementation Signed-off-by: Michael Teyfel --- ivi-shell/ivi-shell.c | 155 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 30190d4e0..902fb923b 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -454,6 +454,161 @@ shell_add_bindings(struct weston_compositor *compositor, shell); } +/* + * libweston-desktop + */ + +static void +desktop_surface_ping_timeout(struct weston_desktop_client *client, + void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_pong(struct weston_desktop_client *client, + void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_added(struct weston_desktop_surface *surface, + void *user_data) +{ + struct ivi_shell *shell = (struct ivi_shell *) user_data; + struct ivi_layout_surface *layout_surface; + struct ivi_shell_surface *ivisurf; + struct weston_surface *weston_surf = + weston_desktop_surface_get_surface(surface); + + layout_surface = ivi_layout_desktop_surface_create(weston_surf); + if (!layout_surface) { + return; + } + + layout_surface->weston_desktop_surface = surface; + + ivisurf = zalloc(sizeof *ivisurf); + if (!ivisurf) { + return; + } + + ivisurf->shell = shell; + ivisurf->id_surface = IVI_INVALID_ID; + + ivisurf->width = 0; + ivisurf->height = 0; + ivisurf->layout_surface = layout_surface; + ivisurf->surface = weston_surf; + + weston_desktop_surface_set_user_data(surface, ivisurf); +} + +static void +desktop_surface_removed(struct weston_desktop_surface *surface, + void *user_data) +{ + struct ivi_shell_surface *ivisurf = (struct ivi_shell_surface *) + weston_desktop_surface_get_user_data(surface); + + assert(ivisurf != NULL); + + if (ivisurf->layout_surface) + layout_surface_cleanup(ivisurf); +} + +static void +desktop_surface_committed(struct weston_desktop_surface *surface, + int32_t sx, int32_t sy, void *user_data) +{ + struct ivi_shell_surface *ivisurf = (struct ivi_shell_surface *) + weston_desktop_surface_get_user_data(surface); + struct weston_surface *weston_surf = + weston_desktop_surface_get_surface(surface); + + if(!ivisurf) + return; + + if (weston_surf->width == 0 || weston_surf->height == 0) + return; + + if (ivisurf->width != weston_surf->width || + ivisurf->height != weston_surf->height) { + ivisurf->width = weston_surf->width; + ivisurf->height = weston_surf->height; + + ivi_layout_desktop_surface_configure(ivisurf->layout_surface, + weston_surf->width, + weston_surf->height); + } +} + +static void +desktop_surface_move(struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial, void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_resize(struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges, void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_fullscreen_requested(struct weston_desktop_surface *surface, + bool fullscreen, + struct weston_output *output, + void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_maximized_requested(struct weston_desktop_surface *surface, + bool maximized, void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_minimized_requested(struct weston_desktop_surface *surface, + void *user_data) +{ + /* Not supported */ +} + +static void +desktop_surface_set_xwayland_position(struct weston_desktop_surface *surface, + int32_t x, int32_t y, void *user_data) +{ + /* Not supported */ +} + +static const struct weston_desktop_api shell_desktop_api = { + .struct_size = sizeof(struct weston_desktop_api), + .ping_timeout = desktop_surface_ping_timeout, + .pong = desktop_surface_pong, + .surface_added = desktop_surface_added, + .surface_removed = desktop_surface_removed, + .committed = desktop_surface_committed, + + .move = desktop_surface_move, + .resize = desktop_surface_resize, + .fullscreen_requested = desktop_surface_fullscreen_requested, + .maximized_requested = desktop_surface_maximized_requested, + .minimized_requested = desktop_surface_minimized_requested, + .set_xwayland_position = desktop_surface_set_xwayland_position, +}; + +/* + * end of libweston-desktop + */ + /* * Initialization of ivi-shell. */ From ed28f020b8dabc10655c238d41474efae9ce7036 Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Tue, 17 Oct 2017 11:10:18 +0200 Subject: [PATCH 0768/1642] ivi-shell: remove surface_destroy_listener Since the surface_destroy_listener is only registered for ivi-shell applications, it should only be removed for ivi-shell applications. Signed-off-by: Michael Teyfel --- ivi-shell/ivi-shell.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 902fb923b..e716e2cee 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -156,6 +156,10 @@ layout_surface_cleanup(struct ivi_shell_surface *ivisurf) { assert(ivisurf->layout_surface != NULL); + /* destroy weston_surface destroy signal. */ + if (!ivisurf->layout_surface->weston_desktop_surface) + wl_list_remove(&ivisurf->surface_destroy_listener.link); + ivi_layout_surface_destroy(ivisurf->layout_surface); ivisurf->layout_surface = NULL; @@ -163,9 +167,6 @@ layout_surface_cleanup(struct ivi_shell_surface *ivisurf) ivisurf->surface->committed_private = NULL; weston_surface_set_label_func(ivisurf->surface, NULL); ivisurf->surface = NULL; - - // destroy weston_surface destroy signal. - wl_list_remove(&ivisurf->surface_destroy_listener.link); } /* From 2763b66d31594ff30102b6d791d499531f38a0cc Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Tue, 17 Oct 2017 11:10:58 +0200 Subject: [PATCH 0769/1642] ivi-shell: create weston_desktop in wet_shell_init Signed-off-by: Michael Teyfel --- ivi-shell/ivi-shell.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index e716e2cee..244290b87 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -631,16 +631,23 @@ wet_shell_init(struct weston_compositor *compositor, shell->wake_listener.notify = wake_handler; wl_signal_add(&compositor->wake_signal, &shell->wake_listener); + shell->desktop = weston_desktop_create(compositor, &shell_desktop_api, shell); + if (!shell->desktop) + goto err_shell; + if (wl_global_create(compositor->wl_display, &ivi_application_interface, 1, shell, bind_ivi_application) == NULL) - goto err_shell; + goto err_desktop; ivi_layout_init_with_compositor(compositor); shell_add_bindings(compositor, shell); return IVI_SUCCEEDED; +err_desktop: + weston_desktop_destroy(shell->desktop); + err_shell: free(shell); From e894b3e4a3fe2cab2151fe439502b4379e69b52d Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Mon, 9 Apr 2018 14:14:50 +0200 Subject: [PATCH 0770/1642] hmi-controller: register for desktop_surface_configured Since ivi-shell now supports xdg-protocol, the surface_created listener can be removed and the desktop_surface_configured listener is needed. ivi-layout: libweston-desktop api is used to send configure event to application. Signed-off-by: Michael Teyfel --- ivi-shell/hmi-controller.c | 70 ++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index 412844136..4f90e0acb 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -131,9 +131,9 @@ struct hmi_controller { struct weston_compositor *compositor; struct wl_listener destroy_listener; - struct wl_listener surface_created; struct wl_listener surface_removed; struct wl_listener surface_configured; + struct wl_listener desktop_surface_configured; struct wl_client *user_interface; struct ui_setting ui_setting; @@ -578,28 +578,6 @@ create_layer(struct weston_output *output, /** * Internal set notification */ -static void -set_notification_create_surface(struct wl_listener *listener, void *data) -{ - struct hmi_controller *hmi_ctrl = - wl_container_of(listener, hmi_ctrl, - surface_created); - struct ivi_layout_surface *ivisurf = data; - struct hmi_controller_layer *layer_link = - wl_container_of(hmi_ctrl->application_layer_list.prev, - layer_link, - link); - struct ivi_layout_layer *application_layer = layer_link->ivilayer; - int32_t ret = 0; - - /* skip ui widgets */ - if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) - return; - - ret = hmi_ctrl->interface->layer_add_surface(application_layer, ivisurf); - assert(!ret); -} - static void set_notification_remove_surface(struct wl_listener *listener, void *data) { @@ -667,6 +645,42 @@ set_notification_configure_surface(struct wl_listener *listener, void *data) switch_mode(hmi_ctrl, hmi_ctrl->layout_mode); } +static void +set_notification_configure_desktop_surface(struct wl_listener *listener, void *data) +{ + struct hmi_controller *hmi_ctrl = + wl_container_of(listener, hmi_ctrl, + desktop_surface_configured); + struct ivi_layout_surface *ivisurf = data; + struct hmi_controller_layer *layer_link = + wl_container_of(hmi_ctrl->application_layer_list.prev, + layer_link, + link); + struct ivi_layout_layer *application_layer = layer_link->ivilayer; + struct weston_surface *surface; + int32_t ret = 0; + + /* skip ui widgets */ + if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) + return; + + ret = hmi_ctrl->interface->layer_add_surface(application_layer, ivisurf); + assert(!ret); + + /* + * if application changes size of wl_buffer. The source rectangle shall be + * fit to the size. + */ + surface = hmi_ctrl->interface->surface_get_weston_surface(ivisurf); + if (surface) { + hmi_ctrl->interface->surface_set_source_rectangle(ivisurf, 0, + 0, surface->width, surface->height); + } + + hmi_ctrl->interface->commit_changes(); + switch_mode(hmi_ctrl, hmi_ctrl->layout_mode); +} + /** * A hmi_controller used 4 ivi_layers to manage ivi_surfaces. The IDs of * corresponding ivi_layer are defined in weston.ini. Default scene graph @@ -870,6 +884,9 @@ hmi_controller_create(struct weston_compositor *ec) hmi_ctrl->surface_configured.notify = set_notification_configure_surface; hmi_ctrl->interface->add_listener_configure_surface(&hmi_ctrl->surface_configured); + hmi_ctrl->desktop_surface_configured.notify = set_notification_configure_desktop_surface; + hmi_ctrl->interface->add_listener_configure_desktop_surface(&hmi_ctrl->desktop_surface_configured); + hmi_ctrl->destroy_listener.notify = hmi_controller_destroy; wl_signal_add(&hmi_ctrl->compositor->destroy_signal, &hmi_ctrl->destroy_listener); @@ -1290,13 +1307,6 @@ ivi_hmi_controller_UI_ready(struct wl_client *client, hmi_ctrl->interface->commit_changes(); ivi_hmi_controller_add_launchers(hmi_ctrl, 256); - - /* Add surface_created listener after the initialization of launchers. - * Otherwise, surfaces of the launchers will be added to application - * layer too.*/ - hmi_ctrl->surface_created.notify = set_notification_create_surface; - hmi_ctrl->interface->add_listener_create_surface(&hmi_ctrl->surface_created); - hmi_ctrl->is_initialized = 1; } From 111d3d8fb474d02744a9e04d93ccaab8d3e11308 Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Tue, 17 Oct 2017 11:13:02 +0200 Subject: [PATCH 0771/1642] simple-egl: remove ivi-application support Signed-off-by: Michael Teyfel --- Makefile.am | 4 +-- clients/simple-egl.c | 86 +++++++------------------------------------- 2 files changed, 13 insertions(+), 77 deletions(-) diff --git a/Makefile.am b/Makefile.am index ae96b8c53..e45087053 100644 --- a/Makefile.am +++ b/Makefile.am @@ -648,9 +648,7 @@ demo_clients += weston-simple-egl weston_simple_egl_SOURCES = clients/simple-egl.c nodist_weston_simple_egl_SOURCES = \ protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-client-protocol.h \ - protocol/ivi-application-protocol.c \ - protocol/ivi-application-client-protocol.h + protocol/xdg-shell-unstable-v6-client-protocol.h weston_simple_egl_CFLAGS = $(AM_CFLAGS) $(SIMPLE_EGL_CLIENT_CFLAGS) weston_simple_egl_LDADD = $(SIMPLE_EGL_CLIENT_LIBS) -lm endif diff --git a/clients/simple-egl.c b/clients/simple-egl.c index 779797f52..252cf55f4 100644 --- a/clients/simple-egl.c +++ b/clients/simple-egl.c @@ -45,8 +45,6 @@ #include "xdg-shell-unstable-v6-client-protocol.h" #include #include -#include "ivi-application-client-protocol.h" -#define IVI_SURFACE_ID 9000 #include "shared/helpers.h" #include "shared/platform.h" @@ -74,7 +72,6 @@ struct display { EGLConfig conf; } egl; struct window *window; - struct ivi_application *ivi_application; PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage; }; @@ -97,7 +94,6 @@ struct window { struct wl_surface *surface; struct zxdg_surface_v6 *xdg_surface; struct zxdg_toplevel_v6 *xdg_toplevel; - struct ivi_surface *ivi_surface; EGLSurface egl_surface; struct wl_callback *callback; int fullscreen, maximized, opaque, buffer_size, frame_sync, delay; @@ -359,27 +355,22 @@ static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { }; static void -handle_ivi_surface_configure(void *data, struct ivi_surface *ivi_surface, - int32_t width, int32_t height) +create_surface(struct window *window) { - struct window *window = data; - - wl_egl_window_resize(window->native, width, height, 0, 0); - - window->geometry.width = width; - window->geometry.height = height; + struct display *display = window->display; + EGLBoolean ret; - if (!window->fullscreen) - window->window_size = window->geometry; -} + window->surface = wl_compositor_create_surface(display->compositor); -static const struct ivi_surface_listener ivi_surface_listener = { - handle_ivi_surface_configure, -}; + window->native = + wl_egl_window_create(window->surface, + window->geometry.width, + window->geometry.height); + window->egl_surface = + weston_platform_create_egl_surface(display->egl.dpy, + display->egl.conf, + window->native, NULL); -static void -create_xdg_surface(struct window *window, struct display *display) -{ window->xdg_surface = zxdg_shell_v6_get_xdg_surface(display->shell, window->surface); zxdg_surface_v6_add_listener(window->xdg_surface, @@ -394,50 +385,6 @@ create_xdg_surface(struct window *window, struct display *display) window->wait_for_configure = true; wl_surface_commit(window->surface); -} - -static void -create_ivi_surface(struct window *window, struct display *display) -{ - uint32_t id_ivisurf = IVI_SURFACE_ID + (uint32_t)getpid(); - window->ivi_surface = - ivi_application_surface_create(display->ivi_application, - id_ivisurf, window->surface); - - if (window->ivi_surface == NULL) { - fprintf(stderr, "Failed to create ivi_client_surface\n"); - abort(); - } - - ivi_surface_add_listener(window->ivi_surface, - &ivi_surface_listener, window); -} - -static void -create_surface(struct window *window) -{ - struct display *display = window->display; - EGLBoolean ret; - - window->surface = wl_compositor_create_surface(display->compositor); - - window->native = - wl_egl_window_create(window->surface, - window->geometry.width, - window->geometry.height); - window->egl_surface = - weston_platform_create_egl_surface(display->egl.dpy, - display->egl.conf, - window->native, NULL); - - - if (display->shell) { - create_xdg_surface(window, display); - } else if (display->ivi_application ) { - create_ivi_surface(window, display); - } else { - assert(0); - } ret = eglMakeCurrent(window->display->egl.dpy, window->egl_surface, window->egl_surface, window->display->egl.ctx); @@ -469,8 +416,6 @@ destroy_surface(struct window *window) zxdg_toplevel_v6_destroy(window->xdg_toplevel); if (window->xdg_surface) zxdg_surface_v6_destroy(window->xdg_surface); - if (window->display->ivi_application) - ivi_surface_destroy(window->ivi_surface); wl_surface_destroy(window->surface); if (window->callback) @@ -825,10 +770,6 @@ registry_handle_global(void *data, struct wl_registry *registry, fprintf(stderr, "unable to load default left pointer\n"); // TODO: abort ? } - } else if (strcmp(interface, "ivi_application") == 0) { - d->ivi_application = - wl_registry_bind(registry, name, - &ivi_application_interface, 1); } } @@ -943,9 +884,6 @@ main(int argc, char **argv) if (display.shell) zxdg_shell_v6_destroy(display.shell); - if (display.ivi_application) - ivi_application_destroy(display.ivi_application); - if (display.compositor) wl_compositor_destroy(display.compositor); From a232b7e22ce502bf9351bfa9788ac6e5408195bb Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Wed, 11 Oct 2017 17:09:57 +0200 Subject: [PATCH 0772/1642] simple-shm: remove ivi-application support Signed-off-by: Michael Teyfel --- Makefile.am | 4 +--- clients/simple-shm.c | 40 ---------------------------------------- 2 files changed, 1 insertion(+), 43 deletions(-) diff --git a/Makefile.am b/Makefile.am index e45087053..03492c46d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -606,9 +606,7 @@ nodist_weston_simple_shm_SOURCES = \ protocol/xdg-shell-unstable-v6-protocol.c \ protocol/xdg-shell-unstable-v6-client-protocol.h \ protocol/fullscreen-shell-unstable-v1-protocol.c \ - protocol/fullscreen-shell-unstable-v1-client-protocol.h \ - protocol/ivi-application-protocol.c \ - protocol/ivi-application-client-protocol.h + protocol/fullscreen-shell-unstable-v1-client-protocol.h weston_simple_shm_CFLAGS = $(AM_CFLAGS) $(SIMPLE_CLIENT_CFLAGS) weston_simple_shm_LDADD = $(SIMPLE_CLIENT_LIBS) libshared.la diff --git a/clients/simple-shm.c b/clients/simple-shm.c index 9fa2e214c..fc2ef0012 100644 --- a/clients/simple-shm.c +++ b/clients/simple-shm.c @@ -40,10 +40,6 @@ #include "xdg-shell-unstable-v6-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" -#include -#include "ivi-application-client-protocol.h" -#define IVI_SURFACE_ID 9000 - struct display { struct wl_display *display; struct wl_registry *registry; @@ -52,7 +48,6 @@ struct display { struct zwp_fullscreen_shell_v1 *fshell; struct wl_shm *shm; bool has_xrgb; - struct ivi_application *ivi_application; }; struct buffer { @@ -67,7 +62,6 @@ struct window { struct wl_surface *surface; struct zxdg_surface_v6 *xdg_surface; struct zxdg_toplevel_v6 *xdg_toplevel; - struct ivi_surface *ivi_surface; struct buffer buffers[2]; struct buffer *prev_buffer; struct wl_callback *callback; @@ -165,17 +159,6 @@ static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { handle_xdg_toplevel_close, }; -static void -handle_ivi_surface_configure(void *data, struct ivi_surface *ivi_surface, - int32_t width, int32_t height) -{ - /* Simple-shm is resizable */ -} - -static const struct ivi_surface_listener ivi_surface_listener = { - handle_ivi_surface_configure, -}; - static struct window * create_window(struct display *display, int width, int height) { @@ -213,19 +196,6 @@ create_window(struct display *display, int width, int height) window->surface, ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, NULL); - } else if (display->ivi_application ) { - uint32_t id_ivisurf = IVI_SURFACE_ID + (uint32_t)getpid(); - window->ivi_surface = - ivi_application_surface_create(display->ivi_application, - id_ivisurf, window->surface); - if (window->ivi_surface == NULL) { - fprintf(stderr, "Failed to create ivi_client_surface\n"); - abort(); - } - - ivi_surface_add_listener(window->ivi_surface, - &ivi_surface_listener, window); - } else { assert(0); } @@ -407,11 +377,6 @@ registry_handle_global(void *data, struct wl_registry *registry, id, &wl_shm_interface, 1); wl_shm_add_listener(d->shm, &shm_listener, d); } - else if (strcmp(interface, "ivi_application") == 0) { - d->ivi_application = - wl_registry_bind(registry, id, - &ivi_application_interface, 1); - } } static void @@ -555,11 +520,6 @@ main(int argc, char **argv) fprintf(stderr, "simple-shm exiting\n"); - if (window->display->ivi_application) { - ivi_surface_destroy(window->ivi_surface); - ivi_application_destroy(window->display->ivi_application); - } - destroy_window(window); destroy_display(display); From d6371b5a6a837486b45c92d2da94ac83b1be3b8b Mon Sep 17 00:00:00 2001 From: Michael Teyfel Date: Wed, 11 Oct 2017 17:17:29 +0200 Subject: [PATCH 0773/1642] window client: remove ivi-application support Signed-off-by: Michael Teyfel --- Makefile.am | 2 -- clients/window.c | 44 +------------------------------------------- 2 files changed, 1 insertion(+), 45 deletions(-) diff --git a/Makefile.am b/Makefile.am index 03492c46d..2f01df114 100644 --- a/Makefile.am +++ b/Makefile.am @@ -715,8 +715,6 @@ nodist_libtoytoolkit_la_SOURCES = \ protocol/viewporter-client-protocol.h \ protocol/xdg-shell-unstable-v6-protocol.c \ protocol/xdg-shell-unstable-v6-client-protocol.h \ - protocol/ivi-application-protocol.c \ - protocol/ivi-application-client-protocol.h \ protocol/pointer-constraints-unstable-v1-protocol.c \ protocol/pointer-constraints-unstable-v1-client-protocol.h \ protocol/relative-pointer-unstable-v1-protocol.c \ diff --git a/clients/window.c b/clients/window.c index 8d2881536..4a7b9d8b2 100644 --- a/clients/window.c +++ b/clients/window.c @@ -83,10 +83,6 @@ typedef void *EGLContext; #include "window.h" -#include -#include "ivi-application-client-protocol.h" -#define IVI_SURFACE_ID 9000 - #define ZWP_RELATIVE_POINTER_MANAGER_V1_VERSION 1 #define ZWP_POINTER_CONSTRAINTS_V1_VERSION 1 @@ -110,7 +106,6 @@ struct display { struct wl_data_device_manager *data_device_manager; struct text_cursor_position *text_cursor_position; struct zxdg_shell_v6 *xdg_shell; - struct ivi_application *ivi_application; /* ivi style shell */ struct zwp_relative_pointer_manager_v1 *relative_pointer_manager; struct zwp_pointer_constraints_v1 *pointer_constraints; EGLDisplay dpy; @@ -272,8 +267,6 @@ struct window { struct window *parent; struct window *last_parent; - struct ivi_surface *ivi_surface; - struct window_frame *frame; /* struct surface::link, contains also main_surface */ @@ -1451,19 +1444,6 @@ window_get_display(struct window *window) return window->display; } -static void -handle_ivi_surface_configure(void *data, struct ivi_surface *ivi_surface, - int32_t width, int32_t height) -{ - struct window *window = data; - - window_schedule_resize(window, width, height); -} - -static const struct ivi_surface_listener ivi_surface_listener = { - handle_ivi_surface_configure, -}; - static void surface_create_surface(struct surface *surface, uint32_t flags) { @@ -1618,9 +1598,6 @@ window_destroy(struct window *window) if (window->xdg_surface) zxdg_surface_v6_destroy(window->xdg_surface); - if (window->ivi_surface) - ivi_surface_destroy(window->ivi_surface); - surface_destroy(window->main_surface); wl_list_remove(&window->link); @@ -5181,7 +5158,7 @@ window_create_internal(struct display *display, int custom) surface = surface_create(window); window->main_surface = surface; - assert(custom || display->xdg_shell || display->ivi_application); + assert(custom || display->xdg_shell); window->custom = custom; window->preferred_format = WINDOW_PREFERRED_FORMAT_NONE; @@ -5201,7 +5178,6 @@ struct window * window_create(struct display *display) { struct window *window; - uint32_t id_ivisurf; window = window_create_internal(display, 0); @@ -5224,16 +5200,6 @@ window_create(struct display *display) window_inhibit_redraw(window); wl_surface_commit(window->main_surface->surface); - } else if (display->ivi_application) { - /* auto generation of ivi_id based on process id + basement of id */ - id_ivisurf = IVI_SURFACE_ID + (uint32_t)getpid(); - window->ivi_surface = - ivi_application_surface_create(display->ivi_application, - id_ivisurf, window->main_surface->surface); - fail_on_null(window->ivi_surface, 0, __FILE__, __LINE__); - - ivi_surface_add_listener(window->ivi_surface, - &ivi_surface_listener, window); } return window; @@ -5988,11 +5954,6 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, wl_registry_bind(registry, id, &wl_subcompositor_interface, 1); } - else if (strcmp(interface, "ivi_application") == 0) { - d->ivi_application = - wl_registry_bind(registry, id, - &ivi_application_interface, 1); - } if (d->global_handler) d->global_handler(d, id, interface, version, d->user_data); @@ -6291,9 +6252,6 @@ display_destroy(struct display *display) if (display->xdg_shell) zxdg_shell_v6_destroy(display->xdg_shell); - if (display->ivi_application) - ivi_application_destroy(display->ivi_application); - if (display->shm) wl_shm_destroy(display->shm); From 4c347b8ee0236c2ce8f85f216f4386e70026178c Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Mon, 8 Oct 2018 16:04:44 +0200 Subject: [PATCH 0774/1642] config-parser: export functions to open a config file The in-tree clients can access the functions via libshared, but they are currently not available for external clients, such as custom shell helper applications similar to weston-desktop-shell or weston-ivi-shell-user-interface. The functions to read the content of the config are already exported. Signed-off-by: Michael Olbrich --- shared/config-parser.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/config-parser.c b/shared/config-parser.c index 7b1402d2c..35f09f006 100644 --- a/shared/config-parser.c +++ b/shared/config-parser.c @@ -326,6 +326,7 @@ weston_config_section_get_bool(struct weston_config_section *section, return 0; } +WL_EXPORT const char * weston_config_get_name_from_env(void) { @@ -387,6 +388,7 @@ section_add_entry(struct weston_config_section *section, return entry; } +WL_EXPORT struct weston_config * weston_config_parse(const char *name) { From 68da919f849b39a69158741a680bdcb5e585e4e3 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 1 Feb 2019 17:01:14 +0200 Subject: [PATCH 0775/1642] README: Meson for build Explain how to use with Meson, as autotools is going away. The instructions have been copied from https://wayland.freedesktop.org/building.html . Signed-off-by: Pekka Paalanen --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d62da5172..7a6aec33c 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,38 @@ to make code or non-technical contributions to Weston. Building Weston =============== -Weston is built using autotools, with `autogen.sh` and `make`. It often depends +Weston is built using [Meson](https://mesonbuild.com/). Weston often depends on the current release versions of [Wayland](https://gitlab.freedesktop.org/wayland/wayland) and [wayland-protocols](https://cgit.freedesktop.org/wayland/wayland-protocols). +If necessary, the latest Meson can be installed as a user with: + + $ pip3 install --user meson + +Weston's Meson build does not do autodetection and it defaults to all +features enabled, which means you likely hit missing dependencies on the first +try. If a dependency is avoidable through a build option, the error message +should tell you what option can be used to avoid it. You may need to disable +several features if you want to avoid certain dependencies. + + $ git clone https://gitlab.freedesktop.org/wayland/weston.git + $ cd weston + $ meson build/ --prefix=... + $ ninja -C build/ install + $ cd .. + +The `meson` command populates the build directory. This step can +fail due to missing dependencies. Any build options you want can be added on +that line, e.g. `meson build/ --prefix=... -Dsimple-dmabuf-drm=intel`. +All the build options can be found in the file +[meson_options.txt](meson_options.txt). + +Once the build directory has been successfully populated, you can inspect the +configuration with `meson configure build/`. If you need to change an +option, you can do e.g. +`meson configure build/ -Dsimple-dmabuf-drm=intel`. + Every push to the Weston master repository and its forks is built using GitLab CI. [Reading the configuration](.gitlab-ci.yml) may provide a useful example of how to build and install Weston. From 2d4cc4f4dd6a039eebbb6dfca9ae4522d83dc90b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 1 Feb 2019 17:05:00 +0200 Subject: [PATCH 0776/1642] configure: require opt-in to autotools Autotools is going away. Break the autotools build so that people are guaranteed to notice before it is gone. If they have problems with Meson, they can still use --enable-autotools to build with autotools, but we really want to hear about any problems. Signed-off-by: Pekka Paalanen --- .gitlab-ci.yml | 2 +- Makefile.am | 2 +- configure.ac | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f4b56d483..cf882797d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -36,7 +36,7 @@ build-native-autotools: - export BUILDDIR="$(pwd)/build-$BUILD_ID" - mkdir "$BUILDDIR" "$PREFIX" - cd "$BUILDDIR" - - ../autogen.sh --prefix="$PREFIX" --disable-setuid-install --enable-xwayland --enable-x11-compositor --enable-drm-compositor --enable-wayland-compositor --enable-headless-compositor --enable-fbdev-compositor --enable-rdp-compositor --enable-screen-sharing --enable-vaapi-recorder --enable-simple-clients --enable-simple-egl-clients --enable-simple-dmabuf-drm-client --enable-simple-dmabuf-v4l-client --enable-clients --enable-resize-optimization --enable-weston-launch --enable-fullscreen-shell --enable-colord --enable-dbus --enable-systemd-login --enable-junit-xml --enable-ivi-shell --enable-wcap-tools --disable-libunwind --enable-demo-clients-install --enable-lcms --with-cairo=image --enable-remoting + - ../autogen.sh --prefix="$PREFIX" --disable-setuid-install --enable-xwayland --enable-x11-compositor --enable-drm-compositor --enable-wayland-compositor --enable-headless-compositor --enable-fbdev-compositor --enable-rdp-compositor --enable-screen-sharing --enable-vaapi-recorder --enable-simple-clients --enable-simple-egl-clients --enable-simple-dmabuf-drm-client --enable-simple-dmabuf-v4l-client --enable-clients --enable-resize-optimization --enable-weston-launch --enable-fullscreen-shell --enable-colord --enable-dbus --enable-systemd-login --enable-junit-xml --enable-ivi-shell --enable-wcap-tools --disable-libunwind --enable-demo-clients-install --enable-lcms --with-cairo=image --enable-remoting --enable-autotools - make all - make check - make install diff --git a/Makefile.am b/Makefile.am index 2f01df114..ce83fc897 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,7 +10,7 @@ libweston_module_LTLIBRARIES = noinst_LTLIBRARIES = BUILT_SOURCES = -AM_DISTCHECK_CONFIGURE_FLAGS = --disable-setuid-install +AM_DISTCHECK_CONFIGURE_FLAGS = --disable-setuid-install --enable-autotools EXTRA_DIST = weston.ini.in ivi-shell/weston.ini.in diff --git a/configure.ac b/configure.ac index cb8b0a5b3..c2001b613 100644 --- a/configure.ac +++ b/configure.ac @@ -84,6 +84,21 @@ AC_PROG_SED LT_PREREQ([2.2]) LT_INIT([disable-static]) +AC_ARG_ENABLE(autotools, + AS_HELP_STRING([--enable-autotools], + [Allow building with autotools]),, + enable_autotools=no) +if test "x$enable_autotools" = "xno"; then + AC_ERROR([ + *** Autotools support will be removed after the 6.0.0 release *** + + Please, try the Meson based build and report any problems you might have + with it. Instructions and references can be found in README.md. + If you still want to continue building with autotools, + use --enable-autotools configure option. + ]) +fi + AC_ARG_VAR([WESTON_NATIVE_BACKEND], [Set the native backend to use, if Weston is not running under Wayland nor X11. @<:@default=drm-backend.so@:>@]) AC_ARG_VAR([WESTON_SHELL_CLIENT], From d8d9f5e6e16c8f6a3c06763d5f56c27dc9a6e52e Mon Sep 17 00:00:00 2001 From: ant8me Date: Wed, 28 Nov 2018 22:46:37 +0100 Subject: [PATCH 0777/1642] libweston-desktop: implement the new xdg_shell (stable) protocol Some clients like the mpv video player now request the xdg_shell protocol so these will fail if the compositor only provides the xdg_shell_unstable_v6 protocol. Compositors like mir and gnome provide both protocols. The two protocols are very similar therefore the code in xdg-shell-v6.c has been refactored to work with the new xdg_shell protocol and now resides in xdg-shell.c. Pekka: - split the patch - fix continued line alignment Daniel - allow anchor_rect to initially have zero dimensions - account for get_popup allowing NULL parent surface Signed-off-by: Pekka Paalanen Signed-off-by: Daniel Stone --- Makefile.am | 6 +- libweston-desktop/internal.h | 4 + libweston-desktop/libweston-desktop.c | 12 +- libweston-desktop/meson.build | 3 + libweston-desktop/xdg-shell.c | 1476 +++++++++++++++++++++++++ protocol/meson.build | 1 + 6 files changed, 1500 insertions(+), 2 deletions(-) create mode 100644 libweston-desktop/xdg-shell.c diff --git a/Makefile.am b/Makefile.am index ce83fc897..b5f5df45a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -132,15 +132,19 @@ libweston_desktop_@LIBWESTON_MAJOR@_la_SOURCES = \ libweston-desktop/surface.c \ libweston-desktop/wl-shell.c \ libweston-desktop/xdg-shell-v6.c \ + libweston-desktop/xdg-shell.c \ libweston-desktop/xwayland.c nodist_libweston_desktop_@LIBWESTON_MAJOR@_la_SOURCES = \ protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-server-protocol.h + protocol/xdg-shell-unstable-v6-server-protocol.h \ + protocol/xdg-shell-protocol.c \ + protocol/xdg-shell-server-protocol.h BUILT_SOURCES += $(nodist_libweston_desktop_@LIBWESTON_MAJOR@_la_SOURCES) libweston-desktop-@LIBWESTON_MAJOR@.la libweston-desktop/libweston_desktop_@LIBWESTON_MAJOR@_la-xdg-shell-v6.lo: protocol/xdg-shell-unstable-v6-server-protocol.h +libweston-desktop-@LIBWESTON_MAJOR@.la libweston-desktop/libweston_desktop_@LIBWESTON_MAJOR@_la-xdg-wm-shell.lo: protocol/xdg-shell-server-protocol.h if SYSTEMD_NOTIFY_SUPPORT module_LTLIBRARIES += systemd-notify.la diff --git a/libweston-desktop/internal.h b/libweston-desktop/internal.h index 564f7b3cb..ce853ba98 100644 --- a/libweston-desktop/internal.h +++ b/libweston-desktop/internal.h @@ -227,11 +227,15 @@ void weston_desktop_destroy_request(struct wl_client *client, struct wl_resource *resource); struct wl_global * +weston_desktop_xdg_wm_base_create(struct weston_desktop *desktop, + struct wl_display *display); +struct wl_global * weston_desktop_xdg_shell_v6_create(struct weston_desktop *desktop, struct wl_display *display); struct wl_global * weston_desktop_wl_shell_create(struct weston_desktop *desktop, struct wl_display *display); + void weston_desktop_xwayland_init(struct weston_desktop *desktop); diff --git a/libweston-desktop/libweston-desktop.c b/libweston-desktop/libweston-desktop.c index c840a8a9d..49cd21027 100644 --- a/libweston-desktop/libweston-desktop.c +++ b/libweston-desktop/libweston-desktop.c @@ -40,7 +40,8 @@ struct weston_desktop { struct weston_compositor *compositor; struct weston_desktop_api api; void *user_data; - struct wl_global *xdg_shell_v6; + struct wl_global *xdg_wm_base; /* Stable protocol xdg_shell replaces xdg_shell_unstable_v6 */ + struct wl_global *xdg_shell_v6; /* Unstable xdg_shell_unstable_v6 protocol. */ struct wl_global *wl_shell; }; @@ -69,6 +70,13 @@ weston_desktop_create(struct weston_compositor *compositor, MIN(sizeof(struct weston_desktop_api), api->struct_size); memcpy(&desktop->api, api, desktop->api.struct_size); + desktop->xdg_wm_base = + weston_desktop_xdg_wm_base_create(desktop, display); + if (desktop->xdg_wm_base == NULL) { + weston_desktop_destroy(desktop); + return NULL; + } + desktop->xdg_shell_v6 = weston_desktop_xdg_shell_v6_create(desktop, display); if (desktop->xdg_shell_v6 == NULL) { @@ -98,6 +106,8 @@ weston_desktop_destroy(struct weston_desktop *desktop) wl_global_destroy(desktop->wl_shell); if (desktop->xdg_shell_v6 != NULL) wl_global_destroy(desktop->xdg_shell_v6); + if (desktop->xdg_wm_base != NULL) + wl_global_destroy(desktop->xdg_wm_base); free(desktop); } diff --git a/libweston-desktop/meson.build b/libweston-desktop/meson.build index d6be7a847..837d4edd4 100644 --- a/libweston-desktop/meson.build +++ b/libweston-desktop/meson.build @@ -5,9 +5,12 @@ srcs_libdesktop = [ 'surface.c', 'xwayland.c', 'wl-shell.c', + 'xdg-shell.c', 'xdg-shell-v6.c', xdg_shell_unstable_v6_server_protocol_h, xdg_shell_unstable_v6_protocol_c, + xdg_shell_server_protocol_h, + xdg_shell_protocol_c, ] lib_desktop = shared_library( 'weston-desktop-@0@'.format(libweston_major), diff --git a/libweston-desktop/xdg-shell.c b/libweston-desktop/xdg-shell.c new file mode 100644 index 000000000..58a1ecdb8 --- /dev/null +++ b/libweston-desktop/xdg-shell.c @@ -0,0 +1,1476 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * Copyright © 2016 Quentin "Sardem FF7" Glidic + * + * 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 "compositor.h" +#include "zalloc.h" +#include "xdg-shell-server-protocol.h" + +#include "libweston-desktop.h" +#include "internal.h" + +/************************************************************************************ + * WARNING: This file implements the stable xdg shell protocol. + * Any changes to this file may also need to be added to the xdg-shell-v6.c file which + * implements the older unstable xdg shell v6 protocol. + ************************************************************************************/ + +#define WD_XDG_SHELL_PROTOCOL_VERSION 1 + +static const char *weston_desktop_xdg_toplevel_role = "xdg_toplevel"; +static const char *weston_desktop_xdg_popup_role = "xdg_popup"; + +struct weston_desktop_xdg_positioner { + struct weston_desktop *desktop; + struct weston_desktop_client *client; + struct wl_resource *resource; + + struct weston_size size; + struct weston_geometry anchor_rect; + enum xdg_positioner_anchor anchor; + enum xdg_positioner_gravity gravity; + enum xdg_positioner_constraint_adjustment constraint_adjustment; + struct weston_position offset; +}; + +enum weston_desktop_xdg_surface_role { + WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE, + WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL, + WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP, +}; + +struct weston_desktop_xdg_surface { + struct wl_resource *resource; + struct weston_desktop *desktop; + struct weston_surface *surface; + struct weston_desktop_surface *desktop_surface; + bool configured; + struct wl_event_source *configure_idle; + struct wl_list configure_list; /* weston_desktop_xdg_surface_configure::link */ + + bool has_next_geometry; + struct weston_geometry next_geometry; + + enum weston_desktop_xdg_surface_role role; +}; + +struct weston_desktop_xdg_surface_configure { + struct wl_list link; /* weston_desktop_xdg_surface::configure_list */ + uint32_t serial; +}; + +struct weston_desktop_xdg_toplevel_state { + bool maximized; + bool fullscreen; + bool resizing; + bool activated; +}; + +struct weston_desktop_xdg_toplevel_configure { + struct weston_desktop_xdg_surface_configure base; + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; +}; + +struct weston_desktop_xdg_toplevel { + struct weston_desktop_xdg_surface base; + + struct wl_resource *resource; + bool added; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } pending; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + struct weston_size min_size, max_size; + } next; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size min_size, max_size; + } current; +}; + +struct weston_desktop_xdg_popup { + struct weston_desktop_xdg_surface base; + + struct wl_resource *resource; + bool committed; + struct weston_desktop_xdg_surface *parent; + struct weston_desktop_seat *seat; + struct weston_geometry geometry; +}; + +#define weston_desktop_surface_role_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) +#define weston_desktop_surface_configure_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) + + +static struct weston_geometry +weston_desktop_xdg_positioner_get_geometry(struct weston_desktop_xdg_positioner *positioner, + struct weston_desktop_surface *dsurface, + struct weston_desktop_surface *parent) +{ + struct weston_geometry geometry = { + .x = positioner->offset.x, + .y = positioner->offset.y, + .width = positioner->size.width, + .height = positioner->size.height, + }; + + switch (positioner->anchor) { + case XDG_POSITIONER_ANCHOR_TOP: + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + case XDG_POSITIONER_ANCHOR_TOP_RIGHT: + geometry.y += positioner->anchor_rect.y; + break; + case XDG_POSITIONER_ANCHOR_BOTTOM: + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height; + break; + default: + geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height / 2; + } + + switch (positioner->anchor) { + case XDG_POSITIONER_ANCHOR_LEFT: + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + geometry.x += positioner->anchor_rect.x; + break; + case XDG_POSITIONER_ANCHOR_RIGHT: + case XDG_POSITIONER_ANCHOR_TOP_RIGHT: + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width; + break; + default: + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width / 2; + } + + switch (positioner->gravity) { + case XDG_POSITIONER_GRAVITY_TOP: + case XDG_POSITIONER_GRAVITY_TOP_LEFT: + case XDG_POSITIONER_GRAVITY_TOP_RIGHT: + geometry.y -= geometry.height; + break; + case XDG_POSITIONER_GRAVITY_BOTTOM: + case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT: + case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT: + geometry.y = geometry.y; + break; + default: + geometry.y -= geometry.height / 2; + } + + switch (positioner->gravity) { + case XDG_POSITIONER_GRAVITY_LEFT: + case XDG_POSITIONER_GRAVITY_TOP_LEFT: + case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT: + geometry.x -= geometry.width; + break; + case XDG_POSITIONER_GRAVITY_RIGHT: + case XDG_POSITIONER_GRAVITY_TOP_RIGHT: + case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT: + geometry.x = geometry.x; + break; + default: + geometry.x -= geometry.width / 2; + } + + if (positioner->constraint_adjustment == XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) + return geometry; + + /* TODO: add compositor policy configuration and the code here */ + + return geometry; +} + +static void +weston_desktop_xdg_positioner_protocol_set_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (width < 1 || height < 1) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "width and height must be positives and non-zero"); + return; + } + + positioner->size.width = width; + positioner->size.height = height; +} + +static void +weston_desktop_xdg_positioner_protocol_set_anchor_rect(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (width < 0 || height < 0) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "width and height must be non-negative"); + return; + } + + positioner->anchor_rect.x = x; + positioner->anchor_rect.y = y; + positioner->anchor_rect.width = width; + positioner->anchor_rect.height = height; +} + +static void +weston_desktop_xdg_positioner_protocol_set_anchor(struct wl_client *wl_client, + struct wl_resource *resource, + enum xdg_positioner_anchor anchor) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->anchor = anchor; +} + +static void +weston_desktop_xdg_positioner_protocol_set_gravity(struct wl_client *wl_client, + struct wl_resource *resource, + enum xdg_positioner_gravity gravity) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->gravity = gravity; +} + +static void +weston_desktop_xdg_positioner_protocol_set_constraint_adjustment(struct wl_client *wl_client, + struct wl_resource *resource, + enum xdg_positioner_constraint_adjustment constraint_adjustment) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->constraint_adjustment = constraint_adjustment; +} + +static void +weston_desktop_xdg_positioner_protocol_set_offset(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->offset.x = x; + positioner->offset.y = y; +} + +static void +weston_desktop_xdg_positioner_destroy(struct wl_resource *resource) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + free(positioner); +} + +static const struct xdg_positioner_interface weston_desktop_xdg_positioner_implementation = { + .destroy = weston_desktop_destroy_request, + .set_size = weston_desktop_xdg_positioner_protocol_set_size, + .set_anchor_rect = weston_desktop_xdg_positioner_protocol_set_anchor_rect, + .set_anchor = weston_desktop_xdg_positioner_protocol_set_anchor, + .set_gravity = weston_desktop_xdg_positioner_protocol_set_gravity, + .set_constraint_adjustment = weston_desktop_xdg_positioner_protocol_set_constraint_adjustment, + .set_offset = weston_desktop_xdg_positioner_protocol_set_offset, +}; + +static void +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface); + +static void +weston_desktop_xdg_toplevel_ensure_added(struct weston_desktop_xdg_toplevel *toplevel) +{ + if (toplevel->added) + return; + + weston_desktop_api_surface_added(toplevel->base.desktop, + toplevel->base.desktop_surface); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); + toplevel->added = true; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_parent(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *parent_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_surface *parent = NULL; + + if (parent_resource != NULL) + parent = wl_resource_get_user_data(parent_resource); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_set_parent(toplevel->base.desktop, dsurface, parent); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_title(struct wl_client *wl_client, + struct wl_resource *resource, + const char *title) +{ + struct weston_desktop_surface *toplevel = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_title(toplevel, title); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_app_id(struct wl_client *wl_client, + struct wl_resource *resource, + const char *app_id) +{ + struct weston_desktop_surface *toplevel = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_app_id(toplevel, app_id); +} + +static void +weston_desktop_xdg_toplevel_protocol_show_window_menu(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + int32_t x, int32_t y) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *seat = + wl_resource_get_user_data(seat_resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_show_window_menu(toplevel->base.desktop, + dsurface, seat, x, y); +} + +static void +weston_desktop_xdg_toplevel_protocol_move(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *seat = + wl_resource_get_user_data(seat_resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_move(toplevel->base.desktop, dsurface, seat, serial); +} + +static void +weston_desktop_xdg_toplevel_protocol_resize(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + enum xdg_toplevel_resize_edge edges) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_seat *seat = + wl_resource_get_user_data(seat_resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + enum weston_desktop_surface_edge surf_edges = + (enum weston_desktop_surface_edge) edges; + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_resize(toplevel->base.desktop, + dsurface, seat, serial, surf_edges); +} + +static void +weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) +{ + toplevel->next.state = configure->state; + toplevel->next.size = configure->size; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_min_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + toplevel->next.min_size.width = width; + toplevel->next.min_size.height = height; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_max_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + toplevel->next.max_size.width = width; + toplevel->next.max_size.height = height; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_maximized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, true); +} + +static void +weston_desktop_xdg_toplevel_protocol_unset_maximized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, false); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_fullscreen(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *output_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_output *output = NULL; + + if (output_resource != NULL) + output = weston_head_from_resource(output_resource)->output; + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, + true, output); +} + +static void +weston_desktop_xdg_toplevel_protocol_unset_fullscreen(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, + false, NULL); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_minimized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_minimized_requested(toplevel->base.desktop, dsurface); +} + +static void +weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) +{ + uint32_t *s; + struct wl_array states; + + configure->state = toplevel->pending.state; + configure->size = toplevel->pending.size; + + wl_array_init(&states); + if (toplevel->pending.state.maximized) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_MAXIMIZED; + } + if (toplevel->pending.state.fullscreen) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_FULLSCREEN; + } + if (toplevel->pending.state.resizing) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_RESIZING; + } + if (toplevel->pending.state.activated) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_ACTIVATED; + } + + xdg_toplevel_send_configure(toplevel->resource, + toplevel->pending.size.width, + toplevel->pending.size.height, + &states); + + wl_array_release(&states); +}; + +static void +weston_desktop_xdg_toplevel_set_maximized(struct weston_desktop_surface *dsurface, + void *user_data, bool maximized) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.maximized = maximized; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data, bool fullscreen) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.fullscreen = fullscreen; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_resizing(struct weston_desktop_surface *dsurface, + void *user_data, bool resizing) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.resizing = resizing; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_activated(struct weston_desktop_surface *dsurface, + void *user_data, bool activated) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.activated = activated; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.size.width = width; + toplevel->pending.size.height = height; + + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplevel, + int32_t sx, int32_t sy) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(toplevel->base.desktop_surface); + + if (!wsurface->buffer_ref.buffer && !toplevel->added) { + weston_desktop_xdg_toplevel_ensure_added(toplevel); + return; + } + if (!wsurface->buffer_ref.buffer) + return; + + struct weston_geometry geometry = + weston_desktop_surface_get_geometry(toplevel->base.desktop_surface); + + if ((toplevel->next.state.maximized || toplevel->next.state.fullscreen) && + (toplevel->next.size.width != geometry.width || + toplevel->next.size.height != geometry.height)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(toplevel->base.desktop_surface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, + "xdg_surface buffer does not match the configured state"); + return; + } + + toplevel->current.state = toplevel->next.state; + toplevel->current.min_size = toplevel->next.min_size; + toplevel->current.max_size = toplevel->next.max_size; + + weston_desktop_api_committed(toplevel->base.desktop, + toplevel->base.desktop_surface, + sx, sy); +} + +static void +weston_desktop_xdg_toplevel_close(struct weston_desktop_xdg_toplevel *toplevel) +{ + xdg_toplevel_send_close(toplevel->resource); +} + +static bool +weston_desktop_xdg_toplevel_get_maximized(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.maximized; +} + +static bool +weston_desktop_xdg_toplevel_get_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.fullscreen; +} + +static bool +weston_desktop_xdg_toplevel_get_resizing(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.resizing; +} + +static bool +weston_desktop_xdg_toplevel_get_activated(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.activated; +} + +static void +weston_desktop_xdg_toplevel_destroy(struct weston_desktop_xdg_toplevel *toplevel) +{ + if (toplevel->added) + weston_desktop_api_surface_removed(toplevel->base.desktop, + toplevel->base.desktop_surface); +} + +static void +weston_desktop_xdg_toplevel_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static const struct xdg_toplevel_interface weston_desktop_xdg_toplevel_implementation = { + .destroy = weston_desktop_destroy_request, + .set_parent = weston_desktop_xdg_toplevel_protocol_set_parent, + .set_title = weston_desktop_xdg_toplevel_protocol_set_title, + .set_app_id = weston_desktop_xdg_toplevel_protocol_set_app_id, + .show_window_menu = weston_desktop_xdg_toplevel_protocol_show_window_menu, + .move = weston_desktop_xdg_toplevel_protocol_move, + .resize = weston_desktop_xdg_toplevel_protocol_resize, + .set_min_size = weston_desktop_xdg_toplevel_protocol_set_min_size, + .set_max_size = weston_desktop_xdg_toplevel_protocol_set_max_size, + .set_maximized = weston_desktop_xdg_toplevel_protocol_set_maximized, + .unset_maximized = weston_desktop_xdg_toplevel_protocol_unset_maximized, + .set_fullscreen = weston_desktop_xdg_toplevel_protocol_set_fullscreen, + .unset_fullscreen = weston_desktop_xdg_toplevel_protocol_unset_fullscreen, + .set_minimized = weston_desktop_xdg_toplevel_protocol_set_minimized, +}; + +static void +weston_desktop_xdg_popup_protocol_grab(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_seat *wseat = wl_resource_get_user_data(seat_resource); + struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); + struct weston_desktop_surface *topmost; + bool parent_is_toplevel = + popup->parent->role == WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; + + /* Check that if we have a valid wseat we also got a valid desktop seat */ + if (wseat != NULL && seat == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + if (popup->committed) { + wl_resource_post_error(popup->resource, + XDG_POPUP_ERROR_INVALID_GRAB, + "xdg_popup already is mapped"); + return; + } + + /* If seat is NULL then get_topmost_surface will return NULL. In + * combination with setting parent_is_toplevel to TRUE here we will + * avoid posting an error, and we will instead gracefully fail the + * grab and dismiss the surface. + * FIXME: this is a hack because currently we cannot check the topmost + * parent with a destroyed weston_seat */ + if (seat == NULL) + parent_is_toplevel = true; + + topmost = weston_desktop_seat_popup_grab_get_topmost_surface(seat); + if ((topmost == NULL && !parent_is_toplevel) || + (topmost != NULL && topmost != popup->parent->desktop_surface)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was not created on the topmost popup"); + return; + } + + popup->seat = seat; + weston_desktop_surface_popup_grab(popup->base.desktop_surface, + popup->seat, serial); +} + +static void +weston_desktop_xdg_popup_send_configure(struct weston_desktop_xdg_popup *popup) +{ + xdg_popup_send_configure(popup->resource, + popup->geometry.x, + popup->geometry.y, + popup->geometry.width, + popup->geometry.height); +} + +static void +weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, + void *user_data); + +static void +weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface (popup->base.desktop_surface); + struct weston_view *view; + + wl_list_for_each(view, &wsurface->views, surface_link) + weston_view_update_transform(view); + + if (!popup->committed) + weston_desktop_xdg_surface_schedule_configure(&popup->base); + popup->committed = true; + weston_desktop_xdg_popup_update_position(popup->base.desktop_surface, + popup); +} + +static void +weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, + void *user_data) +{ +} + +static void +weston_desktop_xdg_popup_close(struct weston_desktop_xdg_popup *popup) +{ + xdg_popup_send_popup_done(popup->resource); +} + +static void +weston_desktop_xdg_popup_destroy(struct weston_desktop_xdg_popup *popup) +{ + struct weston_desktop_surface *topmost; + struct weston_desktop_client *client = + weston_desktop_surface_get_client(popup->base.desktop_surface); + + if (!weston_desktop_surface_get_grab(popup->base.desktop_surface)) + return; + + topmost = weston_desktop_seat_popup_grab_get_topmost_surface(popup->seat); + if (topmost != popup->base.desktop_surface) { + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was destroyed while it was not the topmost popup."); + } + + weston_desktop_surface_popup_ungrab(popup->base.desktop_surface, + popup->seat); +} + +static void +weston_desktop_xdg_popup_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static const struct xdg_popup_interface weston_desktop_xdg_popup_implementation = { + .destroy = weston_desktop_destroy_request, + .grab = weston_desktop_xdg_popup_protocol_grab, +}; + +static void +weston_desktop_xdg_surface_send_configure(void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure; + + surface->configure_idle = NULL; + + configure = zalloc(weston_desktop_surface_configure_biggest_size); + if (configure == NULL) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(surface->desktop_surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + wl_client_post_no_memory(wl_client); + return; + } + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->serial = + wl_display_next_serial(weston_desktop_get_display(surface->desktop)); + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_send_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_send_configure((struct weston_desktop_xdg_popup *) surface); + break; + } + + xdg_surface_send_configure(surface->resource, configure->serial); +} + +static bool +weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) +{ + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } configured; + + if (!toplevel->base.configured) + return false; + + if (wl_list_empty(&toplevel->base.configure_list)) { + /* Last configure is actually the current state, just use it */ + configured.state = toplevel->current.state; + configured.size.width = toplevel->base.surface->width; + configured.size.height = toplevel->base.surface->height; + } else { + struct weston_desktop_xdg_toplevel_configure *configure = + wl_container_of(toplevel->base.configure_list.prev, + configure, base.link); + + configured.state = configure->state; + configured.size = configure->size; + } + + if (toplevel->pending.state.activated != configured.state.activated) + return false; + if (toplevel->pending.state.fullscreen != configured.state.fullscreen) + return false; + if (toplevel->pending.state.maximized != configured.state.maximized) + return false; + if (toplevel->pending.state.resizing != configured.state.resizing) + return false; + + if (toplevel->pending.size.width == configured.size.width && + toplevel->pending.size.height == configured.size.height) + return true; + + if (toplevel->pending.size.width == 0 && + toplevel->pending.size.height == 0) + return true; + + return false; +} + +static void +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface) +{ + struct wl_display *display = weston_desktop_get_display(surface->desktop); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + bool pending_same = false; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + pending_same = weston_desktop_xdg_toplevel_state_compare((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + break; + } + + if (surface->configure_idle != NULL) { + if (!pending_same) + return; + + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + } else { + if (pending_same) + return; + + surface->configure_idle = + wl_event_loop_add_idle(loop, + weston_desktop_xdg_surface_send_configure, + surface); + } +} + +static void +weston_desktop_xdg_surface_protocol_get_toplevel(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(dsurface); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (weston_surface_set_role(wsurface, weston_desktop_xdg_toplevel_role, + resource, XDG_WM_BASE_ERROR_ROLE) < 0) + return; + + toplevel->resource = + weston_desktop_surface_add_resource(toplevel->base.desktop_surface, + &xdg_toplevel_interface, + &weston_desktop_xdg_toplevel_implementation, + id, weston_desktop_xdg_toplevel_resource_destroy); + if (toplevel->resource == NULL) + return; + + toplevel->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; +} + +static void +weston_desktop_xdg_surface_protocol_get_popup(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *parent_resource, + struct wl_resource *positioner_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(dsurface); + struct weston_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_surface *parent_surface; + struct weston_desktop_xdg_surface *parent; + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(positioner_resource); + + /* Popup parents are allowed to be non-null, but only if a parent is + * specified 'using some other protocol' before committing. Since we + * don't support such a protocol yet, clients cannot legitimately + * create a popup with a non-null parent. */ + if (!parent_resource) { + wl_resource_post_error(resource, + XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT, + "popup parent must be non-null"); + return; + } + + parent_surface = wl_resource_get_user_data(parent_resource); + parent = weston_desktop_surface_get_implementation_data(parent_surface); + + /* Checking whether the size and anchor rect both have a positive size + * is enough to verify both have been correctly set */ + if (positioner->size.width == 0 || positioner->anchor_rect.width == 0 || + positioner->anchor_rect.height == 0) { + wl_resource_post_error(resource, + XDG_WM_BASE_ERROR_INVALID_POSITIONER, + "positioner object is not complete"); + return; + } + + if (weston_surface_set_role(wsurface, weston_desktop_xdg_popup_role, + resource, XDG_WM_BASE_ERROR_ROLE) < 0) + return; + + popup->resource = + weston_desktop_surface_add_resource(popup->base.desktop_surface, + &xdg_popup_interface, + &weston_desktop_xdg_popup_implementation, + id, weston_desktop_xdg_popup_resource_destroy); + if (popup->resource == NULL) + return; + + popup->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP; + popup->parent = parent; + + popup->geometry = + weston_desktop_xdg_positioner_get_geometry(positioner, + dsurface, + parent_surface); + + weston_desktop_surface_set_relative_to(popup->base.desktop_surface, + parent_surface, + popup->geometry.x, + popup->geometry.y, + true); +} + +static bool +weston_desktop_xdg_surface_check_role(struct weston_desktop_xdg_surface *surface) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(surface->desktop_surface); + const char *role; + + role = weston_surface_get_role(wsurface); + if (role != NULL && + (role == weston_desktop_xdg_toplevel_role || + role == weston_desktop_xdg_popup_role)) + return true; + + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return false; +} + +static void +weston_desktop_xdg_surface_protocol_set_window_geometry(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!weston_desktop_xdg_surface_check_role(surface)) + return; + + surface->has_next_geometry = true; + surface->next_geometry.x = x; + surface->next_geometry.y = y; + surface->next_geometry.width = width; + surface->next_geometry.height = height; +} + +static void +weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_xdg_surface_configure *configure, *temp; + bool found = false; + + if (!weston_desktop_xdg_surface_check_role(surface)) + return; + + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) { + if (configure->serial < serial) { + wl_list_remove(&configure->link); + free(configure); + } else if (configure->serial == serial) { + wl_list_remove(&configure->link); + found = true; + break; + } else { + break; + } + } + if (!found) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, + "Wrong configure serial: %u", serial); + return; + } + + surface->configured = true; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_ack_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + break; + } + + free(configure); +} + +static void +weston_desktop_xdg_surface_ping(struct weston_desktop_surface *dsurface, + uint32_t serial, void *user_data) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + + xdg_wm_base_send_ping(weston_desktop_client_get_resource(client), + serial); +} + +static void +weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t sx, int32_t sy) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_surface *wsurface = + weston_desktop_surface_get_surface (dsurface); + + if (wsurface->buffer_ref.buffer && !surface->configured) { + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, + "xdg_surface has never been configured"); + return; + } + + if (surface->has_next_geometry) { + surface->has_next_geometry = false; + weston_desktop_surface_set_geometry(surface->desktop_surface, + surface->next_geometry); + } + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_committed((struct weston_desktop_xdg_toplevel *) surface, sx, sy); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_committed((struct weston_desktop_xdg_popup *) surface); + break; + } +} + +static void +weston_desktop_xdg_surface_close(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_close((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_close((struct weston_desktop_xdg_popup *) surface); + break; + } +} + +static void +weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure, *temp; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_destroy((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_destroy((struct weston_desktop_xdg_popup *) surface); + break; + } + + if (surface->configure_idle != NULL) + wl_event_source_remove(surface->configure_idle); + + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) + free(configure); + + free(surface); +} + +static const struct xdg_surface_interface weston_desktop_xdg_surface_implementation = { + .destroy = weston_desktop_destroy_request, + .get_toplevel = weston_desktop_xdg_surface_protocol_get_toplevel, + .get_popup = weston_desktop_xdg_surface_protocol_get_popup, + .set_window_geometry = weston_desktop_xdg_surface_protocol_set_window_geometry, + .ack_configure = weston_desktop_xdg_surface_protocol_ack_configure, +}; + +static const struct weston_desktop_surface_implementation weston_desktop_xdg_surface_internal_implementation = { + /* These are used for toplevel only */ + .set_maximized = weston_desktop_xdg_toplevel_set_maximized, + .set_fullscreen = weston_desktop_xdg_toplevel_set_fullscreen, + .set_resizing = weston_desktop_xdg_toplevel_set_resizing, + .set_activated = weston_desktop_xdg_toplevel_set_activated, + .set_size = weston_desktop_xdg_toplevel_set_size, + + .get_maximized = weston_desktop_xdg_toplevel_get_maximized, + .get_fullscreen = weston_desktop_xdg_toplevel_get_fullscreen, + .get_resizing = weston_desktop_xdg_toplevel_get_resizing, + .get_activated = weston_desktop_xdg_toplevel_get_activated, + + /* These are used for popup only */ + .update_position = weston_desktop_xdg_popup_update_position, + + /* Common API */ + .committed = weston_desktop_xdg_surface_committed, + .ping = weston_desktop_xdg_surface_ping, + .close = weston_desktop_xdg_surface_close, + + .destroy = weston_desktop_xdg_surface_destroy, +}; + +static void +weston_desktop_xdg_shell_protocol_create_positioner(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_positioner *positioner; + + positioner = zalloc(sizeof(struct weston_desktop_xdg_positioner)); + if (positioner == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + positioner->client = client; + positioner->desktop = weston_desktop_client_get_desktop(positioner->client); + + positioner->resource = + wl_resource_create(wl_client, + &xdg_positioner_interface, + wl_resource_get_version(resource), id); + if (positioner->resource == NULL) { + wl_client_post_no_memory(wl_client); + free(positioner); + return; + } + wl_resource_set_implementation(positioner->resource, + &weston_desktop_xdg_positioner_implementation, + positioner, weston_desktop_xdg_positioner_destroy); +} + +static void +weston_desktop_xdg_surface_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static void +weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + wl_resource_get_user_data(surface_resource); + struct weston_desktop_xdg_surface *surface; + + surface = zalloc(weston_desktop_surface_role_biggest_size); + if (surface == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + surface->desktop = weston_desktop_client_get_desktop(client); + surface->surface = wsurface; + wl_list_init(&surface->configure_list); + + surface->desktop_surface = + weston_desktop_surface_create(surface->desktop, client, + surface->surface, + &weston_desktop_xdg_surface_internal_implementation, + surface); + if (surface->desktop_surface == NULL) { + free(surface); + return; + } + + surface->resource = + weston_desktop_surface_add_resource(surface->desktop_surface, + &xdg_surface_interface, + &weston_desktop_xdg_surface_implementation, + id, weston_desktop_xdg_surface_resource_destroy); + if (surface->resource == NULL) + return; + + if (wsurface->buffer_ref.buffer != NULL) { + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, + "xdg_surface must not have a buffer at creation"); + return; + } +} + +static void +weston_desktop_xdg_shell_protocol_pong(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + + weston_desktop_client_pong(client, serial); +} + +static const struct xdg_wm_base_interface weston_desktop_xdg_shell_implementation = { + .destroy = weston_desktop_destroy_request, + .create_positioner = weston_desktop_xdg_shell_protocol_create_positioner, + .get_xdg_surface = weston_desktop_xdg_shell_protocol_get_xdg_surface, + .pong = weston_desktop_xdg_shell_protocol_pong, +}; + +static void +weston_desktop_xdg_shell_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct weston_desktop *desktop = data; + + weston_desktop_client_create(desktop, client, NULL, + &xdg_wm_base_interface, + &weston_desktop_xdg_shell_implementation, + version, id); +} + +struct wl_global * +weston_desktop_xdg_wm_base_create(struct weston_desktop *desktop, + struct wl_display *display) +{ + return wl_global_create(display, &xdg_wm_base_interface, + WD_XDG_SHELL_PROTOCOL_VERSION, desktop, + weston_desktop_xdg_shell_bind); +} diff --git a/protocol/meson.build b/protocol/meson.build index b3ea5b214..34026ff92 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -36,6 +36,7 @@ generated_protocols = [ [ 'weston-touch-calibration', 'internal' ], [ 'xdg-output', 'v1' ], [ 'xdg-shell', 'v6' ], + [ 'xdg-shell', 'stable' ], ] foreach proto: generated_protocols From f99fac22ab5c348a5a786b7cf2f48fb8b2dd3435 Mon Sep 17 00:00:00 2001 From: ant8me Date: Wed, 28 Nov 2018 22:46:37 +0100 Subject: [PATCH 0778/1642] clients: use xdg_shell stable instead of v6 Now that Weston supports the stable revision, use it. Better to excercise the current rather than outdated protocol. Pekka: - split the patch, rewrote commit message - rename xdg_shell_ping to xdg_wm_base_ping - rename xdg_shell_listener to wm_base_listener - rename shell to wm_base - fix continued line alignment - drop unrelated change of adding parentheses around bit-wise and Signed-off-by: Pekka Paalanen --- Makefile.am | 26 +++--- clients/meson.build | 24 +++--- clients/simple-damage.c | 62 +++++++------- clients/simple-dmabuf-drm.c | 62 +++++++------- clients/simple-dmabuf-v4l.c | 64 +++++++------- clients/simple-egl.c | 83 +++++++++--------- clients/simple-shm.c | 62 +++++++------- clients/window.c | 162 ++++++++++++++++++------------------ 8 files changed, 271 insertions(+), 274 deletions(-) diff --git a/Makefile.am b/Makefile.am index b5f5df45a..1c741b25e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -607,8 +607,8 @@ demo_clients += \ weston_simple_shm_SOURCES = clients/simple-shm.c nodist_weston_simple_shm_SOURCES = \ - protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-client-protocol.h \ + protocol/xdg-shell-protocol.c \ + protocol/xdg-shell-client-protocol.h \ protocol/fullscreen-shell-unstable-v1-protocol.c \ protocol/fullscreen-shell-unstable-v1-client-protocol.h weston_simple_shm_CFLAGS = $(AM_CFLAGS) $(SIMPLE_CLIENT_CFLAGS) @@ -618,8 +618,8 @@ weston_simple_damage_SOURCES = clients/simple-damage.c nodist_weston_simple_damage_SOURCES = \ protocol/viewporter-protocol.c \ protocol/viewporter-client-protocol.h \ - protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-client-protocol.h \ + protocol/xdg-shell-protocol.c \ + protocol/xdg-shell-client-protocol.h \ protocol/fullscreen-shell-unstable-v1-protocol.c \ protocol/fullscreen-shell-unstable-v1-client-protocol.h weston_simple_damage_CFLAGS = $(AM_CFLAGS) $(SIMPLE_CLIENT_CFLAGS) @@ -649,8 +649,8 @@ if BUILD_SIMPLE_EGL_CLIENTS demo_clients += weston-simple-egl weston_simple_egl_SOURCES = clients/simple-egl.c nodist_weston_simple_egl_SOURCES = \ - protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-client-protocol.h + protocol/xdg-shell-protocol.c \ + protocol/xdg-shell-client-protocol.h weston_simple_egl_CFLAGS = $(AM_CFLAGS) $(SIMPLE_EGL_CLIENT_CFLAGS) weston_simple_egl_LDADD = $(SIMPLE_EGL_CLIENT_LIBS) -lm endif @@ -661,8 +661,8 @@ weston_simple_dmabuf_drm_SOURCES = \ clients/simple-dmabuf-drm.c \ clients/simple-dmabuf-drm-data.h nodist_weston_simple_dmabuf_drm_SOURCES = \ - protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-client-protocol.h \ + protocol/xdg-shell-protocol.c \ + protocol/xdg-shell-client-protocol.h \ protocol/fullscreen-shell-unstable-v1-protocol.c \ protocol/fullscreen-shell-unstable-v1-client-protocol.h \ protocol/linux-dmabuf-unstable-v1-protocol.c \ @@ -679,8 +679,8 @@ if BUILD_SIMPLE_DMABUF_V4L_CLIENT demo_clients += weston-simple-dmabuf-v4l weston_simple_dmabuf_v4l_SOURCES = clients/simple-dmabuf-v4l.c nodist_weston_simple_dmabuf_v4l_SOURCES = \ - protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-client-protocol.h \ + protocol/xdg-shell-protocol.c \ + protocol/xdg-shell-client-protocol.h \ protocol/fullscreen-shell-unstable-v1-protocol.c \ protocol/fullscreen-shell-unstable-v1-client-protocol.h \ protocol/linux-dmabuf-unstable-v1-protocol.c \ @@ -717,8 +717,8 @@ nodist_libtoytoolkit_la_SOURCES = \ protocol/text-cursor-position-client-protocol.h \ protocol/viewporter-protocol.c \ protocol/viewporter-client-protocol.h \ - protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-client-protocol.h \ + protocol/xdg-shell-protocol.c \ + protocol/xdg-shell-client-protocol.h \ protocol/pointer-constraints-unstable-v1-protocol.c \ protocol/pointer-constraints-unstable-v1-client-protocol.h \ protocol/relative-pointer-unstable-v1-protocol.c \ @@ -966,6 +966,8 @@ BUILT_SOURCES += \ protocol/fullscreen-shell-unstable-v1-client-protocol.h \ protocol/xdg-shell-unstable-v6-protocol.c \ protocol/xdg-shell-unstable-v6-client-protocol.h \ + protocol/xdg-shell-protocol.c \ + protocol/xdg-shell-client-protocol.h \ protocol/ivi-hmi-controller-protocol.c \ protocol/ivi-hmi-controller-client-protocol.h \ protocol/ivi-application-protocol.c \ diff --git a/clients/meson.build b/clients/meson.build index 4d682e3b9..47e9e8ce3 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -4,8 +4,8 @@ endif srcs_toytoolkit = [ 'window.c', - xdg_shell_unstable_v6_client_protocol_h, - xdg_shell_unstable_v6_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, text_cursor_position_client_protocol_h, text_cursor_position_protocol_c, relative_pointer_unstable_v1_client_protocol_h, @@ -41,8 +41,8 @@ simple_clients = [ 'simple-damage.c', viewporter_client_protocol_h, viewporter_protocol_c, - xdg_shell_unstable_v6_client_protocol_h, - xdg_shell_unstable_v6_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, fullscreen_shell_unstable_v1_client_protocol_h, fullscreen_shell_unstable_v1_protocol_c, ], @@ -74,8 +74,8 @@ simple_clients = [ 'simple-dmabuf-v4l.c', linux_dmabuf_unstable_v1_client_protocol_h, linux_dmabuf_unstable_v1_protocol_c, - xdg_shell_unstable_v6_client_protocol_h, - xdg_shell_unstable_v6_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, fullscreen_shell_unstable_v1_client_protocol_h, fullscreen_shell_unstable_v1_protocol_c, ], @@ -85,8 +85,8 @@ simple_clients = [ 'name': 'egl', 'sources': [ 'simple-egl.c', - xdg_shell_unstable_v6_client_protocol_h, - xdg_shell_unstable_v6_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, ivi_application_client_protocol_h, ivi_application_protocol_c, ], @@ -98,8 +98,8 @@ simple_clients = [ 'name': 'shm', 'sources': [ 'simple-shm.c', - xdg_shell_unstable_v6_client_protocol_h, - xdg_shell_unstable_v6_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, fullscreen_shell_unstable_v1_client_protocol_h, fullscreen_shell_unstable_v1_protocol_c, ivi_application_client_protocol_h, @@ -322,8 +322,8 @@ if simple_dmabuf_drm_deps.length() > 0 executable( 'weston-simple-dmabuf-drm', 'simple-dmabuf-drm.c', - xdg_shell_unstable_v6_client_protocol_h, - xdg_shell_unstable_v6_protocol_c, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, fullscreen_shell_unstable_v1_client_protocol_h, fullscreen_shell_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_client_protocol_h, diff --git a/clients/simple-damage.c b/clients/simple-damage.c index ea2d3f94b..f33b9790c 100644 --- a/clients/simple-damage.c +++ b/clients/simple-damage.c @@ -39,7 +39,7 @@ #include #include "shared/os-compatibility.h" #include "shared/zalloc.h" -#include "xdg-shell-unstable-v6-client-protocol.h" +#include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "viewporter-client-protocol.h" @@ -51,7 +51,7 @@ struct display { int compositor_version; struct wl_compositor *compositor; struct wp_viewporter *viewporter; - struct zxdg_shell_v6 *shell; + struct xdg_wm_base *wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct wl_shm *shm; uint32_t formats; @@ -74,8 +74,8 @@ struct window { int width, height, border; struct wl_surface *surface; struct wp_viewport *viewport; - struct zxdg_surface_v6 *xdg_surface; - struct zxdg_toplevel_v6 *xdg_toplevel; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; struct wl_callback *callback; struct buffer buffers[2]; struct buffer *prev_buffer; @@ -149,12 +149,12 @@ create_shm_buffer(struct display *display, struct buffer *buffer, } static void -xdg_surface_handle_configure(void *data, struct zxdg_surface_v6 *surface, +xdg_surface_handle_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; - zxdg_surface_v6_ack_configure(surface, serial); + xdg_surface_ack_configure(surface, serial); if (window->wait_for_configure) { redraw(window, NULL, 0); @@ -162,24 +162,24 @@ xdg_surface_handle_configure(void *data, struct zxdg_surface_v6 *surface, } } -static const struct zxdg_surface_v6_listener xdg_surface_listener = { +static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; static void -xdg_toplevel_handle_configure(void *data, struct zxdg_toplevel_v6 *toplevel, +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { } static void -xdg_toplevel_handle_close(void *data, struct zxdg_toplevel_v6 *xdg_toplevel) +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } -static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { +static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; @@ -317,25 +317,25 @@ create_window(struct display *display, int width, int height, window->viewport = wp_viewporter_get_viewport(display->viewporter, window->surface); - if (display->shell) { + if (display->wm_base) { window->xdg_surface = - zxdg_shell_v6_get_xdg_surface(display->shell, - window->surface); + xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); assert(window->xdg_surface); - zxdg_surface_v6_add_listener(window->xdg_surface, - &xdg_surface_listener, window); + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); window->xdg_toplevel = - zxdg_surface_v6_get_toplevel(window->xdg_surface); + xdg_surface_get_toplevel(window->xdg_surface); assert(window->xdg_toplevel); - zxdg_toplevel_v6_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); - zxdg_toplevel_v6_set_title(window->xdg_toplevel, "simple-damage"); + xdg_toplevel_set_title(window->xdg_toplevel, "simple-damage"); window->wait_for_configure = true; wl_surface_commit(window->surface); @@ -370,9 +370,9 @@ destroy_window(struct window *window) wl_buffer_destroy(window->buffers[1].buffer); if (window->xdg_toplevel) - zxdg_toplevel_v6_destroy(window->xdg_toplevel); + xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) - zxdg_surface_v6_destroy(window->xdg_surface); + xdg_surface_destroy(window->xdg_surface); if (window->viewport) wp_viewport_destroy(window->viewport); wl_surface_destroy(window->surface); @@ -711,13 +711,13 @@ struct wl_shm_listener shm_listener = { }; static void -xdg_shell_ping(void *data, struct zxdg_shell_v6*shell, uint32_t serial) +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { - zxdg_shell_v6_pong(shell, serial); + xdg_wm_base_pong(shell, serial); } -static const struct zxdg_shell_v6_listener xdg_shell_listener = { - xdg_shell_ping, +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, }; static void @@ -743,10 +743,10 @@ registry_handle_global(void *data, struct wl_registry *registry, } else if (strcmp(interface, "wp_viewporter") == 0) { d->viewporter = wl_registry_bind(registry, id, &wp_viewporter_interface, 1); - } else if (strcmp(interface, "zxdg_shell_v6") == 0) { - d->shell = wl_registry_bind(registry, - id, &zxdg_shell_v6_interface, 1); - zxdg_shell_v6_add_listener(d->shell, &xdg_shell_listener, d); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); @@ -808,8 +808,8 @@ destroy_display(struct display *display) if (display->shm) wl_shm_destroy(display->shm); - if (display->shell) - zxdg_shell_v6_destroy(display->shell); + if (display->wm_base) + xdg_wm_base_destroy(display->wm_base); if (display->fshell) zwp_fullscreen_shell_v1_release(display->fshell); diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 82f4bd438..001614911 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -57,7 +57,7 @@ #include #include "shared/zalloc.h" -#include "xdg-shell-unstable-v6-client-protocol.h" +#include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" @@ -77,7 +77,7 @@ struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; - struct zxdg_shell_v6 *shell; + struct xdg_wm_base *wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct zwp_linux_dmabuf_v1 *dmabuf; int xrgb8888_format_found; @@ -135,8 +135,8 @@ struct window { struct display *display; int width, height; struct wl_surface *surface; - struct zxdg_surface_v6 *xdg_surface; - struct zxdg_toplevel_v6 *xdg_toplevel; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; struct buffer buffers[NUM_BUFFERS]; struct buffer *prev_buffer; struct wl_callback *callback; @@ -592,36 +592,36 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, } static void -xdg_surface_handle_configure(void *data, struct zxdg_surface_v6 *surface, +xdg_surface_handle_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; - zxdg_surface_v6_ack_configure(surface, serial); + xdg_surface_ack_configure(surface, serial); if (window->initialized && window->wait_for_configure) redraw(window, NULL, 0); window->wait_for_configure = false; } -static const struct zxdg_surface_v6_listener xdg_surface_listener = { +static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; static void -xdg_toplevel_handle_configure(void *data, struct zxdg_toplevel_v6 *toplevel, +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { } static void -xdg_toplevel_handle_close(void *data, struct zxdg_toplevel_v6 *xdg_toplevel) +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } -static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { +static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; @@ -644,25 +644,25 @@ create_window(struct display *display, int width, int height, int format, window->height = height; window->surface = wl_compositor_create_surface(display->compositor); - if (display->shell) { + if (display->wm_base) { window->xdg_surface = - zxdg_shell_v6_get_xdg_surface(display->shell, - window->surface); + xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); assert(window->xdg_surface); - zxdg_surface_v6_add_listener(window->xdg_surface, - &xdg_surface_listener, window); + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); window->xdg_toplevel = - zxdg_surface_v6_get_toplevel(window->xdg_surface); + xdg_surface_get_toplevel(window->xdg_surface); assert(window->xdg_toplevel); - zxdg_toplevel_v6_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); - zxdg_toplevel_v6_set_title(window->xdg_toplevel, "simple-dmabuf"); + xdg_toplevel_set_title(window->xdg_toplevel, "simple-dmabuf"); window->wait_for_configure = true; wl_surface_commit(window->surface); @@ -707,9 +707,9 @@ destroy_window(struct window *window) } if (window->xdg_toplevel) - zxdg_toplevel_v6_destroy(window->xdg_toplevel); + xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) - zxdg_surface_v6_destroy(window->xdg_surface); + xdg_surface_destroy(window->xdg_surface); wl_surface_destroy(window->surface); free(window); } @@ -796,13 +796,13 @@ static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { }; static void -xdg_shell_ping(void *data, struct zxdg_shell_v6 *shell, uint32_t serial) +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { - zxdg_shell_v6_pong(shell, serial); + xdg_wm_base_pong(shell, serial); } -static const struct zxdg_shell_v6_listener xdg_shell_listener = { - xdg_shell_ping, +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, }; static void @@ -815,10 +815,10 @@ registry_handle_global(void *data, struct wl_registry *registry, d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); - } else if (strcmp(interface, "zxdg_shell_v6") == 0) { - d->shell = wl_registry_bind(registry, - id, &zxdg_shell_v6_interface, 1); - zxdg_shell_v6_add_listener(d->shell, &xdg_shell_listener, d); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); @@ -882,8 +882,8 @@ destroy_display(struct display *display) if (display->dmabuf) zwp_linux_dmabuf_v1_destroy(display->dmabuf); - if (display->shell) - zxdg_shell_v6_destroy(display->shell); + if (display->wm_base) + xdg_wm_base_destroy(display->wm_base); if (display->fshell) zwp_fullscreen_shell_v1_release(display->fshell); diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c index 82b55a99a..a91a07309 100644 --- a/clients/simple-dmabuf-v4l.c +++ b/clients/simple-dmabuf-v4l.c @@ -47,7 +47,7 @@ #include #include "shared/zalloc.h" -#include "xdg-shell-unstable-v6-client-protocol.h" +#include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" @@ -100,7 +100,7 @@ struct display { struct wl_compositor *compositor; struct wl_seat *seat; struct wl_keyboard *keyboard; - struct zxdg_shell_v6 *shell; + struct xdg_wm_base *wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct zwp_linux_dmabuf_v1 *dmabuf; bool requested_format_found; @@ -125,8 +125,8 @@ struct buffer { struct window { struct display *display; struct wl_surface *surface; - struct zxdg_surface_v6 *xdg_surface; - struct zxdg_toplevel_v6 *xdg_toplevel; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; struct buffer buffers[NUM_BUFFERS]; struct wl_callback *callback; bool wait_for_configure; @@ -536,36 +536,36 @@ start_capture(struct display *display) } static void -xdg_surface_handle_configure(void *data, struct zxdg_surface_v6 *surface, +xdg_surface_handle_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; - zxdg_surface_v6_ack_configure(surface, serial); + xdg_surface_ack_configure(surface, serial); if (window->initialized && window->wait_for_configure) redraw(window, NULL, 0); window->wait_for_configure = false; } -static const struct zxdg_surface_v6_listener xdg_surface_listener = { +static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; static void -xdg_toplevel_handle_configure(void *data, struct zxdg_toplevel_v6 *toplevel, +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { } static void -xdg_toplevel_handle_close(void *data, struct zxdg_toplevel_v6 *xdg_toplevel) +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } -static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { +static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; @@ -583,25 +583,25 @@ create_window(struct display *display) window->display = display; window->surface = wl_compositor_create_surface(display->compositor); - if (display->shell) { + if (display->wm_base) { window->xdg_surface = - zxdg_shell_v6_get_xdg_surface(display->shell, - window->surface); + xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); assert(window->xdg_surface); - zxdg_surface_v6_add_listener(window->xdg_surface, - &xdg_surface_listener, window); + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); window->xdg_toplevel = - zxdg_surface_v6_get_toplevel(window->xdg_surface); + xdg_surface_get_toplevel(window->xdg_surface); assert(window->xdg_toplevel); - zxdg_toplevel_v6_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); - zxdg_toplevel_v6_set_title(window->xdg_toplevel, "simple-dmabuf-v4l"); + xdg_toplevel_set_title(window->xdg_toplevel, "simple-dmabuf-v4l"); window->wait_for_configure = true; wl_surface_commit(window->surface); @@ -627,9 +627,9 @@ destroy_window(struct window *window) wl_callback_destroy(window->callback); if (window->xdg_toplevel) - zxdg_toplevel_v6_destroy(window->xdg_toplevel); + xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) - zxdg_surface_v6_destroy(window->xdg_surface); + xdg_surface_destroy(window->xdg_surface); wl_surface_destroy(window->surface); for (i = 0; i < NUM_BUFFERS; i++) { @@ -737,7 +737,7 @@ keyboard_handle_key(void *data, struct wl_keyboard *keyboard, { struct display *d = data; - if (!d->shell) + if (!d->wm_base) return; if (key == KEY_ESC && state) @@ -780,13 +780,13 @@ static const struct wl_seat_listener seat_listener = { }; static void -xdg_shell_ping(void *data, struct zxdg_shell_v6 *shell, uint32_t serial) +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { - zxdg_shell_v6_pong(shell, serial); + xdg_wm_base_pong(shell, serial); } -static const struct zxdg_shell_v6_listener xdg_shell_listener = { - xdg_shell_ping, +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, }; static void @@ -803,10 +803,10 @@ registry_handle_global(void *data, struct wl_registry *registry, d->seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); wl_seat_add_listener(d->seat, &seat_listener, d); - } else if (strcmp(interface, "zxdg_shell_v6") == 0) { - d->shell = wl_registry_bind(registry, - id, &zxdg_shell_v6_interface, 1); - zxdg_shell_v6_add_listener(d->shell, &xdg_shell_listener, d); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, @@ -874,8 +874,8 @@ destroy_display(struct display *display) if (display->dmabuf) zwp_linux_dmabuf_v1_destroy(display->dmabuf); - if (display->shell) - zxdg_shell_v6_destroy(display->shell); + if (display->wm_base) + xdg_wm_base_destroy(display->wm_base); if (display->fshell) zwp_fullscreen_shell_v1_release(display->fshell); diff --git a/clients/simple-egl.c b/clients/simple-egl.c index 252cf55f4..8a086ef0d 100644 --- a/clients/simple-egl.c +++ b/clients/simple-egl.c @@ -42,7 +42,7 @@ #include #include -#include "xdg-shell-unstable-v6-client-protocol.h" +#include "xdg-shell-client-protocol.h" #include #include @@ -57,7 +57,7 @@ struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; - struct zxdg_shell_v6 *shell; + struct xdg_wm_base *wm_base; struct wl_seat *seat; struct wl_pointer *pointer; struct wl_touch *touch; @@ -92,8 +92,8 @@ struct window { uint32_t benchmark_time, frames; struct wl_egl_window *native; struct wl_surface *surface; - struct zxdg_surface_v6 *xdg_surface; - struct zxdg_toplevel_v6 *xdg_toplevel; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; EGLSurface egl_surface; struct wl_callback *callback; int fullscreen, maximized, opaque, buffer_size, frame_sync, delay; @@ -290,22 +290,22 @@ init_gl(struct window *window) } static void -handle_surface_configure(void *data, struct zxdg_surface_v6 *surface, +handle_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; - zxdg_surface_v6_ack_configure(surface, serial); + xdg_surface_ack_configure(surface, serial); window->wait_for_configure = false; } -static const struct zxdg_surface_v6_listener xdg_surface_listener = { +static const struct xdg_surface_listener xdg_surface_listener = { handle_surface_configure }; static void -handle_toplevel_configure(void *data, struct zxdg_toplevel_v6 *toplevel, +handle_toplevel_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { @@ -317,10 +317,10 @@ handle_toplevel_configure(void *data, struct zxdg_toplevel_v6 *toplevel, wl_array_for_each(p, states) { uint32_t state = *p; switch (state) { - case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN: + case XDG_TOPLEVEL_STATE_FULLSCREEN: window->fullscreen = 1; break; - case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED: + case XDG_TOPLEVEL_STATE_MAXIMIZED: window->maximized = 1; break; } @@ -344,12 +344,12 @@ handle_toplevel_configure(void *data, struct zxdg_toplevel_v6 *toplevel, } static void -handle_toplevel_close(void *data, struct zxdg_toplevel_v6 *xdg_toplevel) +handle_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } -static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { +static const struct xdg_toplevel_listener xdg_toplevel_listener = { handle_toplevel_configure, handle_toplevel_close, }; @@ -371,17 +371,17 @@ create_surface(struct window *window) display->egl.conf, window->native, NULL); - window->xdg_surface = zxdg_shell_v6_get_xdg_surface(display->shell, - window->surface); - zxdg_surface_v6_add_listener(window->xdg_surface, - &xdg_surface_listener, window); + window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); window->xdg_toplevel = - zxdg_surface_v6_get_toplevel(window->xdg_surface); - zxdg_toplevel_v6_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); + xdg_surface_get_toplevel(window->xdg_surface); + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); - zxdg_toplevel_v6_set_title(window->xdg_toplevel, "simple-egl"); + xdg_toplevel_set_title(window->xdg_toplevel, "simple-egl"); window->wait_for_configure = true; wl_surface_commit(window->surface); @@ -393,11 +393,11 @@ create_surface(struct window *window) if (!window->frame_sync) eglSwapInterval(display->egl.dpy, 0); - if (!display->shell) + if (!display->wm_base) return; if (window->fullscreen) - zxdg_toplevel_v6_set_fullscreen(window->xdg_toplevel, NULL); + xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); } static void @@ -413,9 +413,9 @@ destroy_surface(struct window *window) wl_egl_window_destroy(window->native); if (window->xdg_toplevel) - zxdg_toplevel_v6_destroy(window->xdg_toplevel); + xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) - zxdg_surface_v6_destroy(window->xdg_surface); + xdg_surface_destroy(window->xdg_surface); wl_surface_destroy(window->surface); if (window->callback) @@ -575,8 +575,8 @@ pointer_handle_button(void *data, struct wl_pointer *wl_pointer, return; if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) - zxdg_toplevel_v6_move(display->window->xdg_toplevel, - display->seat, serial); + xdg_toplevel_move(display->window->xdg_toplevel, + display->seat, serial); } static void @@ -600,10 +600,10 @@ touch_handle_down(void *data, struct wl_touch *wl_touch, { struct display *d = (struct display *)data; - if (!d->shell) + if (!d->wm_base) return; - zxdg_toplevel_v6_move(d->window->xdg_toplevel, d->seat, serial); + xdg_toplevel_move(d->window->xdg_toplevel, d->seat, serial); } static void @@ -662,15 +662,14 @@ keyboard_handle_key(void *data, struct wl_keyboard *keyboard, { struct display *d = data; - if (!d->shell) + if (!d->wm_base) return; if (key == KEY_F11 && state) { if (d->window->fullscreen) - zxdg_toplevel_v6_unset_fullscreen(d->window->xdg_toplevel); + xdg_toplevel_unset_fullscreen(d->window->xdg_toplevel); else - zxdg_toplevel_v6_set_fullscreen(d->window->xdg_toplevel, - NULL); + xdg_toplevel_set_fullscreen(d->window->xdg_toplevel, NULL); } else if (key == KEY_ESC && state) running = 0; } @@ -728,13 +727,13 @@ static const struct wl_seat_listener seat_listener = { }; static void -xdg_shell_ping(void *data, struct zxdg_shell_v6 *shell, uint32_t serial) +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { - zxdg_shell_v6_pong(shell, serial); + xdg_wm_base_pong(shell, serial); } -static const struct zxdg_shell_v6_listener xdg_shell_listener = { - xdg_shell_ping, +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, }; static void @@ -748,10 +747,10 @@ registry_handle_global(void *data, struct wl_registry *registry, wl_registry_bind(registry, name, &wl_compositor_interface, MIN(version, 4)); - } else if (strcmp(interface, "zxdg_shell_v6") == 0) { - d->shell = wl_registry_bind(registry, name, - &zxdg_shell_v6_interface, 1); - zxdg_shell_v6_add_listener(d->shell, &xdg_shell_listener, d); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, name, + &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); } else if (strcmp(interface, "wl_seat") == 0) { d->seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); @@ -881,8 +880,8 @@ main(int argc, char **argv) if (display.cursor_theme) wl_cursor_theme_destroy(display.cursor_theme); - if (display.shell) - zxdg_shell_v6_destroy(display.shell); + if (display.wm_base) + xdg_wm_base_destroy(display.wm_base); if (display.compositor) wl_compositor_destroy(display.compositor); diff --git a/clients/simple-shm.c b/clients/simple-shm.c index fc2ef0012..979d79882 100644 --- a/clients/simple-shm.c +++ b/clients/simple-shm.c @@ -37,14 +37,14 @@ #include #include "shared/os-compatibility.h" #include "shared/zalloc.h" -#include "xdg-shell-unstable-v6-client-protocol.h" +#include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; - struct zxdg_shell_v6 *shell; + struct xdg_wm_base *wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct wl_shm *shm; bool has_xrgb; @@ -60,8 +60,8 @@ struct window { struct display *display; int width, height; struct wl_surface *surface; - struct zxdg_surface_v6 *xdg_surface; - struct zxdg_toplevel_v6 *xdg_toplevel; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; struct buffer buffers[2]; struct buffer *prev_buffer; struct wl_callback *callback; @@ -124,12 +124,12 @@ create_shm_buffer(struct display *display, struct buffer *buffer, } static void -handle_xdg_surface_configure(void *data, struct zxdg_surface_v6 *surface, +handle_xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; - zxdg_surface_v6_ack_configure(surface, serial); + xdg_surface_ack_configure(surface, serial); if (window->wait_for_configure) { redraw(window, NULL, 0); @@ -137,24 +137,24 @@ handle_xdg_surface_configure(void *data, struct zxdg_surface_v6 *surface, } } -static const struct zxdg_surface_v6_listener xdg_surface_listener = { +static const struct xdg_surface_listener xdg_surface_listener = { handle_xdg_surface_configure, }; static void -handle_xdg_toplevel_configure(void *data, struct zxdg_toplevel_v6 *xdg_toplevel, +handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *state) { } static void -handle_xdg_toplevel_close(void *data, struct zxdg_toplevel_v6 *xdg_toplevel) +handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } -static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { +static const struct xdg_toplevel_listener xdg_toplevel_listener = { handle_xdg_toplevel_configure, handle_xdg_toplevel_close, }; @@ -174,21 +174,21 @@ create_window(struct display *display, int width, int height) window->height = height; window->surface = wl_compositor_create_surface(display->compositor); - if (display->shell) { + if (display->wm_base) { window->xdg_surface = - zxdg_shell_v6_get_xdg_surface(display->shell, - window->surface); + xdg_wm_base_get_xdg_surface(display->wm_base, + window->surface); assert(window->xdg_surface); - zxdg_surface_v6_add_listener(window->xdg_surface, - &xdg_surface_listener, window); + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); window->xdg_toplevel = - zxdg_surface_v6_get_toplevel(window->xdg_surface); + xdg_surface_get_toplevel(window->xdg_surface); assert(window->xdg_toplevel); - zxdg_toplevel_v6_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); - zxdg_toplevel_v6_set_title(window->xdg_toplevel, "simple-shm"); + xdg_toplevel_set_title(window->xdg_toplevel, "simple-shm"); wl_surface_commit(window->surface); window->wait_for_configure = true; } else if (display->fshell) { @@ -215,9 +215,9 @@ destroy_window(struct window *window) wl_buffer_destroy(window->buffers[1].buffer); if (window->xdg_toplevel) - zxdg_toplevel_v6_destroy(window->xdg_toplevel); + xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) - zxdg_surface_v6_destroy(window->xdg_surface); + xdg_surface_destroy(window->xdg_surface); wl_surface_destroy(window->surface); free(window); } @@ -346,13 +346,13 @@ struct wl_shm_listener shm_listener = { }; static void -xdg_shell_ping(void *data, struct zxdg_shell_v6 *shell, uint32_t serial) +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { - zxdg_shell_v6_pong(shell, serial); + xdg_wm_base_pong(shell, serial); } -static const struct zxdg_shell_v6_listener xdg_shell_listener = { - xdg_shell_ping, +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + xdg_wm_base_ping, }; static void @@ -365,10 +365,10 @@ registry_handle_global(void *data, struct wl_registry *registry, d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); - } else if (strcmp(interface, "zxdg_shell_v6") == 0) { - d->shell = wl_registry_bind(registry, - id, &zxdg_shell_v6_interface, 1); - zxdg_shell_v6_add_listener(d->shell, &xdg_shell_listener, d); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); @@ -469,8 +469,8 @@ destroy_display(struct display *display) if (display->shm) wl_shm_destroy(display->shm); - if (display->shell) - zxdg_shell_v6_destroy(display->shell); + if (display->wm_base) + xdg_wm_base_destroy(display->wm_base); if (display->fshell) zwp_fullscreen_shell_v1_release(display->fshell); diff --git a/clients/window.c b/clients/window.c index 4a7b9d8b2..bb9c708fe 100644 --- a/clients/window.c +++ b/clients/window.c @@ -74,7 +74,7 @@ typedef void *EGLContext; #include "shared/helpers.h" #include "shared/xalloc.h" #include "shared/zalloc.h" -#include "xdg-shell-unstable-v6-client-protocol.h" +#include "xdg-shell-client-protocol.h" #include "text-cursor-position-client-protocol.h" #include "pointer-constraints-unstable-v1-client-protocol.h" #include "relative-pointer-unstable-v1-client-protocol.h" @@ -105,7 +105,7 @@ struct display { struct wl_shm *shm; struct wl_data_device_manager *data_device_manager; struct text_cursor_position *text_cursor_position; - struct zxdg_shell_v6 *xdg_shell; + struct xdg_wm_base *xdg_shell; struct zwp_relative_pointer_manager_v1 *relative_pointer_manager; struct zwp_pointer_constraints_v1 *pointer_constraints; EGLDisplay dpy; @@ -260,9 +260,9 @@ struct window { window_locked_pointer_motion_handler_t locked_pointer_motion_handler; struct surface *main_surface; - struct zxdg_surface_v6 *xdg_surface; - struct zxdg_toplevel_v6 *xdg_toplevel; - struct zxdg_popup_v6 *xdg_popup; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct xdg_popup *xdg_popup; struct window *parent; struct window *last_parent; @@ -1592,11 +1592,11 @@ window_destroy(struct window *window) window_frame_destroy(window->frame); if (window->xdg_toplevel) - zxdg_toplevel_v6_destroy(window->xdg_toplevel); + xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_popup) - zxdg_popup_v6_destroy(window->xdg_popup); + xdg_popup_destroy(window->xdg_popup); if (window->xdg_surface) - zxdg_surface_v6_destroy(window->xdg_surface); + xdg_surface_destroy(window->xdg_surface); surface_destroy(window->main_surface); @@ -2406,9 +2406,9 @@ frame_handle_status(struct window_frame *frame, struct input *input, if ((status & FRAME_STATUS_MOVE) && window->xdg_toplevel) { input_ungrab(input); - zxdg_toplevel_v6_move(window->xdg_toplevel, - input_get_seat(input), - window->display->serial); + xdg_toplevel_move(window->xdg_toplevel, + input_get_seat(input), + window->display->serial); frame_status_clear(frame->frame, FRAME_STATUS_MOVE); } @@ -2416,10 +2416,10 @@ frame_handle_status(struct window_frame *frame, struct input *input, if ((status & FRAME_STATUS_RESIZE) && window->xdg_toplevel) { input_ungrab(input); - zxdg_toplevel_v6_resize(window->xdg_toplevel, - input_get_seat(input), - window->display->serial, - location); + xdg_toplevel_resize(window->xdg_toplevel, + input_get_seat(input), + window->display->serial, + location); frame_status_clear(frame->frame, FRAME_STATUS_RESIZE); } @@ -4009,7 +4009,7 @@ window_move(struct window *window, struct input *input, uint32_t serial) if (!window->xdg_toplevel) return; - zxdg_toplevel_v6_move(window->xdg_toplevel, input->seat, serial); + xdg_toplevel_move(window->xdg_toplevel, input->seat, serial); } static void @@ -4232,12 +4232,12 @@ window_uninhibit_redraw(struct window *window) static void xdg_surface_handle_configure(void *data, - struct zxdg_surface_v6 *zxdg_surface_v6, + struct xdg_surface *xdg_surface, uint32_t serial) { struct window *window = data; - zxdg_surface_v6_ack_configure(window->xdg_surface, serial); + xdg_surface_ack_configure(window->xdg_surface, serial); if (window->state_changed_handler) window->state_changed_handler(window, window->user_data); @@ -4245,12 +4245,12 @@ xdg_surface_handle_configure(void *data, window_uninhibit_redraw(window); } -static const struct zxdg_surface_v6_listener xdg_surface_listener = { +static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure }; static void -xdg_toplevel_handle_configure(void *data, struct zxdg_toplevel_v6 *xdg_toplevel, +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) { @@ -4265,16 +4265,16 @@ xdg_toplevel_handle_configure(void *data, struct zxdg_toplevel_v6 *xdg_toplevel, wl_array_for_each(p, states) { uint32_t state = *p; switch (state) { - case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED: + case XDG_TOPLEVEL_STATE_MAXIMIZED: window->maximized = 1; break; - case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN: + case XDG_TOPLEVEL_STATE_FULLSCREEN: window->fullscreen = 1; break; - case ZXDG_TOPLEVEL_V6_STATE_RESIZING: + case XDG_TOPLEVEL_STATE_RESIZING: window->resizing = 1; break; - case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED: + case XDG_TOPLEVEL_STATE_ACTIVATED: window->focused = 1; break; default: @@ -4315,13 +4315,13 @@ xdg_toplevel_handle_configure(void *data, struct zxdg_toplevel_v6 *xdg_toplevel, } static void -xdg_toplevel_handle_close(void *data, struct zxdg_toplevel_v6 *xdg_surface) +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_surface) { struct window *window = data; window_close(window); } -static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { +static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; @@ -4329,7 +4329,7 @@ static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { static void window_sync_parent(struct window *window) { - struct zxdg_toplevel_v6 *parent_toplevel; + struct xdg_toplevel *parent_toplevel; if (!window->xdg_surface) return; @@ -4342,7 +4342,7 @@ window_sync_parent(struct window *window) else parent_toplevel = NULL; - zxdg_toplevel_v6_set_parent(window->xdg_toplevel, parent_toplevel); + xdg_toplevel_set_parent(window->xdg_toplevel, parent_toplevel); window->last_parent = window->parent; } @@ -4374,11 +4374,11 @@ window_sync_geometry(struct window *window) geometry.height == window->last_geometry.height) return; - zxdg_surface_v6_set_window_geometry(window->xdg_surface, - geometry.x, - geometry.y, - geometry.width, - geometry.height); + xdg_surface_set_window_geometry(window->xdg_surface, + geometry.x, + geometry.y, + geometry.width, + geometry.height); window->last_geometry = geometry; } @@ -4585,9 +4585,9 @@ window_set_fullscreen(struct window *window, int fullscreen) return; if (fullscreen) - zxdg_toplevel_v6_set_fullscreen(window->xdg_toplevel, NULL); + xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); else - zxdg_toplevel_v6_unset_fullscreen(window->xdg_toplevel); + xdg_toplevel_unset_fullscreen(window->xdg_toplevel); } int @@ -4606,9 +4606,9 @@ window_set_maximized(struct window *window, int maximized) return; if (maximized) - zxdg_toplevel_v6_set_maximized(window->xdg_toplevel); + xdg_toplevel_set_maximized(window->xdg_toplevel); else - zxdg_toplevel_v6_unset_maximized(window->xdg_toplevel); + xdg_toplevel_unset_maximized(window->xdg_toplevel); } int @@ -4623,7 +4623,7 @@ window_set_minimized(struct window *window) if (!window->xdg_toplevel) return; - zxdg_toplevel_v6_set_minimized(window->xdg_toplevel); + xdg_toplevel_set_minimized(window->xdg_toplevel); } void @@ -4727,7 +4727,7 @@ window_set_title(struct window *window, const char *title) widget_schedule_redraw(window->frame->widget); } if (window->xdg_toplevel) - zxdg_toplevel_v6_set_title(window->xdg_toplevel, title); + xdg_toplevel_set_title(window->xdg_toplevel, title); } const char * @@ -5183,19 +5183,19 @@ window_create(struct display *display) if (window->display->xdg_shell) { window->xdg_surface = - zxdg_shell_v6_get_xdg_surface(window->display->xdg_shell, - window->main_surface->surface); + xdg_wm_base_get_xdg_surface(window->display->xdg_shell, + window->main_surface->surface); fail_on_null(window->xdg_surface, 0, __FILE__, __LINE__); - zxdg_surface_v6_add_listener(window->xdg_surface, - &xdg_surface_listener, window); + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); window->xdg_toplevel = - zxdg_surface_v6_get_toplevel(window->xdg_surface); + xdg_surface_get_toplevel(window->xdg_surface); fail_on_null(window->xdg_toplevel, 0, __FILE__, __LINE__); - zxdg_toplevel_v6_add_listener(window->xdg_toplevel, - &xdg_toplevel_listener, window); + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); window_inhibit_redraw(window); @@ -5350,7 +5350,7 @@ menu_redraw_handler(struct widget *widget, void *data) static void xdg_popup_handle_configure(void *data, - struct zxdg_popup_v6 *zxdg_popup_v6, + struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, @@ -5359,7 +5359,7 @@ xdg_popup_handle_configure(void *data, } static void -xdg_popup_handle_popup_done(void *data, struct zxdg_popup_v6 *xdg_popup) +xdg_popup_handle_popup_done(void *data, struct xdg_popup *xdg_popup) { struct window *window = data; struct menu *menu = window->main_surface->widget->user_data; @@ -5368,7 +5368,7 @@ xdg_popup_handle_popup_done(void *data, struct zxdg_popup_v6 *xdg_popup) menu_destroy(menu); } -static const struct zxdg_popup_v6_listener xdg_popup_listener = { +static const struct xdg_popup_listener xdg_popup_listener = { xdg_popup_handle_configure, xdg_popup_handle_popup_done, }; @@ -5424,22 +5424,20 @@ create_menu(struct display *display, return menu; } -static struct zxdg_positioner_v6 * +static struct xdg_positioner * create_simple_positioner(struct display *display, int x, int y, int w, int h) { - struct zxdg_positioner_v6 *positioner; + struct xdg_positioner *positioner; - positioner = zxdg_shell_v6_create_positioner(display->xdg_shell); + positioner = xdg_wm_base_create_positioner(display->xdg_shell); fail_on_null(positioner, 0, __FILE__, __LINE__); - zxdg_positioner_v6_set_anchor_rect(positioner, x, y, 1, 1); - zxdg_positioner_v6_set_size(positioner, w, h); - zxdg_positioner_v6_set_anchor(positioner, - ZXDG_POSITIONER_V6_ANCHOR_TOP | - ZXDG_POSITIONER_V6_ANCHOR_LEFT); - zxdg_positioner_v6_set_gravity(positioner, - ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | - ZXDG_POSITIONER_V6_ANCHOR_RIGHT); + xdg_positioner_set_anchor_rect(positioner, x, y, 1, 1); + xdg_positioner_set_size(positioner, w, h); + xdg_positioner_set_anchor(positioner, + XDG_POSITIONER_ANCHOR_TOP_LEFT); + xdg_positioner_set_gravity(positioner, + XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT); return positioner; } @@ -5454,7 +5452,7 @@ window_show_menu(struct display *display, struct window *window; int32_t ix, iy; struct rectangle parent_geometry; - struct zxdg_positioner_v6 *positioner; + struct xdg_positioner *positioner; menu = create_menu(display, input, time, func, entries, count, parent); @@ -5476,29 +5474,27 @@ window_show_menu(struct display *display, return; window->xdg_surface = - zxdg_shell_v6_get_xdg_surface(display->xdg_shell, - window->main_surface->surface); + xdg_wm_base_get_xdg_surface(display->xdg_shell, + window->main_surface->surface); fail_on_null(window->xdg_surface, 0, __FILE__, __LINE__); - zxdg_surface_v6_add_listener(window->xdg_surface, - &xdg_surface_listener, window); + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); positioner = create_simple_positioner(display, window->x - (ix + parent_geometry.x), window->y - (iy + parent_geometry.y), frame_width(menu->frame), frame_height(menu->frame)); - window->xdg_popup = - zxdg_surface_v6_get_popup(window->xdg_surface, - parent->xdg_surface, - positioner); + window->xdg_popup = xdg_surface_get_popup(window->xdg_surface, + parent->xdg_surface, + positioner); fail_on_null(window->xdg_popup, 0, __FILE__, __LINE__); - zxdg_positioner_v6_destroy(positioner); - zxdg_popup_v6_grab(window->xdg_popup, - input->seat, - display_get_serial(window->display)); - zxdg_popup_v6_add_listener(window->xdg_popup, - &xdg_popup_listener, window); + xdg_positioner_destroy(positioner); + xdg_popup_grab(window->xdg_popup, input->seat, + display_get_serial(window->display)); + xdg_popup_add_listener(window->xdg_popup, + &xdg_popup_listener, window); window_inhibit_redraw(window); @@ -5891,13 +5887,13 @@ struct wl_shm_listener shm_listener = { }; static void -xdg_shell_handle_ping(void *data, struct zxdg_shell_v6 *shell, uint32_t serial) +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { - zxdg_shell_v6_pong(shell, serial); + xdg_wm_base_pong(shell, serial); } -static const struct zxdg_shell_v6_listener xdg_shell_listener = { - xdg_shell_handle_ping, +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, }; static void @@ -5941,10 +5937,10 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, wl_registry_bind(registry, id, &wl_data_device_manager_interface, d->data_device_manager_version); - } else if (strcmp(interface, "zxdg_shell_v6") == 0) { + } else if (strcmp(interface, "xdg_wm_base") == 0) { d->xdg_shell = wl_registry_bind(registry, id, - &zxdg_shell_v6_interface, 1); - zxdg_shell_v6_add_listener(d->xdg_shell, &xdg_shell_listener, d); + &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->xdg_shell, &wm_base_listener, d); } else if (strcmp(interface, "text_cursor_position") == 0) { d->text_cursor_position = wl_registry_bind(registry, id, @@ -6250,7 +6246,7 @@ display_destroy(struct display *display) wl_subcompositor_destroy(display->subcompositor); if (display->xdg_shell) - zxdg_shell_v6_destroy(display->xdg_shell); + xdg_wm_base_destroy(display->xdg_shell); if (display->shm) wl_shm_destroy(display->shm); From 30a285748dbdcf35242176d7eb035f9c6c8df012 Mon Sep 17 00:00:00 2001 From: ant8me Date: Wed, 28 Nov 2018 22:46:37 +0100 Subject: [PATCH 0779/1642] compositor-wayland: use xdg_shell stable instead of v6 Better to excercise the current rather than outdated protocol. Pekka: - split the patch, rewrote commit message - rename xdg_shell_ping to xdg_wm_base_ping - rename xdg_shell_listener to wm_base_listener - fix continued line alignment Signed-off-by: Pekka Paalanen --- Makefile.am | 4 +- libweston/compositor-wayland.c | 104 ++++++++++++++++----------------- libweston/meson.build | 4 +- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/Makefile.am b/Makefile.am index 1c741b25e..1f78d0a78 100644 --- a/Makefile.am +++ b/Makefile.am @@ -457,8 +457,8 @@ wayland_backend_la_SOURCES = \ nodist_wayland_backend_la_SOURCES = \ protocol/fullscreen-shell-unstable-v1-protocol.c \ protocol/fullscreen-shell-unstable-v1-client-protocol.h \ - protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-client-protocol.h + protocol/xdg-shell-protocol.c \ + protocol/xdg-shell-client-protocol.h endif if ENABLE_HEADLESS_COMPOSITOR diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index e80ecc16b..0b1b7fa01 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -55,7 +55,7 @@ #include "shared/cairo-util.h" #include "shared/timespec-util.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" -#include "xdg-shell-unstable-v6-client-protocol.h" +#include "xdg-shell-client-protocol.h" #include "presentation-time-server-protocol.h" #include "linux-dmabuf.h" #include "windowed-output-api.h" @@ -71,7 +71,7 @@ struct wayland_backend { struct wl_registry *registry; struct wl_compositor *compositor; struct wl_shell *shell; - struct zxdg_shell_v6 *xdg_shell; + struct xdg_wm_base *xdg_wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct wl_shm *shm; @@ -104,8 +104,8 @@ struct wayland_output { uint32_t global_id; struct wl_shell_surface *shell_surface; - struct zxdg_surface_v6 *xdg_surface; - struct zxdg_toplevel_v6 *xdg_toplevel; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; int configure_width, configure_height; bool wait_for_configure; } parent; @@ -661,12 +661,12 @@ wayland_backend_destroy_output_surface(struct wayland_output *output) assert(output->parent.surface); if (output->parent.xdg_toplevel) { - zxdg_toplevel_v6_destroy(output->parent.xdg_toplevel); + xdg_toplevel_destroy(output->parent.xdg_toplevel); output->parent.xdg_toplevel = NULL; } if (output->parent.xdg_surface) { - zxdg_surface_v6_destroy(output->parent.xdg_surface); + xdg_surface_destroy(output->parent.xdg_surface); output->parent.xdg_surface = NULL; } @@ -810,11 +810,11 @@ wayland_output_resize_surface(struct wayland_output *output) wl_region_destroy(region); if (output->parent.xdg_surface) { - zxdg_surface_v6_set_window_geometry(output->parent.xdg_surface, - ix, - iy, - iwidth, - iheight); + xdg_surface_set_window_geometry(output->parent.xdg_surface, + ix, + iy, + iwidth, + iheight); } frame_opaque_rect(output->frame, &ix, &iy, &iwidth, &iheight); @@ -837,11 +837,11 @@ wayland_output_resize_surface(struct wayland_output *output) wl_region_destroy(region); if (output->parent.xdg_surface) { - zxdg_surface_v6_set_window_geometry(output->parent.xdg_surface, - 0, - 0, - width, - height); + xdg_surface_set_window_geometry(output->parent.xdg_surface, + 0, + 0, + width, + height); } } @@ -902,7 +902,7 @@ wayland_output_set_windowed(struct wayland_output *output) wayland_output_resize_surface(output); if (output->parent.xdg_toplevel) { - zxdg_toplevel_v6_unset_fullscreen(output->parent.xdg_toplevel); + xdg_toplevel_unset_fullscreen(output->parent.xdg_toplevel); } else if (output->parent.shell_surface) { wl_shell_surface_set_toplevel(output->parent.shell_surface); } else { @@ -925,7 +925,7 @@ wayland_output_set_fullscreen(struct wayland_output *output, wayland_output_resize_surface(output); if (output->parent.xdg_toplevel) { - zxdg_toplevel_v6_set_fullscreen(output->parent.xdg_toplevel, target); + xdg_toplevel_set_fullscreen(output->parent.xdg_toplevel, target); } else if (output->parent.shell_surface) { wl_shell_surface_set_fullscreen(output->parent.shell_surface, method, framerate, target); @@ -1116,18 +1116,18 @@ wayland_output_switch_mode(struct weston_output *output_base, } static void -handle_xdg_surface_configure(void *data, struct zxdg_surface_v6 *surface, +handle_xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) { - zxdg_surface_v6_ack_configure(surface, serial); + xdg_surface_ack_configure(surface, serial); } -static const struct zxdg_surface_v6_listener xdg_surface_listener = { +static const struct xdg_surface_listener xdg_surface_listener = { handle_xdg_surface_configure }; static void -handle_xdg_toplevel_configure(void *data, struct zxdg_toplevel_v6 *toplevel, +handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { @@ -1141,7 +1141,7 @@ handle_xdg_toplevel_configure(void *data, struct zxdg_toplevel_v6 *toplevel, } static void -handle_xdg_toplevel_close(void *data, struct zxdg_toplevel_v6 *xdg_toplevel) +handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { struct wayland_output *output = data; struct weston_compositor *compositor = output->base.compositor; @@ -1152,7 +1152,7 @@ handle_xdg_toplevel_close(void *data, struct zxdg_toplevel_v6 *xdg_toplevel) weston_compositor_exit(compositor); } -static const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { +static const struct xdg_toplevel_listener xdg_toplevel_listener = { handle_xdg_toplevel_configure, handle_xdg_toplevel_close, }; @@ -1173,19 +1173,19 @@ wayland_backend_create_output_surface(struct wayland_output *output) output->parent.draw_initial_frame = true; - if (b->parent.xdg_shell) { + if (b->parent.xdg_wm_base) { output->parent.xdg_surface = - zxdg_shell_v6_get_xdg_surface(b->parent.xdg_shell, - output->parent.surface); - zxdg_surface_v6_add_listener(output->parent.xdg_surface, - &xdg_surface_listener, output); + xdg_wm_base_get_xdg_surface(b->parent.xdg_wm_base, + output->parent.surface); + xdg_surface_add_listener(output->parent.xdg_surface, + &xdg_surface_listener, output); output->parent.xdg_toplevel = - zxdg_surface_v6_get_toplevel(output->parent.xdg_surface); - zxdg_toplevel_v6_add_listener(output->parent.xdg_toplevel, - &xdg_toplevel_listener, output); + xdg_surface_get_toplevel(output->parent.xdg_surface); + xdg_toplevel_add_listener(output->parent.xdg_toplevel, + &xdg_toplevel_listener, output); - zxdg_toplevel_v6_set_title(output->parent.xdg_toplevel, output->title); + xdg_toplevel_set_title(output->parent.xdg_toplevel, output->title); wl_surface_commit(output->parent.surface); @@ -1194,7 +1194,7 @@ wayland_backend_create_output_surface(struct wayland_output *output) while (output->parent.wait_for_configure) wl_display_dispatch(b->parent.wl_display); - weston_log("wayland-backend: Using xdg_shell_v6\n"); + weston_log("wayland-backend: Using xdg_wm_base\n"); } else if (b->parent.shell) { output->parent.shell_surface = @@ -1539,10 +1539,10 @@ wayland_output_setup_fullscreen(struct wayland_output *output, return -1; /* What should size be set if conditional is false? */ - if (b->parent.xdg_shell || b->parent.shell) { + if (b->parent.xdg_wm_base || b->parent.shell) { if (output->parent.xdg_toplevel) - zxdg_toplevel_v6_set_fullscreen(output->parent.xdg_toplevel, - output->parent.output); + xdg_toplevel_set_fullscreen(output->parent.xdg_toplevel, + output->parent.output); else if (output->parent.shell_surface) wl_shell_surface_set_fullscreen(output->parent.shell_surface, 0, 0, NULL); @@ -1774,8 +1774,8 @@ input_handle_button(void *data, struct wl_pointer *pointer, if (frame_status(input->output->frame) & FRAME_STATUS_MOVE) { if (input->output->parent.xdg_toplevel) - zxdg_toplevel_v6_move(input->output->parent.xdg_toplevel, - input->parent.seat, serial); + xdg_toplevel_move(input->output->parent.xdg_toplevel, + input->parent.seat, serial); else if (input->output->parent.shell_surface) wl_shell_surface_move(input->output->parent.shell_surface, input->parent.seat, serial); @@ -2130,8 +2130,8 @@ input_handle_touch_down(void *data, struct wl_touch *wl_touch, if (first_touch && (frame_status(output->frame) & FRAME_STATUS_MOVE)) { input->touch_points--; if (output->parent.xdg_toplevel) - zxdg_toplevel_v6_move(output->parent.xdg_toplevel, - input->parent.seat, serial); + xdg_toplevel_move(output->parent.xdg_toplevel, + input->parent.seat, serial); else if (output->parent.shell_surface) wl_shell_surface_move(output->parent.shell_surface, input->parent.seat, serial); @@ -2528,13 +2528,13 @@ wayland_parent_output_destroy(struct wayland_parent_output *output) } static void -xdg_shell_ping(void *data, struct zxdg_shell_v6 *shell, uint32_t serial) +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { - zxdg_shell_v6_pong(shell, serial); + xdg_wm_base_pong(shell, serial); } -static const struct zxdg_shell_v6_listener xdg_shell_listener = { - xdg_shell_ping, +static const struct xdg_wm_base_listener wm_base_listener = { + xdg_wm_base_ping, }; static void @@ -2548,12 +2548,12 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, wl_registry_bind(registry, name, &wl_compositor_interface, MIN(version, 4)); - } else if (strcmp(interface, "zxdg_shell_v6") == 0) { - b->parent.xdg_shell = + } else if (strcmp(interface, "xdg_wm_base") == 0) { + b->parent.xdg_wm_base = wl_registry_bind(registry, name, - &zxdg_shell_v6_interface, 1); - zxdg_shell_v6_add_listener(b->parent.xdg_shell, - &xdg_shell_listener, b); + &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(b->parent.xdg_wm_base, + &wm_base_listener, b); } else if (strcmp(interface, "wl_shell") == 0) { b->parent.shell = wl_registry_bind(registry, name, @@ -2629,8 +2629,8 @@ wayland_destroy(struct weston_compositor *ec) if (b->parent.shm) wl_shm_destroy(b->parent.shm); - if (b->parent.xdg_shell) - zxdg_shell_v6_destroy(b->parent.xdg_shell); + if (b->parent.xdg_wm_base) + xdg_wm_base_destroy(b->parent.xdg_wm_base); if (b->parent.shell) wl_shell_destroy(b->parent.shell); diff --git a/libweston/meson.build b/libweston/meson.build index fa7950113..6aaa41065 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -307,8 +307,8 @@ if get_option('backend-wayland') fullscreen_shell_unstable_v1_protocol_c, presentation_time_protocol_c, presentation_time_server_protocol_h, - xdg_shell_unstable_v6_client_protocol_h, - xdg_shell_unstable_v6_protocol_c, + xdg_shell_server_protocol_h, + xdg_shell_protocol_c, ] deps_wlwl = [ From cd6bf210a44ecb2abb4aec91a84cbc167e894e06 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 8 Feb 2019 15:10:48 +0200 Subject: [PATCH 0780/1642] libweston: bump major to 6 At least the following commits break the libweston ABI, strictly speaking: - a5630eafec4f139adf1da4a5ba54894715d7b50f - ce62cb3d05505777893ccdfacbf2a013c82e4ce2 - 195dadeb2add729735c32b9e394d7d1397cf065c - c18ffd3939499897fb3eefc6ff3627140d88a23d - ea54c2fda65aacd87c6385461dc7e7f7fb0d425d - 27d7c395c7600e9bf6b13dd8b3d06ee19aab1aba - acff29b3b3c3ab804db54fcc8524df53b6b9e1fb - 676296749a5ddb82d2f378829f419398602e5ce4 - c9c247730b37a0101570f7a2596b1fb8b6ac66b5 - 65e1be1234fcbb7455fc9b41ac9916d258cf39f8 Furhtermore, 64fbd0f41f4eb175b772a2eddba8bfccb66e016a break the weston executable exported ABI. Signed-off-by: Pekka Paalanen --- configure.ac | 4 ++-- meson.build | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index c2001b613..496012e71 100644 --- a/configure.ac +++ b/configure.ac @@ -3,9 +3,9 @@ m4_define([weston_minor_version], [0]) m4_define([weston_micro_version], [90]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) -m4_define([libweston_major_version], [5]) +m4_define([libweston_major_version], [6]) m4_define([libweston_minor_version], [0]) -m4_define([libweston_patch_version], [90]) +m4_define([libweston_patch_version], [0]) AC_PREREQ([2.64]) AC_INIT([weston], diff --git a/meson.build b/meson.build index 12fa7433a..356ae1de8 100644 --- a/meson.build +++ b/meson.build @@ -10,7 +10,7 @@ project('weston', license: 'MIT/Expat', ) -libweston_major = 5 +libweston_major = 6 # libweston_revision is manufactured to follow the autotools build's # library file naming, thanks to libtool From fcd9f674674731c7d37871034c5e9fe5166e248b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 6 Feb 2019 13:51:55 +0200 Subject: [PATCH 0781/1642] meson: set WESTON_DATA_DIR for tests It seems WESTON_DATA_DIR was missed. If you have already installed Weston, then the files will be found in the install location, but if not, they were not found at all. This caused the xwayland test to SEGV the compositor in weston_wm_window_create_frame() when frame_crate() returned NULL. This patch fixes the test suite only. Signed-off-by: Pekka Paalanen --- tests/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/meson.build b/tests/meson.build index ee24562b1..53c55273a 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -232,6 +232,7 @@ endif env_test_weston = [ 'WESTON_TEST_REFERENCE_PATH=@0@/reference'.format(meson.current_source_dir()), 'WESTON_MODULE_MAP=@0@'.format(env_modmap), + 'WESTON_DATA_DIR=' + join_paths(meson.current_source_dir(), '..', 'data'), ] # FIXME: the multiple loops is lame. rethink this. From c58648b0df60410bbb731aef28999eefa020778f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 6 Feb 2019 14:03:21 +0200 Subject: [PATCH 0782/1642] meson: IVI plugin tests do not use config If Weston is not installed, running ivi-layout test would fail on lots of image files not found which presumably causes the creation of some ivi surfaces to fail, leading to an assert failure. Looking at the test setup in weston-tests-env, these IVI plugin tests are supposed to run with --no-config instead. Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/195 Signed-off-by: Pekka Paalanen --- tests/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/meson.build b/tests/meson.build index 53c55273a..03692f477 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -371,7 +371,7 @@ foreach t : tests_weston_plugin # FIXME: Get this from the array ... ? if t.get(0).startswith('ivi-') - args_t += [ '--config=@0@/../ivi-shell/weston-ivi-test.ini'.format(meson.current_build_dir()) ] + args_t += [ '--no-config' ] args_t += [ '--modules=@1@,@0@'.format(exe_plugin_test.full_path(),exe_t.full_path()) ] args_t += [ '--shell=ivi-shell.so' ] else From cca734655764d0d05543653aa37d288e64ba3304 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Wed, 13 Feb 2019 15:10:35 +0100 Subject: [PATCH 0783/1642] meson: add missing libweston EGL dependency if renderer-gl is enabled If the 'renderer-gl' option is enabled, ENABLE_EGL is defined, and libweston/pixel-formats.c includes EGL/egl.h. This requires an egl dependency, as X11-less platforms need the MESA_EGL_NO_X11_HEADERS define from egl.pc cflags: In file included from /usr/include/EGL/egl.h:39:0, from ../libweston/pixel-formats.c:42: /usr/include/EGL/eglplatform.h:124:10: fatal error: X11/Xlib.h: No such file or directory compilation terminated. Signed-off-by: Philipp Zabel --- libweston/meson.build | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libweston/meson.build b/libweston/meson.build index 6aaa41065..d506cdddf 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -67,6 +67,14 @@ install_headers( subdir: dir_include_libweston ) +if get_option('renderer-gl') + dep_egl = dependency('egl', required: false) + if not dep_egl.found() + error('libweston + gl-renderer requires egl which was not found. Or, you can use \'-Drenderer-gl=false\'.') + endif + deps_libweston += dep_egl +endif + lib_weston = shared_library( 'weston-@0@'.format(libweston_major), srcs_libweston, @@ -375,11 +383,10 @@ if get_option('backend-x11') endif if get_option('renderer-gl') - d = dependency('egl', required: false) - if not d.found() + if not dep_egl.found() error('x11-backend + gl-renderer requires egl which was not found. Or, you can use \'-Dbackend-x11=false\' or \'-Drenderer-gl=false\'.') endif - deps_x11 += d + deps_x11 += dep_egl endif plugin_x11 = shared_library( From b68847a8bc8f73e5518285d927e7426005eeaaad Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Wed, 13 Feb 2019 15:59:59 +0100 Subject: [PATCH 0784/1642] meson: fix compositor build with xwayland disabled If xwayland is disabled, compositor/weston is built without compositor/xwayland.c, which defines wet_load_xwayland. compositor/fb12c4d@@weston@exe/main.c.o: In function `main': ../weston-5.0.0-169-g2d4cc4f4dd6a/compositor/main.c:3103: undefined reference to `wet_load_xwayland' Provide an empty stub for wet_load_xwayland if xwayland is disabled. With that we also have to remove xwayland.c from the autotools build if xwayland is disabled, to avoid a multiple definition error. Signed-off-by: Philipp Zabel --- Makefile.am | 5 ++++- compositor/main.c | 12 ++++++++++++ compositor/meson.build | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 1f78d0a78..b2bb61f62 100644 --- a/Makefile.am +++ b/Makefile.am @@ -210,8 +210,11 @@ weston_LDADD = libshared.la libweston-@LIBWESTON_MAJOR@.la \ weston_SOURCES = \ compositor/main.c \ compositor/weston-screenshooter.c \ - compositor/text-backend.c \ + compositor/text-backend.c +if ENABLE_XWAYLAND +weston_SOURCES += \ compositor/xwayland.c +endif # Track this dependency explicitly instead of using BUILT_SOURCES. We # add BUILT_SOURCES to CLEANFILES, but we want to keep git-version.h diff --git a/compositor/main.c b/compositor/main.c index 34d2e7153..19a920e10 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -646,7 +646,9 @@ usage(int error_code) " --shell=MODULE\tShell module, defaults to desktop-shell.so\n" " -S, --socket=NAME\tName of socket to listen on\n" " -i, --idle-time=SECS\tIdle time in seconds\n" +#if defined(BUILD_XWAYLAND) " --xwayland\t\tLoad the xwayland module\n" +#endif " --modules\t\tLoad the comma-separated list of modules\n" " --log=FILE\t\tLog to the given file\n" " -c, --config=FILE\tConfig file to load, defaults to weston.ini\n" @@ -2876,6 +2878,14 @@ copy_command_line(int argc, char * const argv[]) return str; } +#if !defined(BUILD_XWAYLAND) +int +wet_load_xwayland(struct weston_compositor *comp) +{ + return -1; +} +#endif + int main(int argc, char *argv[]) { int ret = EXIT_FAILURE; @@ -2916,7 +2926,9 @@ int main(int argc, char *argv[]) { WESTON_OPTION_STRING, "shell", 0, &shell }, { WESTON_OPTION_STRING, "socket", 'S', &socket_name }, { WESTON_OPTION_INTEGER, "idle-time", 'i', &idle_time }, +#if defined(BUILD_XWAYLAND) { WESTON_OPTION_BOOLEAN, "xwayland", 0, &xwayland }, +#endif { WESTON_OPTION_STRING, "modules", 0, &option_modules }, { WESTON_OPTION_STRING, "log", 0, &log }, { WESTON_OPTION_BOOLEAN, "help", 'h', &help }, diff --git a/compositor/meson.build b/compositor/meson.build index 994b5877f..420a96050 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -20,6 +20,8 @@ deps_weston = [ ] if get_option('xwayland') + config_h.set('BUILD_XWAYLAND', '1') + srcs_weston += 'xwayland.c' config_h.set_quoted('XSERVER_PATH', get_option('xwayland-path')) endif From 4253f2357686e7bb2d44fd9259ea21306060a6a6 Mon Sep 17 00:00:00 2001 From: Greg V Date: Thu, 8 Nov 2018 00:24:15 +0300 Subject: [PATCH 0785/1642] xwayland: fix clipboard related crash --- xwayland/selection.c | 1 + 1 file changed, 1 insertion(+) diff --git a/xwayland/selection.c b/xwayland/selection.c index e0eb3ffc2..bc7318af9 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -408,6 +408,7 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) wm->property_source = NULL; close(fd); wl_array_release(&wm->source_data); + return 1; } weston_log("read %d (available %d, mask 0x%x) bytes: \"%.*s\"\n", From dce10bd1416adecca8cca8bab1d1b8e5fc09f791 Mon Sep 17 00:00:00 2001 From: Greg V Date: Thu, 8 Nov 2018 00:27:04 +0300 Subject: [PATCH 0786/1642] xwm: fix resize grab related crash This crash was happening *during resizing* of an xwayland window that was destroyed. Discovered by: John Good @archiesix [@daniels: Moved tests below declarations.] --- xwayland/window-manager.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index ccdae57fd..77260b0fc 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2661,11 +2661,16 @@ static void send_configure(struct weston_surface *surface, int32_t width, int32_t height) { struct weston_wm_window *window = get_wm_window(surface); - struct weston_wm *wm = window->wm; - struct theme *t = window->wm->theme; + struct weston_wm *wm; + struct theme *t; int new_width, new_height; int vborder, hborder; + if (!window || !window->wm) + return; + wm = window->wm; + t = wm->theme; + if (window->decorate && !window->fullscreen) { hborder = 2 * t->width; vborder = t->titlebar_height + t->width; From ec3f779aa76332b4087e1bc882e83e11a073e478 Mon Sep 17 00:00:00 2001 From: Greg V Date: Thu, 8 Nov 2018 00:24:56 +0300 Subject: [PATCH 0787/1642] desktop-shell: fix resize grab related crash This crash was happening when *releasing* a pointer button on a window that was being resized and got destroyed during the grab. [@daniels: Cosmetic fixes; apply same fix to grab cancel.] --- desktop-shell/shell.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 809f31c8c..e0ee506b1 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -1654,12 +1654,16 @@ resize_grab_button(struct weston_pointer_grab *grab, struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; struct weston_pointer *pointer = grab->pointer; enum wl_pointer_button_state state = state_w; - struct weston_desktop_surface *desktop_surface = - resize->base.shsurf->desktop_surface; if (pointer->button_count == 0 && state == WL_POINTER_BUTTON_STATE_RELEASED) { - weston_desktop_surface_set_resizing(desktop_surface, false); + if (resize->base.shsurf != NULL) { + struct weston_desktop_surface *desktop_surface = + resize->base.shsurf->desktop_surface; + weston_desktop_surface_set_resizing(desktop_surface, + false); + } + shell_grab_end(&resize->base); free(grab); } @@ -1669,10 +1673,13 @@ static void resize_grab_cancel(struct weston_pointer_grab *grab) { struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; - struct weston_desktop_surface *desktop_surface = - resize->base.shsurf->desktop_surface; - weston_desktop_surface_set_resizing(desktop_surface, false); + if (resize->base.shsurf != NULL) { + struct weston_desktop_surface *desktop_surface = + resize->base.shsurf->desktop_surface; + weston_desktop_surface_set_resizing(desktop_surface, false); + } + shell_grab_end(&resize->base); free(grab); } From 15d3d3004b0c5344be3b3f7fa99381be392dfc4a Mon Sep 17 00:00:00 2001 From: Greg V Date: Fri, 7 Dec 2018 01:33:34 +0300 Subject: [PATCH 0788/1642] desktop-shell: remove surface destroy listener when focus state is destroyed Changing the focused surface did remove the surface_destroy_listener from the wl_signal list, but destroying the focus state did not. As a result, sometimes the same listener would be added to two surfaces, which would join their wl_signal lists together, which would cause infinite loops and use-after-frees when closing desktop surfaces. --- desktop-shell/shell.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index e0ee506b1..aac23ac7c 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -663,6 +663,10 @@ focus_state_surface_destroy(struct wl_listener *listener, void *data) next = get_default_view(main_surface); if (next) { + if (state->keyboard_focus) { + wl_list_remove(&state->surface_destroy_listener.link); + wl_list_init(&state->surface_destroy_listener.link); + } state->keyboard_focus = NULL; activate(state->shell, next, state->seat, WESTON_ACTIVATE_FLAG_CONFIGURE); From 14ef201295724f5c9ed254979845d0a2d5988bd3 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 11 Feb 2019 22:55:09 +0300 Subject: [PATCH 0789/1642] desktop-shell: don't crash if a surface disappears while grabbed A surface can get destroyed while a shell grab is active, which can for example happen if the command running in weston-terminal exits. When a surface gets destroyed, grab->shsurf is reset to NULL by destroy_shell_grab_shsurf(), but otherwise the grab remains active and its callbacks continue to be called. Thus, dereferencing grab->shsurf in a callback without checking it for NULL first can lead to undefined behavior, including crashes. Several functions were already properly checking grab->shsurf for NULL, move_grab_motion() being one example. Others, however, were not, which is what this commit fixes. Related to https://gitlab.freedesktop.org/wayland/weston/issues/192 Signed-off-by: Sergey Bugaev --- desktop-shell/shell.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index aac23ac7c..34b447539 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -3559,8 +3559,7 @@ rotate_grab_motion(struct weston_pointer_grab *grab, container_of(grab, struct rotate_grab, base.grab); struct weston_pointer *pointer = grab->pointer; struct shell_surface *shsurf = rotate->base.shsurf; - struct weston_surface *surface = - weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_surface *surface; float cx, cy, dx, dy, cposx, cposy, dposx, dposy, r; weston_pointer_move(pointer, event); @@ -3568,6 +3567,8 @@ rotate_grab_motion(struct weston_pointer_grab *grab, if (!shsurf) return; + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + cx = 0.5f * surface->width; cy = 0.5f * surface->height; From 521056b37b674ef0a7b66017c8bc7c429f1a42d1 Mon Sep 17 00:00:00 2001 From: emersion Date: Fri, 15 Feb 2019 22:51:31 +0100 Subject: [PATCH 0790/1642] Add .editorconfig --- .editorconfig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..9a694af80 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = tab +indent_size = 8 +max_line_length = 80 + +[*.xml,meson.build,meson_options.txt] +indent_style = space +indent_size = 2 +tab_width = 8 From ee1d968e641ecd0aa672faafcdb37a780cb96ef0 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 31 Jan 2019 00:02:25 +0000 Subject: [PATCH 0791/1642] compositor-drm: Fall back if GBM surface fails with modifiers If we cannot create a gbm_surface using a list of modifiers, fall back to using the old pre-modifier version. This fixes initialisation on systems where KMS supports modifiers but the GBM driver does not, such as old i915 systems like Pine View using the unified KMS driver but the old i915 Mesa driver. Signed-off-by: Daniel Stone --- libweston/compositor-drm.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 54230eccc..a5de66111 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -5061,7 +5061,12 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) output->gbm_format, plane->formats[i].modifiers, plane->formats[i].count_modifiers); - } else + } + + /* If allocating with modifiers fails, try again without. This can + * happen when the KMS display device supports modifiers but the + * GBM driver does not, e.g. the old i915 Mesa driver. */ + if (!output->gbm_surface) #endif { output->gbm_surface = From 438de4f5592606b8111ff060aeacdd8d128d6f44 Mon Sep 17 00:00:00 2001 From: emersion Date: Mon, 18 Feb 2019 11:21:07 +0100 Subject: [PATCH 0792/1642] Fix .editorconfig: use tabs for Meson files --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 9a694af80..e0fc85315 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,7 +9,7 @@ indent_style = tab indent_size = 8 max_line_length = 80 -[*.xml,meson.build,meson_options.txt] +[*.xml] indent_style = space indent_size = 2 tab_width = 8 From 0a9c95382520e39c5400f49426a298df23af7b26 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Sat, 16 Feb 2019 20:43:24 +0200 Subject: [PATCH 0793/1642] meson: Remove freerdp1 as it no longer builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ../libweston/compositor-rdp.c: In function ‘rdp_peer_refresh_rfx’: ../libweston/compositor-rdp.c:213:25: error: invalid type argument of unary ‘*’ (have ‘SURFACE_BITS_COMMAND’ {aka ‘struct _SURFACE_BITS_COMMAND’}) memset(&cmd, 0, sizeof(*cmd)); Signed-off-by: Marius Vlad --- libweston/meson.build | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libweston/meson.build b/libweston/meson.build index d506cdddf..33ab970e2 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -270,10 +270,7 @@ if get_option('backend-rdp') dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) if not dep_frdp.found() - dep_frdp = dependency('freerdp', version: '>= 1.1.0', required: false) - endif - if not dep_frdp.found() - error('RDP-backend requires freerdp which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + error('RDP-backend requires freerdp2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') endif if cc.has_header('freerdp/version.h', dependencies: dep_frdp) From d4c7bc58ab95b593d074123952585aa502e9c98f Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Sat, 16 Feb 2019 21:19:51 +0200 Subject: [PATCH 0794/1642] compositor-drm: Print pixel format in human-friendly form when failing to assign view to a overlay plane Signed-off-by: Marius Vlad --- libweston/compositor-drm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index a5de66111..46db6d6c0 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -3350,9 +3350,10 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, break; case NO_PLANES_WITH_FORMAT: drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " - "no free overlay planes matching format 0x%lx, " + "no free overlay planes matching format %s (0x%lx) " "modifier 0x%llx\n", - ev, (unsigned long) fb->format, + ev, fb->format->drm_format_name, + (unsigned long) fb->format, (unsigned long long) fb->modifier); break; case NO_PLANES_ACCEPTED: From 433f4e77b77297148ea1143d49b7e62a0bfb7a97 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Sun, 17 Feb 2019 22:14:23 +0200 Subject: [PATCH 0795/1642] compositor: Fix scene-graph debug scope missing views based on sub-surfaces Signed-off-by: Marius Vlad --- libweston/compositor.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 0f1f01fb5..d87522e73 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6645,6 +6645,33 @@ debug_scene_view_print(FILE *fp, struct weston_view *view, int view_idx) debug_scene_view_print_buffer(fp, view); } +static void +debug_scene_view_print_tree(struct weston_view *view, + FILE *fp, int view_idx) +{ + struct weston_subsurface *sub; + struct weston_view *ev; + + /* + * print the view first, then we recursively go on printing + * sub-surfaces. We bail out once no more sub-surfaces are available. + */ + debug_scene_view_print(fp, view, view_idx++); + + /* no more sub-surfaces */ + if (wl_list_empty(&view->surface->subsurface_list)) + return; + + wl_list_for_each(sub, &view->surface->subsurface_list, parent_link) { + wl_list_for_each(ev, &sub->surface->views, surface_link) { + /* do not print again the parent view */ + if (view == ev) + continue; + debug_scene_view_print_tree(ev, fp, view_idx); + } + } +} + /** * Output information on how libweston is currently composing the scene * graph. @@ -6715,7 +6742,7 @@ weston_compositor_print_scene_graph(struct weston_compositor *ec) } wl_list_for_each(view, &layer->view_list.link, layer_link.link) - debug_scene_view_print(fp, view, view_idx++); + debug_scene_view_print_tree(view, fp, view_idx); if (wl_list_empty(&layer->view_list.link)) fprintf(fp, "\t[no views]\n"); From e578004b3c3584079d41d7e7f7f1f46245a9774d Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 19 Feb 2019 12:48:10 -0600 Subject: [PATCH 0796/1642] configure.ac: bump to version 5.0.91 for the alpha release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 496012e71..06348bf98 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [5]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [90]) +m4_define([weston_micro_version], [91]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [6]) From 426c24673f2f5c4e3f0e388e87d2a355b82cd4e7 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Wed, 20 Feb 2019 16:33:32 +0100 Subject: [PATCH 0797/1642] Fix typos all around (thanks codespell!) --- CONTRIBUTING.md | 2 +- clients/simple-dmabuf-egl.c | 2 +- compositor/main.c | 2 +- desktop-shell/shell.c | 4 ++-- fullscreen-shell/fullscreen-shell.c | 2 +- ivi-shell/ivi-layout-export.h | 4 ++-- ivi-shell/ivi-layout.c | 4 ++-- libweston/compositor-wayland.c | 2 +- libweston/libbacklight.h | 2 +- libweston/libinput-seat.c | 2 +- libweston/pixel-formats.c | 2 +- libweston/vaapi-recorder.c | 2 +- libweston/weston-debug.c | 2 +- man/weston-debug.man | 2 +- man/weston-drm.man | 4 ++-- protocol/weston-debug.xml | 4 ++-- remoting/remoting-plugin.c | 4 ++-- tools/zunitc/src/zuc_collector.h | 4 ++-- xwayland/selection.c | 2 +- 19 files changed, 26 insertions(+), 26 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c1f9aa39..21b3847a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -258,7 +258,7 @@ During review, the following matters should be checked: - Stable ABI or API additions must be justified by actual use cases, not only by speculation. They must also be documented, and it is strongly recommended to -include tests excercising the additions in the test suite. +include tests exercising the additions in the test suite. - The code fits the existing software architecture, e.g. no layering violations. diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c index e81f1629c..5353f5a0f 100644 --- a/clients/simple-dmabuf-egl.c +++ b/clients/simple-dmabuf-egl.c @@ -1160,7 +1160,7 @@ display_set_up_egl(struct display *display) if (!weston_check_egl_extension(gl_extensions, "GL_OES_EGL_image")) { - fprintf(stderr, "GL_OES_EGL_image not suported\n"); + fprintf(stderr, "GL_OES_EGL_image not supported\n"); goto error; } diff --git a/compositor/main.c b/compositor/main.c index 19a920e10..c32067831 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -381,7 +381,7 @@ child_client_exec(int sockfd, const char *path) sigfillset(&allsigs); sigprocmask(SIG_UNBLOCK, &allsigs, NULL); - /* Launch clients as the user. Do not lauch clients with wrong euid.*/ + /* Launch clients as the user. Do not launch clients with wrong euid. */ if (seteuid(getuid()) == -1) { weston_log("compositor: failed seteuid\n"); return; diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 34b447539..93b1c70b5 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -1121,7 +1121,7 @@ animate_workspace_change_frame(struct weston_animation *animation, else timespec_add_msec(&shell->workspaces.anim_timestamp, time, - /* Invers of movement function 'y' below. */ + /* Inverse of movement function 'y' below. */ -(asin(1.0 - shell->workspaces.anim_current) * DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH * M_2_PI)); @@ -2223,7 +2223,7 @@ get_focused_output(struct weston_compositor *compositor) /* Priority has touch focus, then pointer and * then keyboard focus. We should probably have - * three for loops and check frist for touch, + * three for loops and check first for touch, * then for pointer, etc. but unless somebody has some * objections, I think this is sufficient. */ if (touch && touch->focus) diff --git a/fullscreen-shell/fullscreen-shell.c b/fullscreen-shell/fullscreen-shell.c index 898847947..99e9dbbc2 100644 --- a/fullscreen-shell/fullscreen-shell.c +++ b/fullscreen-shell/fullscreen-shell.c @@ -546,7 +546,7 @@ fs_output_configure_for_mode(struct fs_output *fsout, &surf_width, &surf_height); /* The actual output mode is in physical units. We need to - * transform the surface size to physical unit size by flipping ans + * transform the surface size to physical unit size by flipping and * possibly scaling it. */ switch (fsout->output->transform) { diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index c65eb306c..77b06de94 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -28,7 +28,7 @@ * surface and layer which groups surfaces. A unique ID whose type is integer is * required to create surface and layer. With the unique ID, surface and layer * are identified to control them. The API set consists of APIs to control - * properties of surface and layers about followings, + * properties of surface and layers about the following: * - visibility. * - opacity. * - clipping (x,y,width,height). @@ -554,7 +554,7 @@ struct ivi_layout_interface { const int32_t number); /** - * transision animation for layer + * transition animation for layer */ void (*transition_move_layer_cancel)(struct ivi_layout_layer *layer); int32_t (*layer_set_fade_info)(struct ivi_layout_layer* ivilayer, diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index ccb78dc37..2c450f31d 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -42,8 +42,8 @@ * per each frame. See ivi-layout-transition.c in details. Transition * animation interpolates frames between previous properties of ivi_surface * and new ones. - * For example, when a property of ivi_surface is changed from invisibile - * to visibile, it behaves like fade-in. When ivi_layout_commitChange is + * For example, when a property of ivi_surface is changed from invisible + * to visible, it behaves like fade-in. When ivi_layout_commitChange is * called during transition animation, it cancels the transition and * re-start transition to new properties from current properties of final * frame just before the cancellation. diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 0b1b7fa01..e1485ca63 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -1934,7 +1934,7 @@ input_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, input->keyboard_state_update = STATE_UPDATE_NONE; } else if (format == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { - weston_log("No keymap provided; falling back to defalt\n"); + weston_log("No keymap provided; falling back to default\n"); keymap = NULL; input->keyboard_state_update = STATE_UPDATE_AUTOMATIC; } else { diff --git a/libweston/libbacklight.h b/libweston/libbacklight.h index 8717ab107..200782422 100644 --- a/libweston/libbacklight.h +++ b/libweston/libbacklight.h @@ -52,7 +52,7 @@ struct backlight { /* * Find and set up a backlight for a valid udev connector device, i.e. one - * matching drm subsytem and with status of connected. + * matching drm subsystem and with status of connected. */ struct backlight *backlight_init(struct udev_device *drm_device, uint32_t connector_type); diff --git a/libweston/libinput-seat.c b/libweston/libinput-seat.c index ac1e8e99d..6625daff5 100644 --- a/libweston/libinput-seat.c +++ b/libweston/libinput-seat.c @@ -392,7 +392,7 @@ udev_seat_output_changed(struct udev_seat *seat, struct weston_output *output) wl_list_for_each(device, &seat->devices_list, link) { /* If we find any input device without an associated output * or an output name to associate with, just tie it with the - * output we got here - the default assingment. + * output we got here - the default assignment. */ if (!device->output_name) { if (!device->output) diff --git a/libweston/pixel-formats.c b/libweston/pixel-formats.c index 875e65261..cade24226 100644 --- a/libweston/pixel-formats.c +++ b/libweston/pixel-formats.c @@ -387,7 +387,7 @@ pixel_format_get_info_shm(uint32_t format) return pixel_format_get_info(format); } -/** Retrive a pixel format information structure from a DRM FOURCC format +/** Retrieve a pixel format information structure from a DRM FOURCC format * * \param format a DRM FOURCC format */ diff --git a/libweston/vaapi-recorder.c b/libweston/vaapi-recorder.c index 1228f7d1e..7d6d8cc9c 100644 --- a/libweston/vaapi-recorder.c +++ b/libweston/vaapi-recorder.c @@ -116,7 +116,7 @@ struct vaapi_recorder { static void * worker_thread_function(void *); -/* bistream code used for writing the packed headers */ +/* bitstream code used for writing the packed headers */ #define BITSTREAM_ALLOCATE_STEPPING 4096 diff --git a/libweston/weston-debug.c b/libweston/weston-debug.c index 2776c8816..b1349bc38 100644 --- a/libweston/weston-debug.c +++ b/libweston/weston-debug.c @@ -318,7 +318,7 @@ weston_debug_compositor_destroy(struct weston_compositor *compositor) * \param compositor The libweston compositor where to enable. * * This enables the weston_debug_v1 Wayland protocol extension which any client - * can use to get debug messsages from the compositor. + * can use to get debug messages from the compositor. * * WARNING: This feature should not be used in production. If a client * provides a file descriptor that blocks writes, it will block the whole diff --git a/man/weston-debug.man b/man/weston-debug.man index b3e0a5b75..907783ec4 100644 --- a/man/weston-debug.man +++ b/man/weston-debug.man @@ -13,7 +13,7 @@ debug messages from the compositor. The debug messages are categorized into diff debug streams by the compositor (example: logs, proto, list, etc.,) and the compositor requires a file descriptor to stream the messages. -This tool accepts a file name or a file desciptor (not both) and any desired debug stream +This tool accepts a file name or a file descriptor (not both) and any desired debug stream names from the user as command line arguments and subscribes the desired streams from the compositor by using the weston_debug_v1 interface. After the subscription, the compositor will start to write the debug messages to the shared file descriptor. diff --git a/man/weston-drm.man b/man/weston-drm.man index 6f8fa0244..3cc490c05 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -93,7 +93,7 @@ in user-mode (drmModeModeInfo) flag bits 19-22. For the non-CEA modes a value of Each CEA-mode is identified by a unique, Video Identification Code (VIC). For example, VIC=4 is 1280x720@60 aspect-ratio 16:9. This mode will be different than a non-CEA mode 1280x720@60 0:0. When the video mode -1280x720@60 0:0 is applied, since its timing doesnt exactly match with the CEA +1280x720@60 0:0 is applied, since its timing doesn't exactly match with the CEA information for VIC=4, it would be treated as a non-CEA mode. Also, while setting the HDMI-AVI-Inforframe, VIC parameter will be given as '0'. If video mode 1280x720@60 16:9 is applied, its CEA timimgs matches with that of video mode with @@ -192,7 +192,7 @@ status. For example, use Use graphics and input devices designated for seat .I seatid instead of the seat defined in the environment variable -.BR XDG_SEAT ". If neither is specifed, seat0 will be assumed." +.BR XDG_SEAT ". If neither is specified, seat0 will be assumed." .TP \fB\-\-tty\fR=\fIx\fR Launch Weston on tty diff --git a/protocol/weston-debug.xml b/protocol/weston-debug.xml index effa1a190..ac62661d3 100644 --- a/protocol/weston-debug.xml +++ b/protocol/weston-debug.xml @@ -31,7 +31,7 @@ object advertized through wl_registry. WARNING: This interface by design allows a denial-of-service attack. It - should not be offered in production, or proper authorization mechnisms + should not be offered in production, or proper authorization mechanisms must be enforced. The idea is for a client to provide a file descriptor that the server @@ -39,7 +39,7 @@ descriptor in blocking writes mode, which exposes the denial-of-service risk. The blocking mode is necessary to ensure all debug messages can be easily printed in place. It also ensures message ordering if a - client subcribes to more than one debug stream. + client subscribes to more than one debug stream. The available debugging features depend on the server. diff --git a/remoting/remoting-plugin.c b/remoting/remoting-plugin.c index 3715b22bf..e99d61e15 100644 --- a/remoting/remoting-plugin.c +++ b/remoting/remoting-plugin.c @@ -354,7 +354,7 @@ remoting_gstpipe_handler(int fd, uint32_t mask, void *data) struct gstpipe_msg_data msg; struct remoted_output *output = data; - /* recieve message */ + /* receive message */ ret = read(fd, &msg, sizeof(msg)); if (ret != sizeof(msg)) { weston_log("ERROR: failed to read, ret=%zd, errno=%d\n", @@ -371,7 +371,7 @@ remoting_gstpipe_handler(int fd, uint32_t mask, void *data) remoting_output_buffer_release(output, msg.data); break; default: - weston_log("Recieved unknown message! msg=%d\n", msg.type); + weston_log("Received unknown message! msg=%d\n", msg.type); } return 1; } diff --git a/tools/zunitc/src/zuc_collector.h b/tools/zunitc/src/zuc_collector.h index d123b2785..56f8a5fc6 100644 --- a/tools/zunitc/src/zuc_collector.h +++ b/tools/zunitc/src/zuc_collector.h @@ -38,7 +38,7 @@ struct zuc_test; * current test. Otherwise events will be passed back via IPC over this * pipe with the expectation that the payload will be handled in the parent * process via zuc_process_message(). - * @return a new collector intance. + * @return a new collector instance. * @see zuc_process_message() */ struct zuc_event_listener * @@ -47,7 +47,7 @@ zuc_collector_create(int *pipe_fd); /** * Reads events from the given pipe and processes them. * - * @param test the currently active test to attache events for. + * @param test the currently active test to attach events for. * @param pipe_fd the file descriptor of the pipe to read from. * @return a positive value if a message was received, 0 if the end has * been reached and -1 if an error has occurred. diff --git a/xwayland/selection.c b/xwayland/selection.c index bc7318af9..411fceda8 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -537,7 +537,7 @@ weston_wm_send_incr_chunk(struct weston_wm *wm) } else if (length > 0) { /* Transfer is all done, but queue a flush for * the delete of the last chunk so we can set - * the 0 sized propert to signal the end of + * the 0 sized property to signal the end of * the transfer. */ wm->flush_property_on_delete = 1; wl_array_release(&wm->source_data); From 2914a6da8b73e4e49d781b8db60f9179fa08fe45 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Sat, 16 Feb 2019 16:16:10 +0000 Subject: [PATCH 0798/1642] compositor-drm: Add missing newline to debug print The 'created new mode blob' print was missing a newline, unlike all the others. Signed-off-by: Daniel Stone --- libweston/compositor-drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 46db6d6c0..393976305 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2506,7 +2506,7 @@ drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode) if (ret != 0) weston_log("failed to create mode property blob: %m\n"); - drm_debug(backend, "\t\t\t[atomic] created new mode blob %lu for %s", + drm_debug(backend, "\t\t\t[atomic] created new mode blob %lu for %s\n", (unsigned long) mode->blob_id, mode->mode_info.name); return ret; From 1c49b5445ebeb84f1767811b6400a0fe87897acf Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Fri, 15 Feb 2019 14:52:00 +0100 Subject: [PATCH 0799/1642] compositor-drm: fix gbm_bo_get_handle_for_plane error handling gbm_bo_get_handle_for_plane returns -1 on error, not 0. Signed-off-by: Philipp Zabel --- libweston/compositor-drm.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 393976305..905a0869d 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -1246,9 +1246,12 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, fb->num_planes = dmabuf->attributes.n_planes; for (i = 0; i < dmabuf->attributes.n_planes; i++) { - fb->handles[i] = gbm_bo_get_handle_for_plane(fb->bo, i).u32; - if (!fb->handles[i]) + union gbm_bo_handle handle; + + handle = gbm_bo_get_handle_for_plane(fb->bo, i); + if (handle.s32 == -1) goto err_free; + fb->handles[i] = handle.u32; } if (drm_fb_addfb(backend, fb) != 0) From 10a733961108f1495894e6143d5281d40ef84ff3 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Tue, 5 Mar 2019 12:51:14 +0200 Subject: [PATCH 0800/1642] clients/simple-dmabuf-egl: Create the EGL display using the GBM platform Since we are managing and rendering to buffers on our own with GBM, create the EGL display using the GBM platform with the DRM render node, instead of using the Wayland EGL platform. Signed-off-by: Alexandros Frantzis --- clients/simple-dmabuf-egl.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c index 5353f5a0f..142d73e73 100644 --- a/clients/simple-dmabuf-egl.c +++ b/clients/simple-dmabuf-egl.c @@ -1105,8 +1105,8 @@ display_set_up_egl(struct display *display) const char *gl_extensions = NULL; display->egl.display = - weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, - display->display, NULL); + weston_platform_get_egl_display(EGL_PLATFORM_GBM_KHR, + display->gbm.device, NULL); if (display->egl.display == EGL_NO_DISPLAY) { fprintf(stderr, "Failed to create EGLDisplay\n"); goto error; @@ -1340,13 +1340,15 @@ create_display(char const *drm_render_node, int opts) goto error; } - if (!display_set_up_egl(display)) + /* GBM needs to be initialized before EGL, so that we have a valid + * render node gbm_device to create the EGL display from. */ + if (!display_set_up_gbm(display, drm_render_node)) goto error; - if (!display_update_supported_modifiers_for_egl(display)) + if (!display_set_up_egl(display)) goto error; - if (!display_set_up_gbm(display, drm_render_node)) + if (!display_update_supported_modifiers_for_egl(display)) goto error; /* We use explicit synchronization only if the user hasn't disabled it, From 7825f141ad7bdc0a8d875b45a7928bae29326182 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 5 Mar 2019 17:21:49 -0600 Subject: [PATCH 0801/1642] configure.ac: bump to version 5.0.92 for the beta release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 06348bf98..a330a51d5 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [5]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [91]) +m4_define([weston_micro_version], [92]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [6]) From 2293cb42384e0297e53fb876697342528926a710 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Tue, 5 Mar 2019 17:28:08 +0100 Subject: [PATCH 0802/1642] meson: allow to build weston-simple-dmabuf-egl It is all hooked up in clients/meson.build, just the option to enable it was missing. Signed-off-by: Philipp Zabel --- meson_options.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson_options.txt b/meson_options.txt index 31362ba4d..0e1d18338 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -180,7 +180,7 @@ option( option( 'simple-clients', type: 'array', - choices: [ 'all', 'damage', 'im', 'egl', 'shm', 'touch', 'dmabuf-v4l' ], + choices: [ 'all', 'damage', 'im', 'egl', 'shm', 'touch', 'dmabuf-v4l', 'dmabuf-egl' ], value: [ 'all' ], description: 'Sample clients: simple test programs' ) From 5c8eef147c27a95ebb8ba79e19ebb190b025cbe0 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Wed, 6 Mar 2019 11:12:47 +0100 Subject: [PATCH 0803/1642] compositor-drm: clear gbm_surface pointer after destroying the GBM surface Since commit ee1d968e641e ("compositor-drm: Fall back if GBM surface fails with modifiers"), drm_output_init_egl requires output->gbm_surface to be NULL, or gbm_surface_create will not be called if HAVE_GBM_MODIFIERS is enabled but no modifiers are supported by the plane. This could happen if _init_egl is called after drm_ouptut_fini_egl drom drm_output_switch_mode. Add an assert to guarantee the requirement and clears the gbm_surface pointer after the surface is destroyed. Signed-off-by: Philipp Zabel --- libweston/compositor-drm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 905a0869d..c1101105d 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -5045,6 +5045,8 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) struct drm_plane *plane = output->scanout_plane; unsigned int i; + assert(output->gbm_surface == NULL); + for (i = 0; i < plane->count_formats; i++) { if (plane->formats[i].format == output->gbm_format) break; @@ -5094,6 +5096,7 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) n_formats) < 0) { weston_log("failed to create gl renderer output state\n"); gbm_surface_destroy(output->gbm_surface); + output->gbm_surface = NULL; return -1; } @@ -5120,6 +5123,7 @@ drm_output_fini_egl(struct drm_output *output) gl_renderer->output_destroy(&output->base); gbm_surface_destroy(output->gbm_surface); + output->gbm_surface = NULL; drm_output_fini_cursor_egl(output); } From 6f897960cb12bc161430f546ccdb51eb505b09f6 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 13 Mar 2019 21:27:12 -0500 Subject: [PATCH 0804/1642] configure.ac: bump version to 5.0.93 for the RC1 release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index a330a51d5..19a85713f 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [5]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [92]) +m4_define([weston_micro_version], [93]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [6]) From 483c6d4d06c16807042111043d62a58af0457076 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 20 Mar 2019 00:09:51 +0100 Subject: [PATCH 0805/1642] meson: fix building screen-share module When building screen-share module with meson loading the module fails with: [00:01:28.604] Failed to load module: /usr/local/lib/weston/screen-share.so: undefined symbol: os_create_anonymous_file Failed to process Wayland connection: Broken pipe failed to create display: Broken pipe The function os_create_anonymous_file is defined in libshared, adding libshared to the dependency makes sure the function gets compiled into screen-share.so. Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/208 Signed-off-by: Stefan Agner --- compositor/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/compositor/meson.build b/compositor/meson.build index 420a96050..d5d7282f7 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -61,6 +61,7 @@ if get_option('screenshare') fullscreen_shell_unstable_v1_protocol_c, ] deps_screenshare = [ + dep_libshared, dep_libweston, dep_wayland_client, ] From cc64cc3717ab39882df37bc244d1ac6cf8526f93 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 20 Mar 2019 19:49:58 -0500 Subject: [PATCH 0806/1642] configure.ac/meson.build: bump to version 5.0.94 for the RC2 release --- configure.ac | 2 +- meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 19a85713f..1175285a7 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ m4_define([weston_major_version], [5]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [93]) +m4_define([weston_micro_version], [94]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [6]) diff --git a/meson.build b/meson.build index 356ae1de8..014183a85 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('weston', 'c', - version: '5.0.90', + version: '5.0.94', default_options: [ 'warning_level=2', 'c_std=gnu99', From 825077d5916ab4cfa5716dcc6b9658495ec8a295 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Wed, 27 Mar 2019 11:15:53 +0200 Subject: [PATCH 0807/1642] autotools: Fix tags/cscope targets with autools This was introduced with a95bb6f7e554c1a7 (clients: Support explicit synchronization) Signed-off-by: Marius Vlad --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index b2bb61f62..5407b593e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -703,7 +703,7 @@ nodist_weston_simple_dmabuf_egl_SOURCES = \ protocol/linux-dmabuf-unstable-v1-protocol.c \ protocol/linux-dmabuf-unstable-v1-client-protocol.h \ protocol/linux-explicit-synchronization-unstable-v1-protocol.c \ - protocol/linux-explicit-synchronization-v1-client-protocol.h + protocol/linux-explicit-synchronization-unstable-v1-client-protocol.h weston_simple_dmabuf_egl_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_EGL_CLIENT_CFLAGS) weston_simple_dmabuf_egl_LDADD = $(SIMPLE_DMABUF_EGL_CLIENT_LIBS) libshared.la -lm endif From ea5ea00d5884cda5d8d16374136a2889d412b828 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 27 Mar 2019 20:11:52 -0500 Subject: [PATCH 0808/1642] configure.ac/meson.build: bump version to 6.0.0 for the official release --- configure.ac | 4 ++-- meson.build | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 1175285a7..c05ad0116 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ -m4_define([weston_major_version], [5]) +m4_define([weston_major_version], [6]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [94]) +m4_define([weston_micro_version], [0]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [6]) diff --git a/meson.build b/meson.build index 014183a85..2155b7b14 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('weston', 'c', - version: '5.0.94', + version: '6.0.0', default_options: [ 'warning_level=2', 'c_std=gnu99', From 9912d8295db70d4daa932e398d671d19a163b31a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 28 Mar 2019 10:50:11 +0200 Subject: [PATCH 0809/1642] build: reopen master for regular development Signed-off-by: Pekka Paalanen --- configure.ac | 4 ++-- meson.build | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index c05ad0116..5d50c3855 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,11 @@ m4_define([weston_major_version], [6]) m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [0]) +m4_define([weston_micro_version], [90]) m4_define([weston_version], [weston_major_version.weston_minor_version.weston_micro_version]) m4_define([libweston_major_version], [6]) m4_define([libweston_minor_version], [0]) -m4_define([libweston_patch_version], [0]) +m4_define([libweston_patch_version], [90]) AC_PREREQ([2.64]) AC_INIT([weston], diff --git a/meson.build b/meson.build index 2155b7b14..aae962614 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('weston', 'c', - version: '6.0.0', + version: '6.0.90', default_options: [ 'warning_level=2', 'c_std=gnu99', From 8a4585c27f5fa88511dbfafe4ae7403df3f83edc Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 22 Mar 2019 12:26:19 +0000 Subject: [PATCH 0810/1642] compositor: Don't ignore --use-pixman for Wayland backend We loaded the use-pixman configuration value from both the command line and the configuration file, but completely ignored the former. Make sure we actually use both. Tested with all permutations of config/command line. Signed-off-by: Daniel Stone Reviewed-by: Pekka Paalanen Tested-by: Emmanuel Gil Peyrot --- compositor/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compositor/main.c b/compositor/main.c index c32067831..57e3de89f 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2758,7 +2758,7 @@ load_wayland_backend(struct weston_compositor *c, parse_options(wayland_options, ARRAY_LENGTH(wayland_options), argc, argv); config.sprawl = sprawl_; config.fullscreen = fullscreen_; - config.use_pixman = use_pixman_config_; + config.use_pixman = use_pixman_; section = weston_config_get_section(wc, "shell", NULL, NULL); weston_config_section_get_string(section, "cursor-theme", From cbffca980b19fd6a6de14d9f2b4dca6a7711d9e0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 22 Mar 2019 13:58:30 +0200 Subject: [PATCH 0811/1642] meson: link editor with gobject-2.0 editor.c calls g_clear_object(), so it should link to gobject directly instead of relying on pangocairo pulling it in in its pkg-config. Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/211 Signed-off-by: Pekka Paalanen --- clients/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/meson.build b/clients/meson.build index 47e9e8ce3..53a94212f 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -236,7 +236,7 @@ demo_clients = [ text_input_unstable_v1_client_protocol_h, text_input_unstable_v1_protocol_c, ], - 'deps': [ 'pangocairo' ] + 'deps': [ 'pangocairo', 'gobject-2.0' ] }, { 'basename': 'eventdemo' }, { 'basename': 'flower' }, From 191c453f831d3c955cec9beb8d9f1fc8fafc0e59 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 22 Mar 2019 14:20:51 +0200 Subject: [PATCH 0812/1642] meson: link cms-colord with glib and gobject cms-colord.c calls things like g_string_free() and g_object_unref(), so it needs to link glib-2.0 and gobject-2.0 explicitly, instead of relying on colord pkg-config bringing them in. Signed-off-by: Pekka Paalanen --- compositor/meson.build | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/compositor/meson.build b/compositor/meson.build index d5d7282f7..3824d6ffb 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -117,11 +117,21 @@ if get_option('color-management-colord') error('cms-colord requires colord >= 0.1.27 which was not found. Or, you can use \'-Dcolor-management-colord=false\'.') endif + plugin_colord_deps = [ dep_libweston, dep_colord ] + + foreach depname : [ 'glib-2.0', 'gobject-2.0' ] + dep = dependency(depname, required: false) + if not dep.found() + error('cms-colord requires \'@0@\' which was not found. If you rather not build this, set \'-Dcolor-management-colord=false\'.'.format(depname)) + endif + plugin_colord_deps += dep + endforeach + plugin_colord = shared_library( 'cms-colord', srcs_colord, include_directories: include_directories('..', '../shared'), - dependencies: [ dep_libweston, dep_colord ], + dependencies: plugin_colord_deps, name_prefix: '', install: true, install_dir: dir_module_weston From 292aaf930832f152efabf0e6314d17dbbe70271d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 22 Mar 2019 14:37:39 +0200 Subject: [PATCH 0813/1642] meson: link remoting with glib and gobject remoting-plugin.c calls things like g_error_free() and g_object_set(), so it needs to link glib-2.0 and gobject-2.0 explicitly, instead of relying on GStreamer pkg-config bringing them in. Signed-off-by: Pekka Paalanen --- remoting/meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/remoting/meson.build b/remoting/meson.build index 224e8cd96..703ecdbb0 100644 --- a/remoting/meson.build +++ b/remoting/meson.build @@ -7,7 +7,8 @@ if get_option('remoting') depnames = [ 'gstreamer-1.0', 'gstreamer-allocators-1.0', - 'gstreamer-app-1.0', 'gstreamer-video-1.0' + 'gstreamer-app-1.0', 'gstreamer-video-1.0', + 'gobject-2.0', 'glib-2.0' ] deps_remoting = [ dep_libweston ] foreach depname : depnames From fe6dd7bcefa6a073f28bae778059a915328e3849 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 22 Mar 2019 17:05:11 +0200 Subject: [PATCH 0814/1642] meson: DRM-backend demands GBM All the GBM code is unconditional in compositor-drm.c, so while disabling the GL-renderer would stop GBM from being used, GBM headers would still be needed for building and GBM library for linking. Leave a note to fix it properly later. At least we now check for GBM and do not mislead with the error message. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libweston/meson.build b/libweston/meson.build index 33ab970e2..5d7bfa275 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -189,10 +189,11 @@ if get_option('backend-drm') dependency('libudev', version: '>= 136'), ] - if get_option('renderer-gl') + # XXX: Actually let DRM-backend build without GBM, it really should + if true # get_option('renderer-gl') dep_gbm = dependency('gbm', required: false) if not dep_gbm.found() - error('drm-backend + gl-renderer requires gbm which was not found. Or, you can use \'-Dbackend-drm=false\' or \'-Drenderer-gl=false\'.') + error('drm-backend requires gbm which was not found. Or, you can use \'-Dbackend-drm=false\'.') endif if dep_gbm.version().version_compare('>= 17.1') config_h.set('HAVE_GBM_MODIFIERS', '1') From ff98a9080fcc1e8689bb5996dd03fc4f1c313983 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 13 Mar 2019 12:16:29 +0200 Subject: [PATCH 0815/1642] clients/simple-dmabuf-egl: Properly check for error in gbm_bo_get_handle_for_plane gbm_bo_get_handle_for_plane returns handle.s32 == -1 on error, at least for the Mesa dri implementation. Reported-by: Marius Vlad Signed-off-by: Alexandros Frantzis --- clients/simple-dmabuf-egl.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c index 142d73e73..75d880e08 100644 --- a/clients/simple-dmabuf-egl.c +++ b/clients/simple-dmabuf-egl.c @@ -369,9 +369,17 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, #ifdef HAVE_GBM_MODIFIERS buffer->plane_count = gbm_bo_get_plane_count(buffer->bo); for (i = 0; i < buffer->plane_count; ++i) { - uint32_t handle = gbm_bo_get_handle_for_plane(buffer->bo, i).u32; - int ret = drmPrimeHandleToFD(display->gbm.drm_fd, handle, 0, - &buffer->dmabuf_fds[i]); + int ret; + union gbm_bo_handle handle; + + handle = gbm_bo_get_handle_for_plane(buffer->bo, i); + if (handle.s32 == -1) { + fprintf(stderr, "error: failed to get gbm_bo_handle\n"); + goto error; + } + + ret = drmPrimeHandleToFD(display->gbm.drm_fd, handle.u32, 0, + &buffer->dmabuf_fds[i]); if (ret < 0 || buffer->dmabuf_fds[i] < 0) { fprintf(stderr, "error: failed to get dmabuf_fd\n"); goto error; From f6d276062765d19807b057519e5055f905d3b748 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 18 Mar 2019 15:51:07 +0100 Subject: [PATCH 0816/1642] CI: containerize the CI Reuse the templates from wayland/ci-templates: whenever DEBIAN_TAG is changed, this will rebuild a new container. This adds two things: - better reliability (we do not randomly pull packages whenever the CI runs and we can reproduce with this particular environment) - faster builds, as we do not need to pull the universe at each run Signed-off-by: Benjamin Tissoires [Pekka: bump DEBIAN_TAG] Signed-off-by: Pekka Paalanen --- .gitlab-ci.yml | 64 ++++++++++++++++++------------------ .gitlab-ci/debian-install.sh | 12 +++++++ 2 files changed, 44 insertions(+), 32 deletions(-) create mode 100644 .gitlab-ci/debian-install.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cf882797d..129e99060 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,24 +1,34 @@ -image: debian:stretch +# vim: set expandtab shiftwidth=2 tabstop=8 textwidth=0: + +variables: + UPSTREAM_REPO: wayland/weston + DEBIAN_VERSION: stretch + DEBIAN_EXEC: 'bash .gitlab-ci/debian-install.sh' + + DEBIAN_TAG: '2019-03-28.0' + DEBIAN_CONTAINER_IMAGE: $CI_REGISTRY_IMAGE/debian/$DEBIAN_VERSION:$DEBIAN_TAG + + +include: + - project: 'wayland/ci-templates' + ref: c73dae8b84697ef18e2dbbf4fed7386d9652b0cd + file: '/templates/debian.yml' + stages: + - container_prep - build -before_script: - - echo 'path-exclude=/usr/share/doc/*' > /etc/dpkg/dpkg.cfg.d/99-exclude-cruft - - echo 'path-exclude=/usr/share/man/*' >> /etc/dpkg/dpkg.cfg.d/99-exclude-cruft - - echo '#!/bin/sh' > /usr/sbin/policy-rc.d - - echo 'exit 101' >> /usr/sbin/policy-rc.d - - chmod +x /usr/sbin/policy-rc.d - - echo 'deb http://deb.debian.org/debian stretch-backports main' >> /etc/apt/sources.list - - apt-get update - - apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libevdev-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev freerdp2-dev curl python3-pip python3-setuptools ninja-build - - pip3 install --user git+https://github.com/mesonbuild/meson.git@0.49 - - mkdir -p /tmp/.X11-unix - - chmod 777 /tmp/.X11-unix -build-native-autotools: +container_prep: + extends: .debian@container-ifnot-exists + stage: container_prep + + +.build-native: stage: build - script: + image: $DEBIAN_CONTAINER_IMAGE + before_script: - git clone --depth=1 git://anongit.freedesktop.org/git/wayland/wayland-protocols - export WAYLAND_PROTOCOLS_DIR="$(pwd)/prefix-wayland-protocols" - export PKG_CONFIG_PATH="$WAYLAND_PROTOCOLS_DIR/share/pkgconfig:$PKG_CONFIG_PATH" @@ -35,6 +45,11 @@ build-native-autotools: - export PREFIX="$(pwd)/prefix-$BUILD_ID" - export BUILDDIR="$(pwd)/build-$BUILD_ID" - mkdir "$BUILDDIR" "$PREFIX" + + +build-native-autotools: + extends: .build-native + script: - cd "$BUILDDIR" - ../autogen.sh --prefix="$PREFIX" --disable-setuid-install --enable-xwayland --enable-x11-compositor --enable-drm-compositor --enable-wayland-compositor --enable-headless-compositor --enable-fbdev-compositor --enable-rdp-compositor --enable-screen-sharing --enable-vaapi-recorder --enable-simple-clients --enable-simple-egl-clients --enable-simple-dmabuf-drm-client --enable-simple-dmabuf-v4l-client --enable-clients --enable-resize-optimization --enable-weston-launch --enable-fullscreen-shell --enable-colord --enable-dbus --enable-systemd-login --enable-junit-xml --enable-ivi-shell --enable-wcap-tools --disable-libunwind --enable-demo-clients-install --enable-lcms --with-cairo=image --enable-remoting --enable-autotools - make all @@ -50,26 +65,11 @@ build-native-autotools: - build-*/logs - prefix-* + build-native-meson: - stage: build + extends: .build-native script: - - git clone --depth=1 git://anongit.freedesktop.org/git/wayland/wayland-protocols - - export WAYLAND_PROTOCOLS_DIR="$(pwd)/prefix-wayland-protocols" - - export PKG_CONFIG_PATH="$WAYLAND_PROTOCOLS_DIR/share/pkgconfig:$PKG_CONFIG_PATH" - - export MAKEFLAGS="-j4" - - cd wayland-protocols - - git show -s HEAD - - mkdir build - - cd build - - ../autogen.sh --prefix="$WAYLAND_PROTOCOLS_DIR" - - make install - - cd ../../ - - export XDG_RUNTIME_DIR="$(mktemp -p $(pwd) -d xdg-runtime-XXXXXX)" - - export BUILD_ID="weston-$CI_JOB_NAME_$CI_COMMIT_SHA-$CI_JOB_ID" - - export PREFIX="$(pwd)/prefix-$BUILD_ID" - - export BUILDDIR="$(pwd)/build-$BUILD_ID" - export PATH=~/.local/bin:$PATH - - mkdir "$BUILDDIR" "$PREFIX" - cd "$BUILDDIR" - meson --prefix="$PREFIX" -Dsimple-dmabuf-drm=intel .. - ninja -k0 diff --git a/.gitlab-ci/debian-install.sh b/.gitlab-ci/debian-install.sh new file mode 100644 index 000000000..758c15dd2 --- /dev/null +++ b/.gitlab-ci/debian-install.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -o xtrace + +echo 'deb http://deb.debian.org/debian stretch-backports main' >> /etc/apt/sources.list +apt-get update +apt-get -y --no-install-recommends install build-essential automake autoconf libtool pkg-config libexpat1-dev libffi-dev libxml2-dev libpixman-1-dev libpng-dev libjpeg-dev libcolord-dev mesa-common-dev libglu1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libwayland-dev libxcb1-dev libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xkb-dev libx11-xcb-dev libx11-dev libudev-dev libgbm-dev libxkbcommon-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libxcursor-dev libmtdev-dev libpam0g-dev libvpx-dev libsystemd-dev libevdev-dev libinput-dev libwebp-dev libjpeg-dev libva-dev liblcms2-dev git libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev freerdp2-dev curl python3-pip python3-setuptools ninja-build + +pip3 install --user git+https://github.com/mesonbuild/meson.git@0.49 + +mkdir -p /tmp/.X11-unix +chmod 777 /tmp/.X11-unix From 57875cd0189823af49ff844eb24dca19282c71fd Mon Sep 17 00:00:00 2001 From: Riku Viitanen Date: Thu, 28 Mar 2019 10:09:52 +0000 Subject: [PATCH 0817/1642] Optimize PNGs with zopflipng --- data/background.png | Bin 245579 -> 135501 bytes data/border.png | Bin 1969 -> 1585 bytes data/fullscreen.png | Bin 3406 -> 3152 bytes data/home.png | Bin 4629 -> 4234 bytes data/icon_editor.png | Bin 915 -> 737 bytes data/icon_flower.png | Bin 1408 -> 1160 bytes data/icon_ivi_clickdot.png | Bin 39523 -> 30955 bytes data/icon_ivi_flower.png | Bin 24475 -> 20214 bytes data/icon_ivi_simple-egl.png | Bin 29316 -> 8688 bytes data/icon_ivi_simple-shm.png | Bin 71120 -> 39650 bytes data/icon_ivi_smoke.png | Bin 46577 -> 24569 bytes data/icon_terminal.png | Bin 653 -> 525 bytes data/icon_window.png | Bin 161 -> 86 bytes data/panel.png | Bin 41955 -> 27761 bytes data/pattern.png | Bin 1846 -> 825 bytes data/random.png | Bin 4891 -> 4553 bytes data/sidebyside.png | Bin 3929 -> 3630 bytes data/sign_close.png | Bin 235 -> 104 bytes data/sign_maximize.png | Bin 204 -> 85 bytes data/sign_minimize.png | Bin 191 -> 79 bytes data/terminal.png | Bin 1005 -> 765 bytes data/tiling.png | Bin 5620 -> 5148 bytes data/wayland.png | Bin 6023 -> 4596 bytes .../reference/internal-screenshot-bad-00.png | Bin 5149 -> 4204 bytes .../reference/internal-screenshot-good-00.png | Bin 970 -> 733 bytes tests/reference/subsurface_z_order-00.png | Bin 825 -> 122 bytes tests/reference/subsurface_z_order-01.png | Bin 858 -> 154 bytes tests/reference/subsurface_z_order-02.png | Bin 889 -> 166 bytes tests/reference/subsurface_z_order-03.png | Bin 903 -> 233 bytes tests/reference/subsurface_z_order-04.png | Bin 902 -> 237 bytes 30 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/background.png b/data/background.png index 60c317c945ae3a8c9f0875cc59a80fd248692fac..67caee38c48b2f9ea146d1eeb37f3bee0d80052d 100644 GIT binary patch literal 135501 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%0D%gKDBS-i7v#{{g5F`DU!T*zY(Xrk0 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

YKLj$Mur0@2vW#2 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

OW^!jaQ{x>?5yYBZ9& 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�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

jvuH z%X(^!J9ie&fXLNH+N3%uz$Lr`s*{S{uBCrh45j;JK$^N;X~)6dKw#h5xW2S zK+x#E1pPCEa{$-idcfBoD7$5Slicd-H2>yauD2mJ8iSdR$N@bjI_BqHYmX>Bj`*4} zcZwF9MvvPw#5a*ZkM}N9D&0L#bsLP&xvK zf@g}ko_|~?jRADi2OgmB-%m(|^7C zxb)-}Pv+kzk$HcC*R!er&DA@9{_CrENOTwy+$+%7zd?(5ofh_5EtJOo<=Vtcw4oPk zV=vHVpC_?=mJIPs8RYL2uuqlIo-6}CLB@Q%jQzKA2LFX1|E(ZK?SXNc@QS>i_Tcy= z$V@tW(sQ!v$?WXwZQ$(h+&cQP{d%WKabJ@#im6(A5LSB`B&~gpz?KbmG6oT2dN)Z3 z0SEaS@nTX%!$l2R1RQFu>$Sj{HI@k0ek zt-l^1={2~)4OG($D82&qw64R)0M6;xh+#oTt-;6!qhaj7nvI7D&W-ETlXtE?_FwB- z_S@Fp*U9<#1^*Dp1bPf(Cl>2r?h^N;kqh^vN3O-P{^~W@TCZ?y>Tl}aUMJ90a9_C3dU~zEUa*4) z2*gq7o9u`^dz0+CfrcC&vzj$;mJi1!_99X{slvxy$B ze+Zo2V_EOv_X2y>&wcM5qXq(HuWrZCl-BTfOkd!a=A#-+@45pR=?(v27fZ2B#edh149`Wh#Ts`7b-?{pWPksC9&ja7O`m;}d>*`NG@vW=>{)ul60xU$*xjlI~ z2`vq}uATT!A>aRNz5Z+6^N4Sc*N@q-9@j9g-R{xR*0U!{uYV-SzCU3EhwRn~9zaTehB>Hx4Nc^b&{SFpTPa1`3U4X z23XgfqMzSD(9Qd-PaLBWV$X)xs3LN{PH-IO#JcO+*bh50oWZvD2{buxk{LL z9y2=}R~?SIGxXRT*yDDK;qhL_{(e998uYbzz23{R@69$NL6%)Z7o6Oz2k3xc9S98| z_66dem?z#T+~5MSO>sw}A;A#8aEf?^Z&74!yo9T8(tLCgnLMmvlX!?ez(We&;Nd7D zlZ$|-W+cGiB||79igUP{pT%*dM?;f@kt}tOOqQuhIjG>zlcYs4sK}UM*uc8G?w}hU zwDFUsjbfV5*=ICvIzRTOYkH7IVayuPNf*P8sbjOh4YdV^xVO2#He8JDez{p+F+u)60J>T;dyosTaa?_{P{FAeIkI&F~ILCGPW*jks zPQz8!!`g^}@eKg&gJH(tPP4~oCW{&o&TgD>BQ|FeOAwQNnyM)Mt7(Hiej!yE^RiOR8o{G4MGc`o2O zTn`P3JnUR7+zoW^&_hG(xel(k;H%ecaD5PKul08hqkHhp9FE#6IGnm}4o@B@m-+c2 z4-bSqriKUNG1pi%KZl^!Mb^pMi8pKgs5|5r{6izO4h!lDdg!LOf2lD?11`XvxH;fa z0ZtXSx=vpLTr?QS89X%(l%{2@u6p;B1+#VvZ7i*{ZaTJIPYvkzCj!0BxIe)mM`WB$ zz3!j)_xk$}6_>81-&w<@_3Eehw4>)kY_LDw2e#k9$T^_f+DM-)=)M#Bwr+G>-?>HyMM}HOhDQlV$*NRK>5^?%L^*(2Pl>4Rl zS)bATN4%E~eBu|szPfdn77z`-7WI6I=ySB#XKLY3m&iUh_{3`RA15O|R)+p- zIf5pCY4CxX<+BF=Pt@T5$;T6a{PAxD{@cgCarH;SfBk6U{}TR7VI&t4(5Jy{#z({c zsg7?CJo*HWDqQ;P?0qn=>wD;DUxUyG1&85)_ffbuQ7T|M93VYN2!=uaM(mx;(YOhn zz(L+2hJwht6j+FIBQ6*dtVUiJ=q}7ViAi!KzI|9=BEO9UrDS3vQ((P6$e76p5(jD9 z5C8&0-j4`0{2x@3BAIb6fk3gw_1q+r2%V_7FG)L+HAzeB8HpOiT4%z3t$%7CAd)!f zuM3DuT(ax9Cx&aoISRbe-ZyFhZ!Phv=vp*a-uO?OC` zsX0Aamc~#Z6K~oR@j;Bc;qQ9Fe%MzG3QIfqoqdxy+2=dPKJy7J*Cfu}q{TiJOROb| zSI1B3X2gw(fMd*5+=S1jMiVdA?q1wEc1HXdBaZF>w>z({J?^RZZuhhWj6$r|pS+yh zOnw&PZ?#J|K-ncZJ9E)eKnd72cp)2`3!vv9{DH;|PP9gE424R6vxq`lGatOQHAv(N zV!_4KX!Ii&C4a~@=Fd`G@hy?cO*H$X)7Ptxbe-Ha@Jzb`u2s%zm;Lt*zV@zlr~2l`-y9m8Nv^Qp$&cg( zoQMY)#RWgKK;vRCSh~=CpV}~e9|IowBWeU7|KKBr>(mb&ychKZ@bTgd*WgJom`2Wk?_Eo^9@ZCo)gEYw%>Bq2 z@~?A79|oYa52e|s&J8F>PE-xN!J2eaSnE5iZ~gZLeP}SyG_s>=E$933bNQUO@z1Hj zD~Ka+!zLa}cl&0mxy*NycXm`K(_fXDK#a-?(-oF3COlz_cEex>u?g z9eq;hn&MPi#`WAEy<2gAE1&THJC*Q@z9 z)&|{;{iAfhM0>6KKWvQacFgk7V&(@GYw%ZeFPoU>Xw35Dj;D881 zHxduh%4Zg!PFR!F%|D{si>`fQg>XYuC)-FUBC8AW*GurT$<2g&ugDdRK82Qu3-Kt5 z-c6AuLPFpVh@2mc$#Xu%|8P8on+-k#0g5q>=iEVFU6X5b4=3R$up;SH;?cTj zzh8^S{qgz~Ys{Qtk1hYc#hc$dH122U=bd1zul8emM3UNUIg(HL0DokXaZ#M{8U?X@ z?P3^yz#n4qt(7>4JL1j5wU5!T=ru9MXZI_%+z7uxvBdds4PL`97j6`wz1d&*MA5bv zZUra9L>75_lV`m-6!uyNMJt0aCq#5$Cd1SaH~YM1ue~@$Gtdh+`6OCmh`qgpy~%#w z7kgrTjtvjS@WFV}Q-wmJaa~yWd=oyJH)kBW5wG|bK)=_9T8p`M310tc@x&e*H;qvw zjhvGl-8$Q>>4*Msuk3yCI6^Py00Y zumv&VhR1qr4c@uebrv69%Q}Kz?&BII)5E0mtC4-r#!>pS48}-y;|6zT|pp2{gpHH}kHW+v2qL zKm&kpGw)7a^nQ1oHLJFfF+SVk%9y%KjHRv}Vod9GZ5P90?TMzHy<$sj5X05d+xJ!; zf%A@&o=f0A$8kJ5jb8hkVZ{rL0 zp`VMLSL|!~-;K7mll2?cWn^zcfBQ3?6N!p2NLPEqy4kF@`U`0TR;X%MCytBD~ z6O;5RtY|2rpc0|pBLcqnU#g%I(RM@w5BZ`PS8{M84+)hd?i1f!B=J<7gzE<6o~`+% zv1VOv;92K{8eoBB2G~b{hImJP#&Miua1CFNB*lHWUk(m3>9dYwL!Si#>jrO(q9LF1 zzghc=VnntBG$8>c$3VRw$28a$fJ8jM1u$*%n#C@&7zta{9`9;NHjB(-^X4Qw6A8#f zV$OgySro=FpfHlaWyJK^g95+q??7bu2=5%LZvIIKA6Mh@ z2{knzRgxfSyic*o=ltWZ_xvJ#e%Nnp<@V+?j zLJ51j2pU{%{K2o;Y?tq2HnBHTWIy`yH3wFT}OBW9lBhr}IRIbH>OU zz7JdtZ&&6rSVcHB0C@m;H9||JriggA)Q|#l9)TQJK-&`_JtjlO|3|HaFKV@Nr zE(TZ7P4R_X0td(~aN2`sil6uX{MX`2*pVWte@Z$ zH2?Z6ntxE|f9RKLfOI{Ypcq)|e(#E6E1MyB?1A-YDA923C7zI$c1L_wY?-UpYV3r) z&;i@eHl5mBcDEmj$AizMpF<zjS@T>E6pofP~>zihd({Il4o zVdO(2@}+G%-#U^Sa!;hOL{O*<;Ts*azDYYs48GhR<-Dn(l+*yz>e%!E54c z>hU@N<66YAxsH8_b=M`6=hgTRyQXgJHHpFAcG<`*k?G<{7+(96U_UhAjd1EdH_w zh8U@DW#f+Gk-@;o`4XOYz&{T~@sIIh{qE0vta+MToqS!O`;Tv26vF1L8@mgAZ^qeB z!8Ww5vo`D;6fWq|L~4dPZY>$G)@&G>L~iP@2hJLO-ZS?1+wcAQg}Cpwwph3Ju*PEz z2pofDht9{~lhEzULpeC(cr+jIGqEz`G6w|Id!t9IJQ#JKVwCu!-y#-?)k{6vCl$lD z-l!880K8QP-^V}rr$jdJ^WR+E_p@Ke=N$Js-QYF4*(+42y+i^1A}xmDz_Y*kqpN3X zu}}TlKVLmr1bTvmkZ%k;RtDi41CN$bbBGx36KA{rzM(dTa`OYozKkYGmvE`Pr>#9rhPQ7F18miUSI zyMb_1AI-eZizcE(pX;UYGyP5RC&6ZeT1h|zw{Y#EMKAyw9}8AaiEC0Ezvp`-NUTSkpQjxikA>qfx|2_(SxY@aI?P zZX@(V-1=*YA1Dz_baA{MmkOJkz(RZ(Q>?FlZ^hZYE+MDnX@>z4VzZs&&{&=($Oln8 z!0E=}h*S6~zh?8CJavH{$oH}+&*NjQ6;qM8YH;RQ;=a+mX)Po!l9n|mnzLHBb?1Sf z<(i2V&gWWOm%^Sk&^%HY1=nTAXr5%>r;F3rKlwYkNiug)F&h2SRy6k5*k?2E!L2p= zF`v%Cu5-}LL!k1|8Acu`#QW#x_G@=6gJssStZT0I`0v>m?5oiB+y@JLuCc9LG-1Oi zjw-Igz8-MN=1T#zaGtMAn(wJsQSc6ZW>{3ZEI;QEVCr!U0mKEgIC+#@qFHef#4sA} zyr1GnJc3I!?=c7vw~T1+1;_mTYv}rR2A}8LP``H#G<@IWgqy6n zKERrz!J<`=Ec_P4!lUu=+es)jS_!XJrm^+2eGvuL#k0mKw>1ow#r_z&md zI(da$1gFo205MEf9cHfL4+aw5?qTn;dAE1i+}sL&^*v5REwa6LJ-F6HzhCF7x!`pI z8o0CEjXit)Icv|X(=wmW`g0j%Rn8oc22l4!?Pv7QfzIJP!`H&@GjGp){jz{`L|{GP z7x|tw_CbRO3}-d^s3lt*oyE{~!<^H28hWGGi=R5bk8qrg`mV)?(c`V-EY2>X4-5M1 zLR_=mXBnK=!>_*XeSml5UD^@5qEq|8!2H68$*VcY%RVN*DMh}dcKU!~^w#rr&|MR!dW}AOof7Gr^x?fqR&Q7Gx?e&4E+w=Fl-T&maNsTajAJ;Kb2#^@@bqUk1LhA>Vt1embhMt5DJULT7(?MU>E!|__%&92_r?Ou3tiw!h}HQI$}@| zXoCTSS?fj&Lm)#mrw}8&*ynZHFMH;{xO^i|>}7)-2vCS-F-oGuK7iMN_m}2AFdKZX zJBz+!lb=s2rhr?tZ#PV_NIzS9#|F;=*uVK;@r6V3V22E}r zh|hP?RH(tB=z6bW)eVlp?-XkMH?71i=k$gjF2ciTID&u4Jr@^~d)0Uh4o)6Uw9hJb z`u?~k*Jgk04eq~L_h%j6*E{*Tw)WG97jCZjo%d6JeieIQ{clkGa80hyJt?RtVp4n3 zPxuaQTU28lO(LVQs)QbcAf zW&A|3thjN^xHt|TRh%S`ti3gm4KB?%c^Cf(>=q~R=>7ZyvJodVF3GQG-VJdMkoVl! z$Lsd{{`u{|J&e5XjMYG`iMVBrtd%tz@BWwE#{=3Au|K|T5Xd1ku|)okd^6(PgZZC@ z#yGyQ=Nu^1pT$%@LBIA~62(5bq;&8skD#CXU}1;#_d1*%w_oQXbZ@Vt!|Uto{X@a` zINP_^@BEW_iCA#{T9Lzp)TrB|9nZPjwemhQ?|DsrlKm_OAA9UoEtGMWTp>4*C*Ty? z3+IR{23&9(jRw4izl%;g$Dq@?R_b&OYX{G&PBE{-p3{EAZcDD$-unIrj&_P>aCVaG89$bDqYR4q_8jXFSjMy!!1ogW1h^)7K5~6(0U$4rnzOWlydC-fvh!9#=?UTg!t z9^=?y;iPPcP0dC8Kt1XK!CU0_TQuq~kRxLVpf_G6lDwAxzh6Q7W(6w#f2<7_Pr6dELUi~No?0T`KB_u~TCL5L(GFx2UT zorAx^S3>#drIo};#Be^E)k?&#At5RTNRn>GNy5Yge9wj>n~rk=#A8qR*JN!J z58KZUe?F1tb=&tiaYGja5Uq`MhOM>sD0Hx|_Lz;_ucFzZ`9XI+APtJW)r5;;+xh&X0# z(u<5?qqGD~*}#A-Ui+9jhx55k*PXFffi>L!(;O18HV;6&=ehQE{eA7|Ppnh-Cnl{g z*gmvHzHlQK!vpy#^h6qaHax($GmlyCw>+`ul)|2SoPYdtpkGUf=C4n4U(&)5AJA6Mt@HDuGSUPF3_2|P?;-L@@0bPnwK z4-WL2gPa}Bf6IelYQT(rYL5IlF+Z>2Ux%+DaJ~5eYSs_j6YyZmu^y;F0sePg5zx9_ zU*sUsHAZq9{!n8%hRkbp9eW(>jiwRivwS}^qHG)-kj~LQ6>;C&C^lSP)^Zso;ZM~6O5BYWGSok>y?B-$fP8yiuRMUcq~}$n|Ow<`p8w%S4R3M1U8`@z0mj zpDQOn>zhBidWLTBG&wRFe%<(qT0s0mg2LZuf$i4m;%5|WRXlZ2Xg!q1|1YkC)_nTDaCWUzv?6dX z_&gAZr#jlZhXIhyLhcOM7PLkShCc-I`ZW;*6pF*PZ};)l zxCVk}StPMfb1e8J+n^WlDw;jn5xZhzGzcHWP(VJTiKAijK)_E_DK=?j@E^yACh2^9 z^KxA?ipi{6O1SSF^HV!1ShI1Eh~T97(m4Iqy5&$HH8AA>m6H}4Q<$Zywy zW4d0XPUJnT58NNly}q<;*N5Xkj&ICW12{Ayfejd3O=O%P1z4!WzDvkn>zMNEcshT``~~WWr^j`i39KA#Ys9q#_>=Kj_~9>x zZe_eSK2^-_&3}(w@~U;rF?(0(*meA z@yktp&+Q|>pJC*{mJ0(p9AE#ut{?B?3fpykU+2@vsmofmcJD>|%Wu`ypLiEnBc}=@ zr^>F*r}zM$ppn5l`47K7!2bde&Agl)&AiCioB3yc>)6acK{t53{{cV+R{Wj668&iC zfi|!-^)iCL-oHH6SHTg@J#d3>?2Wz}=F#jI4h?@b{n_|`}P#1dy*G4gpMH2Vo=zhy~*EfJFTLCGZ~*-Xmd0)T8K@xFBK>3~_sf2=_8w z13{oz;kwTipSTA^<$eUYgPWjcEe`75=w}emU?xZc+RGlq9ecq2p>cN4hXU`x82b~Z z7?dCsY?~0ivRMu+T5~4b?nQSr`>p7Hp?rhCQz%qGQ3N3NPEHg{N=hV;w~=V7Fv^1P zH&^dg(hu-j@4*oY#CHl=%w*EfO`{0f6PJ)l z>!-075*GU+VeQE(6O{*5RcOFd+2~nH?9Z%%V=p#>x1emD{7oP9q_hvLx(`?1PMF(! zqPh-eyfSXb-Tn77_&#G@+nN*FQ`>7cgZb4pkj)^xZ2$FF@seTyeZU7Vpm;JTDVpRz z{E2^~ND>#|5{((Wdmmq^W)L7SsN_9#3C_LcN5721hGGUTa6PW;;baU0bZ^#?e$Dt{ zz4jk_vR`cLZc7&dskhlgyP-ZCIv?`R0oL-4v*YIV#`OpCzFvnB4Ss#q-J4+6$;QGRwIZMex> z&^6^IsPo<|oN3O~n<|cn*1)y=RvdhWb8y>xrXY4Pj7^rO;^pC4%dl$RMU;N zus8dD_#A(eU!pNmol`z{j-klLW(*-ZmL?k>hfVXz`q?mxdy)gmlhO25&Sjn6W7=`s zQ`;BzWWZqC^?^dq!Q5x^+<@=-9{Ja~I-AjIM7dsFKi8G(u@;XZz;Awc^RiKWQfKU(s(;|WcH+i;v59#DKGlK<=L_In*4 z@d4_3y|E`&xG%9xys(y4uX13CpRHAJ#XgSs5yHmUx9qyx)ErN4+pjy4zZ~-}A#4h$ zxwg71d|h_7j?IQYdEGS(yTe`fbF7zEo2Q1ghZUtk)~9UbGw+idT>H}J| zpTy6Gy`IJ!YBtw~{hOm z7y$f7HU1s~co5Lx!t79B#ot$|_$uU3V14>>+L-@)fP1z39-4kbz6zqshe^T=3=^*t zjsT2e4U&mqG(15r1v!NaN$a%|FNz(+3i3Q4;d!5gJOn+40KZeS_v@>-i)4t|8zn3V z3WR*6gaiS(s}w#9Ko=@cFMJapP|TzmtSWRwNmG&p9L&{C~F+3fCis>z_@pPNYdLC=An^Id0Wf%R;LL4!eF>k^c{@LYmt1cL?@z32t9764NASKQ z$pt@e$J1?^U5%ofM0`C50yHKnPE@3zOOfy>n59k8h=_~k>U+gchLva)zf&NQ2q}Ew zDIBNx-iMVgD&ZHLB4*4Z^T(V?zD%x6j^v(u(PD0#+3Xb8*!WIipLFzft$s3UFZio` zW)L6tor{&Hd{MS3y|FK&DV0B?5v?Ya_{6TW37s*GpNah#1b`^6Q#0@n8iN0$sglp4 zP!-&y1}I|14fufu;bQ~7M6tUen)x07oOoTp$L4iH+oD*W14h=fH2r8?>DdduwQ3jp zMzbOhmR@zOnnm*kgl=6z4PNy6aGt<*yKgcttiEAwyKm6rIiRsW*4D^F+KDtZ49@}ad@3vPTpc)^z2eI32fhY(JbyZ zHf+a0*>$)30*0*#G#0ImA#BwL)Q1cKTqg$VIL_f3+?Vy>uk=&a5*|>zMf+h85e+NQ zUTJXbv(dZ`$S$&tjalbVCrX$w(_kXAzxGxxMyigah z&!y3?JnFya9}hxGgI$|Py(1mqVO7-S@)y3tkN7ldosD1CxU~*XU&jzlT0&03$FGym zZ}H^a4g6@>h5sr-1WQY(Vc+m`sXowDi|AAkpsJ~l+biV|M0RV#iMjcDh#JLpeB(S@5J(|1c z*W{3UAu8P4g%0a+6ZV7>leOt8Apy0w9&E;F{!hgu@o1y$cRNg5pPiG#fd1KRuI!uj z{sFHKpEcZW01%>5wn$+~JME1X!t5gO?Nwd?4Xo z8%3w^+_Go*9bjYW#|vnbWOr;&ffSH0@CjP;GV$VN@)N!ah$C)L@gtz{eKmsuAphPR zt>FhWhZI&o_u{^+A^0(xM0Bt1L+C@xsej3ng(GFZbDks?epK^+6TftXkD717KSF46 z`NuK`&;}`aAe}FzDAcoTZ)9x9&)pRND+;B0}i*I--9($v85j2YYtY#5J z!zgeqt{DxZ;O35d58rA{tZnt@jP)MxM}LyflDosF)ii})jj(6^tbgNXyml;h96pNgfG$`j*ID&)KY9Q24}-Bvk^Y{rTJiQf@wxN(AjKM3cY!` zA!4Y*fJe0lO)nS&3mW1L(IkU7W`c)U{&|LX3ETt3{py}OxW9jL4&Zz@_H!MXa{>%N zgfTQ}d5QX}|8B(4MDR7dr#?8r?)U&-;G^Vv@|&1*4zoVN6?g-e;8*ZYd?fy@IiVxc zd-4H!f}FiK_)!B@&Og=yvtF3BMApQ;hIW0u!hO{RY(3K6L){K}p0B6Zb=}@ufvD$Q z!`EK=9@+P7^0T%XPw}P2*wOV&Hu&{GLp~rb@k=)N*=M0;5ij^P_@3Z*;tC(9e}8`U zTE!{xi(iTN@TsnkMnwHldOrP`dHk-gfB)(hjs9r%&l-B52EL;=_Io2fp0=Oh|Gb2) z5&sVZ*@T}xt^Z#I4+Ro^onKps>(|$zAJ@rcT_F4n(mFW^)3bw7`vy`E(l~-1_;1fs7yjo}bwle!q?xJ_35*-(Wez;rJ7D|AWqr zz~oHl6c~If{+)62p}^Pd_gZ*cYYp9reWcCsMb{sIfAO~)L2FSIwlow~#6}abaWo23 zicuG!;vXDjSY^IGK%qzBMbT7x^*-?yxRB!Wcj>s`aTQW;7w*$_@(GHPt-$q3%5eNm zYB0Dj!v+vVmZ8{T0Cu;4ezMktnntGZ)nov)@#R4KB6$;sfodS5NE=Y|LBS@QfE+qx zEE(9%jRxSK0Io5i6Cu zJ2IDjH{d*fhLN`ley<-hhj<{beYj^09sAl=|CjG$kLh!o3;X)gKqv+g5eGF4YQFX0 z!9MH*hZr`*py3yxxo9t1e4n3qN~>wK(QmXon&fJnsZG#(>Ju7p*C^zMq31)(Q^$vf z5B<*d9J$nS#u+AJ9h7Mm-cfst zrciiu>U zc82fRJvpAd&Ic6K)%gV9^-9O^CH1uYm=7VgZ&E9+PJLMG;!p3g7Pb$Metf=;Uk3nw z+JCzz{@V~B2LZ-~hQBq=v%&7o{%rWc|6zRat2e_3692yqem_6^%ESi?+cBrt{vhk< z>%NRJC}?~4ljenp9XBAQF$!@Zi|#BB-Ffi(+B5g9TI$EL2x4X$gr+@J;4b2CZKYe#~_cGo;T!Je#@HOJb8 z5CQGQg%Ep-f|USm;HfwJ-|@E({?kP~u3<^CfTAQofg;GpQ3T80FRlVKlW!Hk6g%=Y z{-KbKq~$~vNy-W8S66RCoS1q?BQ=u}$OQY_NI(pKLa=n+8;S?7m1x1I;DPS@lD|(L zur?P`6i~hB4SNdMx7&Mg!Oeb$eOCTE__Cv&2d^ep{~FN@X)RgEO@BYbjft@rzvXxK zXW(Oew`1|;349#q`&#Bw*v|H24O$Cpa{+32{p`g>-E~E&5y!Rj`!!e2Pp*5fv3~I} zp4N(s=Anm8uUPn31(SI52A(WY@JUnP6h#sm1IN&!ktmsl8}%8-aBM*5Mbob91St3# zxW^^%xzEXKBv?o7RX!#XTDEl4^ADhnt*;OZ2&76VAmqbl{qNJK}WkEIDxG*g2POhveG!6nf(r zT%WPE#gEqh0{->+#x?dxZadRG?1Fu;6Z}I1vD-aANMK6_nfC}W%n9VNXSRjUZYH_k zP3;UE+aspK&cdmk&+Ruf5}u0-$g8g2BTF2Qs@pJca_;>i2`Xp-< z{A|4&dSW==;q!sCZ3jg%=aA-Wcviee%1gUHCln z0h;-X(n@J$)hN**6T6C8^kNKvbbk+kUMOuja;I}p#|Rvo>s~b|h8|-N%6rlf4+yB4 z&~x-1&5wMd{XC%k@V~D0K|rs)sLRPC{ONT&F~wlwlFo#_s7`i_2FDuDB9=>Eh;jH9 zd1R|q;J~O=!f!R~@o=YVl^E*aJJ$^Ou;Gxm2mktj=BJgL$rqU`c0aM+)FErn)+@;# zp~+2mmY&EqHDu=)0Q}L%zd640|6lcb`P=+L3CWbS#s4?(<-q@E;41~ae*c%R{@-VKpYfo67kJH2BYxko-6QWkk!z|U!!j&r@ZJjl z;X_|-`%Zg|Zx@uk_%nw5Mn&2*zICzL8=r)qo87q`-lkZ?qtl7`>~+2t0tW_U2Eb^=6Tu z=J9nR_dPlvaebAp_lhES3TqZaG^X79`4ZJAhAIe`9-!;I%uoUP(!kFLUecj;ASkb=38?$}-U<1IHPkxrQwS2irHPUMBH_@8$e;Hs zVd86i924~NIj?!2&q+F*hq!S)?t$ND(wRQDzp*oR_t*aM(=#N)xxxF^4{R-6&iwoONH|W;YTC#a<+g6g@Fuw9 zrb7IYpNScB=nOO^ZbaZ6*NEaJKoj!QxEJ>vsAdE|MEsFp3hVFl7!VB%>p$VX_i*5S zaSfiGam2inEAu zfdoH?V-@RV5AQekI`(03OP3WQHg#wLXX^$PpPZXpSZi+SH$TqMY zuva3BhwBhU219}cI+yEgIJ>s@k%dd@0~&4(vHjuR$~oDm^R4kL{V{!#NP8Rh!H({e ziOJW_$(NsS(t11^cytkHFYIgAKM;0ob_+Wh7wp9~;3#}RcO3(vf2?D~fhjprHm=FbhFf9@F9 z)jejDkoOT4bPjc%uQyXX&4;G*53-{%^BC+O$*;d0v6IpZ4p)*eRVQ(*7AojUll zDL2=>VfUXSo_#xX{MVxOx6FSP$geT?|3Tyt`uT|Xxk0t!wOEel8i{%BMBH=ynXA7p zJ=f`{wcZnZ-1i^c2rg{H0({)^Gd_yiRk@fPO#UTaXtIeTnrbwFm?Qp(N#Ybu^+OBm z?>m2y^(t{q3`ec1b0W78ueuJg8iTviFEl9{F>-y>i+O+7ss_K0y3(i`AqE4wjVL=WI za%jM?V(tUXfd+d`>^8_@!HSDRg3TBBCk6!aA3nvub1?AjWiarU65mIP2#*nI9xIYX z5K0svu7i)jgXFIk;qRufQXoX3AAyxYXdc8prE=u2D!~$R?P3aZm2eI%d!x!J5Y&1b!byp!ZXI>d5{5 zO3l3*ec|N;)!geRjlVw!uPnUE&{|)mb-r47%};)H^%`9dyjHl|KXq?#PvBp#UZ*v_ z?q|O~;2ks^3zRhYT3kcESD#mN@^-;ZgZGBr@KxAWHh--^lA&3GBNXKn>F|a@${6y9 zQx?Bb+IDqKC*?;AqrWL2F`mCTJ48@tNRa!ayWlSUkDz)rA(}MTB!mro<$H z&f!re_#6}|nJX?B5=AnvriA!l$n{FuAd(ZwK?xm>nwQal$Zpwyq%AwYu=9P@7{`-g zn!|>-(=hZ-}t>XoR)|X|9+r=1fnF2h``g->1*B5aFItU}_Ec8UA+tBjKYrQKW^<%3q3^ z2i)jWlnEI}v)IHQoQEyoh6@L-O}u2`kj0rBMD4;QJ&tU2>l&pmxV zxu;`c7%@Q%2+92(o113pvxte>D>lQ1*faT`8kyYxNoWbaT8i+m{&Vp1Y1Ty*e()F_ z8#*V=a}$5oY(|4A>`#Ml+h7{i*e!_WZ#JD1y&2Wd!@09j9H{%`z1Id3WD~hIk^%Np zhz7dt73_BSL%`R53~2Wc)Vjj1VB5#U-1BY=+i^D8y$RP(n)3_T|L3SbKZ9z)YjJjt zuwNS-)-cw}KS*7C*!Fk)cdDlScAt4Lki&t>?E=FfVt`l}+D#KF-L__ zV^8iS#)(DZ_bg_4-!;zAX`M&@UK;4iTUp;^jRTIu&eYk|TBC-SIzKfx2bVPeXm03j zs}s8JM2^u~{Qz!!3)e#cp+AgU_agnFCf4=m5PjBaphvNnTz??i$+I5D z)UaQ2v#z?3TEX7{nhpD`NetH}*|evA8~LZGb8wqY{8sPY7;Bvyzq|hRL$Eds z0KHCrf$H5`fAXpCM05DZpDakkd7}JBV<@18vf;k~3CBJNh;IQ1%OIe>2{48L#@5`9 zr{vqW0DzyeyK(N#v>WZ&bY}y891uJN?ANI4=JmgFc;BBL(bstyeP0uO{{r@Cwh5H& zx;<_0{D%?K7pH>)`Em{l>Kg>&K>r58Ur3Z06#Ny#0+H#_BI{p^+>aIUAEy9#yc)SD zC_tX1fO)C{=cx*$zf(XxU4iw?@BH}cnF_dPEAXDH0DPVT@%akM=PO8GpkRHW@FL;G z3f{XUws$FzJMt;_zg)dULEeF|t;lA7fy4n}1&BG#>T9DBsmW-GVF;oS-&82kcR>wQ{Csu zvg=c@>oT*WS9F!`5N%_ALS->?kolK(-wA zMR3cm*ff$P=r$ED+0#kpMFI)L4G#OJ@k`ooS^f#hMRb<%k`sS z8Vyn=u}ofCS0%2Ok~DzU|4Q+c{lxynolMj>`&DG2DBpaPQkfc<8R}-bq|uFbc{84DByw; zzQ7Uo1t-`q8Ufd_4|c=0`0IZ85a289%V)$9#}W(hA6sFkd>T-;(S6`;4mD=c&}|aQ zx(eX5iJLw@z1^PTp1|k$DO>J6!9C_+Y!)qEkBe*ub*#0=RTM|%>x`)uR~1`;JqIYX zfbL?J~3%!)P4-|&PhWv)V;jwug zO=&$rrulS}^+-_7S2mX4s%G-oQ1<5Tv^f<1jGw9({_G)u^8$5^<@b(lEugP+!Payp zzE!9uRIhszy5LhCWv{Z`L&3O)>Za5C2{gq#H8=hejJ40F?R{^jdlz;)fxUS@*tg^C z_znB@&(;xW>+SnF41r|pX)|%v1CucfNG@MD#7Rw?T82KNUkrDxTj(iTMQjsKL&u3- z)gaU)G>O!K)G^NC#O@gMk-H;*7b2hj>&T}ghw7ZnpP56+pMIFN)=!yJ`+FP{52Jeh z0JH|KAM4P)qYmI94SJ$RKZeZJ{J-bdH3TT+ARzU|wF7%OUvoCnUUOf2#~Av8!&=w% z0&L{^BkXu$zrGIcne~Qi^K9VBn>6j@-%(>klfKp$_ql zCfI%_PsUH)tJi184$SLk!%t54LyBCF^Q_U=d_e7sT-?Eajs3HZw8_yf#$B@Gtw(HD)!|NYgUDo|+z=WyU#(Iozn0(>6~jDY}7!s95a7r1E#C@>VUYYjh6LgSgCqFyr{c<=i8FsL?!ce;FC@jK7#c{Z z`Gx_5gGULERvY*MZ{`)rk9=?LL5evxdhgAdN>U zU>~I*X05~oYiH=;*chmNvfl%UogLW6*goUd62mLOPktrj&sTLw+yn8spkp})_!=)0xHj-$$M=4*z*+$7>hR#fxQBs6 z4JMWWkggvIm&7K#kt9vx)4D(;Gm;TWNg$DRytNWm{3Ma;=WBF4c(u+oyw})c?T!0z zPZt1Q6YI=DR3DuD{OZ-C$zJga;pJ|;0}LisybP!*VQ>+H7K0`|1}>RIW9Xu0gN7!W zs@6=od5T6!9HQBKx;V8r?@k~w)m9vn@JM{e27WfDy=m#lCu{i>$T)qGeN6h%r5cPL_1&;a-Whwv%>b2Ep1pC_AA zpirEUgt<@JDjPY58>fvN1u@0(D3oa|vUu*r^nBt9-0*DRIGPO~*UtAf`+fYhH4E}) z-&$X{=Z+k%tnlEWf91O)iumpK{&^zL%|XYG<2_l&8IH&1HMn;0)fh!x7I482b3|as zNPGlORE*NN<8yNbEh5&@6*%$^72_1;6z&WI(HiuC*yni8gKKck&40w&#uwK5Sgjjd z2Uip`+3+V<%#Ua+<@;zPmA4HyaeE`^CjCzv{=1<%?B5mrUWL96h7Mzhu&q1L_GR=v z2S#%`8`Ee`_jnknM!v^ULW-X=uI#=LbU?%e_u4=9^}_hKL-5*L*oU%ySiUG0p3|a zH;e~bqrZdC`mw;*0QYlkgWrvldoJe{#%mqVI`hHBZ(~mkDcEl`_CWT)HqqEi>&TDT zE^D29*ycKh8s#o(6xp4;88wM=C;HlJk+UY>zI7eqrrY&MXs2tE1^qo8*Eo}?Yw4O? zw>Rw01)Wb(J1m3oIY>6YJj9M6xb|tT1*HFQj=GH+E+E@rpG2?yE+LN{b>M03*Xq7z z<5%hZTTfLoqiLXdc$_x$SZ(gF)mYFVJyJ%Y0a*qF0?o^x3jbYA&7UZ!V@S{;v50{I zkdRDd6ZuCC@qRn5pBt|wFp0c&HlGIfRwJr4OlWNZn$v7h|5U}}p9PLh7+^1fb{$_E zcu%oTfoAx+0mwe=h{1-M?b9K~Hi-B?^!vr(!gzmTH23{=U3+czYdvj!quDQvhQIt| ze{_3eBWyOBUg0l3D{d4=qIs1dN8_phGib1BU~>qN4LCdly*Y<}(X=b*|29CAtEOJu zjHm7XslXfPzx#C@YvlPLuQ~BIx<231wGCFGbIE| zvo8UTZ(gVXeUTcMyZCkmO?Wk~uaI!l%+lDtMuL9#&whP%&(D7oc%20Q^%DMo62Zi8 zuU@D3UiU8`kGn^>Tj#u1=f76h;abM4l`vmT6CFS6b=|W!)OpPD{WCCRc)1eJfcCu^%%~=j(d?UWc{RI?oD0_WY!>MI>zs zK>3EmM`C`V{7DhQkcB3mV&x4Ch{QP$h2E^!yf+*D*|c$;XUNZP@+ls$Wi)JBTi8bX zh^9c=>0&t>wbr1}tOztQ@dQpf+|ipMqc=;Ju%8px^G{Is1WV%tr2n&d+Mcdz{j(|P zjmvCij?KW1Y!2KwWwX>?M{||f8;FimE~!CI+yQqs-i$vNk#9X9iYJQFRt9PChQS=1 zxTkm%jX(^ED2#t|^;Q+faEJlq-D)0a`YF=PABr%pn@{}zDejrEIXMIt9s${J<%8CBmwWd$#kUSR*hkyb!c|aQ?ah7+ zz~Jh}&){D*@4=z2Ic4wL^2LBRhPCvIKz=-+9dil&dIj!rin{+rv^Ac-@6Y=0!oI(Y z=*R8YkGXI>_u2<@6PMmkjPvVWi`^o>rQNeB6qHxp*f~eKsYAob4>X#@JG#C%bJ+r$jjCV#P-1@|5f2_{vID6r z#vCNLF>k1*Jq7{{2x?I9?q6^406}o0&$)38n*3k?1bB}32gFe4vW#DfaKgMb$c`Jm#*S1({F;HFje7Pe=&&I60*)3 zH{LsYUm(wPzT`u!8opdY`HF{9T$i=NMTT2%(H?HST*dQCR3zV}H(&7mpI<#sn|-zn zK=J!`GRjkBpr^=KPn6LpZfQXNRzX7JG=~Kfk5*7+Q}ey6M@5swPZhKc95V~wyvAsH zbWJyZk8?9eQ7y0rifz{8hKKb%?K?k?wbJ-JLrw%yyo*>A^3T$K*weGs1U;wW*>ZBc zAN!r)e6GcHxyMs^*F)CaBrD z+j_Bg*mv6cxzS+#ED#M>2aT8l3%s!5MQYY))bghqH*wT}uL(7Fr|&n2=yxetIksP8 zLEU4=3kT+!`Wkn59|MZ11M0K=+3eSeVGD#G{1`HS191YD}`v1 zX|&ahzoziopQ$-lgMPQf{T>>38h9Y#KS1wu44|oh4bU}c-nlOK;9lI9`;Vquu+78n zVYj30@O|gO@_R!x=h2{7?0)yB!Lw}C_qY{L#Z~y~W*rXGq=$})*VeLK&t_v@dR96X zF|JsNp{U}9A(vxm#(c)T;rP$tVhv0cmmPl`C|yvY8ZlhjFbAa>>!}S>6XMUYfQsY} z7p*R4qlp-(Vz&z4M|V_FJR7kK(3(%*KY_35d-P)4I27C}{IcNA0xyc=`g^x1;-pt@ z&=`ccNh8@mhr}+wzd$^QWG;=T@OYKDMqxrCB_Z}C$?vaMg1lSFk>g1GFV?wShih_u z?!~=X2Zbl=jP+|j>@yk+`Hd%Mqal-TdK2b`YcxebjnbbCe67&m6F&U)t3M8W?F94v zKLO(!C)X)Na~4frZ^+h0Y#$c%2JCVZ2HcRgW=uBm6GcB&jGxh%&E{-r(u|Eq=56PZ zIo|o>W-S|n}b1An-|I#-B0%20{ivf11Il#pL^UZiD>N+v%?k~M0T%$g z;^zwoj>oL;=kNY(2MyDUmG`)A{`Bi3+^B+Vw9e7jgoZvpF!vU4V+OQ;;^w)+bJUo* z(K50LOWOrxN9_4@{ya>!{X5m0G~eSZ%Z74qA83PRs2#}0KHx^5IIo}U044`~6YU(G z&$WUFhWOw?V4~Jk>w_Dtojsr-&nSL8v+X_j(YS$4v3>YK8iB^Vv<3z*SB*pN$$>#O z&v&bSLU%kYKx?c&Z|;pB%{fiF>bbY6Ub~OxUA3Es0UijvvzmRH{s(?NhXcGnnt$MP zh5LS)*ULdb4FiOGblxdU709unX>vEA2~BKrlXH$j(U!+?JdP7QlAI)utz;!6l)xYX0?B~%)4A)rs_NOb z_q)&O2Fbp2e^{UMhP|Iq&#JI$zYPO=|6Dc*T+nAx|C9{#gzkG>6Fe?KGm3QKQQ;AZ zynu>zqkk6QIwF~F6L()tjl}%K0|~cKJJ%e->XAFaNMPXxx)wo`-U@V}H!EvhlJj=!bUJvo9*L=@)alUcybhaVD zz(XS2gY3VcIhS}qPEw5mYy|EzqQO$Q$4Jpu(4J_%6tJsdF*gAl1VZ42$eYkV56rKf z?ho{_4bK~Eu$>FFPS!m`?5Fm|UY96alx+;zYS>QpoHbHp_ebS7oWsm->@!mXVe%Ux z{nq{?;6M1YeJK6tW3M6Ng@qp(ujk)H`Bq2&&aZ2n2F9$#8O3NbafqGAkQGB+ZD^}rd{=q3>y&pqJ_ z?9uy`&lHEE8B&6N;*-(sE0oQ*L!HeMYu7Hv9$1`X$M`i!JJ&V%*CFg}AouFO3l4zs zi;s5`JK7WZ!HdQX#qDbLfFas4mOPMRtpvTfqFm0|B7C1x?$}PKeVm^&ixZzv{2+!{ zuqiM7w#7s6rUZq7z!(T_&ECVehR(;15;GP=5>WgNQPrS$w;ld)<+J7ezYlVr<@{bB zYu29FBYS1f>>WR`U=lu|y!>(5=VPi*uvg%9+wfW883dH<>SlyR)T`NSkp1PaoC7i5 zVhZFrabEZwK3;-~^W1=@T@@k_OY zIp$b2#iNILdM%zk2G7{1_Uxtrc?_;Cc=l1*&rN|D$C6(kFui)$9^Gp>FRh9@xph0HBb)PB$Ku z6HxS009#~=ycEa?E{h%AjKH;Mkugi8JeUIIVT8R7fFisw#0o-%LVciprb8T!gnB(b ztLqkMV;B0ohr?qYr{=LTL_8xh+59} zkj|K<0_T3c2!qf;f!qL+Lzi#5SwUl>ni1VUb>|4k5&^voQB`#x`Ov_|MVAIzoWqZX`y}{rx$S(F1#V_oI-LM}`D(p$zyBnWT9CX7#i6VYA@UL#l zaWdFPf1e`4#l+hKyr+qPyCdDO*pQKMQ zN3q9R`PBh?a8lPve43b)X%Gj_?M`jgmylaM~IjB?;_ozeIeX8 zKCAbO&r9I<*I?yW0Bb+b9#8*s4{Kt7JAsHGwD4OByb||^87)1;`kze>{yjj4^ zc@l;7y*z1iA(K4Oxw(iIN1mj3rf@Ef$fqfkM-xa~aM7uFL_y5oKh*o;$EQ_jQd}Zf zKBB^r;s`Oq_=paQcz6nDSy$%=dtg74u&@1N6B;0sos6_$iSV*-@uzcW{7uvujk8*! z?iI%Qb>y6d={y%}6ym9So$GX_<7`2m?Ks~B{3Jf@)6cyyidf*7flZ?!NP#^#F?bQh zEhtgen?H^v;>msD3GpNZe(=R2Uz~ZA#-W8*aOVldcnf`m|A%sfC8Tj;j%A;^=2y7!_QoW8C6@ZHc^Asdc|5$nSj}cJ?O-f2({(w5^(kTb{OOJIRxeeUW3-cx@w)Qc^-GC*o%FBM|Q$y*v}0D0lR)w;>~uJ zz471P93b{1R^k^3A=)05Pm&)H(zAwuz?r)lfPfPh-~`+#^d^8sesU?q-4emKb;LD; zi~c3T)F@z{5@2`dFG-4@tnWVZozVGN%>ni{H3x#D*Z^B}jy_u)Ey#u+lReE<#lMNK zkuOJMO6M>*Yu*;}tP-4Eo!>Y|pc<+gSj0__m**SJpwfCrBLE(jua(A-D7bAG*&ea5Gl+>!-Bb4LkSfB+CahLjua+C%xhol zlYL(!5mLefdt&oTRh)2EB`!)xaMqlAJu#ek5&x0FbBwQTovpw8Ukm(yLfF9k|BA4I z{(n^v_AFipzS>?dvGTuS1Z#5{v99_5Z6WsJ|9B#MGit9B$`0WZh@cWylh_(QQNCb5 zcs_ifI1jhQ=i;z=o1874&P9Llb8u7VI=HE`4sPn~#m)Q01B-p0dgrfddQ%G?!Kvb; zxu*n8p+H(r4W$0Ldpe z-voRZzDS^VvCm z{}I{mqp~Fh!lz{4Ps`R68^1@fOkr6?W`g&8&i7nr0jK-8pK+?FVEiX_9?ZwQA@*N( zfp8B+qRxSWtqOsqr~^~dPw@|A1H{oaMkM1BRb9*tL8drSqOAn?3<&WZG5u6OFn`}g zAt*6eqOUL$ev5))Jgxsipo$3>6bO0jknj_UW+N~JT^Dk)CpPT@t_tTWmZM0fIF7=7 z5^lvA6~xII#F`6XL|Te{3d&Ea$fW=^XXLkVr6@}OVZj_y{8#bJH5q5shyx0*-PHbYv2axlw@nT+VCMV#zIWcj; zd;s{w;DeAhS#o0V0(5Q&7WiPsj}-Z``%>haCygs5^zTo;6dpn#2#<_-6a0DHg5p)5 z^<4BLKq3we;V)iUP${NZOr?+tgwTO=#GWCJ5*EMWS%El3lk)eKS3n7Wi~o#gBgToR z2Kd3)J;_Z(GlDO@F$w4z6LG z5vQ?3+U9p;FE;{YM{J61u`@OwaT_t1LhuRY37?(L5%EtvKq$vqr|6g1b3AXNe~3Jg z{tFH%FAcE=l8=GnX!12tJnrb<5A^$uiR%>$2j5pYzfEJSK!dCEK4WEXBorQ&hAAJw zZh*!D{$QUlQdkfFC}8vOl_mOj^?E`d1LW8_C#bcsF4o67=R!Wfo@$RYe%bR*%^e!W zcL~@I8_v%DE0djpIgsZuoqwF`GKM*}5$lY39CVWxWSpD}|0(_(8D~3s4E|N1#@%cj zfN5RTG+^xy${t7U{-0mnI&%f%^Z%7--;K}S3nb#slxSNMcylo~7neiC4KW(xZ7$sA zqRq_=B|nQcB|Zx_8K??e7qv1(6t&TWxIzbiAR7w^Xo}fun?OTg-y{wz- z)CKte(X1KPIE0PO9R&z%Zy}`f zxLU!5r}+px3woU$;*ay>Y;RZayF+Jxw}K*q5#jiNlHP+#fDb7VK7zPaVzj75Kr3mw z*`%Taq5Z6qY2hOXRVCT&!1orh1r@++f}J_QQLW9wJpUqq z3#|RY9|8$tfG}2(@C%+*vjE>mh`V6`^4BNCf>nsG9d5w3`y&Q^ zck9ZWqhNvWc=-LT>l6>+_x12wV7;u{zfghW+7GsmX4Q{<=5PTU!=X>fHoqe~{VoM_ zihYXBDl-48K;g+}f1qNLYodrm!2N9NlZZIkm13RZ{TcD?X`SN}nx_hqM`c@z#s{-# zL@ZEDR0DwGp^AmuvQRw4zwuXrM$tBkIoV=aARv0K2eOyNkZibFBrK6d1IQ%orb0tq zxBSU;VLVyy0!-*@8%1g>Mu+Hf0lFwYl~0zUi-M{OtSUeQ6k%C(XHn*&6i~2Hyd@rx z&3j=7EbJ-%DCWg`7pJ3WRB_4AGV$PyzzmVaMWu&tKTZU}%}=Tt;i(Ww1sUv37NvyG4Dd~q>svTO#- zx!8DUE;b%!bExx>!ruHV-YLG^tFyT;dA7kXo(#h)#T|1?XZqNRz#&FdBY^XNnx~II zd;o3=h;!8xcn|m&oi5MC&WG6O9O+k#=5dG;HH4q zfUkK?LdESr`dO_d*1&k5*0{ETY=fOXh7Zb~*x15f_K#m?MeOF>9?=;-q&&jea@NF` z5`pky2>tX?;P@?Wg}4jet;Oz$-%}io_?=LWER=sAF)BWH)Eo(#helgdfwkIaM(i#` z{Bh$V!9K}V#Pq=APZO{WLU@Yv1#DNoR*2X?<$#_CcEV^>EZ#4#I~-!p`I-u~-o6L# zDG@n*;sQ7XWS4=oTiLMeY8zwsb_UC{xq<%!OPymo-}rUl)C>ldI{klqb!);`wod==fnERos|j9zdFwR&Eu8vSUrzA4 zP@gsY<>GaJe*`#fG4He{trg_D&nQR`4&hj$Food86Jdnz?CEb8eT!c}$XNa9n4k3O zKzs9`3Tqe9B1+V(1rJrA#1ZG=C-(|E7Ck%Uf;AOxU%aMKH9_LpSbm;Z++RTG6_(%Cy>4<9EOrBjl-O@oG3RENin}3x zl?-T}U|&Be7qIuW3i99@1@=i0gYc)}H~GGNteSZS_X>1t^hD`}t&h?Pi=LUn; zjy>;|jqj0AaRY%Sht8zVnKR?;+%V9Yenf@Tb2JAQ;B(HL^M)HV2_AXrjd8yB>ufns z&XPHqyX?i9Y$p{%H>w!ALF=vcyFpmCqG@2TUm6J6MA#0xhIceL>vIETXYdES#_*1VkICv}z<4m>^786zZyU=k1D z#UK7OgwP+S00|ND3yF}Qn=4!oN6Zb4$vn?Ve87XJ^4zf}w&n~Fm-mR%6r>cM6q*#4 z6x$SwRUBGGs2D^DN7474)h0j{X!R8S08ozU!n}U*F&F*?iF)Oz=|2Y9!9VxN@7i_7 ztSJx}4T1VAY%~Shnq7$IUIJCn_J&myz(ed%D5rg@c%DSfBxFLsEW`>zMfUE(2u>4T) zlkN1!nIC3T0nT^6!*#^C1-wvka5M%Q*WtYz0dQX5b8W=Pd7Q*|0KTvt>|bscqLn`=Py|B1xE*OBuRr@3e5cZX@%VrR!z&PnIRIdZO?wS}Qj;;%%W{ZSl(S6#4$ zxStxc;h*87<)`*lU@oUX8@@aD;dh7S7=bvkJja1000E^tQnBve-iz3 zp?}I>eo5f(m$y#-3-Bf3i}|-+3x9qj_~)&Y606_WST(lh2B+vX>p1nVKsF9q(;%7` zUzzVqV6Wrpa%&Ql=p5par{EQ$g-hk=m+G+nG)^R_213D4_bmDakqJ~Y;BfHu{&m2FKK6j-WNtSS44MhiP+*OK z^%^3dn+qM<6Z@K)4Y}8RP2-`O4+!kSy!n6)18zWoY(Ttc)cLfrE%bA4i+!I_t(m>W ze%;KGZLr}G{7OCupqmheN>H~UbiR`UM@b%G^Qa2h#}PA1d>{GAJ6j)DaSA@B#EG!s z^Cy)M5sHr}8FJ745=Ar&?$$VWNHig`5mL;7xC){7(hzzf@?MmH{oxRJZXhWzEgMHi zgt{kT(X8=86n2E5WZn|uh(zpC!n1?V;#NPtxd#al2nG8+!N@Ys5CyJg^{^27s?h-?~o!^PJNUF68H%@B3#R^Y1^Ya~)W|Pn76ghl*+n>lGjz+zeS@yU)j1{9TO8^<5m4AlQV2 z27+S+2#4Q8kRS^5dcyMB_}tgl--H`KZUTk40B#Cd?4;me3`B?4A!!3_m|@-ALJ>4ri63PHSuzix;rR*dF=d=YVvf8Ymym<@nG zq8ZQ{0)GH3%75-_?PvCrLi+H`x5JkV`IYBLf2(=iY)j!^^KlK=4=G3}mgw9 z@qOWIfbk~8925HdtcP`7lR}R*vv&4SjfuI)`>vZH`?Y`UfDpR~V4t)VVs0Oh{oF_Z z*mE0nk>64GkH(Cgp`QV3sAkL%eLOwZKHLD%Ua<*61_5)s68K#R6p4}t5F1L^k0J7u z{GR0};SzQqQ*s2nenyG*i68%)ui;wmxi=dCCH@)rW(5n*23wa+Cwoq|gdfF~sgV@|>fE?fVySRE&AT%0#1fnH}?XI8#4nn56a$qk+Ku|oOX98<$??fZT| zh-Oia&)=N8nnlH%?zh+v`?`6T{%Wr1Z1BzUL;P-VMQ3ZSAY$`L_`}&0@KbOVP4I)p z(Q_));0u3w|E!9!XI^`2>uFu{r1(RV$<2Y6f9f&dQi)!K@x2nrZYl}PiJ*q7)g(Z4 z-)f#kli()c7%=(w+^4f~)0#Ktp}AOyICW)mvwS`LeZt`DVPGy}+qCa{N1LCuv~?}v zGt9rB1jrnf{40TXWMT&-`Gk}HCQD_5k!4Mzd#I4#RfEzBps{lVj_=%xF z6X{lsS0ba^%;G}p@;bHVVIS?&O#<<`erfO&za&V1t6$DkW0A(!@9^|qBHT?eiT7xv z(E#}Q*6-5{I1Jn{@ZV@4Ao?x*OYArBIoB{Y%>|kVpH>e0ZLOgi0Qfv>#Ba-A>latV z8DdxW(-KGJqvKqB9+LpNNj^IBPk?{gM-#(Ow`&AUe%rAw>iIkWJboS1zvu7Tm^oL? zzd#88h$j=Kb%&Unqkj*^c*LZNOWUHalfNB(-G{^S-h#ec&dKM?&$hv6h7_7zs-dy8 zOEf4t8V?-Drl!NJvCta?;uXGLaV`8a#lQVi+{{?kee|Ks$M|XF&CHhqTqi%_w{X8e zlg}WJ{V@E3oXT}vPoAxuMjjq{P4h4x^D;kcah!8}i@0W=(fWtKw0stR^4+Zmzxso% zqbPDIdMSj@R>6Fh8vI z-Sva`j7k}*KU z4q+kZxTqQdJcZXGQM^{MYVofCc#RIW1poC4kcd$VcR%S98E#Qge~1EK0o4V+3jaGq zoV&goV!%%h5eJ9_k?lSa?>-UmegQEG9^`3bK|%W=C7A~|z;_6b{=PxUsm5dM`!pAG zB9td#^4iv&TH75WHS4@h>t)^X1d}J=Ay6z%1UD?S-x~z>e(+y}v;{UPTsuQFN#>sg z>UxhMr*Shw=9mY0g+|#55yCM61?f6 zA3k054H+~j$+r⪻2o(9WT- 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

@%VVMulpvgf#bWYr+`7OIuEXJ1jCB9Y70ql+4 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

F z4qjH%6CPLWvPdI#DMsVV7GsLz#C2j9IRM`$23&*qK!hkRT+dTT#fX~_Dgc4bQ%VF3 z$oGM`hHHs^ZZy&?);J;LBgT}Fk2s_JA_0SNA=Zqhqw`5N9kup|H^iFCBl6kIBNp@a z(}>;OFQ*)lwrYOqr}PUkAL1SVT=&t!@)s!f*+QP_z~Me?_x3Rd9}3Bf88^UoO#$`? zsu@5tqc;O&myrvb&EQZpl!8Yf8UT?CH^hJD#NZZB@l*6C$1M2and*qk5Uo0Q^9@n2 zqVOsefx$aO{KP%;PyD$dIXL5?x%iqnc&l=mb1dQ-D8C`Ld6+@IyOW0=JZ)G0voOyb z>ZbZi9yauu^Rd3?I&!w}Q~qWg#;XS=%*VW};Z~aL_(Jo88zk(zAH;)`SG-?0wzPly zg!~Vi^m;66FbRCwk9-U!kstnot@nK){Km~M8umJisUDje_aJJr$lZ~@C!eZD`aB;7 z2fDzra0VK8TWFrcVW+2|zbXvjzKRr=DKv+eFUUS~Q6EpOCt)Ar-lE>ZepB2pMg2Y? z-?EsOpA|}QpFhJ_Cdg00dB&IjoiNQ~^!am5v*v)-JsJTZ_qfpjK=iu_l7e40v^~?_ zJ_ntL&rIBzgghcx_(BL@1Z{~~1nUhJsuHK;iFOK51gHG!Hu=}>@-sxG1tfw|erTa6 zfmpa#vE@GFwZyCWnO@6}jq%-U{tnz@xAzDJc4_;tdAKYZN{2O1EJUmSrS#f?=?&?s5( z;&5=Yh5Z#6OQ5;()OUZhb=3KvS3;os>*6Z9!Fx#@6kG+~H}L&lC6*BQ5`ci@f)I3a zi3DRoD5rH|0Sc%H{z>##vG(;Ltad4(Snw-ol%VQGh@k*f1eEy>1sCa-97t%&XZ0AIyO)D#m%&S^J#xtlN3d#PmQF>_-aS zv!n0_F(31;A=Y+;Slm0!k@M!db}7-Fmi#TwoC4rfaZ0a2yuQ;tgwK0ydWn;USKWdPvipUfn0Yr zdq<2OaXrQR%%1`!fM>pyPvXnJHhnC)wB!?K;;1I- zO^tzxE9MIrO@YQ6+11?1oM`^E=JWh|fIvz)zGX0HHu$zsZ#gILDG#eV;_W#<@#lM$QyyoNG?zXB{^yf0EB>LJXm= zHD9l_vxnw7$JQ42(QI*3NHKz6Y*^TTVJ_+|_W17= zAl_7eTC^dEVT=k#9a$Pec_DEXFz{|yT@ATDJG zniJS$G$Rn161Ldu&L%W(E1@ZyA{O0j0E$0uCI~kw4wZnWL2$id(Im#dyLFx5e==0i z*~PDf;+H~K4a6@Nt^}$B4jBCxLi{2qp*VKLzxLS4B#jI(pNMCKJwA$)?Fyu_s^E_FFo@92fy~>)=^Fl0*M0gb`Hry za3Y~lBdG{3++;M1eg9p8&llfSLjdHs;2a&ytTE8AheSm&s30+tm7!#%V0(rFiIY_a zNy~{V3q}Q^85E#SJwmwH%=?@KNs0<$H5Y4OJrt`ha+eD<>MU=Z-q^RJa@K6DAzr;JZPlQ6)^SK4T z2zU1kDozICGZ!sf%UCLK?(=vtCa+_>7>jwCpLHA(QCZiGDr9aD*=bZYpZnHWi}~C6nmje{luVUg$Bqy3I1vFU&a$jqwRa_9rQ7_6?X}( z`&dJJLEse-d4-wKlc<^ry)_~BABiUTysi>@*%XWc#o;cC3b{3v}_! z1$^=kMEr|*T5-=JA^pxz1Iyot(dEY-^?mZb{CM!cd_Y+9fu*2uv+OUvoaZkrgnjI{ zxH{Ql^~@!V5z1l9X4CvU8FXqYA5GN)&rYe4t8{7m?uBaTjUj+{N z#CekcE|3bL%9Z3Dgg^O`T;!aoBI*|9r$a9(ZzA?pfZw6qbtmH7`7`n!&GBf4XYNDL zXWlC~|IrA~oXP!+!+7LF#wI5s@^96A%xjS^-1LJ0Yod|Q`dBA8s6x?D?j+w|`)%QQ zgn#5w3xA+|%6-|{f#f-ij6b;tAohMe^LA{XJ8 z;hW@3*HYaVb$(U%bbkz=To?KMp^BT?BVNOy^wk!tcS0A) z2=y-9H!(ehp8n2Zv2U!5@3qt#Ywcb9PYnQz|7-yGC;LG5b^`#&-gEI^C;^_(IUF&3 z&vLAIT}_8B=2?>ky~Vtcdl-958)Fw5BPHlB71v78Bfbz_LuesB_&GUGWyR?su*7>m ziI*U~iznssnS13&_e;b+P(mJ|i{M50@+4LM2p$=T&$;FyM3(LW+<(7n*!v{7?m=*A z?7Jm|@76paf`26O^-4Z%MF_gwd+u74b{srwn{QS4!xV2`kg z?Sc5zI$~WCpc1v%e(YcSyH#=M7R8}z0^EoYRSdd5h3LT)q6o?o`&R>@9 zvQadgUX5mx?8^CJbIy(P_=wKvxu5;Q=kv633unUlavco-cwjEd_GR}&vU~l?1A8;C zZ7n-5n=7_+v$di1yQ#7R;-894_uTu{A8Z}f#nIU^NC=@6I~M&Z>`62v94%mz6UG_+>c zZDs8+7S_j_UBJj`t{|D~K(8j*)A83qU#|ndkzzq)xmiWT zA(7!WiVGDQcZxK3rARkapcwi7Zhd#Bocj)ui0g0v(ZBiL+bB$gLoWk~c4PY7U=Abh zWem?pv9s-*Z~J@C$6Olc77;fZ0zdIyC~U6P-mcLeuX0o1<*h3eG%nNreSSVOol~9- zHp70{vh04I2nfI6*|~DS5Yj4IfE=_?+%!Mi=i~c&J-8eEt%5TeSei%k)%n7MZMpo< z!KsPMamK;PiKBixvjc+1F9wIenA>Rx{@$3&rd%}N!shc|#4Y$CZk*N|MLJ(Me&nl&PW%_YCYD+FE6&jzuUJcb z9l2BaAg`S*b84s!72*_;RazoPsz>ZYE1 zqNbVhaK!71xg+kb#M@}xZ8i$RXL|!11c#;uL2nWS!j}ul!-1W?OC)CteQeL?zww$U z__L?_Jn>)RZvy*)-j}!yHApp&AGCCIOf?3J!xN$Pb74JUKV(XzOCV zV~;v_ize}45=hT)9S}dR6i*O&7DwXIL4Fc0ULjTxN4JQ37Qza9`y|@$k#F2B{xcSW`gU=B2qkg-W(2A@ZgDEmRIot(atc)8 zI<1qnf)Y>f6@G%MaTpVL4s0O5Von4bYhW!mX>k4A(=xb}G; zPwW+EY^Su*BRuWrR|JYxPbg+RMI+!134X*sPnZ+WDApk~pZdvLzRpbm-N*fmW82!U z5}ule`8XTqNBpy%nF!T-Svz~^c0HKFe`ZVZA%y4)<_A0(f?p^O8RC?MegU2}l#maf z;%~kFWf5alV_?k58j9l}d5$oic>Z0VaWt&Nah+#z+|3=?628J+pO?<+N&dZmo)Lea z*12&ek15ZDuUgz8U(sZ+fo6jY{OT z27@&THy~yeXjtb#kn0BQ;X3v*Kr+tH+jZP~y~e41-bB%md%KmQg5rWggMx#?Lj}m) zDn#!7NfsuFyXDw?ewWC?HC)R*+T2b z3)Qe0Vz>$7A&j>L{B76P{X!S{OHNzdYb_<}T{KZZiQC|SoYMT}i4FXw0Mq{9c$|N5 zhy9yVb^gWkjq^xeF8NqUUM>N*IySyb{`qeLPQvXQ3_h#B?*eTO&$qVb1bZ^l=W3tz zS-&>MProy7UbS7X5pO8`-B1z^6nC+DCh+% z;slXWh3d&JQlmgUiGPN%A}WsuDUOz+X(lYYxD<{CO=J!rHW#R;_a8R&S^izSzr@@O zxrV^zI%EA+Bxhkv5lk^&1-g-fE(JLD+NEOAg+78^w%pBAL)o>20k(Fd$IZ2QW3Bt$ z2J$;Y*ACy+7>va^(%h=%)0sZc6d(fYVO_Id4G0zhWUuIsw0RA z#D)_7#1Z1>t;%7ev8`NmhjJ3laT@64&wCL3Du|6YLFO^%+{~}!R^3P5V+`_O*L*P+}!A@cc@^DEEf{0mI$o5$+P z9ggiS4jamC*E$|6*Ab6(9+Asm_JfVe3>ObHAP% z4Digr&kJzSTntW*n4TP6qOKkH3yc?IXCq@E_&gQJ6xI2Zj6!>f|MNmT?d;Ir`FmBN zu9YonJjNcv=pULe5)dLA;;6r0exCP?F&HnOWR{RQQ2SsVtWOA0BLQpiig1-^wSY#f zO5o^>59zFLQ~teOXMdNtaJP7YkiUzeY&=-YZ|kz7NQEqY3cStw&_n#}ua? zSIl~XpOe!7`03BLo{_k;z!aWVjC&H1$&+S;CgNZBP3Q2&>>OyS+{sy3{HOS=^Z0r^ z^&cWs`zRsG9+!<2Y{J=GnLM9!eBJ<A{bLL4QfG+mu%nwdvaQcc@!DslbMQHh^LZ!EM&EHw`NqBSr#g>2 zG#{~ep#aQNB`UcI2YHF2_bW|Mj-}bt`ulmgo*c6-jCk012857hIKic3GTEqc5 z%p&R4B!U#a2T2$t5E7!25F-6nC9Ol1*hr{Ou5WGK2K3&75$Kv*Mao-%?z=6@Y%{#?V%jyacY*zIUr z3b}W`F6cWqQ@rju7|-jiwT|`1T3KHeQ@jrQ%zw;0&VfDv4$q zLEU2O{wd3OeT{`e#PF?gBRzV!R9gSz~`u zb1;wQyO<{#-J31W&p@r`}Eks>=_k2#luM?S}9+a;IWNC7^- z@Bx}QaN1ngd%^j_W_0q(FFXo8?_qh@s=O~b$MbW81N3l5*jsk_Ci^MJ*0m8A60&CA`=}liZQUSt3X9$PQ<3xWQO)8zUuB`^FdG2!$ zo?oTJ$l1G@2*=@e^7&#VM*Mj+gtT_{aIT6Fk{f$-6G4KbcrZAS;$eCIwrkm(GiYbE zCbSF3zDKN$1>C^NdAo@3JQ!!5=ibl!RPFJ!5<8!n&K=R^U((1ounin2RB>>YiX#8o zN5G#z@FN?oD)<~<9b*yr8DrrL(3#NWt(ao|WDIe9Qk=q<>*2wOwdPYaSVtTYH_V~T z`!o|P&l4|bjBn(#j5B~3JfPe)d%xXh0`)*sHMw}kn~zRbs#*We}b zP23!ff4G_aWQ{!7fT#B$q$u8JprHTB=I2~1afiqQ2H zvAj3-rL**Y5HQ*cYrF`~jz)d*PrO`f3YEYg0#Yb(ZvnXwk)WC%LrnJ9^?n=laVjs4 z+-OYA&4`zZm7n|8j^h7XiKy$eUkfz(kVUgNhLJAt&f!tT7a9SND`q{(Ps>%8gQsW!ATsqi-#@17IKw(O#$mj1W}1gN z`?*OGyz!m5TDu)`|7iSVlcx*+Yxudn_U8tf_I^X1qii$IPIjwv!>-X7(^+v&$^p@^c}HgxXVlM! z?>Wynn>W_aMscy94fEdmEuD?ery2x#P6e&AgE-$~eZ>1IN8_jb6PKf|`@WKrNOwJ= zTAR2D5vc{w0Yr;dGsX|Fyz3 zN)Xp*gBFLv)xuSXL_>*Sysr208Q0Z4E;u#DwHoVM)=dGKYX>*DArMUk3uCRT_R(?M zt0Atv2fe>l`zUeEecWGrxk2L|WG|Y>`|+L%u{Zt(a!t7|L-%3}#<^iMEn<9Z7V8YK z)&gsC^Q6}7bJ!W|XYKPivpBbU?ZSm($9TtQ%U=UwR>=Eor)Hy>eY8*8BF~qzyHWw} z3MHe<lOE zPz_(hJ^6&O6+~_bXpVg}1LWK;tmFhCgsa$5@uBq|An_~NUoB^Zvz$4_+l?>MY>IP& zL$)2wn>T)8@H%|tEuWvc)cgWlxd=3eCVMdt^U-_=o5@~s_OcmkbYjJ3U;KghuxYOp zK+Q&H%SIMM8;C-GLKOU5A9jr~W_Dzrv|$KD+0V99VMRejA<}HezAOZ_XZDXR+z60; z?y${%?na4iC417aBL3hj*ao|oO&FK)yDezwuol)8`+mXe_x=>{;|cP&wsuSKoQ?pL z2to{51R4^8ZV0rdz!E8}wte5=F^n7+5oRG)MMe}Cv6d(>&fsYn|DA{?$Xp!#-E>wy zlg{(x_>$k84V-YmH@}11J{ya#&TSR5#cc}rj8`L0C8{Ai;yK)1;V;Gf)x>R`7h~i5 zoExA)1?qKuZctnU?o-V0nli>O*A-3QX!bh3(=;or#3IKbjmv)V>q}(=#~yP>{)6w} zH?kwP#nyY0H@n?DO5RlCKq#*G%*BJzKfodqXfn*dU*22XS`+fS{x|%AJ8)($ z;)5p=?BYrH*ICRP4bX^t7kwq-u>C2)>q3bp`#0iCJgSBw4P0W=48*I1pm3tUH{e%ttS}l>LOgVs{Gl*>0YA`qo>%j; zZuV3SS;ueL&Ni0qo#z_w@aI{x3f@(`>J2MUF)Dd7TnERKj< z@Q-+#W{7q$KI})I+Q0y4fP%oV>FEM zr?|H~>w>wIe1dCB%s7_&>3r&YU@fx;H`IeGs0TUq(15=*zQ$**Hk-lYwmoZaV_)n? z>toIEcf|0F-}ouDNN>i#XJUdmT{TJ8ahy5)Hpgdm$;5ZpCH!g+-W!qgsu5{FNMAsF z6YG{BHqQYk*Wa(*UtoOCQ)u(gnxf`-A=j+Gv)t^w9Q+UT+}vYv)=waXSjF4@5^~c6 zDVjPFD>;wpVQlcp`B3M{`ND(8R7d$qv%u>Djf00(yCY5?6dw?t_v^mT!Q0AZAUBhSyXbxgp&FiK_HUX~Cx%t-~{L(~cH5xo$;#VEf)bTH6 zWCJ&IEXpO$Wvko2`(xQo;+=+@e4GYZG;e+yO*PryXF~(vjd!*lry0Os-v1=dh-)5J zZR|7BeGjxVx|cK3xV=eGfE#nbm*7mHKd9ru4Nc6dvuHjXb*lv4Aw-@$LCzO*VU}Rk+tRhwI=4dR!&2bxr#)sKzBgKxLn4uxRA45s)OB6ktniPLF8gl z1lULNc7V$ucZBfux{rJJ$zd4hLINg%GYhaNycAS5?A?oGHhwpiAMsTTVTSAO(^DueXD~*Y(_Ec#LUG#;+oU zd70k=io`^c%5%EV!8j5dXK+~wCLBjL!EV=(%*7{jlf){{;T!k~;{SGuT6_nex<$M+ zA9dZ5i;OepV%YdH>?_+cKXx4)H2;1uY|6T~vuP!$5;I2%t~YdVjG^(;Ugu;&JFk-2 zwl<38#u#vghMlDB`$m%^{)&$1c&wEv}F z!V}`wJWd8GPF5l8cwBL^!Jq5<8mGdJ@42@&f7xyLbI#h}L&u#^90p5jcLzj6%)+Y!Jc)(FUd3C@-E`>(KV zp4EOsR4frZG2l$X9Bo+ptRsAO4Vt5`qoJ>XbD82z`UKoaUU1f3*t?ko{#s&NBKu?& zrX|3uS#%=!tFLUG@K;|B@K2SD_;t$h;Lkz+LZ$F$?QgyA@7JL3kLOoS1Jf@~0y%g7 z`sBp_KK=TrHxJkc`(a<~&y9m@8USp7EsE=%=c|p)v@!NBzE?bTOlVv(%(KR|1>a~! z)V&7HbjD1+73b<)jpT>9lIMI_G(L^Bw>XAR@X&!c8+Bg!DGjog3yiGE6sMgNIuySS zkEk_zz1QJ-k@11%V_xb?#|UCY#)pUzQ(OQM7Z&k>n83aS_GeJnIzCjr*lNa(es1mT zHe-a~Guw<6lV9c;*72P~}IRv+FSBeLpD8JXshIsf>7l%e8K#i#t6> z!qurhh#YCVr;YLB5&v=zj(^nY+B@-pxZoI=jZI?4DJ=$0h+nu>ta&e3YqSIAs|M$( z|GPb?$#``jn*sg_J^u!ue*z&5=Y{}{5bd9az)j-&&B~E9Om35nZu<^RfmgTgl-;5! zAmOf>>pt1_ehGUwVN{De^r}R^#s6DdkJ7aH$=d_*VEQf9#ql6`s$*2wP}^i}=X3Mf z_46yl_l40AWQ-h-X3Y_fbU65m zlb_T<@zY;tF*Lmv^84jDMiwB<8IB$6U~Oe@+m`)luh@XIx4;_WR6TAuz3ha_rm*8AY|g1-_SG!hu+N(BkVEZ*){@?^f^ zENd`l72t!j;;YtZ0n#`d=Qy3+I5UC0##!m_;OsW&+{^omXT_HiYRrF57CQJ+*iD5L z3ES&evZt`2FrZkVkf4~se%e2Nj6JZ81DNbY{BSdmxR9~I@u9_p9uE+4G!?HUCfKf1 zOrRM?T)0AWGPjX-?zj|ajRQ(Z>d~b|bV{?woEQoLe~tkvt* z`q{^P&u@uC;O!ldaWm~Q*+!6UHhCs{XdMS$2pf36+Q%hI-o>$fN@k9U84u5q$h5dE zUUeRsC-{07FzMsX&z;B*gYwbgrwd;lqCwnc?#{!rc!DJU5xa|f_=yXDifVYLy|QN) z(Xs(&VEZcx2j67BvVFH@v!i+CT$6bwxy0GqmgE@}An}-zO#XUi5ezb{EdK~#4%#;ZA^_|$VM{84<00^mk+fV2J#VChliq ze><$*mxy^1%Qa|yU^EHbDA4=b3(($b&r59BURe8O!haav&D?qJ#m^xqGM-sf%xaHs zj#w6=?%Vd2b7}6$e=J&wXO&A^OxQ6Fv{---_dGO^z8-NQ{6F&3;gIoSM`T=~smlB1 z*nPekrZ_cXQDXM_Y%esdD^_LW+VP6KoH?6#wT#WgWMZ!CsVV2pG=Sq(i&c(O8KV@R zB1dJe%d;;ol%E%`XZ{SzrS2Y@E=3g!=50Wowo|7L7d`6zEhcxgla%0%O?7dgD^i*5O##L8j zKCq@r`|=B;5&L^Bk@lV08EEg@qFp}&cCxKvj<8qQZ0W02do1hV^4qg%9$`<{A(QWt z4{3_{L2vV2H~WU)PQJVJS@>?>&>5^f{NYTnJr95CK_qp{_$#9_}C}xgaW zVYm5gh9vKHpP#6|0Rn*j^4Z4c9f$jTiRqdX8s_g6gp7ROgWUM8eb&*6gyRhbI%EEw zv37r7!*@9_T)z^Y6DX78mnsOr@8UM|?N!j|e1^woYh7n0kKu4M zZ6@wooKBl2oTu_8_(hqNd{3XV0Ucln3Qn|T#s;DAmUYk^C(1 z?V9?0yjNTsP%PxU2QvN*WLz9^Qh+Cie)5ZfTaEZ`8AEUSvEpc(n?13&*mv0AI*D4_ zCwW#e6}|?~B92x=l=HYi=fe4LR_DO6;#cFA<6-h?6hFj05_847^tJN4!^fjd^q5!U zRUGX_4S=Ig&?M*L<4DNalrsOKk+-i3Xr4fcbLN};53b>lH1o)HGzPG#&zWXGp6Bc= zbk;s|T{F%A`-_k8(s@&7aP<##20n*43-~+$zqfdcfY=l9$NAa5P)MJ! zU;K3Idi)|$ep27VnaahnW^qUJIv0yKQOy0wvCsKe+;M#RlHybGrt+(7&lxx#nKO|$ zJ6~oT(V6$W;XI%M_c#^4Awc85gRuPXB^(3(6#SEMG~hql*zn(UvtI0 z%e8=5C+nSrviG39Q~~cIzUR1}!`4uC>3MXCL9RH@L z9PWFN*DP^BwLJ5<0S{{a2G+D4d`;dT$j^9KV#MCqW9Wnw{n|ed9WJJxYPpV>H9bVh zn(7OxsYX2|yN}p4&v&lzGtWirTEwsBSChYN`bfmC;LaNF!Qg%&`M11o_)r^v$-PlO zYn|pEyc3Fd#W`ZO>p0nh`WG9y@g!bhPi*X_vhoo*hg`ygt*Up)jpRde3b_ehaAuqj zxns&3)En6d&^#NphHF=9&Uq7ft=6cs*a`jGfvhReYBjEFi$XR6$}bJq#Phm18U$g# zZ6BI}tdq%&(Kw)9PH6uC-C;jZScBN#C!a6CVeIIdT4zM_=EAQ|O$KaDqXGWo2b|v& z4;Hny{E2ho*9k|J(76~V!l`gF91kZ~VQ@7?f(nM~D6A=vcoN0ot8nG-jUvGfDv%E9 zyK6-duH_!?Js{#-uAp$4CcI3J%lMaQF&9&iYR(HqGz)s5pmx3m&=0oGgDe1trPz=` zT&O}GoR`7_q#^fM;1_V}68dvk^cV8LETR&p0|Ei?0V^N?9r$Sp#8MIE6!6y>C_ps? zifkg@8bQIbPkxtS?emkI>WT**mt0PR<=p3 z*{(ZtiTo1!L(uB1EMhrFovWSGJovUbau!ZElB*H~&c%~0{DFju)8H`jg)8g}hy@ki z6w&DiHzC-5BGIqEH(3hhCB$@paUUNG-UsI|NWl-+;rZEG59>PX^DiWigR_I5 z=4Eh`y_u8n(VQ$~05v~lmqhWke{badr}859#PmPo@!g-Z+xSfKORKY*}JPz(Us*l|X1F+oF7 z$?FQmAHZ3FD;1Nj{61n?K=>el5O2geL=oc1B1u3rA@%^`Tjy&i3EqGp)S1HrVjzGA z!3pAH2eI-O68RJAvx=h|V4Mbzo%483&CMEa&{|j<0*`gG5B7AW#N(WQio1<}oWliO z&?Wblyz7mCS?nX$t;D(EyWOXUpRb`^*T$JA?eg#Rarj#Po&03Itb1@0KAMa0FgVDb zT{Pq0vIRD=kBcwGk;r-C!%OMwdG>A&aOVBIU*k;TT$ZS7>a30!{rcHB6FkT3Z9ctT z@`bomuV-u0xNrs@v$x{R)yWy`fIaXn{0>{e7so2u5}RUQ^F*-#+mjFlyKW2`QJ)+25RSOv`g{|JQP&lP&-1tL?Wl)&pz3?siMk%hZXu zkyD2MG@qG#G<~J~rD9k|i)WM1G_jw4Q?UPZbYEI~-!R5mX-sbND>yb{O7aUEV2`MW zgHv#cb9G}$^#OU296(drO=lX+$`#}dn&9L@=ML3Nu5;0Nt}&+if_fT`%<76(Q}lYG zpTme~0x)6M!56|c4iaIDj#`s>q^~Y{%f4z|BpNeKE=G})97+={QkuIY-)L**bC0djx)Zka1Q6^ zOoDd}^?8EpDo`-S>bdE>oJcv7w6krTcAadh0wC>qJ{@-LX&*&|_Gfz{;?tfM>JnJC zs}c$^!S+RD>;SIuwH@5Q293@5#`3!t1jaJj96BGGI#+y8f(M}t6t_4#&fCygUhRf} z1QOz!gr{>R*3sxWh=@W=l^_%s28thzCj+|n2H|?$$NhmAZv$adb!-@En7ZzfO4Ou8Kmfb#PX3U^D>I_t*U19EG3aSaAbB6c+{u{#EDi{6|Be z&S-unv$Fs+2iG{@&)R$7^PEs;+%fT`05^gk9dMHCW2`oZ=4CxzQ_jit#TE8S%%gFS zJ@(6Yt7+ueC4RU8Ap2r#;uL4#{HOEb%s5kW4aH9t&L#e*0$HLGs5l-4t_t1{0g0$d zNQRjFpoCUnE+m&2!m?rQx(QvVf;O*3ZLDcokXK<}dx>H^1p#{%su=BsVddc9x%j;u zw==&wMlVsh7tC@%`e6AVel^Y_&Vyo@bsgt3iS-`#OiI|>{&5D~z6fI@#j=I7g!HO# z@4~#{H^J|MzZ>|C8UA+hTKIILzh0uQUBxthpEsa&fyFxC_g~sxvPn=4f{AO%C37fj zSe#ko%8Vy@UXF)4Lmn=<*yqWp&UkPl<6hNUA%K;)SL>?f~3_TBbi9;4jvG+7?lsGy`xTud;o_Wle8A|CizlvXyQojDfhUY{URrN2gt z%bTeC6Jxx5z1{eUF$bvFxQ3o5>h&6y_a&ma18P07wr-2TkHHH88@izjCl(9_My}1RiI2VHVC8 z5d?I$@Zu5`&z$w8DY6WLg>X5)00AnfXF!aA?|10)x_%(WsPPPqe`!HQ>b5}WER2;+agA~hIL<3&)*>w^ET*LKONo*a^7>vg} z%za5~0$@Woz(6*^@HOm?VDUL1x@ZRA6P%IGYB$ii?T9+lajw}Q@!2AXzUR5lr6Izv zY!L81TM2m1mS5%gY&lbjqOx1s>WZ|}fwWV{RWBkkr}qaCoVw0-^*N`l$GLX2^H?J9 zt8p09nS#c8_dtQlAp}0xikm%!?yM#_$eQ@KkMxA z@4fQ%HGUR1;g*|o=G}U%9BQ?5VOQC!iy`mVTwU9b~2Ge=tdA~(kfK|hK5F1UvXmq`Ehj^8;9e!KlG=>2~#^w0CV z8Md28pRF|yHktyneNMKhUkuEDC6J8(Y}+^mk0uUv{-}T-+!2yH#h<7L=QUvPDC@x4 z`@pCb8%IjGhi?W~f-iHhr%N89&l*v{DL!6~RD`4LiE3o@G^F_=U8X#jv#EWVM zMUEstj=ZRxNc}z@1`|4CtEix1oMbG#g8Qm+J%NJKZi5mqFUI@zxCl*?aKPJPgiNd@;XtZkPSTw`c&U z@S@m)NRWu4N(B|g)LujZf&ei<(L_;AQ9)6nBH~(vzY2@1RG?m=f`M!Hi)a_AV7O4k z+baSx&Uq4&jCr=m$@piwkW}HIqKdE_M>+r1;uvr-EACxG zp(^f~f4t_iLS75J*2Q`YjNjk0Uw_WEwQrDPd2Y?coY$&YzK)_<#j~N;*Xc9Xwe4K<9KWO)`pX1Q;L)CwOh-cjtbIvz4Hi zkDbR;+#Kryi6G8-pL`f!WKQO`FDuYLHb0sI56C^5g6tR$;wa)`YV+y||N(i3LuOc)?!A%jy7NY5qymtddfdAzKtVeL8 z1worAJD&T+us7?+{@BvL)JR(fUUDOhywcb7Ei@9{F1YRmv9g+c3#>?be{HKIH@u0qxh%hV*ZOI zj$@s#I4-caai(9DJ(82y%C<{;Vq5GCBBo>iM4d~=_&(2vv&r)rXJSrI4JyD|u=B95 zV-`)vu$OI>c4B|F4^5NuWBxATYvlL3o_mNrZr))VP%(3<(S?7W`&yab7JTje|F#3)c)99x7$ z5ot6L(u>MbTqc&E7k2ut(8oWf74ZWuT>XO^~2CHicTZ%ZL9gib0CP(MVCzxGb!z;H{!{68I9h{U`InQq<1{eG~Of z+=C&y3tf0Gg?he6$R9cWX63VKtl4~P$aQt$54IKl_0I>eRocx3ylvWw^b(j=nCIDW zPMlYW!HGY;4xDktJi%wG{?+@egD3FW#Hm?BXz(XEYVMdn_?WN|`fD6A%%c<^gG>MD zJBfL z6$C2cDfmxyK_Fs@>?b#YZ<5I&5WOd8fg^Y=aC|``kxONbyoC}1fLDGGJAS5sh8Y^w0&FYo)AU#7x#zqoPn3w7=a9tu1@dx-`( zWD%PZnRYnhL+*aLg9-^l*hko~gnVaKHnDXwXtDcBn-w9iJ2N%YreGHF-bU3+oL#_H&=s3}2iqG#+bYO=l}%?5Tu7!7u)mZ^O-#RJ0Ii z%-i(6$*;aFz5@yn{A%*8ct80@z1|LSPe=KyUZ>xVf~)&hA^ks~b(XKqd`<~l@#&;d zbm4zW+@Fo4?Z##Lp_8`~C^2vNBceCOF0m2YahBnmd1mPkd-*3*V;7wk-5mlT_U*d?1@5W!^dR*+6Qa) z9+VR~qn%IlY% zz^UXDaqFBf$^O{B>|Tw8Y%JkFXM8>%7y$cLG!*eQHyHr2r+`2DVZt|@Bk@z$8lTCU zW7HiWMAS)$C%wkUdEL<;2)H)sSjLM5dfnj~!>ITZIlto6h-^*Ph-_g1-snhBZMwL;rq>F4h~?L_HYStc}%?bI;anaX$(B zZNT^ElozGdrCbZ5a`XXSiEuF=_Y-o*h6zU0@a+sx76t$Cb{?!oWOA#>#C zzP9nd^Il^IzpJL!8j`1jhj6cWx5hbmwvC{hb8WJZg|_(?0)_^OQJm}CGZyyW4ket% z>psQ;o};*~briIYof`s^u=?O1vB%h}_gzR^l)&nZMc4kEeVspbeAXA{LDul*fzBgh zPSy^pmpDJy?>b{Y)KJW;dchBVlwaxrk8@|`6Xg09xJny!yJ5$f;9oQtIfLL~)W5~Qib0$s=SkeL zR@6CP{iW}39d#*5n*>fVp@e=p5Q#2(@y)IMDhen(N#gsI#LrjqrVu}!FdA_x0(7Q09m1QeYUV(lynRfZ zqsWwazEXb=sMv!agA)oOh{+2jD&fTi3MS?T0YxHaCKd$di)KIy37&dW1X7Gk0I-g| z8fPDCpVyA%uHo@0OtkrP#f;P>`<`+4|w z`0nJ7o$G=00ek^8{}?FW={{w|`?hwk_zpjx`}zg^xF7v z{f*c8eThrmQ)lSL4+S~#mb27c)re&ctYyRhI7=6ClP$Xc5qGhJg+FnH^UK(VEga9V zh3;`o#vbXHjJx++vWaZ&^W*G{I=?!f`576AbjM|#7tmNVj?FpF4&D{upXQ`t%N{PG zIZVSrds%|GG2!(@G_^QSgXRrr@3}5MkG=BnpE{2mc|L~pQOXmrENP$Icb8RyOR;y{;yg!3V;S38JVS^WE~FQN3%HPO zqH7e=iQfZReAd&bwc=F>@n7P&3;!jWs9r%A|6JSOZwKFNfu65Fy*BS$yc@ah*ae-Js3_z5NE8+V+_hyq2-&gItZH(;T zGyJuDRo8Q$8)h2Ed{sQzjY>nyTRAY$-&i?oLqHq@pH-1#?K9o3WK9f z^i2(dLY&ofZbyQ4e;cpn81US&j^)~7&9(lC6N4AVwC^Fhuu0ly)Bwvmpw|L$2dGZx z4BULoyh3bo1BfP%&ae362L?Llssqdm=Z%*m|0})`_sE~*M&}IGoz5A`7guM#=R4rNf!8!A;T0fQh!}U+zte-fik*C3X-g=L98$i zQwI%-yaf|r8y{gB!DSysDTS1R#-#{f#2^JYVzxjLN@GCZU8(@HU)RBH85_+Hmt<~4Bdw1)k;2Ijp;g(2J? zsKRg!<2s2U-CJvAOxwWgoYr@MV%!1+yyMqb>NBq`3v}B=<1p@p3f6lSw9l9G;bSEB zvuUO+{0yOT8i5#JlM|mz^8PHzTVlErxP30pwft@;_&ToFeJ)xxj=4af&jb{lh=$P+ zq7*Lh0%4$avwj!(+K+97eXzF>_SQZnL^k4x#&QhF<^b`7`B?|+VvW5a2zQAY5i2qV zBv$>r`}895ls^}`Ppx6;cO5a-{~3QPU^DMn%kam^Cjon5PvMs<1*Oj;&q8M(Vv@#= ziaT)wj_lznsq6`FTu4$7Al49QSrBjraIX9oK3<84MnJj=@V5QP{-f_Xzl$ZX@PP{@ zRX?)QDi}gypX}uP{ z@|)Ru@u^xX{-m{rFGcgrYm^OwXFs%k+=vo?Pyd1nntzaZ`}^Rw3mfOH$XzFWMkt?x zhvipFqQkfNy9D3chY$feGoC7^A6X1EUxM$^oC|;A46&7cN;b9F=IjvBvOnj*xw#>4 zJ1Dj+&rEi}->?JrtC->@tj4r0IEQM|YhL){Mu_dA7_)}YgKQ}Bq`iTCOkiI$Q3}CV z+xT6=jTpvCcx=zPXllZL8ll=tHB;Cl4Ho;f5$G_l5v^Cjijo9GJXY*sDH$pjKAbr*E=f8Nx|7J zd0ATsYlUhFex5`vh4Kz4VciAx^0S>#*Y`2k(bhWll6$PWs29-9HY2uY{2eiPQ4fK6 z?2Vdef>PJJx0HAkM)p;u^Vvnv~o@P0DBR3|=~C zz#;LMvB*d8g&YOvBfg0v%4_rZwy4WmeDm4Y`RBuwI`7HGn`iO<7T-3s)OK<1u-NY2 z`h0;t=METg*r>H9*vn#Xg*o5SF2$>^;fhm(O9AtU#*2J$Su@hqE8dVh%@^fKH;v$l z;sf;sO>oXW9xlYg%X?n?x2>c0>WlMq!MVEdERpL>-MCw1I9+5oO-cAv4SKQ$Ct#c? zLYyE%951nNLKG<+B#Ibc+WN4A#AcxQ5GbM)SwM5H4+6-O1R}T(3?o)IX{D>QhCHPA z89K}Y2Wt{El8of3UqMjSd#_z^WRBv< zvHU8hk`_gRlhvFjyLrM{qDDmt9AW=1V6cbmiA`w&@gAFBptHa>=gKw+#SOcVh?EFV zcCj)w8Y}J`E4#2h?9y#wd(_^u$X>>e9xuQ~yx0NDdjlDl>~raN-S=eQ>1jcnA#u{h zfrEPN>G-rSJb({nV+*44N5y9Qr0fNs;8ite;9J=pdtb^w;YjYnS9p8*kKgiV_?Uf4 zyq|Fu;Uiz?Oe}ts4>%Wcg3rbRGS0|I(YULiwHkUouF>@fPk2)6-=kuN@tKF*a~@A5 zh%Mqp#Fp?u)`@@F|M0cpZ*R)q;M42Dr|xT7fA=-!jde@FXh@Ha5p7cr5C^ zY5N}zE}*%drUK`bXJrwKc-367=iD#nMI7ztMe#;6E#qj#&=G5Lzr-MHG0sZ&VXt$) z7Uwj4hkeS{vB&r|o{Rn3mkYXVx)mt?jD zOMbyQat*xVT8bzNZ_W`P@PiO|BfFlcIk8*Ws29k!zaEo_4I?Hk;?amRRj2eE4@S|N zH9*IxTRK|JuoE`MYjZ8v0J46X748GE{(-))MD4fsU$JwFpF+=5t`|2s(_^T4k(`Jo z-3lk<@Agl}CYq`tlzS|4odZWQ9uZfFNmD+PuOiaLo2f>KhZpc@)C$|-m55)4?sKh^ zb&i|pnIm0~h<|X9dL=}E#5gxSQ~VQ89IM4o3psIgi2sV!iqYmQF@d;8TuA;pjuF!; zE@T|QhNA{aZrktd|KW?_mvIL6*M@aJZ{R%f3nS0Dqt1VZDZiE%Nb>tC=xytz!znt?kUlvEjg}u*{FIy}V58;^~ zn!To+PaYB1XhJ!EDDG9BbnX!ML+`a(H2DAr>g;{qoO3(ZwOR|izN@&l7SBd(s`w== zwb0N*i_iM!%NW;RucFqa*8}xgLhD~`0^}ZR&xI+z<%4F~Xf%{MpO!pI{``z{>dj-?IJs>?d*aeGRf>k3rP3Vb~RWJEhY)P0D zvm)1S@c~}IkF%9{&Ls(vAQ9apDH0aRi$L=t#kug(Pq!{ng1dwQmLLT58u+t@i*+5i zNXd}SU= zq4J;fo$fOVCe`@b)>k^Z-wb|$cF$t02w`?fm?T2l3E}WBXs{qD5Q(Q`$Dk=5`0epqGWly+VF{w8QW-*C< zEMijFxS)L--R||-WDnb<>;gD1#xEN&H-c#RSp@URUh+4dAP>R+ZS#da+IHg69*R5d z{~Y}88~B~raZ2N$?;YW+y`L?gVy)gcPr=x;e0qvE z>B}Hvk3T17WNhG`3zX;ByG5PxitWkR*cOiBzu07F#C2Sc&vSpB3*)*_*E~~fj9BEt z-tnicJOBE#JRk1!8i^@N=FCZBettgLm`8*T{s*tZ2gu72Q|tp^6z1U%9m6+(b9SP5 z4a!$Q_mT3A5tlkLj?CV#p1$Cvj9KC7UH3OEOXkae}@3f zxP+!nW*o!)X zHM@z4SaV%IiF#tFMZI`9kJsYlh}jW`D_&O(Z(vV2YebWe_<_A`cj8Fq(*bG~xIK$2 zpr7NBVIFsi7s-(|tng|L&dp$LjE+8MttZzuVcMfbzBxI?8`sdqOX3ckB=(4tClP;& zj{@gL9Oo=YeZ9!_#2Rx=B7d3l$Z?KS)K3ZCJD)i3Dd#DVaK9h!60^u5#H`96;tK0x zjl~7mP1;wTW90yBY5#VdYVoOJOU0X^$)rh|Mk#d7RrvLRu6KR~NKIG&?T^DlYwTF8 zeWTF;dZS@95`^A7kbNAx(`FT4=ZyjJuJg3>uDGVNDSqL{_%^;?btAFC4;*;dpz|hX z5qq51#0fY;O-O@?zqd=@9Ce|K5fw}n-@=6=&^~>(mxLz*ovQ)PQUE+t1Mb$qr)vTV zdnSa;L(F7T;216NXf5=^ZV0qS0FZNiP@DTek_mBvArPr$oLLfEiv0hAz@J3^BXTLV^xj8<0IDB?`J-c^(yxBU&8uPz)n`@twJjmM{ z116@)2myt(uNwj+agsEI%YbKxC#aKPNeFb&T{tO`Cc!xO)Bj0{lfJj{2I<{fKE@yUAkYV8+j59hz|n)hU}L@_RhV!syn+W&3^Z2V!D67#9}gaphf z1XkJWBnA5u99$>gz;Q_u_78$=CeGm>1lr~wy(lanaS^u`bUVslW?j(!x?y%NW9672 zi^{>9L==CSWHVTG4pl?oI43|QKN25Bn2MHNN_~L7<+&6^h$Y#3FHdtT9u+SV zJ|lh3`M@n=Q=JRr^!UUaj!`;~lU4BhJSfnKRTMqQW}j{+z_ESsQW$cXp8 zChs#CM_}A4L@B%|!kC*ifKw>iWCzyAI$7@-JZ1mN~74+)|7>6fL*iPen9*S4ZYwir&(Ig1lxfsTFT04F?;!%oneAV2- zjw;4&ON4bHi1)rW~Irq!2UmV{B{D$kR93-XalksPlnOoE_)MIl~P% zlSdwVW9!16C&UfTf8>TQ{V>j%yr3~FC-nGJ{!{+q*upt$eUTgDtOI%8zalKfjPnrl zF|UPQLhG>z9I-Fr-|%PF-m&uC%+EXEcZci&?*qhui~|`*E0#_mHVDMl7GDdTC%%R4 z9XE)(8GAVsH_;6Fp@2UU`}}ts#Ptap(4fXmc)pCC16m*c9iluNb{6G;cv>+vZIL!A zTLw42;`4}x-jXAArYq;&MK!+A;Mf7k;~m>HZd{lAiF~WQjz-T;O`oVU-!ryZ%aLNX zXKY|JyQG~a8+M-%WM?-%WcTs~&ctU_Ijb5^I@iiqa1VaK8#ix=E4^-3o;ru75;=-y zk#db=jN%G)4*8utJ_9u}HFMRfstJfck=I{w-gEq^x)jceldQ*_BkqV_=2X?7+H3Gc z_L7bKz+>pL%pa92$O{>dD@Jz>Hq%~RX92X!&@$+)Len~lNeh)e+6i4d#rv+=*3stO z=KmsMEcXDkH!$=U$VS7Y#X4lCrppSmdc10G*V|J*8;t_zv-BhLxHt<(lbhltoODhT z7u^8WxpV$*GKmlI02kgxUb{*8?5NXKnCup5_J};^P|%RjM67)j;S`Y~-bE5j7m9cn zh@5=Sb$e9k>{fAex{8`pRXCokiB8gl6mG}KAqf5?3la*BRE}nnX(I%W-T)Xy%8Y39 zC;BCFgKTMhVfloEfx1ICfz*0>m-6cJYtj+PXx7X1~Nt;O(tTUVF=*Ukn$`M_r$y@WPjV>T&jp z%8-YX~C;Ru%08SMeSjnfn5Ei)L6Q>_tNif0z~VApJrJ(cgT7^D=aHoZmp6X`uqk zcA|fYaj(Xj1tTH(5y&P$1|eBj&SBb@Y}BG$s&y(Dz@<_1~r zi$;}w;@{*GZ*A?jUxaU5T6q2E;V1fzYw(kb+2teqR4_39 za4z-<6yOEEGe^Xk;|dhV1MyiD z&HbK06xAUZjL+8VoYU{~wW0TQ&tfdkhfr&UdHHrVh35X<3M!CAX8DNo-th16Z5LRD z@^AdlzLc-u!<4KGEje zSZD4h_BIt+M%reU=dfeuxr${YhAj)K5x3&3Ie+*8f8D?o*Q0@{LSDYHhbOm+Blu4= z>muhk$9*q+&CSXYhsZ_5q4YV$27Jyjqx?;ACj2e)Q1>|+NBEt6Fa2*v#I@YRy}M}g zS?UG#|cRajwTT*5|~o__vF^#6z9pgD~`@`&O(1}tGyO6eWtlOmU;^e4W!RM0orFf zp^vdGG~PCAsXSG=YWjS>mjP=__8YHf|4Y(llS0qQm8UCrFLhevFroaf`&jd(sTP+H z=seB&fV&8Y1WbY}#VU!6q7)*kumrs3Gm1y9 zIa4G&T_itEf%FuS?<5UMkUUNS^4S0RFLI=402JvyDAFM?e@hE~e-=O$D2Av}p!v-t z-BjTGy^NIwPLU2^*dkn^9Z($N;;;wdmJm7CNcDd!Hb`BFJp!J_niC$n&$ID?5DMN@z3>-^P6)Xf2aGk_~T|Vq! zTLpl)KPvzN_=3ehAP|oNK*1Ogl#MB2RuUaP(@}}{NU=6XV$A0PB-fKXFi5a~wU0f; zK7$+DJB5&Jf^96KWIt?a+rkr)C-$e{qFAL!RTAU89E%W~6l@g4#3hP4&ROw^Gv7n; z>|`h)>=CjgNEPA4E(=^0-g79Cl1!J_?(>4Uwm8xhkU1??|q#Li*dik!>>HPFwL23$|edv*VoT}yf5}w`<88n zZNCe`_UEd|v5)FpIHT^T!~y%L&UW~x&K?fHg_Fbyd=ZX>AAZB}1>Y;53tv-i!PkcP z-p0>B`5Jy!zNSL5`&sv~;S0lGGB-ItWvnDewI?AXUyTB3fo0xG{HH?YFBcWd95%)E zDb6>n<+6^o{PF)6%=VJ|o9(~yUCVi8-?aC14tYj(X0yB}jI$3ez>5*@hJYZ(WqcDC z;UwIQ2In`kxQe(IF)c(k&1E+X5vgCF8iS5q-}!MgDO=of)1b!~Am6*dkWZE`HgrGj zz6;zK?WkC^{9Fj%U&esV?`N+Uv29<)65>jaEe(!&Yo9Hy?{N|EFlr4OaV+=8`mtZc zs*F#{7o&zyPN9CFP9ZLFUQ=A6R!C9h+8~A1A|5&CNkH}(G-6N1oLSrvzqX4x6>BQq zIMys4hAkm}uf`n%(V@Qs%l8Ycj8Ot*Ky&u>1Y)hxd}HlxA1if7#oXVjCP2pG68Zz_ zgR{B=yV@U@$UHzZw*9Jw^EsMdQKY!%-0I;()hDiFt47gTlZVhWRj0VAhELN>s~V;J zn)(*)lzx3KJ}qCSw!mlMAspSv=fHVbe1)gvRB~w5xEjxOkH)7tH|knyT5_i9Sl6%4 zm*mQ?1uw~weF1Jn!Y16!(gAd9E8dZJEbXV~iX&;&F-T#7;S?u$BXMgvP4Z zYvax4T%fP3uX6*npCg1D0+EMCE=n5>n+f5EiQ#93xsOeLmA+LzWj_jEBBn$$p!{U= ziKxp%v-y`R;`;u7d|~S-ME`CPY!AtZBt*e0vYs!p4v3iNDtMmt{EuCLbI++F;mLA_ z6XhHfYsY;?qF;_gQpDMCKnV8%E#kMdp!aKG2nv$xAX^gdH?^sy=qFj1QMN6d2H^@- zB(*|mcIaya0s8LUbeguq#}}^$J`?yv{$2ZS4Sno)i`sJ%xG-#1w(R6f+xsj=VH6~D zQAE%nc@dCD02aCU=N?rM@q~_M06~esNzg>pkyr_o2)N|hxyiK)5G05d3788dZuTl5 zp8w)cx6V`2K1aduECtgubHJRlf(kC3r5iRBU>%$_iBH8@y*`yf&EWG1)Az!1oK-MiM}FpY)cfrb^VV7dyGYb< zo}U9#Scm7<*aXrg!x2y+P&d@I%%>s`axL+%4XgHOjE zqABA&$iKY@)=yF6rbz6;;I;CKeOyKPS>JwDKK_dB;-<1}XulRe?9cLJ_%zEgS=;{xYh5RttE%RR!Jym#(Jh)BKwVD$% zmX5et@sd1H`4PcEBN?$lo}3AdZ###%A(}Ce*s@E$U}1^?NFNOUlixXx2o*;P2;1_< z!@#k?sC%aSj|3ivxT5+H-?#r45HfzP*kkB>t~2VsSjQShTwDY9a+Rln8U*Y z;%19G6+8F7qWGzrW1s%|y=z=uBcVoZbdpaJ;-mW)v9^@zebQ+zVS+gY5-SXG$Ctiz$*w}Bjc*&MJ?Y3mEI zl0IXQaX)*Ke{)NiwzBYmsa96B%RjqYPnYg7)8 zoL%|4e9ZpE1Bc|fxh$^vAw%Z2;@AZ=ox~*`Xc^{M@=Tw@zlmqc%j9Hn7p^fr^N>@W zf4}@gtw*&EoU8g)yc-R2#SPcBT{hQdYamt^>izPX!()hWBj;bP>)k?otG{Ef+o5c-Ks@p9_6ziUGx?CP^r1!W zDba5q!gtDl%5P|Xi3e_Yi5G5C^XnD)gCEj<;X7MbeD#N0N1Z3qo+na*a}+So7OBq? zDR+zPrz^pnCL*395}vHUcY?@wyas@D$H?J6tbsqI32;saI^nmpi1*8(S=d_f^_!#k znl}CG1_lX|&5W0Z-6@#;nhUaClr=J~6=bu|+BIuM#k)lnDWibtSQa<^v*mZo_d8&@ zhK^Y=1jK8MNf)GCp-ipL78BT@FVaNa0#>oSOP1>3dtLR zfG{91O0?{I?d`3-694BbaGHzyj%&Df_cvb_7hm%Irz%LBj~NtCR8Tx21;4q8P@oe^ zL(0KX$@zZ)35N`l+kv&OeLjQ6I7SI4puk%o2pgKqXx|xeZ;iuT#%l9GJ_QKm0I>t= zec>;F?9P*i<#$Wen4D3;^R@Zgx>zGg#mGHi%VX&rkS4Rj$7^cmL}y8dKf9)M^foJ11{(dap!_Zntv(i)E^dBT(8 zN`Ydoqwfbc||FS{ZLA;UQf345{|C08U`zhaNzk4JQShv^AdO!by z$7U{k3yzxCDiED_R5%ikh)e5vCkmU9b4EcEac{~mijBgQOIkj099_iMC1$a?$Mp&S zPhz~6@cq9F|2whW>+L>UyMHGf;dRYcn`ri4JCDN1{lYeRa8}?^dt{L|&xrvS$>hdW zkrTyr&y$fW1#vZsv=B9L*m1aGE^*d=we+dXrEZo++>00zapW`!)Ln>%Y&PO+)Nl9@ z*sZu?-*aq<_|mZl5i$@Wp7>FqCNyXwB9P_3;B4@{h*9Sdr{vQe_&DHuBkwa9OJjNt z1ov2v)`$2;5V7vu!+@KW3$*WRuhb2-S2q%9KoCzgw_}flTf|hw9|^U+G*4B7?BgNC zh)2Xz)x=ecXxtu4IR|1LXH3E@1yp}+#;d@x zJ_bA0$Q9StKU?{ZG4*%m55oLuW%Gyf31O#Z*{B;4q^rI#)gBi0Jg|{3%pM>pUsZnD z^nde4B*-iuf-D=t_(UOn8r$L@ zEdCPJPfGM=o9Po&sV5qF%5L6XtY5-%>eM6?fzXfC(_VqXh;A4S)S;Qvh}#oyEh z+4OH{^F|tHeh6`paj1FNjTGAlwg_Cm24)3YM;32~Ly6=KP&Cxv#9D!|6MB)dgr9`` z3a~csSbL(`WupOr&C7Z6j5se7b{5Qa#*mVrQUwh}tl&)ngD4PpXc(PHu|j}`fFx>y zA%T%#$y0tM-t!UuufFBie8zWYilDni)YC=S(-kyN6M0Wna6g5ltMN|KxaO%y4p(V5 zfa4S}nVaJ2SOrbi0*8+wa2^&-;0$pWg_y@=Y>;!g@qoZ2ShJ1-O$myEj`6+L>ic4h zj=fD;)&pp)1H z5*%k&qW4susOUW5#i?I@aqCniZDPqF+ z%$Yoc+V42uVUKNW)IZnt-A+E+W~XkOvWM+}4JZJ#pIzD;96y}`0sj#1_k1Ud2>gWN zf8BKzA0S_A80&u1V;hy@g3G&U%@ z6${ws+1m3S2?Va;>{&neV^_HACIIVD-1%TN0mw(rMXkUg4~-l&&pnZI6a!n1*~ldo z``6-YVHu14gj?ctfmLyPB>3pcXaAmHz8NIaH_*S+U*EUG_d8wJ=iUZu_I&pZfXagf z^5CZUnH8dy6RA@wFIH}x<;O*itUO8KO|GO6=SiG+>YSN^hX*M39U865LC!mhEBI&l z&9}vS37w1`j-T?M9yiXA(8b@+L_{N~i5mdnD&IPrU)ji?&QZa4uHwYG%CP~xeo5j* zz88?-ogqIb_5i)cPl-iK5P#-~`RaYm)$iFDv##?!x>svv?SRmG@7 z;Jqtm)cb+F?kt3v?mb&$*(SCJYtUXZwpI)>RFAN}b0iMAkMSK>6{oR1dCPHCd4^m_ z(d~GuSXyz^@lt1PK~^zJeAtLnj#X2=47ZAF5wD7eRVybyD|Ti4+C=etf#UuO{|$Vg z;JK#xH+?|@r~5(er+lIL!1Taqsu`U(M$X8lm+~_ODDgtvN;Pu;_JT{qWYgbmHidqVs0Pp_TWmsJRDnO|`xy{$S<3 zeQX=}`@CiaWtZ{VaUXR1CAw{m@^>N6VU{19BU)pOhpIf}(|P;sd4Q<82Y%R3$PudL zB0u0C-!}*0rTv12r{W}BhT9Qu#dXKpH?|J`>zi9gQJ5X~x6f}KtE6*`$cG~`(T6g4 zh2a091^@fyyzkc{-=_snqQ8igWXjDCqov_T1Hi0i*Fxu%MHOK@2ES%mkcxyt7D- z&xV-e9*|=kk1!-Slh75cPatS3c%O)%)coLNl0Iva`1C#zS%!)NB?Q)f8sdvWVTrH5 ztnUhWZGn3Q#xNMqsPWr88=o~O2%JJPO7ME8_v14P8tun_`CMa->y~p2)Ec#RQ2TG# zm9{utg{m7m;>76`QVB#a=e}D3Yd1v}#hkdX6S!tT_rNj6a6_|0^G$Y-^@M#lZCiG! zJ%>GjY=VfkZFsG7=Y2=n3^dyedz1~@-p^3s!rI5aC^#r~6|C(i6rb{ub17B;;yA%) z_8(n$mI`I=Wen`V*w~!;S=Y(`oQ+-k&UeCZ(qHfe$9;-cVt*B`fMTQ28vsUr)~<1p zQgK0JxuKRm$6WR+`7Vtad;rZ23>@L`iXmM1X#$H zD4xA8fn4ts{j-^1?r7h89RBM4c5~JH+4psC`|Q1-#$C?0#K!y!NYqcikiectAoA;6 zMjl+|LY-;l!K@qV=~2&vg()AlLU5y?%iKsEM1FMfRt30nB*i(u+A6=0UpZIOknaAV zJVSn|yh1D?b`UorUSw>Pf8Yn?$+O6#@(W@FKC%kp2eE-z!nMSe6w!MSvlVlQx5Qn? z&x)IhQN(HDremkpLJS)@DdStkyNrpEr)k74K!aA_8I?0foXqAf`z02zXXl8X7X;#D z%M0uwzb=^lMAx3Q<{xKIklW(}$ zQMIEGe$fOCO)tf8`$IO0?GF+(*xG^`e<*I~T;K!e#F=q^XCvIjd2$AM^88odjt5FV z0?H-SoBE#X%mZ;{oHzU*bwhH#bG`Uo+{RB6;{k(e#HdN9hYavy_JDyJ(=}(WM8FZi$s_+{?0mK2X!;ON# zulsKWoGA$Xd*DAS$W1|S`F;%=?C@q}F8#y-+!*wJ=QyN@$zB*mF= za*AaK00jhsgoB89K+xgmMsy{UjK|ZxL=+PM35o>({++JnzGDc)8iTPMpaIF8;GM$X^J^Wf#lfEfK>Iy~!i5BZ$daf%O?zM; z?B@&|O?%rdr`wGkfRfaiG&!{QJrrfw<=+*|Auv~hd1qiP2Ct|0J|9F^uGg=XH0v|J zUc(PS?se_^9N*CVUYFOIYla^ZeuFPS$(z4t=il-DxHjL<_6`<%?sF$%pVMAf*R0?< z_VjMSy=|;LnqvZM(B}hvUDG`^)@~K3@Cja(-<)1TU%>ZHMI0-z!_AW=`cIPRw-5cF z^dTC(@LNR*oIj@gN51X&pW+{%pNsz}D2NFivu1#=$?Lf{@!|ieVwflO6gKj`Y82wf z5l2)!;ae3)w1-_3VTciumTWM%p}(9ZpYa{nuDO9vq1^?5b+lBoW;nGEgvrOpr1}<9*lY;&UrHj3h=>sFzOtO z6%}V6_)BtO6lcZl5A$nC3Uv7l&8NzX=Clj7DQ}#_6F`KqbB2m8garbFTtTjssNYS# zlz-#r5d-Eik!JabdHMN>U&ILImk}G3dmI-eVe@kPTs@^8cs1P*!e_`lCMlCh+6r*pFNvT`f=weswE-4UN7nti=X^?i<-BO9h8W;(`XoJqk2f2gNs z56vp>jC#5FSTQK#kQ#rV~L7`1pk90K^!@Fl=ra$nSx z+UHbLhA(|EpJZYui`n5rJtuIkafWaaZn{a7e&gm8HI{wm^{qXsInKggk zsADuSI9iK1S_k!E1?UfRNLt*7D4;692_FPHqz~wjKHvs{iYlNDzMo*n;p73m4{$(* zen>kWl!D-PAV>l{zyJQJL4 zadAgf0XhCAKXw2d+8!#h0RdV#4v2S#lWZkg`GkDcNZ)|36w>&IedK?)INtq(7!H(( zhzG0j+%dgS33vs3XOO7ldM9nhBHob9GvP(dq42}!v@YeDqqW|MJKB@|OL0ebd3f-ac-=UaqH5 z>$R9ajHs`t@H>fk>@^ec!ta6*BeOi3dD3}NdC<9k#uGeti-iXHMSOQmq#>rF&udHc*l5sbmp>WgtFICx=kd`^ zfgU4UY@Fg=#3dB9EKuzNhb2|u)Ogg4|qaSDzhL{Iu+JamDd zZgOXRz%X%-A%*T>e}4>U`&<)3_xIIn_JdQ&UNskq2kJZAD(P12| z!#G-p@nJyGsm*;zoBWU*+y$04?jlQu_<#=QxBg1SmICDaRVdOhaM1;-SuhSJjjv$y zn+iM=6!3t8)_?-suY(f%pu}|+6IEEu;I8Q={*#eK-ZmK5uZ`Q$1^-wdYt7)?_Av$D zX}=L{lY~V)Y?XF%(}lvDB9;V%6Ug4cNy~v&$&Iri;o>|bWd|MwG|p6K%Q;*05cm{u zPQnQx55ZAHJOx6@aZe?1BEkqJ3NWXOl&6b)XQ*h|4G~4iGbR4cRPgm*z2B{%e1^W` zy3<9{T_Q7MFy6@;_hc1S%tsTz94y}1>0c1f#I@oX`;g;*NRD4gx%OKeL(JG7e<`~V z2%LBhd%svNeRgrtTz+Xe_IGwd5_^y9HyIa0i-*5MM zM_scMDtV1WR`DcJ1>JV2YkYklPgu^?*EWk$6{n&g#Kz%Q3x6V+Q{3y!IJ;wLZcrpq z9Feq^enc^Z4^d3uKXCO$6?x*=@CgYjb5LA^UtCK(<9^0EPUFHG_~P89b+9hD@ILYr z`_Nv0^N+|wiqqf^zK}U)iu2A3pZ>ccaHoGVmLk}{5&%sw3p_C&B704McWfu<{S*Gh zb^ZMsJZH>5eyy-ro3Z+2z65>l739pr&D2|DH#5wVV7<$G_DyYPrim!+Zzb2-QSk~h# z$T8p{BQ@N%Z2*kJ5oKtm5gW}`+9V5v>;R6HCMMsE!{qo*EW=GCh z;4{Cj^#E$BwqD0^{L?-2YbX3yzF;3RhgEC9)xl51lExJ;X+FmTsT82*=X!uvac0C#_WHt9BcA+?IJemEq#dyTQa9LN+3RATvpsgyejC5*p-JES z*f&0JZYQtdwAN6Zt{erA#`%k9{Q+y`Ct|4uy<#ut?DJRMzlR5Gen|L^xczo;8!gcI zzRzRP$%i86Lr%G6yPmrABV9sSRbQ} zA=Zx8Mn9|#WAqQn=mf+MiVzs;0|G*o;6Z>HMRXOkDg=I01;HeOv*=m~{r{|jq6&;c z6(8W&Ks})uMOi}?F~1CItUd=APd29YxT&yMFMAjbf!y0@2xM_&^kN(vQ6!B5n?RjG zxf~Nx78xOY5|J}88A+f70|&7i zHwGVy6B%NjAQ3!llkqBI*1~6oucWQ*C&J86viKy1Imym_g&g7pp0Uf#OX61Z75D*9PF8|* zV+|of!GM2|7{z&F53vY7?ots5-|#E^ik$f$QE$X};*#U8<{|evUlnp4{<{QgTOw;M7qGQ%;j~;kF>&Nj)}c5W@w?Ve zgI$GR!HxFFtz$35dhTH_j@9g^pm8-md-Gfk-s?cx(tjs}jqAHPmg70bkG-s6ZC!CM zQMA|y?B9@gM)S^Uv!$0%Ye z@k2t`wyJX`UW_;q=j`S|_rKv=d43)BImqkXB=Q(qBVf#o8JsiodEH;9PT{;GKlD66 zo~eB}9ul`5`y+P|=YtEG15#62XF1kq9Ie&@b76jr^6PfUe(b1FMgIg%V`!;yrvue-dxpw5B| z(d>eH6kQ6w1aN|`&d`CM09FCFlN(}Z&~g$pFW^Tu2t?{-Q8sf-2eSyu6yXNNmI%vt z7J#~*dl}E;Yd+>CSskslnPWYumAI48-VADKqZD<&(Ku!xV2W$WH=~5l;ZWE2F+h#a zT;se1)&lCh>Z~&Gv6oR8b4E?{_-yK2@=P3L}MkB}geDZAiQ~8y=Vj-Wu+?bsW zPrb+^{d`;u&Er*^Qx-vvSt&9Rfy68ddd{!n)-q-t3Ao3xCF2Xwm=*%eQ?VusBzSYm zw@T<|T||ycfe`smAQxI_E?(FBM8#=CV|f4MOKgxi({@mtGiMRJ8M{xVkYlahf9{<) zow0c%&Sp%tSXkPi_OJ1PxMs ze8l!7MsSWgXZXBr5xk>LL!tpEE**7ptBkl z7Tk&P8QGM}y0$UHPCjL%meB;+{#;eqGMnqcUf$~#dHuwK^fTA=D< z^h?^ef+q-gMwVIhqVeB;NIM^9`m-8$OHx;nIh1jgZT4(0s z+D~J$IZ)#Bb3kWYO@ef|2!IM`bU(!h7vWjx8RCosISGK^EpAodc0nPcI$+}zB5*?`v+M=719!94m>BCIP`u_dDQ`{3uG{i5mk- z;^Le+SV<7B$d194_!UxdWr|P9ljO*Z8^wq1Vw2-j^X={86BsdSC4MZSm{U)D4c@;O z6oA&D}!G@lgXhl!?0a@+{v{G{&iC3gUSVw1Bg8-R8tK}O-eLH5J3)b5 zLcOn_jXY<@#*MJv>c!L0@r8~(&&zYbM%Co{$%1V|L-CfIJ{@NjR~2e&aIyUn`9a{8qxZ>T?dT!7RIrT=eY?Lb9O#-epKENPs%Cs3v^r+;7{y? zy_~CIM{O9L{=;X({?-HAd-iMjR!xlku5;?RdYyh98bPs{n!r#~?D(wftyqlt%E#r~ zSYtN*AZ&xJuxt1w^F2ULR?e&Ocdk3PsRICQ4A5#<@aJ{BG_?6(^1x*cs|^8b=deF( z;?LZizn!CKudp9$6@SJ#_!i%5T~iM+AJwpNgM^-q8a8i|P%nmt7F5SpEi+P6-`2MC zxI~RH>(gb8s;2_nvkZN#{v_7Hdf2E}jk5jlC-x(@U89KOs!`Zbu|`}~Oeu~+59sMC z-(CMY`0Igu?@hY%o_F7M2Y%vf1017q<`@HY2S4xP|K+XL1u?%ZhhpBzXXdhqZSh#e zI5j}^^QfWXv#g&9^8U*YABf{$*hmf1hyYBIN)l7>kO%}&1h0ab;I^Y0CSYCuV{7q2$~lz1YsYa*lA zwSbA(l4z+0H5=3Mc&@~FCV~WcyhkBP;!ZMK8v!B6gP^$<*HNfP7+q+-YdAu?qvX#E#R3v2GQEut7KZ6lE7=3b6_(1#^*_ zK(T~WCPZ|PLu53tedck_!drxuP5mOQMOqZ(i6XIrgalwMAkNRipJQ`P&B0^~X~ugZ z$v9TygiSoeAt$#) zlYWJ*iT}}WAZpB;Yo7Dk9v2_$n418_dHd4=lixm(+$M}o%)x2h{5xmNVc`#W%bb;) zl+KspqT(R@x&#y`LEOSOMQ#gQHpeUkh_T8Si7Cf3F%>aPEOPAHXv#*QGyNIw`@GqDu`q5LZ0`HX1lboH90cr%;+Ub5lRN@vk>ji4s%7%bMoC72!ZvPoRXNrHjc#+j|gOo>?Nda{{fmm-?`Jq%6#q53*)@r za0&F|vBP&sbhnSlvIf~6LZfMsUBjN(vdHYFS;2l`FYj13f;6^fybC}U%3I)$vRmN0zksgPX~ZW-b;35Vn_2~@-sJLBY9#7pzlg=i2e)+xw5NyBj#R$X}6e2Q_!k-=Nqv~=9X zoc^KsYlugSEQ9p{bNdy1OxeQRzl-#=$>Nye8Lj_V`xKI_nVeI;4PjC_=FM5SN$U;t z#z^JZrXPams6T3uSRZq(^({de{>9Jmw;Sr>@#gbqLW1)|)zB88z#{Z1%sJJ(Ca&gi z5A&QJgE6;x)pc7OSG^Bx{x6@)@A7@+g9LN=o?IApJ!^WzfCY5;i~7pnA!iLICaU%T zazY#Sf#B7y0W&Xxo8e@M+yZyWc<)BccH>`-NkEOu)3ga`*;oPZdj+;o1Tg7X0^Y!Me_}E+=w)7@J zYsV3DK)yux5O?BZ5U~do7X{*lc;u&bNulG(pBsJtZA^*7iKQ7wYM_E6t9iQFG$4m+ z(?I%Eu@R`)=>EyE5skih&|m{V{-+L99y<=k@3tpn-10g$9l1PsUG~J5u4`pCYq*B< zQFdIm+VW3%w{q_CF0;Y)3u-=jO@A=n*KzZFz|&+`{udW`?tz%=(x1gG1vU2t{^W1l z*uMw3jIo5Ug>h^?X(O~=ivv9eV`_y!Ygg;msHM8=f3+E)rv|9E$(!fR2+afEOur~@ z1NcN7Say6VKu%%v!FRdYnSC4a^8U|!`Qe2myA0wi5PKk-;4*kKP!~xRL7n7O&6fyi zyR%V*LlY$dSmL@i2z*D?PYO&}Om3*ngPYfPv5&dky}f&9y-2aia0=8SK^5${F5;?gNB$K3)+lkVs08!!?89YiN68wM?OQ-<90u7~; zqY@y4a~vZQu5mk9V@?$9n6uZy`Zl4gf=k?0%!SPqZ&4gYh%fF1(eZ{K6mKqs#GDG@ zDu7$;y+Wvvc43S-s94Bv2R-7U#M@RBDfSR}6+}dZgjf|re2%~IulyNy!-gW6Ac7WI zJcblRu^QiKM#AP2PO@{7f$S{Nlkv0{t}0$tz;wrB3Z5I}=bA6z9LFx>F>b}MiX`!C z!oP@N2y2hoBSu-vh|LCZHDYVUSH@TuC|@tGtpcR=SFAB_)+3I~Si>(W$jxno*DA&o zU&Ut~Pv$YWqc;dHHa6=ir@2u8dV?{VX??%R{NlJ1ZpLRr&g=88yfM$6V0H2qw6pgj9;7 z6i60Yr`Srt9VjjqA6G86kRrBwgMb*0ob;FVz_r9GaR*P5mr3ma-P<#e9F}W~**XJAuILA$&>WtSFaqo4(HE#5ZZqRxhFe}JewN;T&w3V{b2E%VpqOYj%72O&29O;8XbX{D2VF|^ZmZ7>2r#AJBB)r zM$>%8Q^nHusQ$Pk=l6U}#$ADPA4QE1j&t*R^O}LUUVf2S#%E&qmsquL1CAGQw|SZP zElxHcaX!Ik_4#_g-qRTMlj|{7U01-C)Hk5qHA++q*u8vU*e8BH z>n{1;nmc{nn+e2S=L=63t-7SVfICb7z^Sc&ya}K>^=h;ayj|Rl=8^WjzQNH?4a8iD zcXCJ^OWZ0}lY`WxhAm}t$Dp-9X`a-Yj=j}XiEDXnj=h3oRjBig*{DrijdMchm)=E# zSbGrXQ*#h^vA&Fl^mTK3_f$J4%E!acvSVxA3-FokJ^KyuS)ll2>pO}!%onZSnB(9H z<-z2M@Q*e|nkSa8nlG9&%6HTe8dmvm!ZydA16THb4t#!$%hN>i4X8P&PRG2Mo8Y+? zq?v0S_c6}#IQXBj>2@utQ?kc(lT^VkyId?gIi&u0wuD_0=WeKz#_7ry= za}{si5a=QaIrAkZ>u+pD#9>=(F29D~Ap(&KBmr4~Dul!gaiTZ~A8U6OBCt3@j7C5#j9iXh+98p;#gJuWi$QQX6QmT z;p=bkj?cR>AI6>`YIcm<;tlz!!^AwMkk2n8TW3x3hr!VAFfWH#*-+3IsZ$gbX ze5kuF_^&~{&EIQYQC3Dykl<7RP+xRPpK_+Y9wMbLX6+A?!OuFU1<-nz@D)a#{oRiZukk`0>Tw za161f`HO2}{)peOgX;lx0I|_E0NZJ*tB(-yTD`9P9O3S!b=0IbcUM zbJVEX#4+?msnj)uZP?$nF;TU&@ZFye|HS)-j_G=t=L_}OdVh`I=hf$DO}<6)c)uR= zX}In`g*smA@VT`a-*Z%rU-n5~?y)%IP%$=riQg*UdXvQU9n6~Y7>YXre5*seoWi&T%$(^s0>@0;fh^S!t}0M|#&Yo3oeh~HJS z!w1Ac%y-@4ul&QN5o5APaNg3y0c&CFVE9o_EvD}6290yE8?ickCI6M}Rak;Bg-LreRal7SI|(zpGJWzTxM*LvButxdazB z4~9Se6qw>1d*a8|6k+cRO_6?FIQ`W=13W!<*6Gq}smq2R4cF_g)stL;xEDV_dGCec#^f=aVWG|IHr(xG8Mfd0;pn%-!Y`W5$0# z{{~QoKcESKa?VP`noLbdMe&@>k(9o0C!~vr2rfm71YZ$&i#dvlaEgHvcLeD8xk1Op zxezB~%^>bTJ_Qqw7vYl@=0P@H z$ZiO*idm6bKk7CbDmN>unJUbNYBp8XSPB07<9eM<)_rWpPk7wfVEOu<&up%|QK053 z3odKy0!t$m_P{O@I}mW#4m-N2%J&fuuMa^7L9khr1=S=r;)}eExT0`!4*p~e#=<|& zH~HmCT*x0WFZqL_vIJ;F9!6R=m>?J>B1bRANh1uD+8*0QhWe{b%*?%G;*9P_oE z@SW?{RBYMQ&ylHm(Q^J`g{c`v6?1 zS__gN#qqAY4Rij{gf#C*J%*p*Y4Lt?`AtoJ7JN?KZLWHtvAKqt)4aBMe{nusUw;}j zo)$_De8wG6)zpl^6?;3!DZgo8olnZ80rAx2t%dw-aoJQ4~gX~-TRSXko?#G}YlVZ7ww8+7X8vn2&!>oX0!?b3yma%Kt>aX8mvh?}-zh0ycXtuFenW z`fGk@t{?u>KYw^3Cvh>*Np!**s24p9G#>#GD8_cgc(hE50RooYoDsBY{`>Db@)64X4AD0NR)JdUkF~RI zIYtTZcQihPD=&PX7z=VxNz>qJ*8}`NTO}NArc}0E^!}U{nhVQ~x z?IQ$o72)0_2-~;VXnSMpv~w4&wy#Cug~fR=^z$cuTyS zjnW&V00><$<0rf{q0c~`dxee1??B&2!Rz`nqC9t82bde-Rp5rSIJU9zXn?U8J8a;1 z7WX)&5!N>iYBs6j98f;hPoqA|V^g5sYgF6!aH{!q_};ZLePdhPvqRZpd(Z2w-`g+y z@A5^DTi{w}eKlt@$H~!yo3&YJn;?#(0qADg+-!ap$d`dbXahZggJ5(%m7QC3mpz$} z=0^REd24;Ke(I=%x4r`;@4m5KttpPzI`FsVA7jTgbnTdn>IOBV_$wc?-9`6oZnYUH z9-~GW=Cp~Ei(VdH)31ZSJf^s;_-umtT%zU+?@d_zzY^4V4R7vhE;+xu>o-R~2RG-Z z9eTZWea&e!Fy?gCM;e&k6xbV>sI9;+_iBQF!#VY z$uXCjqVsWRyyB6i@ro}1I*$#GUmu&~6WTDJ7#n1DjC@>8e>be<>*OJ>y?mV;LFM1F zhiqXEV*RW+_$KxKy3t2NDsGO?IS!jq|F!lQk8$OPShKl^c}Y*PanpLLv}@^B^U=mh z-VAWwPi_D+cMKm(4hhcjHGbz)<BBuQ)y8B>7Db*xdgu#w<~u>oTWB$d}Upv zE-9X{ZPa%4OIsh6T#wylXUAN}Ta3kcXRpBq1pXwaoKMQBkxLUZXYQen&3JKMR$etn zR2(_CW`2Q~qikTEpghACX^+$ikxy)1V3(?WX&+w z_bRar9=}hc6QqsYp-N$rq|lff0HB<;UFQ^VUNb>}4GP#puzku#hK@nxDR{tey6x6> zTG!9@)!ZHd>!0WCsByK9d0nrCpe~F#gZ6hI8>AAa&jE4c7y?BC=gvB@ry^7Y& z!jm@g9ivzq>%|Uk_9w13Qw$%Yb8DOnyUAW9Ag8_NX22?LYZD*^;zdD=b#He+T+G5= z#Jcl~oN0dW?zMBtd83@Ee2H`YZ;>(|@IyhoxZqvoc}#Oco!g%)SNLi0dx5!M?QM|6f5BrPsOv7Vvfew#Sl14??jo*)fy1}lTBewbr z6a#8D(714lxbGhS4bJ1w0mQZOvpIP2UjrUD7Zx81)>LS$dq5AugK?aXUo{4dnqZD( zKFyQSz>6Qz3&~B*IdF}6M*LD71HWKT>{?BE+BGW+8RPUO z0HC%Mhudz=%jRRaST;)g+;H&B#mYVBoN+Jb@+sQe*Biz-jmZ;}6PnLQUOA`2M=O^C z_6L5D4=P`5`-n5yo%jx$tHx8`qmFyCcW(H)smn9})i4aI11fA_f9S}_{7 zw$0gyp$3i4b5+p$KyV5-r@4 zZIOpbcb?azTSQ@4oX6`Hy>(2!z94^4*H_YXgC7AwU{n(2X2sZ`a1b5eJlPu)C8ijc zYp4lys`&ii(VGC5{@kvyhpOgNu0*H@Y<_^#>uhg_)lW~ znG!hXe7=7*hEQ{ex$L}L{79_9wX;ZueZ_IbZ`fZOE{HR14wsq77%#?Ud|y-bBI-sr z-R3W9PB+y{Of-)fC48HI%xkWL)I`_H!C?}&Ep|r(Zn$x=7%VuH=qHA9Ft zK_V{WehNehK{t$B&P^_QB=1gcn(xo!Hq6|N>yevoRF#(j^AUL&>s4c*oc!F%Nj5mh zM|eE^2j4gsiH}u}lbf7tUAy3Kd>sD$&QA^@j-TX{uru~AdnbQmUu=tA51W=x^1F`Hh*I4GZs!)`@6kp0YSfEaXKmPV6ZrOOI4wgK@DhwEoyem-Jf zaqqD^?%l{$tb1NH$Gfp^^q(!v*B6-Q3>!>4WbRciq8)0kZG&15djD+h1oH`tectJqAvHZ&Wx;RNqjz2HWQwPe=~IINq@n8R!`;V$^9beGm7-;_^= zFWUC9b8(8}O7R{Lj}(^>i$`oaX7~6{bUYj5ysX6m#dPJp;yG}{^A^W!8Y+e>?pp7a zUMYXI-osy^Pd8uTCp4eyi@5_0-@Zv-WPK^<|G>{j(^F%w8ehJ{Z`2+405>{1YqjS? zO;qm^apetP`O)>Kbadw)>vpb5J7<17zfQmEhRj(j>|br7g@46u32(0PUVe^mCh${^ znd8^BD({xBGRHJu<{k6T4K=;F-ea{JRWh_`i7Vz*0do~_K5g;%}0JsW4-1VJ{~psMcL)sf9_WvzWrwdAO7Ws z_eMUvpd=vp3<)Y{`*fAoDQZG%q^Tikf_D7?s7RMDjw4R zd(yStooFNvMB`!*g+T&nG{gNkQ`#wP6yqpnuua5P+Xb7%^&ws6)OKhQ9&2E2td+Y) z{F%XrfBWW3;?|eJ<+rpyhW^)VhQHImcct2vVLR-G&9GMpU)zd2l$~Nd_?9`?gV$9wo*kT4!4qv%Ql_0){@8BoLsgM<6GlmORafP_0 z@*lrw6EyN1F`+!J=3k=kC{z{=@_!Wm&M$AYSs-|GjTo^VEaVs`^6Q3+pMd5-&R^Xz zs%BZ6msg1EHxMY~JM)4wK)j!unEAPW_wiTb74n)EysXbS=QrWc$MHIUjcYDzVRrCY zA~!k3X%=JTW1OpbUGW$-P7Qx?Fu6W?thh{E;5tVAs#@VX)v@XtM0{CrDQ=C7Ux_Q= zcqO;sL8Aq8aDf{|xJ&$K6i@aB*&V(F;tx>yZ7va?7|AQmE#jBK5ryY+jBxRvdHk<5 zw{brV@|ye8J%L(7-|^Oel1Mu#x5KV#eoyWW-zk49cUxp`b<(jKaTc*HL8-W{*gW&n zB0a{-I$d!iO_Ty%u>;RLeiTCi=2UT%vGfFdJn?7DF}X%ui|eWBvF2bd%!#?NcIyfF z3oY^K;xROY;q?u8Ls9FW(EEHokB>1*7w8&opVE%gMmPBt7`DmvqZ7S(aG@2SKp)S! zY#cCG6Nn9pXvvYYSlXY8b%uz@(YI7|FQeNzpX+?0a75nR=E0JupsKgD=# z9LS!;nYbzJ7y1yJX|6GEX=v6t>m%GkT`VqJoL(HK}|r-CR&C7`2h3x;5eVuh7TE`Tje0Bd*IFHlAWLDB{wwqs<`45BcX9 zBJa#i@JZ*|%Cp|2^CsvOh&wRf(>Z}S-?`WQQ*oE_j19Hm1Gp2OXkI3-!q!>uv1zQI zJR*0h#`n_}*noVYo@>3e*2rIROXRlJb(tsJa9U5Lu3Q;6<6K*R9%Nlz{m4RZW>?HA zZmH9ZSFtM)zmDPRH>M8l_$`}t>|!s+tYdZ93g4%_mOipZ>Ubo6E@I{%$9pd*U~0fE z;5+Dz4E`>V6B2E8oX@So>G4Hqqi`xH<*ZQ*TmXvT`cW7lQRV;nZXs27IZS%fp$I37 zCkdzJpy{k@L=fnl<;ZO(v#uqF8q9&_$ifbI*US)c8LXkeE)wgSJ4qIjU?iFYMMaK; zok`X~0}uohvmpW;KvnP+L@s3EN0FLI#=)atavXGhci#@CV5TrbI|k`f=w zzoWrxpO!DNE&jV#yj;Ls#9U17!G&%Xn^ACdw>W$*Uz4!-65oYi4tU$(X^H`ckC9 znHJfb5a+mV_>+H?i!tUb0x2ZQzQ!p?ncoFgLw${#b3B&EMddtq{W+1|t2x#0 z3proSS;yqh{%o#Gf+=IjcEwNGpp+O2U#=#k(XkIWZvIRtUQo;8TUj6?rEYi8$t=l+FeDjvu>nmlmZXg`j{8P}g1 zT31~sk4^5;`+Q!wITyPCMEhtm5-Y!wuPeutXT`V0+iEyIQFr8=Z)d|n?io`Lf%(}y zCy@H6G!mfsr&io>e)0Wm_+vdSvIF`8!0-(|li%@e)vo5Z!Gjwgh2QaaYGnLN!?O4t z8(I%^;}Y{?;|DHU8fm{lkbIK-(OiMHF$XBmo!gl+E1#Xefy%Yay^GugKfPXW|1x%9 z9`hu))~PpyHAnN%T0=lwR_i0rS+98!8`S2YbbD!baWAb#}$j($*C_ z)Pss0>4lD;6+;{FUjRP}2y0P#`4 z^}jgDPsSxTRU9f7i;Jj{=Ae$t$vYLBKzUw$Tj(I=2JjpM{RQDVCUMRgFt)B^&Vx&t zH*;Z5bpyg&nUAxVMvC)1A8A7D7Ixio1^x&>`oCg0SBL{O_39%kUS{u5b<&!8!f|*5 z$LYkyah-c}+zGXYV7AVTee znt`x_A`tIWa9sfPBtnccozAuLE=&<*%tykEIo2+y)=QS{cEzC zLy-5@T0@5Z900{wDmw<4OpF*v-)_jt_ z)~yDyNS<}GW^JP6S|LOU1DY_J*&r_wZ8Rl%Oi7P?|7A8S@xBB=#5FcxjS!?r%$p){ z9j}G?@cd@)F&KW?B1>_I4 zT{)C_)PEidsE5?uumi{jzM6OP3ScvG%Nu1Qzog5QV|ViG0zO9I9RBQrF%B(rertJZ znOi~3oln>E_FHLFY-pd|)S?r?zw!&U9nOsN#B;WN*P_xe)cVOyvS(oE7}?PF(*`4) zly+E6%-|67K;`+C&y@$Azm+F9IeX^m2MhnBTk7=#i--+(AyK_hGN!nYlQzRhLul+ z)TgO+9n)8vS-sgn%#r7%YjhK1YIABVng*V-u9-MBfceHhC*UA~x}Nph*AY~=i-%^- zW{zlk|M?Xd|;ypU-!*1^5ADGKB|_vcFcGp#)O#<7x)$s zIpX-e2V(kLetO|*tcAMnm`{U^Ip%uCy4JPge2?|80dc;?_>6Dx$%nUp?9&e~%w!bq zOl+H+ldmSS63$7sUld0vi72$;L?uU%91bS932K5_kK6`X5+EjK1t@{1VwwfDg=&1p zF%-JIQ&?a2gZK+#^{$Za+!Uc^5S554!M2Gr39Vr`jCh4ja7`p3H$rgv^)A9*4$85Y zxH>+-^|-DHZ(^>)>-6SC2gpSDPbgt)QX~l_h!g##L)h4)*u^@C55gM+m*T4amg^uh z2`ofOH*G0k)abcU1B2K$KtvFo;l|^N^h|PyD`KlaftPrrS#kqaqCj_b5pp5g{C{Du zL>%YkBgiG*;(E+Ma}1FtVO9-{1%ioC@e2`J6pYO+6{3>_9lMUtih(^|7GX4;!v~=l zeYdTVF{Ze7JSm1oybvQ6*UtHf0TsMh6C|(A7Hw8arBQsw)zI053vF;uVr5sjwfOQ>UX9N^e7Mq$R<inG*OQpRCybU0Df`18M*eD9^>!@w|8QKD8kLPaIHA zz##xWA*N>20pB1PDpv%ALy3nL5MXQE5q;*m<8>ix#lrdd75aFOr<^_E+{ygef4=~^ zW|+e#ubTtJ|COua0%PX$y^Vkp8>5MMSD;TFdu$WUdDqR7^RE$kD7%(zL7l_-tC7Oify*X8Y#wt{9f8AS^Qa{>LFfkQ1#JEm z@g@H{7klZ?nf|0|3ZSMN^Siq-g$kS$Nlt=(>XNq$~ial@oeU=@RP3+=Xmq_%>&GD%(d1a zjQn?#mmH8h2A?&zojib^X-zXY!MwocYjeXw^Zd}o(#BP18jANB{}uBS75~nG&IN^QteZ|L=tZ=kN*dB5Ve5o^-gU1zb8VtCZI zEtdUsYsT~WgzIYL)3?I(dEhdZXB+{?m*Vc@PvFHJKXDHqdxeN|fBYP4;Jp5ZtN9jw z>f;aJ^e=ug@KXZo{ic5zuLr*G=YQwng}?V>AA4y0c;Z{`!4tlk#7tTebtY&zXBK29 zA&yqURq;g}5ikmVf*j%xvJ}xrVbUf+kT1mzQeGPb+>y&WT5h)TNrJrVNE7P1WF4qOt}cX}eR*Ik?=c&7k!AWvM0u<>&Vvd3VOnm8M?{Jj(A5xksZzj2sz zpNREg59~AThAnMh+q>+aWSbihQ=BPgiX@v958;5%X)IKrX>#UnDgvTJgzprn&`Ust zm>}k82-OIVrf)WU#A3vr{yajBXf~u1et&E95OLkqRWmmmxv@czLM;Uv0;2_m$(nVW zn9D8}6HmtU73_x-H^;^}G&xla^X^*ur~RUWwKf6}N8C-)Qx~ze??NTscl| zOQ0c?Llo<8zWH1G`aApFbn&D1Woz3ueAIWz!hdJ8+)oNfB!_@RL=poflx2H(Jum=56XUjfC!=1WHt= z_}T^7x`T3xFmN;!&w=&CJ*XiVO+hyXIftvJpqn9tdA6 zh0i3)BbUxE70r!-$XRkXa$39+JOl5jrc^!Arf1X;{h8FcF)*JPm>UA(ylhwg1K?XX6E2R0X`Hbj@lzm3|ws-6d$p^^*8%-gRuBW$lv{0Z~hmTxdsDm zp1{FemwJG$TocM({bZJEXx3Y;J-A;R_khj&dVSiY|J&s6Jx&TRiZ%GVa_Q0JE5fGuzoCIFz^qR3$6Hr>2=et$x}{n7eCpOSg-T5b!>oh5pz?>O%YcO zt%Z#K9uvVa)Syp6T6 zHmx((j&O6+zad1gz8P6!H*AE+4MDisBSft1fGRcgJ2uM>vV|JxJL1lExq^?G zm}3+y{5Ehspz9}cF1N26HOC2EgX?-u<%_-HFHz~npT@gKh3w|YsS;W9s6$fe+uKg{ufl$us9wBH8!=;Ld-NGj%i#Bejo3> zc7A5mG1>SKe>wJ53~p;Ge`XCDepGXicD28H(_ntF(Nzta^=8(Sn_5xaSvj=(I>$oF7rRB&`*5VsSaAW0a=j&x1TD3@d>{|2~biDZ)BuCH9bzgVq z>eVk?^K`Avw!dTl^Uk?!X&cW*{q6>CacFUMbMNHenLE}oa6i`GcU=&C-7W40DX)3<> ze^nGIL@GEm5<$Qs+<6zagd8NB!VSTe1!0mbNCLSfgNgpzH~=A;AWlG9B8?LeS`kT+ zNfJttN)d~Qu#hBk5KWU!z+`g}PX3)u;ND#$qX>Kw&my1c>p%&%jetZ#6(c7BO&l?G z1D{*tY~ypiIgk3Ao3(+E>u~ICe_K=b!mfF@`-s7a0|`Fju*9AlUKM|eJ26Sqi^xIn zAb=24h%5^>#2k-(60gE~RB?ZuxcNJB?tYXYycB#EZ-}lCTtW-3J3xdPek}fg)&zW~ z#_Q7B00Ok;HUw+VF+Pv^GL~} zr+CmNkp)A|=Mao=#wi$ITVmB4Bm!bq$R`o*!Jqee02?uu#}#K;92R#8d*gW*G|AZ5 z@O%8^T&-!WP3z-MU#?Mt_*h>)DG_-w#qku#C4wc0;p1@^@FvzAzbDA8Mm8CVh%+}Z zhGSH4z6>~S<7XmYXA|b%10$wEe(v8rUR{Ul#(dQHe0Hsib!yFO0=%&|?!?N+s^=^=AiPs-I{5)(xvTXXBCNoaCAODgN;5e(xTN zZ;Xw1;L7CDk(_j zUGi_lr#1l0Gl9vw=uf!!4*za{_95hH%>MIXK4fxaYMr$E&&D$L!r{`1|!g%hzxE@NYi6aC!0hyWjKc z^O(y;O;lh32ij#bmci*@T?ISfV%9$RH9`2@f$N z{(@8?T|H9Fqr`lJd%T8_AzqQD8pVJvO@iO(`+XWAl1+zJB&& z^!3g4KDHh;O{`6_O|0}r!xAbjG-9p@i`TiKiM8m9sYVV_Fj@O+!v^F`7fE9Esz5s4 zh_&9t8u50uOa2&6k>9BCJhtG?vxxN)k9yP(|AO8GhjYHPif!?Hq5r zUR=M#>71)#*y|YUakIS5eay4x7B*6BJqEt7LBcJ?+x#LyY_51zOdfaG47lDvL<$H> zV|)(Axf-*skzx~}xqJ+AgQ9F_d&yR559K&HR!u?XTzocjPIfB$#8{nk_1zF_eKkks z9in&>tYFqK>edjgnUhg3yn)cQpoDAi-IkAFZX77zO1y4&3oka04S4p%)p5ca)10ij zbJfVtIV!KKNvyoqn%odl|Dq1&CZ8L^xxp8}KCac|yz)MCKR3o|GwcRvh4K5ZrOjI> z%-UbqS~YuZP>F6B)5uVi+`nitbv5mMBz_9PoD;29M`6K^Fsa$pRoZCCn(3sY4SPZEOkym8fWC~ zRnE>_CGN=$Y3V@e)m=xImPYTE_T6;zc_)ANd@rZ*pUwVbxH|V0Va)T{jr>^WW3X)| zY_}`S{v>(|A#Cn151db`o$uxGk^*Y2Yme&7N=(Z&1@e9Ss0#*laz z;s9_RpdHWJ^+d4oQ>0&W3-sS0oA6Eg6YPu{60^n-iSw9=IN#%LkDpO^u0&*umk=(a zsq2Prl3n=PB9^xkagnzVw6aM(>U*+aayw;aZ;;-7Eu`j2uwhbEjEq&Jw zcZnM}*}9vgM!OnGfG8>vW^pC~ID9T35JALW-x-+#7O3g;9g^aW$rp-|j1&KzZEl)o z9*932J8njWt4+*%pm8hsZX@wmkkFq`VgdX)AphROJkMilLkQFyYSUFY&IT>(iFGMP z>TXtU6djLA0+k*FFrCU3Pyx0iQz*$ex9=sq8A-jyf8#)pc{!OFxC8*u>V< zH|*VwkwHu+w)i$UUa`K{$kq&8&#_**p>0!k(Rx@PF%+>x%xR+~>V@uL&syn*-aOe{ zSsYfJws`URb@SOm)bR1oIqy6+uxmzR)?{PMcdR3E)Mj)2!R=|I;PZPm+M58C*W|En znuL!OOKjYSFTd-PUx+xU<{us>4nTLp-mx&ubgU1Z<*^z_PxjXr{=b%x!XcXVyB97&}6Ju;$tMaCC zJZ&P(&wD8!v&rZ1kNB6(5qKA!75pfD6@C@3;&hT)G7gb_C9tzu8sZuf3gS^xD``tu2N~;&V@OcQ*rG^e z9Kr_CW073oJqevS#-dh_TbhU^YO-i|!H(;tFfAgQ1hkP(kRk=czw_GYV?dpI5|YTy zLaR*S`I4lNX0AolJi;Pn9s$UOEGkZv?AB-Lmf zjYT&R6u)R1Bqpo)$>y<6iY&GkDTb|7a42pO2NFbV97Lh+rjff%BEuzwnh1*w`)|%n zh!#X5M*qBy<$Ro~jw`I#uJ{fpzN0DC^(xNS-G~d(!MwMI(sv;$?qjWrecy$uhC=Z} z>?mFW#7YTVgu`CE93P|ctOg>R&)O`g#-U?@#}mnyDrkap2)~D~)jZ-;zGQET85#o# zneb;7IOR+gI3CAWaf|gKY`5nBX#CrM_TeaKk9{-wlQ_>Y`cFX$^64{pOFmrw(*{oz zaS2HLg|Ea};UoJ7&I+IC4mG|YH^Zj#LFC&06i02&xbasm;)nWf6E|%kF83U(Z?L|( zvs4XA%teTpnaxS^9pW?NY;hgj^|%1Vw*t8uag2yjOe=3QcXj6#QKP#Zkn7`)Gq&DphVs51ft`)*$op<)JNJ(} zd{rC713dnFY$O10CWzm8JQ-dX5dSw^2jcjvI#6SFEtufCC)j|xdLLYkL7nk6JokdR ztpRsYue7dx9a@`fo%wD;8*Oo{_^;|4IT!YmJ%d}Zv+~R}O}TX;^doPSFXRbAf7Ub$ z{N}>qpWvZc$F@g(gV(IlTJ%}u8k>MF@!faFKDKaq$L$%IV+eEn!jqz(c@_?Ve*NTL ztpDWnuFqj7*X7CIU60Go#Utb_zL@;2{0sh5&hZ35`19m%{fCE$O=3cE6tMzd`-^JT z6h=&muZhv9yN(w$o8!fGQ}I%pMB-j_kT~~v3I`4;J;dBM4m!UGl(iP5lZ>jhkHNNk zx!BL;HRIU_*O`_vj$iF%{a)~d)C~px30C?IB?w? zYrlG861&-khn<%X3)W=fqsMN@e=>!)yzQCM~~)?EJ1zy4tVH1zww`ryOg4!kA&|3VKCkdW5v7@!Nv`n-=b zic}j8h7OPsJOoseY`_6n$+wX$Leh6aBj5@CO)!UWCE?t_tb}vZ5+v*0Hvr)};SdS6 z3#U6762vKnd$*p%^Dcc6a3)|j{c2(WguDsj0Z7Bdaf3Q%f^ll>-T;uWD{-d{0Pjkc zSO+m**#wb?z$*YW!r+ zY%s8EA~z+rxmB~H`LQX14=w5>+@^?&pS&NR`z|Essi}c9qX6p;RQZkKtb!SK)ZHaW z(ju$KaXocVgh)X!5fZ16-wgp$ux5Nej{V#!PLQkf;9fy$RP*5C)CH=Vg(7>_rMywY zK(nwK2HhEZF)-tcBBt1?%>rUdu|$5YhCxM*M!|7}pYRbr2?0$`IeuoYEnz$a4!$BT z5W`%H!WMH-9?3`PpKUY1KCwT-j%{BRoGL5y z{H)x}?@lSXVHh!cv>o68g1T#-Ib3y7F5$g&3i*1$N4#4u4UCs&^5F8&e}o^-Pd?3hr7Du+h1f|jAJ>E zaTwn zAG`kEr-|{UQkS0(yrM^Z&&`2t4WQOG@z@5y;AUx6)rZuly(vHVxofgHrFi5@lS@m4=#>1Ke|>fd zjdfmU!1K5PbM!oO&dpVYNB`t$KsAH0^mAR~@BW+rn6Jlt-tv*(eR#pfd^Xg;RN^>! zHUSn;5>?YeB88KcOnyv%KIW`PA4~WWd?7w`x3Usy-&Gq4RpOUBVQN%sXDvz9clJX7 z(=iuf`X=bNT z)3r%X#F`QF^rQZ=9cwC~!#W|37H5zX#R+jEdyZx$Yti0Gb|v@BJS0^_>~!44Enh(*G{9 zNq8~mn2!V-gk~bULt<_cZkvG%kO!3u%7+QY1bjK25ZAfEI8Mi|&(s7Ix+(Z*35PS9u&4G8m!$Ie?U z=dQP6C+yGlxV9dH^%DZoRNV;W5;72elbwlt2!lt^iln*jbh^BHJw`xx=g&so!VHiP zis^`9HM|+Sj$3$hJ_%#HNemIcYHmT*0o8)}8pLs@fU2xGgj{Nk5 zz634S;QYkS#{6>xuX*4;hgAN1%$f7VlXAW06LXWO6(47=FD{O?u`YhX$>!zafJ@BJ z{km+_c51{mxK3U3tcg=#ZX(mLO|LMHTkBhE?Da9Pm@D;DzOJ0oriS?}>WR24@(7(V z@8VHkI+x;H#$sJuPks)&i^C#*J4YhtrKQYYCCnr=o41lrnrp;GqcMJ@x%Zbn?G5^` zL=%4pKXUQAzft<)+odnzrhj70=@PT~zkCwF>nb;HV>E8Z<}+`Mb<^`@fu z$h?!yD3;zaxo|sU zrM}uX0~FtivB+!qf;fvgZyQU1^|4OsX2qRjPfY_2L&e~#p~kQ+w(femVpLFV1+IyK z@YhYPeHpmkACVl&wn{4{dB{l%+CL5fx-LMR>F(kc|n#q{|%}h_*#^-MYT+66y-bHCK zkk{a5U|oYvh?sjLVzGa+F0{1}F)H}A-#=g-j$Mf9aRX1Ne#|<=^%YxVbMYu~-dg0- z%18Vp-Hi_6i$7<)OG}iG75AWgKjJ@g;9>*8FmF5qcN=t%bB^7}Im|f;%+qkst#uSw z*A=9x65ssqKh!@Bj``+)7q9>AhaTP*`sszYL{WUpCqDD=mT1K9g3lWq|JjGPMuYm+ z5Lj>h&Cfl2e-P65Mj>r{D2WQ_<=w8Qvti&0DBp~_s0t%PTmosbjKfu zjmP^@B!pwkwEdrI9_(1mm+FvHZ{`f^U4|{VC z=gST+vA$YY%wI7;EJREY8;X;N7sXA)&s&HgVyR+k;WPcd;;dqgb1!2JU>n9(+=NJCx!yBRqB!1`N4N(YQA0+tt zAimfC+;1m-_O}*3{F{j&_UEU&K3he?PfAxD@`x7w`F#%HcSc zbHs%a&l!gqd*m7MhTk)HE4C_Dz7Kr-CCxAKhYxR#2KOFx495$#o{qO1JJ%L*rWmW( zpY;Sjix`8OGrw8u%5T<89U+f9$Mt)>zhUM2#QP?EesjdY823G(Y7p~C&IrJdU3=h+ z;0icn)&TO|xz1Vxvb%EJ+!^-Y_%m!}l-=5X@t$(lfEPH=92(=AN7}w2zOrr?ml~eq zbKyF~xo`sGv9|EvT%VxW8k`208yM@p57tv_0`OtiheWS$uC3-uth*jcBjB&WRn)o< zpf%vB_#OYsuMu;^A7e1Ccq(j69E+!-VI+6SVe(nIUHnAOi=UdC;G_VY5gc-I%}eiKK5jhiZ;M*W*{<(QHNu+Duc zHCFSJ^@p@a@`?0S@bM3Q{jUUREc9)B_TJ!T)kV&W*o5mGb6n54l(F}@m}AZtUKQ8= zN~|T;&F^zfQ#O`YevgUdh*s zI(4T{La(VWh**OY6YgaS@Fl+wsE^XIqo?SYPyN@LyVlY3A9l*PkNJ(bSIvyLcfC{{ z2|vN@@V$7~u@B&1X^l^3K9d8X8Od?wLd1g5IT0M~ytn}|bO+*-iL3L$el4NqVD4dl z%|9UYp60K0STp4sFZI$<Nkl2PO5&U=(k%>NmAd{qWRbrX@P~rOIsDA=AN0nY^yhv`PD1Lur z^1@;&evK2WA4#SnL2gfL<_N+gisJU{amwsthX~WlYqXKA&bnW%4}@c+e??298r5aht^>yV0^W6Gd~#&VxVd=;!{P9xWBUNt*Dv36yWKCAj{wj##xz^4R>Srx%kw-diXuBZJL8UF z4ELWF38vG_oKB~=_Kkh*_g}-s#Z%ZtxWE4ccXz*Gxqn=Km`o-=-1I4>TpoN7v9x*6 zT9@nfdUs+2Q>s(QB+;YM=(C;7=a-ky#2!PQ7oZftX0v&+SS)_}L8z+A_YiCi^=s~! zrow`L5F!aoC|dfxUJv#oxc(SpURhzrrb*UDovbWNw9)71=ltbq4|t$RT4SfvQI_03 z7hpev>wga-wyn69+Y+H(YDT5HzEYu#gJ_{DAQfHh-T=5lAqYIQKebF3V{1bu)JxU1 zRAeqktP@N$oyr{z?%M=H47p9)hDxPV>ZNMt$YsSM915Avs;Xo(BXHB{;zW9xcHNoK zI!J_KNJccwH^s?}7b?){Y`WN*{%uHw`Y`>E6Mw4s{qFBidkt67LIppaUTUMx&UD)B z{nI|;$c>i!??Odr^KW}+f+9BzL{Y5l_sFT{rhTw1UX|^8A|B=wQQ0i&!>6R_U9YRU z>;YLIpG$6g0v|vf2LX)<|L;q6=yhv=WVw|A6N+`Y$kKV9HBNN`{RvW0F3q|!J7Zn0 z0e_3yp6XEB0KHPVea=o~rDE%NhB~mbmC7*`>z#$LWDO8qMVAb%srR091^f%?BE&@rGU&;dXu&=KUj+60fcbRLp%2-@|OQB&@96N5hUl`>ssQ#0@<0`Wo9bpew_n5i8+YVe_U>wRLvJ)Yk#~l-o(q*-vDry7ZVAYfNQ+VH6;dsSYl?`W#7kv z?Xq~5SGiq30(2aFhPvHP5Q}WL*p~GM*rFpVH69>47er>E^WJv9^6ubv1LYW5ehpPJ zrFMg{C^sOQ`JLvSd;1|&;vRYdO+0BT_(O9Av~;x2or^qqf0F$UbNEwfmY0|rPBsuya} zn1?BaTeWCj&d9?XQ?{o;E{!D%3>!j4e$maj-Q((YqI=Hs?^LxP#{Z7WoDbpcjK=!~ z$Qr)(Sv*_(Zz#T{JL26m^VcFkT*i~Fna;@mtMN~ zxh!!!_%%Z5!^{I`iULsa0u$c^%`(jKAl^APuFaU0&BKfXwIWPKf@B=tm2+QSxn})f z!caRl9GvnBzlzL4la)~YTUYq?$7B8*SO@jt=Ap|rcL0e)WhQc*sv16XDS!JH*9hwt T6#3*K00000NkvXXu0mjfB-aL( delta 1956 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 literal 4629 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 LNkvXXu0mjf_(~g- diff --git a/data/icon_editor.png b/data/icon_editor.png index 19aa494c5f9bf0ff92b4ad9f3e19f30c41fd6379..d1324ad97e9969eec8209930b3b0289b83e0c2b7 100644 GIT binary patch delta 714 zcmV;*0yX`U2jK;fBYy&@Nkly`<;!3y{v?yh z0b$T#E@BJ0)+^75MS?+Zk@*|+fIz7k6p+{ag6C1dT{eLO`M zVQXs(e}8{}+_-TgLKJ@{5o-$6^L;5TEfpv(E=ER122#WC!^i$S?%jI{Wpmn;ueRaL^zh{sgc-GFdQ z0|NsExCBT^P7;Jfm{E0gwICMvr{@8V-KV<&8$*5r8qn9*zt6R8OO1?d0008>0n+ITDgXcg z8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10}x3>K~zYIwUy6n6KNR7 zKQozWrb%jy(P^ncQM*6p^-bX@A|llx6=0K@fz!1X|jQ zD=DrA!HXBI%PzRPHfdThHJ!A^c9Kkz`LPGnV3SxH+5N(Mc;#dqQrpa&j??38Y zSomS3R9g6MVt*oAUn2sP%jMmznr{LED=S~+P4jyym4Rk8B7oyK!sh^fS0!E-kKX(-7W?a?5*J_t&K3ZML8;_te1F_USC_-Zi#CSwX9FOSI72M< z7(ocIf4})^;-}*;`0&HH0U2lq3P3@sZ$5Cq;p9mNuNV6JWqiKNTk^@Iz>OOzAWJlQ zfuqM~vQ=Xbu&=rtAOoZu0Q2+nzzq33+`hdvXIUb5?nEdS9}*5lYm?<$K2dp4dC;%ky%O;`r;C&PI>Tpy>xVR;Pd&AWf`~IZGXzV zDRw}XWrD$A4X|%-kT1VJQOhZcf+R^uQbX&`0DixpzP`R4HSa_sfFKCYf96;x31DMm zV=Uv8Cx~{+8E-wH?B9V4o*ZXYSZu$BaMe+A|JifjypbGh49{qm-;jt_{ Tq-kJK00000NkvXXu0mjfqluuh diff --git a/data/icon_flower.png b/data/icon_flower.png index 6619352785eb348a48dfbc47b23032e264eecb5c..1e80f2c8cde36d8ceeb69bc2d9ba62ab808d5258 100644 GIT binary patch delta 1140 zcmV-)1dIEC3y2AjBYy->Nkla3`$H<}R6#>kUoyPk(sAZUK>zlTOFPtW(hR z3@on=o9%#`B)-0bI==PRhLW(b;U~=I-0xwoS+nx7kZW*tEi8_WU0Dgu&%^viu*~6B zm7RUl(iWWf{PT0l#%6$m7TaKoJ2W-&co3p7t_>QDJLu;(qymb5hgq)D2dT1V&B4>( zefN;Evg)g#*?(y${u37YK(YBDgmOb}Xc%FvS##9%oSc2i)3fhxy?#Kti2C^S&-?Mm zlUJ>+JC{J)s%l`iT z7n)3YInc|?!=wLyZ@#(i(!1{-sS?TJ3?2^u?U`H4% zagl*|;D3n9hYk+CM?kA>Rkxkcc8l5E;b3U!l!~OJEh-^l(;eh;3V93;9$ji{TaXV6 z++m7ltK9fhcbAyV?Q{z)@P?HkTCcY69xAs131 zuYT_BJwZXIa%geZ)3c}~DQUC%pedH_&}M- z)PJ`Unw_<`?|C&me1URx9VOMTfP?j6a{w&%5PNmH0bf9eh0uKjYzWa@xG&lSg@=z^ z_{A58ON>T2ZkHjjI}*ly=;M>WM;pB*i*eed)CqeuFl9o3A z#1~(jQ;v>96ttTMnsm!7_|!hd4vco>$13aK_EXtv50Yl~{UPv|MDs{G%S zm9xLFR$G7(DE|0+yc{+A8ph9Phu@( z^8DUNAK?WPD*-4f_OZ=HyZ68(sdojabys@P%be+d0pMU z9jjKYKGxDw(W$Eb4RF!}?c=yJ5`Y@Hc{6%g5KI?D334Tn7GcbSU3JaP^{>T`PO{2gSMr$})0>K3DV%i)EPEDr=4cCTEy_NimX z_8YFNor9IHKxjYY^02@jLmZbfckXvzs_OUk{r!m@nauT5@TUdq960F|6%DI?zn^q^ zSz~jvc7Ifo++0P)qZNk^9kCrp-Ulyy2IdGPQqY}+_r|+yG}CRq)vMe7mLz|LruAea zk?_S>EdNP9Z+6z!mcFxO$-)n0xwa&cNFd8HilPvU#pvofaT8vA3G^fw8kjn?+1xd0 zOo3XS&E`~1>qpleug}-IqOs9DEC_kMq$E&UQGXGT`uh5??SgX0aS8@XvS`~L6Ve&D zl7qJ=fln~`7vafbD4qqy#UP4c3SMvL=9ZR+UsKg;#dY0D z1b?<|Gcq!QDC)NBro)coYN9B}mSsBw1JNt-xaUn!a}%a+zXTZt0umGnpa@`k7A+b% zJA3vcjizZL2m+EMVObVEJv~Gs*KNqAeZD$xbMxaxKHmexVlggU_$8OgNUK2Ulqx}qn8_!$)7i=YEdYi_IO=4 zWx!G!QpyB^yTeduu67|1XxLF-KfmGV(VyIWJ~~`hR;31mAvKXm0Lovmb`PX9Xu56~ z4ZqoT@6)au-2~+k2*jb(nT`P1K6l={rf))_Wse;^c)&6Yrvo zkVy3A!O$U=g26153bx&N3?BL&ie*R`&};r{7gbk({Z?DsvU8!(nia>7pDHqp+*w$= z4RSH4`D18EY_F+NoRSh6Au54lL$?Lb8?ZbBo+OkdU@!%J#uNpv5DXrEUzUS4r%#_@ zc(^A4%Rhqr2xOBG&gXJPzdBAP9e)gl{5Ni#-(s4r?}9%En4^ctnTR+=!B*kgwb-ZU z&mS2Khp(7W{R7PHhgiBWrRz+n8gd-luB{CUf}lSO!*d{@OuYm5AZT|pnSleEHhU#h zzYc4*7oh($07*qoM6N<$f;^LcVE_OC diff --git a/data/icon_ivi_clickdot.png b/data/icon_ivi_clickdot.png index 711bcfcbd616fc6715053a87bc2cb7410caf3493..392ca95a6278321c102589459d74ae9c6ea84b4b 100644 GIT binary patch literal 30955 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�ki~KAiaLq$=5q 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 diff --git a/data/icon_ivi_simple-egl.png b/data/icon_ivi_simple-egl.png index 02539503312941be368a5b69b21c57e135d4f5f5..dedc8eecdcf6037f1cf005f6b197acafbfd690cf 100644 GIT binary patch literal 8688 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 diff --git a/data/icon_ivi_simple-shm.png b/data/icon_ivi_simple-shm.png index 6aacaaa8743c2ce103fe033911052d737511ee24..85676f29ff5806a32ac6713e601fdcb71dd03777 100644 GIT binary patch literal 39650 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 literal 46577 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 z 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 diff --git a/data/icon_terminal.png b/data/icon_terminal.png index 93ac8ff43324dc760c11b26d814b27446a35c31f..4c4c3b69468f6083a659092d7522991ec2632eed 100644 GIT binary patch delta 500 zcmVw&|)nVC7~PTgh}N>)ws~@b`b;tH9rD4(E#W3*;s2ao6TTfTRa30Xrv9WD(fsV zo56+_?z@9MTU_$X&v?0j`$*=_IgCaljK^aLG(fxE1}ll82zU#0Ob1~zMDiwq^Br=R zqxsT=+dH^d4u8>?22xA>5*Ebj`O9t%gfI;8`1KJ_y(cvC2I9{#j>jX&G)-?~ zGX+IiHBX@5DhM7*;G)0}SOv`#m8&|EYb9DGTx}p|6ZyCBaDV(axPyT@wamL1uu;^R zS(cINQblzv$@OGa5!H=G!R>a7^?HqZz0Pl5Bka~cMQs_&q}pJ!*%WLZZc35_yWI}_ z)`?Vk1vM`dHedA#I60Y2DEiwisqb>mNGR8QtJMNeCK$a3yh?e2lL2e0<>m0000 zSokqUj*pKyIXTHd3$U}ZLrRH$zYiZ^SMB0@j`903(L^K)y~`f)cO<@v^uPB}Ulrba zy!ZJgB7(IRV@w;+$T`RMk8Sqf?bDSmd*Ak$PNz(#Q-5Z&S=;tzq?Cv;5<u50e@?zq0bW@BT6x~{3J3h(_|+kn?WEWpjp zO)|z%Rc;mI@tDbEf-xrFsHuF_T0T!HNEF{FYqc>&uGe@;0)wh5d8yb`7fR~WhsmNGB|?Fc`5Y~eY11|aZs L^>bP0l+XkK;f4`} delta 131 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

    BVOQw`}7NBF}@+O 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 diff --git a/data/pattern.png b/data/pattern.png index 5ac8986d6721b4c19794f1d24e513af7015aa395..27dddc4baa644f9a2ffb07a3e9d64f30a49c0f29 100644 GIT binary patch delta 813 zcmV+|1JeAq4!H)98Gi!+007n^QPKba08UU$R7HGsa({SriGqKMgn@&7dWV31f_!;} zetdm)bBTk1fO&R)cXWb#c!+_1gnfH~dUu9@eSdd#gL`>|e0qXEy2AM9q;UV- zKQCB{DZ2u+8e+%;1}3(O8J;(#(z?z zV)C{%d{N;QiIx-EzfA7>7A$XgB?fHx5u+ z#6Nqoq%e6>E5QDrer~YjzubjtNqvFsA9KA&u&u3RNpXs^@3PD`rJWM80>t6*E)uTU z*n)?lV}C=-C7dE_P2%-%KR0ON04=3lam~9)K#RoUQZa9w5)PpM&s%pKepF6kP=|=4 zj9ghTPAO+9t|`1RHgP&o%>sCC3GQ;jsf_d5ECzKWy2Sl`d;YxeVH(bIPECql_PpQk z!-)3lnB8AMn{2bisEmIu2k^e)Y^m{#m*A3JAAgo)FST3i065|=hXU`<75DkRaMW(7 zC0Fcri?%K>_u-Awrd|00Gsd#6V^?1A`<~9oYd>H5y>d5K7L3}jz{wl8fAEF^xA)|$4Wsw^ r$0m@|20VzEG14<;nq&vnR&aFyM3E>hR6!tH00000NkvXXu0mjfKTn7h delta 1842 zcmV-22hI4o2DT268Gix*003^DHn#u(010qNS#tmY3ljhU3ljkVnw%H_000McNliru z+yNOB5;J8|T{r*$2GvPKK~z}7tytTV>naWfB*2c-d;jZH&D;F+nNDI1x||0kgm#?N zo>N2hLs!g2m!-uL{y+cz7XX}2Cm(`yZrir46z80CZduNN;D3(BfB*W+eH?k3a?Y>6 ztH}5Fcb}uGb=y9l)kmvsU2o{UKP`**{=Q4j!H0FdIp?;moN)QDMF=712@!kYNKXPu*M_F~u08bB>6pQD3m$ZukFH zopUMY<$R7QT7Pl?VVdW8KHcNdnoS!A6Prsh#+agW&U-)4r_<@a%6oskUh7tO3x)Z1 zyV-^zMtpT>nrCxkU2no{v1zSYgj8gnPt&w_)x4di*}UrL8cx05_n)S5$rw~sEoHrY zf4AD0n2Ad%*Xxy;Z30?^cEfugW308R>h*d}Ik(oFbAQxY&N%=aCeB96iKr2gg)`^u zLuj>?vRaU0jE9H*a8JaL`8x338pLQvj?wCg7G_oFy*o4=F%pq;uGS~A$FJ^x9NznW z^nt~~8~|Q3?wmtJRW0jkA@|;Y{rdGf4FH^T5I~*EO9_Ve0cN5;r!Vy0r6l4~$vMZuQ=V$8B4YkqoL(!k*CjnH_Rgo6n92JP5oK6{ zrK|v%(%X~VGfeaxm=d*nhepg7 z3_hq+Ri)NO@ObYLafjm$<@<-Nl)7y(Ml0>*d=}4*oFCV+ za}FHD6!Q=!RV8XE< ztx+pm=?oQPI$X6Z%K*_pL}e?r)*YjV9YY0xU%!8!zrGH+mZ(khoFA&--9>s&h!3^Z zG1yN6IaGoGrofEL8#Uw8ag$HC^dbC>8FJ1kC1DBS_-vcT!kw7LI5G1(DI5S?Yk#}b zM-YFqm>yLE04ZhDF+Duq`ws%VK5o=$no~{&wsl?26QX86{$X`nFflP5wiu>MEuwjvx@7_2v@CWp5fKxA?l@9Ph!G>I2oZPP=bQ_*9aPV{t`FsK zugjcLXFx#MO1WLHuZ>1;y1e&0mw&wX^MhNgo9IiPeS>NUVOh@8JYz&t4Kc;@*H`Dz zP`6Ef-dkl|jdW8=qu$u1l8C;)zstIs?rybRzQ4OJ?KQNgk!dn0mvyydZl$d2olm{@ z%lV+;v)0bI6-~3x^T|?BN@1A>LI40O!S(`3Gu?Y1AiRhS0!^UCyhc8eJ8q#mRXp(;bm88I@ms+yebyw+;nrFbjl5I<%Iv-CV8 z;-R^A;TtL>#;0Vl{=_cfv41^zJQ|dp-ajld5h?3B3Xk46?%FUw++CleqWIBcuWQ$C zVT3_X^t#@dlMev`u&^-ikY{rCCzgO8AgQ%7^EAy?`xtSaPorgH>c`D*w`=_Q&LYf2 zbXW8U7=H@>921#3e%vFk<%NK7)V*5oV4D5jZd@-Rgp_j1DW-_=7k@QkCT1Q}pHd#f z(OPruxE_MO2zI^8XZ`%1AcyZ%s;`n95j+2@i1{P(4;uw!k=oiwy<6QnpU0Hq<9VS` zvxZDnS($}_VaM5xXr!GO<1|g3o6EY6Nvm7!VeTc{8)1B~f+@K6ke&Dl>s@e-gt{!t z2qr+#oX3*4X#H#!o_~?Ex4`jba)+@}*4C<3-dN4kbibkpy@okwbJ%JQT5Gjc`|FGo zjyl9?o=0*})mpc*?Fj@CcRXWaaMyJ*bKN#-Z5iHIx2+s|HFO$xK|L< gX!fG_PKy%J-z0HV3sWdKrT_o{07*qoM6N<$f);|4nE(I) diff --git a/data/random.png b/data/random.png index 7640aa052db2815aa29cae857f9cceed13774e07..2ed2a1c56e96e802bdd82c6a1c87a320669375b4 100644 GIT binary patch literal 4553 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 literal 4891 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 diff --git a/data/sign_close.png b/data/sign_close.png index 741ea0b05e6a75505fe09dd3b09db60fcf0b7768..56182a3c460813b04cb1b928db76b00ffed1328b 100644 GIT binary patch delta 74 zcmaFOm@z@cMc>oKF{FYq`2d&O!Fmlf9e0O|^)-)Fq?#T~ek1BM>*Pnir+N!4cr^N0 ePhCFvfr;Vx3!N(qZ5~uG0D-5gpUXO@geCx7z8)z6 delta 206 zcmd0J%{W1&p0PN{-HBn{IhmIX3=C{Z-tI08|3PrU-sO{lBAf*tk;M!Q+`=Ht$S`Y; z1W=H@#M9T6{SGU$n6*LS-Y^@WkYtH#M2T~LZfk(;NBV~EA+w-c?o7z{X^$!d z+0GWa=nh+`uo_!fv#Af?@5(ej@)Wnk16ovB4k_-iRPv3y> zMm}+%B6Uv}#}JFt$q5pl8U#E4&X@V?qj{y|Y<S6G7^>bP0l+XkKJIywQ diff --git a/data/sign_minimize.png b/data/sign_minimize.png index f4f3d9d23b74dad1958ec9f20137cb5b003ecf03..fd3a64aaffa6afd4117a5b13e5c7416cc6555253 100644 GIT binary patch delta 49 zcmdnb=s!V4Tg=nNF{FYqSz=#;NKe!M7K5j3Jo*d_f7k@yPq=qelmQ4lUHx3vIVCg! E0GZqnga7~l delta 162 zcmea@&p1J)p0PN{-HBn{IhmIX3=C{Z-tI08|3PrU-sO{lBAf*tk;M!Q+`=Ht$S`Y; z1W=H@#M9T6{SGU$m^Dw_C9T~+A;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4k_-iRPv3y> zMm}+%A}LQ7#}JFt$q5pyixVOk+;o!u%XnTqZX(sj!(idT$a3+{-C00o44$rjF6*2U FngH+_FT?-< diff --git a/data/terminal.png b/data/terminal.png index 3c02dd2100aa31389ace70cbf649215a982674b2..6295753447c26151ff4fdedb191f344abcb24bc2 100644 GIT binary patch delta 742 zcmVR4#oxbw!>MU7Gc)tb*)!)y zuz)xq!}D^A0IgJ0nk8uzj6YSjZ9Gjqm$8TO%!X!~OK3c|?KQM*yD!tXBhPnPR^%7y zj5zpD!ZghlGz_EUoF7K-!}Gj6ecvy+c3l_y5B?tooHu|{3V-uUOV|xuzj8%1BZxs5 zhRF5e%g2w%LDMubo}QUTeO*0LsT3YQdWd&#-=VC$4AWE7ux%T$SPaQzQfPag=#i@% z)YaGF-+%s<4^N&tMFXI%t(|5HO4ks*)!5vG@v$+~)YU>aL;_0FNCo@rw-N)5WI6={ zmk}I3dIY~rOn>0w#S4H&QEn+3e){wosdO3|`_R$h*xm+I>4E zz|aty8X8%m6h43bLc?YlmI=PjqXjbvP!^UW$O_stT&+Zf9x!Ty1<_SR9sPZMsIPC3 zXzxCF0L{=5i^t%(9vs(&JkLd@uu1(ny+Vi2Y{!;zgd~TF^t0lm=WV=-YHDj? zMj0?tqv-db>6$b+_kI|NL0&i@J)VDf^M3tRlrBYyw{b3#c}2nYxWdMU`)%&240000NbVXQnL3MO!Z*l-kVQ_CCLt$)TZDDR?a(`uM-;o+B0000fbVXQn zL}hbha%pgMX>V=-LSb`dWFT*5AWC6tbz&e>bY*F7WpXETAa`$aYbVnunE(I+_(?=T zR7i=vls#`8WfX;<_hWXG;Mn^GHVp>32o>pfkz)Br;2@EL0?{A}{ssl2L=;FsK`P`3 zA%ZQze@GORAb%ktgae6VTkGAKcjo=L6#L;_VxaJbGrH1@G;_|}bI*H~i0~|-pDFwQ zAz%UCdH3zFVvO61BsRv}{PdI0u3LZ@(j7PiHXcU`}AxqGq$n{{B7}FJ0m}qj_axgMW6jMecGA4xi9zcd)e#5wNuyS&Y=v z1>%^{74B?{rZWg0i(r&sl;P#|bsCK(&K2Cde;;iP{gXbv@|0CYSvrb5r>aKh;mi(F ziZdx{j1H8wSYx>J(>)YWRu!u+tdeCJ(P$EoG5W^Xj2HqT1Wu0oV}Y4-Fl&+b6*6<3 z41Wd~V=%^0`U+P%;y8veMq&(8^5Fe{r|9zXGMTlQEW^&M2W>Q3>uEw#90^Q_qxbj_ z`1AKa{yjxyS&~GsfT+{5TEW|=+ zLt;ch6afGG9NXq9n_xXx$>nl@+$8pTY@_vJ+p2B5x?p)_m9`q=gRNP!&Lj{3AiWrr zPJs`Z|NWp4$eG~_IOY}%uOA7K1Xo$R$;TsMn9cs6+h#R14Nv%&f&fuM6o!d%$_8Db zs0gw;JKE;DiMza^aHx`sT)Zsaa$G z2)Ec&(yW{O!9X~i$>+n-kk4*mLI}i4ufvZKbU((Aj~Xuv(zHsgQRxj-Rb5KO;^~Zv5*WG~ zbvaJjIkD614-`c{b@Y??_<|l%e=evynhXhbTg^d-J>&DJ$LyAYYKA}zgZrJn{{wHo zm*Fv7XIvE~1DHVsyh=)&Hn+woUQ2)P9hjyz(cp5fb$@OIcT}*LS)#3AeIOLC-rtsG z9-&&)dsh$u1W@IHk)`i{y<*F(97VPeFffj~`qKOCO{iG}HpSyN$Wc~3GJ&y>&Qxq3 zS$2G~U$DfK3G8DP$7sg)p8>JQjMxfG(%MgZg-gr9)udBTqUIW>INP~4FU>sxM>T5s z$45a}4dzWYLqm*r?l!+R7%s0_F&I(?CkTrg9IF zLUevp#v+CLqp=&r`8^G-uQXu!EGcL^B%U3_*AB{+RqD6(9Psv-LnX-klVgvQIFycz z5mTJ87fDpE7h>1|80ulHW} z{^z&%e#+}Dah%AKn+heic>oAasM#?N9o>GhJUQA!@W&4W5Cn%G?LaQs1QyY(jLy&V`$PO4WLw!XBTH@!}AL% z+X1#3T4@3~ij)3ep#Nubd66xb6?H$0h}?;wETLx!Q%evBkTjC0b31XIY{?oxcrcO( z-EMDtZx7V!b`FgIg<&|KPXGNUKl=Co{7=FW(q9z7;r$0M|K!hf^?(2APtH!?lvp9V z90U{%1Wp*8zOh5H#n`hW8bH!u3BUW?NP^tZc!++L`2DBiQmT=?GVG+T#;_HU+Xn#)=;xMcb(bf;?hGxeaZuR@mKlp@7`Wbr%4=R#}U#M z;lI}jZ1Ds2x~oB`Y;@Jl8dxC>BO*{MNW*Y=IWq*+AfT7PN=UkO)hLRFZ?C@n^{+>9 zd^VY+S!T5no6`Xx%a*grBs@N@8=(pMT;KWn*TXm-H3&B@7%gEwnM9gk5n&Wvdo)Od zd__ml1H_dBvmlVnAJmIP@8xz%u<=Y!qdzxbQKefspN{#{n3jHH(& zVRU-(TK9HWndN;}RoP~Q!fM$0P=XFYxrqi#s1W47GPm zU?=IUD55y7s?rh4QX;#|;TAzNt%#t`lL|7x_jv%~-bfmx%t~4Pd0=y>_xrSfNG(i_R z>u+vBQ7CJW30#A6t(cPrCN=~qOQvZ7s6eRz++M0e>Q=8IZK*3@lK_@r>(DMY4v0_} zM`)xPz)FBXX$Y7qER6+?o3z!SxCU2?6Rdn>1Z68&f^><=$rjK>)&I~g;FG_iw~=l) zvM4K8hZ?q4$pyx?3^>45H-xoZ9Z08u8#I6;5P%RgA^|L+u5AC2Cuz^iNIp7&r)ZrN zCx`|lC$YLx(wd2pvUA7|Wg1YpAV7RkrW(3%tqwFd#j}}IgK>GGdH}qpe2AaGhA2sI z4Ri#c+7>Gw%e7LN=%=`nCc#8S_YZ|YXxmM4S$D|dI00FmMw`V`c}`CRL4y~9jRAss zQ=Zx$sMHetqR5s65SYLS?G8tO``dElbr4N)JofDhZt7hDmdgJH7I}7HbI=FJ{x`-K z;vYe`E_9iS!?b~OpV-pzgpInGI?|`JiY_hSC6L5VtMPYgc&uyC#U_B>I3kjQKv(FjI6j;9s zNUPZ&m@2@##1bsT5exzvHaj5OZB1ZfK++(;TT=lMBn?JjjR9S3j)+zaxFW1t0p2+X zR`CXaFbbnM4x=d0n{#{$2WtVQj!l)(866>95n@d+MQR=qB1$^05hAC75uiZ;O$ehP zumn)V%t%%b_sv zr^`i(y)He)EYkGN>(_Zvo{Yv#M7(s(65^s?s4pymzmx@OXEi(PXV*h53f+3%aVx z#WGuz)!xx@5Cq4&JGxgWj_g^>F!Y0 z-Esczt@Z~YV*8Ir>cgYNcysg-%h@bjF6{?-ts!9k)aFT*#wnP}U!G z^!evss+~0zRup6TjZzrtHKAE?^m-5WcJiXM-kbyD?s>Wqf!sa6V8Z>q-K;3%FwjRK zb9bOE&L8yhv#Isw$X~$V`!76;s!EKz2l{cGsZw>Ix7C0C+)E&F>P7<7-~iy&phSY8 z!B2>SQh^fvK}a&PqIi3CJ(;JVi>wY*pfvjA@@hPr_2Z-{D|GdM6J!H~6&J-P7gyuy zyq`1(x6$zC=6?FR)O3-sd3ii`Tq__^du#^*TA~hMsM{Ou(RY&T-A3& z=MN&nT;2SECqPXge~@Vw5!z}9tcK~#5b*XII00FOkPfW-^WEv&+M6??rx@_qxglR> z+#htR-92AhBi!_57&vdvYjZ2N zM3XZ`SS=J(i?~6+_pL}88pH*-*$ja+lsVNgBXHy_h>>k*xgwp$X`E_h6bV5P!xl`y}$UIzxVosIDs_SKC&AN z5E^~XAN1ThYqFxS5YPM{Wn~$~iM=VJ-m0qf5Lo{^LQ%wVf+eoIdmfUz2RhJ-fRzDs zh+s9OX_gmaGJyzj9A`yo2o-*@V#s2Ox>#q;$#^`OF8~w(X+wUKj-uD2vG#{W$p(G780XTs)gq&bCm{z3e>(TinO&bJY zzVE4%V%t&+oAOI;R%{{sRP&;dgOce~6=S8zE|5nZm$qLJ*p6dhVQIj%H|!#aUS znd*=-nE*=C0JPMs;kPG9I;jj0^2l$6|>w~2=fxV+705T~;e+i|?%I`h-a%=OJ^5WMZBG3=z1S-I)ct;TYT$>BO#)c4bxe6mg zxHNemw(oyGVY5Ipk)Bq7T;H!pk%a^{nk6>2J#9>YpAR9tDTn~H$sG@p(GrB4RAT^9 zeX0NgmbNzbAf^F39vlO)DzeR0tv44we4g0+J_xryly?iz&kgPdevcw;&4WO`2ACRt zBn=Qg22qm7`v`?y^cn;Bfkfv5M1pV~2;+wZh|pkd8$si*FsvCDNS#`Fcsjt{?~YJ! zK*j+I6;eJxZy*zbz#^Om(LqHTfC!q8YZ{c(Ab+(+1Z4?cr@hOAfPyfJqBxFB$u*jb zgQmMD44Q9|41oMWhM<2?K7dx?q`cE<%43yRu zC9wvCc~lbsNx$rJVm4`(QFIS9>3nY7J!PqL2$M8bqWuY0BEsP8)OmAgl%=&ls48E% zRD&W#28BGUJtTtL3x;rFHE059pxcaV18E4)PEQx<;ynR0p>9Q`3D-4YMJN3-ffY*H zQ=7Fn=bK;qO499}PiJYCE0C5k)LLs*x*VTte~^thC}(Ha%jJtd_@gJEe)g~?lp1g=!nPs&;U7JD@$#WZ zLrcQ#HEi|${-7v6IKEq!<&pCTVH+~}gO+)*EUVpvqx~Q_+8Okd*pk~IIe#J?hv7!1{&r$;&f5_gLujn45}>Wi{)uign^RQuCfo#eT<8!xrL5>4w?{SB5Fd307b1~oj^ry1=y9rXfn&oG7RFTKggXN5sRXj7CA7W2Tk&9 z8|tvkibAyJApZ%(VT9Zenq3dT4wJoWmS7PxBD4-ry>Lmhtg28oo%Vd~3kn#6NZyZ* zsGpT(d7}#+0%gT{B>eA3PiuQ4yv1!N1Lgt{~rA4Q!jZ;B%~0 zQ^r8sVS&(sKnSlYKa*epoED;46rrLgc=IpF5Nd}g1zYzJ-Vh+*WP!=fizLUVN{}zK zkiK8Cs}6FQRYlRMFfh-)NtDE4Q78Zq3Gyj<;5-E>KtgFX>!GKjPnBkZ!xms$RLeC`Zu_9&2u$Q?{8(by_F^mZL(zAr#1)&)M$xODWc5ZH_C zAQ+vE)K-5e-Yb21m z!3BsqihlXDA#kTPebfV6eRXv2@cHxJ&Q7RDQ2*#|c6qKpxhf{pFbH;gU5A7=H&Z}y zs_?fLZU_*10|dTtMlA*>%6mO?G);LG7Ns6LIy(0rJ)Gpl|DB(QWwz{uQT+JXy-!~J z?t^=W+x@aCE5sI40(kfOUrDr$Xln>;_&SNkP#DL(!H$m3e+@^M^EAY=G_%uScW`I# z>Bk>E+TYE~vZ|`~E5&v?puN_1QQo|!e5kT(pZ^=3>*%~Xzx;n)F=pF2XskIMF-vj7z8LDtJc8!000$}NklYf7w zU`PN250eBPi1**~{GVE@C43|oGYEx+vAD?8Q%2w*US0We3;^ijDF zeN}f?&omCntI$FWF@)*)Ix8zHGb{6}68`JI{d+JvoB!vR%i$md)QAX;e;5D&@N0Yi zoBd&}ISSpKz0H66se6ByeIIlr!}DP@pJ}(<-hI&D=(sp#C&-J1y$eq&8RY!iio5XgVVtu|M11p z@HE_g6m>SFKOD4nAM8DR_~N6F9zNdR>2*S_HS=9+2O5l=*FAYx*2gwj001B%l;ik* zpyxB3UOssKeRp?928jFJ?yp`x+kd=&HJzP}$B1}0Q^$9wAig8BsxxX%El8>3)3f8t ziInn_=TE+O@Io@6>&VC3n>)SE*?2s>ng~JH^3>my3%yedvZ+2+(8bJ~`a!?jez4hl zv2#xX05gP<4zxx@AxI#t(GNGUf;-nR-jN3D)+_=M5g`#0A~1wn`%xqT0GK1zS~DOZ zA|kA^ATEH~fB#Lo*ws=sc|QFXCc{rguUSW0%Q_RN4K;@DQX^#m04!;%q&>8H8Z`dq z%6GZe7nw4!Ra~z1W>^*$c!dw7%KN^{KbD0fkug&&8n#yFSFn+Ds9F=RjY+uXT`>+e zh;wDzY_je#(N0-MRQlroTTn>{GBcalh8VEu-cx-A5HuE{iz*@$br(hs5W2(QnSvGOFNJwDdFz4)z_c^Yy$=5+AJXH@oz z_+2)j+y`OqgG|k`Ip5_uuV#;oP>E1ASryhr0tNtPw3-fMH3pUK1)G-$a8=X;#*%f$ zrXtN4N<>702+@poYy?;=T7CXP)|wFji3$&`625@N44QR}faynE8LTa6=SeYZ4Y>@N znt8?Cm_US74NWp11)`8rDV6D`T*HY4w=H(9HFJmnLP#QFbL6l-bwNO_br=Q=AcP&hi%*cc(=0BkZebI9pGMbnI(G$K(L_~Wy)!TH!$?TFKJw)IySH!McI$C} z%MU|I)W5&;>o;$nKYt!YA?wNz3k516RSm*#-aLP9$iQ_at_ngFN}~QwDnnS5p|Zs! zN=NbTt=n!r>2Lc{=t!yK&IQI%3}ctX4BY8<4_>@@_3`V;eC`LqisGvfLOCkX+G(`} z(uV6S1rb4~(>XX;iNIeE0?|gxS%yRe?M~<5#fw+3Ur%NR0yabt0x{Cs$b*U&NC%rN~w76Oj;dbb4|*ow|<0 zkRW&|!rzJrrQ|10dB{M}0DwsKGFTKuB!nDh2$fO%Z7{f)d4#6hEv)$}Lb)E4$xTE` zslmzR=kMMr$2pr!Jm1Gki-U{q&G(nH$wcn$#e+~vot|9&{@pw2I70)WHep2c{pEZz zQG0u_9;8xN>trY)1StZgG7L^Gf1e`EQv_^=pqbIwbjea?4Gat{2zxEJC1rJgQBu%S zATO7Wl!%xZCj$e^BEkvZe)FiVcNEHw` zZoK3O!_n2;OQINCnj<8{8X3w6);L&3C@ZKGp;)sYgjb$7p3X!PLa>@EB9T<;YVOUK zzUwIFyrRXL{UAsXY;DLGNf1e;F6Z9d&k!(22!pG+H=fNzJV~}(kw^$7Yh|z^RARAI z>S{hWWGHntY*jKu5J-YZVEQSRt9)r2GHErIDj|p%BM=f9UYa2<2vG8qBIKopxV(pg zP#_W!mC9IX1ZgBfB&!Sn6@Gt3%r>uto+<5z4t(US~s0vm{LZRp+E@n~`Pf#Bzz&C{`k% zy=-f07_qi!T^6QVovS1aMH$$#Akf+$ObE9 zs0U%gKp25(#na7uhG;GJ0Af=kvM$F)2*t1SjXR2UN~F3cn&`fE4|2^PF0T83Ooz}i z-l7k>Z8!JZ-%YcQK;8-ui=E3G82~r5!TN>`<8{`|HLbs#f?dNd45!a{b5pt%H)%bU zfSV&&AC-lj7FwcIo&|Abxw287!y-QtZ4+6ciIu;KHW|!X4p)M%iNMBhx%?uwHFvR? z8Wa$)Sd-2Y2_h1L1d)h00>xE;d6F(ji1>lhl*Ikn7=(xr0rG8Dn#RVYNg;@+lA~K> zu=duH*HjT8MG!FzB4S+pPcsJ2w!6tFaHS8zRNsgwl~j%+l@dycgz;&sNt2LD2`Q5_ zpYsiblqrIvl8vHucnN|sijD??%48@Y6moJBQYs;oQI!K=AVQ_2<2Xt=(g{Rjgv@B?Gqr)JIj?P9`HT7V#CVh5tyj*zPZe_ceh39=ZJPg9< zXgIpe5fCd1!UW;NV&V1Lt!x=AJnus+!!VbDZ3sj}F@opyS}j0821EnlES7#!NeN1-n zI3A5Yf0rQ4eV^(p$}z%X;a!YIyZwH=t1TgvWUy`s7Ypy=e6)M7AHRx>QH&;2&kwL* zVCBdV%P>A4-S77qFgC6d{e@X;$93=T?=O5`ISv7AIF2I(7L)vPMTKF*b?$9$K7IPs zb=@e^_aEd4UB^)df{l01z|dWZV9H?QT`@CkxX%3yLF@Yu_PrFLQE7%2uP!gO)@220t(D{K?C(dK_qKW)u4_#eaYG6?3!PpR-I9cOO0a_-8*yfIolv$Ft+3C_9n& zEkOVPLfn1y_y?c-TuQl$A2c4E?(OY$I^6~8r+yX|i0 z+lSC{+X9_-OOnVoE;&b$86vHBc6Q&q`Ay8M(X3ZESP~?q`fzyoySJaY?e?SX%^-{< zQGe(DuYdFA`Lo+0r0kw=7~$demLEor6q*wrL(cACX6BS1^!nrJeC`JUSc`g`>|&JT zgj&0;mO$(}j!zK*wmaQ}gLMe$R-+c7BtuQbqaXvVL})S!!mi^e5fPG=AC&K?{V+Va zyqe5Cqp?~(JV%0*;~ZUFj%Ks23G{yM6eSeNC>5zqw(cvI`6tB!gXXw5N2K1 z4Rv!FatkUWaN%7t3vwyD$2)reeh~!!GZ$~iqgiMct$Y!`#pi#D;6%734Y>>dGDMy{A{Gj2qh)~_|AOc|ES7o?JWGEq6 z;u->Cuw2e2S8{I`5t+5o+(l{Ko)A&DuUN{B<=c8~98LSC700J#2mm#XFNo%cxFz9V>efrCP zPM9@OR6yh^vqpY6I{N(GTbB?KqDTipJ%YtalT_-%;o;}+-nuEfCkXuB)@A{r_Vi=I z?#U5i8N#r)z5VGg{$&k6$cg~K%w{>#?9D@z@`DauzMRcHKMb)xxgsHyqx>jRj;mS6 zBw4MaRS3oTPY}Y;aa@iV3hbW88M_CojV~(#>zJID@q-RtzMOfUAA~E4?j(fcIDQy8 zjvGgIT+!9Qgf1P<=HAh0G?_2ZaBn5XGm%hA9S%pM>2#yj(wgI7p|#dgIGuY(qw{GY z29yu;!7#PbXUEb+bt z38|c;Y!u8nqoSms+>kW~VKA95#j1%t07TJg%c;^>lXYDw zN{Bybxkw<*oCu3aT#>wFBG|N?9Ah!ezlxABqe?Plji{LTa{~mMoc35GR}xClBw%py zq&9+TA-e~@BhR{Ttyslul z`cU6Gc_^ch-1csV+g}UExAs(D1A?qmB4Yl>I`Ie=q9s~_lwm+v;fJ6F7FRMG!FFC4 z?J?R0hfwzj*l4a)94_A6PuxBZ;`P6Z<+Xf65??*3S6X#lyu`wCSi|oBi{H!)q-=v+ zKZg}Qf8nF7je(ZQFEC?mf2=;JoS0P-JXx_-BqAY%aSKC{xPT_JLXeTk<&?ssNo|w)v>^h5H=lnvJPe}f@N6{6 zk3$vdPnZfJ#o2Mn%}K4yi}^u1dY_aU^3h{vK}J=3kRc?WqL;FJ;$j7vCozg=Q4mQ= zd3Jof@D@1&qUXh<(1*kG^hlPm(6LY#FCc);2j%eyX*?SJ_U(VTt@inJ=J^3(Z5k6R zbm4gyqw}49Ki<-6uclIAHecy3hLg+XuX4ceDDw66@*D=^VTE`5d z8U(Ohq*_8q`9T$g`qFQL(0lqU=Lgjw)ZUNLb=1Ah-m|AqTdmf-mLJsbzy9%0o_+M{ zQGaX0bwaIm<2!-d?e^wIZ?;^%`r(hCe)RF<7$LgGrMc~PdvjxBwp_mY(T|_Ldj0r@ zGE@+*BSWXVnjfT``+E;7(Eh~3Rz*L70Izdi@S0>VyT zh`r5T*L51oP(`?xBQ%1@%sgP8E@FOAx#Wrf%uzI7ERVxb3bN&{SkpUUX4cG+)}huA z;Bgp=3OyLWdJjla05cIVK|n4kp?GkygcY~WBdx=T5yMypZjg&-qd+1d#BeeTqDTtI zn!Tz4z&=5wub1lnsfO$zO+&4RH3g6$ZzRjSpP z$%mDqhh(h*IN!mGYzMz`L0N-P!x^dxW?UI$9j4jCVputCTqC}9H6`blFwP7U1?eyZ z%|c1#y2^EA7*gG3GBvMkcv)UvQRJo;YP~fN8s4Ul_RPZC)yQfCN;yIb)|%6+sN}|j zeQdV}r-P**9FEQt!dmrog;_K!<|l;{|Le2^>BC%uzd}KFdUCmdQxTolX9#&CTZrM= zkUHJgW=}c}A;^b6`;?Z8KYZ~;Fu6LL%`;XR7o#NF=VYDn@amMMe)U#0I}KmZcp@5A$px%dA3;(q~#LuX2fffuR( O0000^MBTRPK?-qj;2%-Jt8U-MI6Zz3r!M9W7G2Q;*F?Zz4YtlsY zl*%%hD!}4$jF^KBP6u}u@u=sT4^sM&zT)D2sy3t4BF)tLx5v(%W27O1Dq-EOP*MYyuqTCzd{YX%=2;3Ikk zFzy*=Cb#a7Jc@{8-VR;?rapQn@n+n0nIH82y9TEK zy*9=%4+X0LAv;X#%qHSZ-8ArvI(YHE7>vIWDhEPEUzqx2a0)PBPaJbea0>9N?TlxY z4BDTBGUD`(9ptHx!<={j{N8RW25S{C?06h=STG9ki*1f)3j_x3M?#tV33w(sjJXuY zY?0~$>@F+Cx!7M0&&CS^y*4DFG?js0l=`jsp6h2{0VdvsW0p#F0q&6{#N1dOo_#O; z!_SA7S`x~r3!vXlNa7Q&&z3O$23YtrR1VSoEwZlw3x6bL8W)02A_2R|#+g|K_;!8N zKTtWCgfsU;nE48fIzPTa>$?rqP5<{?!-U%)yhQd*{GRJUYYQ<`KMSTlR!(!p3qW4S z7r?VU+C7~+=K~T>@68e$xOtyJ?F2O8v-`NhCN^^)&A%wlF5)tS}#zSBSMopfeWNxG{}RlS}1*=o+CO+u0eF=!Ky{-(@# z%QkF`puWBB%dtBdew2ZCaSuclJyPSWuT<}TtQAr}o1i@Rxq(qmoJT_yelT1}Q;fT; z>+SuM%P}`rvH~EAZR;2bI@PTKcyyiO7ec7l6jhU$2knJNV&UV=qtER81~B>@R>tsS z(jBLSleBzDQXmFp{!Qz!N9F3P^6yy;1&?%g1HfIaf|`1zf~NG=eoxQynXzaL=S^Jq<( zZD4O&B?|NE2V$0iOVb8$?zDHpA!8c5NG_)yJDL80&at1ASp)d=ka>peRJOu`eCdmY zgwkp6B3@B9i+S$vh$A>O5HY9OgllznKnoc)Tn z#Fj7wyuafnL@BBmc9`z*Z?$^@pfU#=5;7|r)Xx)i=iZ(vTMY^!m%D_5hud>aJjYx37Bx7`Utb4_!5(eeOIe z$FMR=Uo)L&c52pf3NfUmM}6Cc0SrG@;0aL1%9!zSnn&rPcw`qmjMtKc14$il7Mag? zwB8y3_4tCE8vt~I@ocjI>=Jse*77)rzc(yEkjf)_PPm3O06~g*Nt$n#X3zg%g=ANQz0M80d2+OwzvVF7{ zS--IbaHopv{#L*)LkERV%2gsu(#qKdAs+oDCzjZdMKJPYO~c!X1%Ts->i*7RZ9w}k z!WQ)iF|;BQKG>S$F2!nE0+Vjln|u0uEC7U!MI(QovleQnNhhORnfVnEl`Zus5Xx~! zBE{Ta1TMte8v44`vGubDj?-&RnIB&-@(0ZLNcuJOY-R(%r1lrR8P@lcx&GGuC~!ynO*^JYaOiQ zCFaHo6jpX=002B`>F%fCO!rCK`y!AKJwX@i>(? z0G-U3Nw=97R)CKlf0hkFgMJ3k{5;l1-E5Z)tJX`e2ujdPTEz@&3UT+xWdOA^TCc|a z#ZXT`1#^B%cbAWHv%FOB`I4eX!tEUeJdGw5y+6EUtE&~9D9^69oaiPX% z4^4NT*cK`wz*9uEW}!Y%-_RphyMPtb?*e?fPk2K3v*my-TZ(^OIsdS7OyLV4*`B_mS_6-}M z;89lwFgKec=yc%;QQs%$2$*}XtDs`OP5pQSw$I*ksG&@>Sf&6cWHW$$geQbXP6FXdug0Qj6ouJ~T& zCjhE{*9cFFkZSILh@X75PV8{nJ`P;R`f!z@3?}IQ?6Po=hKo-;KfyUeV-?=VI=O_6 z|6)Zz=+R0Gxq(@Eobo~VygLIpO(i?{Q}KxlpW++=6})gBvL#yqIKz_5w)=4OBGr8ox+o%V6rS+rd1&!RR=RZfsf(8MW5qs~xI{7`o=NwK$DV#v3&B$OkflS2{}GZ6gQ*d(zN(Z#qC zG$CFw{3sXgYM<804o=8Uj!d1HfxFS{e6e=O4X_B6u8^9blXObN+%0SXaK*-rF&CJg zFV+sZLh%dIcYl3q5QsqxZPKSbg--wqmpIW>i-=`5Z(YG70@+1kz>`wC*_BT{^eTiT zY4Oz3@M5ej<=9ZxD{~#=vOaK7rF%*gJ;OOc-7FU^_3t&Q6ZpP-X!yhSu=>X$tMm<2 z>kuh#Bk;7EbSpyA&^<~rV4pV9uM_wW9n_)y0mBZLFK*!uXfWlG3-&at6pFY&fo#Rh zWw63^ji4S587ukp`te39k*6R}pDwrQ7x0+<2`mc73jP~jGuNmbEAt+$924JrAM!eB zR=2hKXAnb1{zF^Xr;9JrJgH*%(Q!CbkcwUUI&<%J+J6iKF$>7kv2&5)*AhL0dRw2! z>~C=);AIo2o~0oE{BHy1hH~a%N0^=W;C(n2RF02+BM{L=87Al``?hf&9U)Q`bIf^y zP8J14En*NTv1aNJ@)v~^-7QcF1iz0Y`$~2Vv#A>l<;?W=6(raKD11WjM@05hET}G! zUi_UP*EA}hPQ-yNjX<)coI+DY0b+aR8Wkh(l$1N~A+mh~u_YWS*Sh6OcZ)yO)ihMV z@Cr(nn^#7Fry>ep$kL}X`l=qr5GdjQE_{PC&6j|{}1 zMV~ymf{E2E4JdO+nh`|eNO6YeM zMelW7_VH8*|B)$;p;U>%n5ZYj zR_wj5+~R6MQv8C;yM_al(3d^0h3wO2AxV=MjET0g{VY!&p->5+t(s#lg6bWnnEiwN zsy}qng(L%Fuwqnj$fJ2yUt-L;ECBQ;^2jx@a@Pm8y^yF&@)e8MI0gY8A2P^ck6~c& zzHt};+Qyjx#k3^Z*W|sQLJT7G=t=8G>&(yX-mS>bbI;_m`iY;7Rnmo@XL25G5|S*4 zL5y~bLS*GG>?a>dlc)YVp!h|}`R9k+R^Z8vL5z|A*`x36b_MIepRbFVpT%ncbAHrQ z{GtK21Zxysz404BO2?62))hr`>j{iJC0+xV`VcFl=xMnMUs&;z5HW}oZRbZ>w^y0| zfqLy<`Le1KE$N^pO0FQK?Y}-G2@-=7=FxHj-N&HKubt+8b**0^9W|1(Ve~m7%HEJC zha_QQa73h;`}BqN5728(Xb983U)PV{092Yxl=YPG_m@QpIw8pcF*qfZM9)ffAw}_p zK&6X_dyc<4W3gv}g@r5i!I)t&M^ zA3p6;J9gxl7}_QCXiZMg!}5iya>9Ypg_nNM3lKxQ=HIjqqe@9p2fOI?A%BaCGb&v8 zZUaza)3>}V9^Wy}a${u~(4ve9J6VxZ<{VAo`I|?y$ zV8W@CPdjnc_C7(suty7=Z{MJGc!hmBg_E>$Na~=((CL^*8?Z-*hrEt`dXYW)nQ_r( zDQ+{XLKZOU>wOgR!H{Qjf_910;zCjvmT*J>olQ`ENDs zye?{Mi&|~p$M+tscC)s%b?H-iUDaA_t%@SZ5-W;|xIzUAvacZt2_bv#o&GV2xi|OR zd*;l{y$P1@=fej%XU@!c&+p9g+XfDEn8O_AF#o2Ap6)=pkriYCLx?hxC?yC61A~El zkK$1z`w;8{_7Y+j5q5~_)Md2O8RTOn0CE8(bvv@CjfT5VoxPAg~_E`@m{K zyh=51iA#xC){B(#OklJS;!)eWOgN+kKrW<+Y^EV82g-pl8En20Sb<;(f+f7nJ7Nye z47TqCYXOi__67bMm@b5vV&5g7_7+e=4xa%&2YeF9Nq3u?Y(p}SFpr9Jd9SBCql=XQ zNGT@*PXk8--xfmLsBGe&rIvJ6@X)aH-Qs`5Fgsq6<6<4h9S8PxE46VRlOPK^o`K47S?cEjZH~V-_%g^F?bD$MY)i|G)kRi6XiwNdz6pF^l=GZU6;jH} zfj{{a`>YV+uLeEzlqhY45Y0f!moKFp2s{kT>bUeYPzkI9jt8Pd+jp>Q?pVV4m(aR5 zht{1CY3!;b+(*68Ay+`(@gUow`UR+64$-#6Mq74+?3j&)eHr?Ufn9SUlm~r|hDg2o zGSf}s%Lh0am@k*|ccNS`rsLO<%QRX#4loh`A*JjG+zFhHnp-~QYB-lt_6Jq~$HZLT z0GuR*I0zt@F_kF422S+vZ5W1J2@U^%1FQ5p5$Xf7rAr`s7j3}L2{T8O(cJx}lu`gy zK#@;9_X#0xGU-4Fcm((cFd4P7{DetGNqCU>mk%%&_@!JyAGw6PiLxA>mk(;#-u-+1 zmk$ta>a8yyARD-y2&?3aoZ)eQ9R4pKV7qAtLa3M2((&&*g%Hn}Ru+qt@`t4hbP%LanwR1OC^vA`iY!szv6wdrQe3 z?y$K-9s%4$Ew9K5Mt590TJu?R0qFR97cgH4p}jpxg_JS}xFltv>>?QV3m46O+&F~@ ztK?#4l0g9&g)(@!VlvRrx747|LQ%QHW*sBKFyb3<#x@xAxlFb^5W%xFZPfX3T7liB z4P^lcA%1UKK@xJcOOE>$tegbVR>xg%K)3)#-vMhbhuXhqy7dquYph1O9X1+RZ-q=I z$v*~0e6y!};Z1ujygCZ@SM2VG>B=$16r0YDlAKrUtm)r-Fu_`#7kgUGN- zAe5`xJ!KV~{JL6{_7-vm>6Wyb0<3HS-H|Uc1;LXbgV&ok5(ZrYd#$Xd!5H&B$Q=q> z?}3^Zp{*u!ZToSk34mP01VX$36rgYIjHl!?nH7MI-^pBCLCKH+BuoKP%2PWmc-qLt z^dro(KrsN#hSSo(^W0AZp*(-naBoC^4d8qiFdgOasD+dW1dpMNJc{Nm$_Nvbtp3W{ zS1?)qF@16Q$HK_jFlix7c}kIG*@n8&Rt<~ABq`;|Qc7K|VI1~Op-cG|l0=>E zDQ`o8o{=wsy?=uCdZ>F1cK=TQxg(B)6P|~>5o$i)dN*wUm6FY#f1lHdO(bfxChLJkeTn~`NoxsSX_ZvPiu9}k1m_1kju6dh&jJz49z6X5+ zPPK(og6S6qo)5Xh^vdp<3-#})|CanvF5<{woih{wxrEbzuca(x<8wFQlgeD&o7jFI zyf+gXHw1i8z`nof7awwkUO5?oEkBCA(uWZL7o=xkAOLb6Vc>q>XB#(y^l;eJL1dec z0_j4wLG_~G&mDMEy(}HV`A~e3UfJpe(A>=oAUIDh{>b@Oi{K+b5nlsN@+~LZln|IQ z>UJocs(z58?t;OWn%8xxP<*ix0v&ez+WWfTURj|ZOA%-nAQw^!{80OWh7Xjv*Mz=f zm43dH&EGex=TKtE=k>}pZ-<(sqtOT=+yQ8*IfIP=S=)6C73`#GGko}6`+Cwz-Y7L%O?nY> zv{ktA6kh}(%|K`WMP(nN+@@2NzQ|^C6Y)Wb{@82prM`#lM{7t8x*(79}kKkjoi>%o%L=e0PR@8BSfR{w_FC z)n7alPF!q0f7?>>Y5lUnY$S!Wa80TL{v$vg7lQv>k`&u@fJ*@wc`HnK$lKX6XE2Ps z#a&wo2pBWR<8n3>U#MSA<3EEod|j%W{sJJmDrk#wv{hR@PIi$SCz0X{;pm@`(wH;i zo6zSd`?kCYWqXf?{m)cRu{uN=pgC~HnPcVB+7d!qzIR39t^@6#j zvwOdSU@ib4ysd=EM*x*_)!zL@*A$wHTuT8MtZ8KFB$Nv!pGv%5`4qf)Djayju0Ndw zng83W?NsX$&&HAqfaGlR)?#qsnw1yW(WQ&5!eHC1PQ1dwYhDbt4HH>vh41wzXD|#n zSHG_PFWG8Uhs#Aw=zcM|Ian~wG`=H__5=2H1a-rTrDT>`0|cdLni8I%{r|)hYF^JC z0Buz&!q;{e1F(OYeH|gnP}$i&!czb`5;Z1BixJ+b(t^>)LI8#bH(>y3k=Oq$sCquw zb6b7@A_tl`cZHgTk5KC8K4a9M!B;3>!64Ku4OZR@!5KiDCyDn6AUV^%wGJWMIT!sy*QB;6HTS^XCK?fPz((4dObI+Gjo$qaWc`=4X?ycgjD7~r`hOHL4{($tKR6~t|ak_mvNX)Fd80szo=T(I&-oM5*2 zi=}Xis>db87w9H_=%VsTSFH=$h*LZy6M$o#G&l5GWqff#&=Fw7*WI*hx`s%GuGeoT zEj!)DJA`}$Kvs|kjBwIeTMhj4Ca8MWzOEo6Kz6YTGIc@eRc==C&|&!tsQjr}jB&WV zH*qWK#1TL;3Rm$@`<^xkq)0-L#oUlDm~H*M2<5>DqcpsU3@FpL{B>dyi;$W)rAjgZ z=zZiLr$2WNPL`q8109v_(68uw?4+V3;P8)3hf7>sIG@)Vz_AOCBSJZP3$ngrq2E-! zI-{-d+5}j6Qeu=%EIB1m`X#+OlM%`dCYYdn4O{oxwVO!X`}GolK-obuO5w@=WrlnK z?HO$MZ`6-m|BkfbhRFFA3Y@R|a8SyW~WzF)S0(yR6AiZrS` z1f4YdWLoJvE|~9LcShbE{23eTUvl#0g04)iULxH$aFwAPm2oTG6!_?;`kk(9>^kgv z1X}lW{kvtS|1+i;O|u7@xtbkZW7eJ@1zqnrQ2<0h4@@`h6M(TlQ_789bL~6khX^It zqh}A)&*;7L*NKZO-AHB=&tcs~X&f^3!qf(tdJ53@;3)aDa-Z7u|0=O^CMZf0K~s9Q z-riqEVEg@v*AJ}Hb6y#AN$~7t{Ss_2{j%g!RdaDHL%yK)|2k~HFZsQ?*YxTb@_GHmX5R_H{1H2eGz5P}m!__F>?HvD z2K@E|dN1E~4&GQjzsQCoHFF0v{3Eqyt@=gH4hVhMah-&ZUPN3T;1XXmE4xmI@tOjv zoHuEcD#nYU*C`L(K1adWd-N@ZJ0B*cVWn!mzdoRo(v%^0T?{Zxd6&MZ=>4(B+oq>TF(ZLyd&WzxWhsSD8h1D7hGp@W}-r!uny~-P!Y)*>nEHyA(&p5^tx~J{vHQ20t8W1#W~VMvaFW^le{i)c7qCP`{Bib)0uaW%_Myq~ z6kx(WO`n9e8hsC((yP>2#>ps}u2)ZY(wQ<}#=qZ00XT=fka&)*743CuU)z&Z`5tiI zMWyCB%px1cdZ$VTRN@B{yn;wwA>+>&N)UjKv+dV)zi!RJ_ncR$O{5VHzOP@tX0d)i zOYz0t%Bj2&(9giR`oNpel-4OTF1*OwzA5qWZMGymmMuLR)&vVti#7*y2S4T^<@+B? z>D6ZD<8*%g03!a~diPM^-V7Biw~bp&PB78d37}NN;~(H7gHJdghMQ#7kx)6$;5luY zsUdE7NfsEg!g*Ho9vuM;f+~XwZ0Q#v+6XnvVCdC`A?-r#988bg@n@B|G~U+ZJp$}! zCE!*U*}O%?%4HfEQO2sEG<*;|5^31i;JA4(FrepeEXO?s$Nx<&AZU<*fAh>)_f9`~gC1GHq~J53cH?jRbm8#RlaSLw?h zi#>JvhGKtqKH@qY3DK3Qtb%&ep!)eV=rwdJaCk z!)={{72<+KLtiQZcpCy(26bpytLJ*1XJdY8$h_A=T68*YL3XTj?eRm_4r zWXIW#38Y?TMoIF^SWvaVd6h}2O(6i)i`0o*sfKwM+0Jzd#-Xa`GjpDwY}5DfNk%4j0rhX0WC7JfB>#|83V;yFhmK$!2*DA@tN)HR zL8Y^V2gzyNV4upZWry-2$^^N?gXP3G|LxewI8=ovCjF0if+xsW0>a^%P zL8XEpf4}l0G-Y1^E20fKgW<$Q9TCL(r}Aj$l6JG%k=dl3~4N@?q^26_Z5j9Kn zQ@5J(&UB@C8h#_3y3X7-?;^K>4h`#+xAO4oq0qpMJQw?VlG}1+ecKSbeF=N9cwXVmc`k3}^{} zC}%OS!m2AZ%hd4JNn0AXSk*B*jpi-tAn9aVJwId=D${JD1$A8Pm^rg1%N{b|>~ThnM5(m7Nq2K zEosuT%V@Ce-^g41ef$rsN|TeORF=v&r@|YPlAi`@g3$Y5wFRKVRqRK;1$Aj~?{%i? z{YS7!rQN@Uej33WX&D8C+7TUC>AKo=RP&Th(Le@gQO*+xM^?i6ca%=RHJ!()>(W*O zJ09>ax94%3X6tINg*BJM{+Hd=sgOv%(%tbR8NodcB3uvH+bJXUspIra(FyQ0q3J`Y zwcHKIlGPt3J_@5OvzaFHxF|<^ixpS^I`jhf27S(x&3C%jetG5Ub;YjVIIeFlY)#=- z9-LYrPyz!ppQ9$e4{R}dHkQaiH(9V_NvHdHR@ff`-R5#Z<{=uKy|=u+^J}*Nw3l-(g6E3gGN%(v=P>R1;-gc-={wh27Sob>rj;Q7Xn!Cp6og7G+lM>q11Oxf->a94*q;a?E(@aS^}~7e zp(vx7FtflS$3T0X^5)T1y+#^T3}7ayeMzre%Wl{lAhBd}B!X|*toXffE&u?cj4C3W z1}rqLM7F`&>)_p){$J0ox#>%*nvA;F^wu03ZgJrT-vrDO)9}b~Ye!lH06@&3ff_!A z;Bl*psu!peJ(A_^iMGJjd#su;BHHS|V6A=GMZ%|AqMR?vdD^xjFVZFe&=F>SE>{9} zoBY;FSp8YGHmF`eoXTOtEt!osj3w2&qqP#&T!E#UdpjY{6z7p5#K{iVc|ejPmvTFj zyGV6lwJrsdRHn}TFS$HbGdSS_IBTc+I~Ey%cW2mII^Rby%T&dyOU48MKrUl4QT_}V z9G}rk8TZThY^AbY8GH6TYFeWN?k3C~9Ra#&C8GiWAQw}?0?>(E!WiIw z;QXHKM-NktdfLp1Bv8Ovbar@vB$r7dgv7b03a7q zL^eN0avf>jp7sK^0N?1aqV_1gMgTgI6^tgrY~ag)g9Cee6>lS$!*ZUFJ+z@G>2(6o ziCn}WLfn90HsCa1z6V%|;4aSPxt^HkQ_*V$pcA=}QnI-L$yLxxPd4|a4OoO^F6BJi z+g^MpyK+ z!qAT74PYUHd7>PjW5|0Khok_+A|-+fjz>~PlrjWo0Rz(6B(MgkK=2YFDnuC>JUG!U zhr9rIAr~=%Fyn#o2#!KB1{ebj0!r}TW;OzQf$hL1RI0pxB3Vx}>pLQ&J{Ei|1t1l2 z4k6B@1jtXg874|A`@{^#sqznVn8O_AFo)@V{0pqW+`!e6eTx79002ovPDHLkV1n+} Bc1!>O diff --git a/tests/reference/internal-screenshot-bad-00.png b/tests/reference/internal-screenshot-bad-00.png index 5dc79576345241122f9aba308c93e5e9c7797fbd..eb90de6b0934b9a8bae7d98f32f11700a1ba08f9 100644 GIT binary patch literal 4204 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 literal 5149 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 VKn2Bhr*?z1db;|#taD0e0sw)Ws$u{D literal 970 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*+7ad$=lt9;Xep2*t>i(0|Rrt zr;B4q#hkZS4YODb8CV=S|NNJ~6MepM@!`h9x%{C9I~`V(@&R=Y6gcqt-DUO%)4%Wh z9+9$sy1CJt*DskryuA1Mvu=8V0W5%ea(SV>@iXb_Q9B|&pxK%@U Twq4rW?;y>du6{1-oD!M_Fwb>ppMHTpTLm0%S9Iy85}Sb4q9e00-0~ A`v3p{ literal 825 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX5Ps$$$P@Hb9Ck$=lt9;Xep2*t>i(0|V1L zPZ!6KiaBquI`T3(inusVQ%IX=T;|45HlbP8=FQXRsjEf4JYCvf{FeQ~mpO6_G3p5% z!Uipz5{FoL5}Dcz7@cP{DEb@_P@})_w^?!lpMKXZQ%~7*ecrqRZn;6=rU3{BqO9ET rIHtOaxnLsufW{b6PNij%+Fi(0|PU^ zr;B4q#hkZyHu4^F5Mg!f(Kt5cahZ>wwO)IX!Xn1x!l)T1{i{yCN;f}yx{7Jmxy9@Z zzd0oivG61^wHYuv&uCEeIUt~xz#(kVG62G{Zu}aRIo~e|8?8HSRdp!7pph)+d{)0U z-Dmmp7o7LEzOA>?-~PDlkA?W4@g&W{b$5?kKvC?qzx_ZDADGQeR+Qc_ssD6d*kB5I hx(7J9rfODhL8pZOKY^Kr!PC{xWt~$(69BBr`2GL@ diff --git a/tests/reference/subsurface_z_order-02.png b/tests/reference/subsurface_z_order-02.png index 7f3d4cce79f4a7a0af1662bd2c5e1cb37b77b729..737e2f9ccb3e5452a4ab2fd402e5165d8042c4f1 100644 GIT binary patch delta 150 zcmey#wv2ItL_HHT0|Nt(bLbBc72p%%su5WAAIN6-&+z|$)Z?iMKxU|?i(^Oyy&bw7H;5d-ObVJv@k$JgbPM(Z}-&@ pQCk>rV^)Afi_^jnl|Wqw7$sL&*Rb#_zEuVp;_2$=vd$@?2>>maFwp=2 literal 889 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX5Ps$$$P@Hb9Ck$=lt9;Xep2*t>i(0|T?3 zr;B4q#hkZyHs&675MgyZ6<`=*Sz;NYv;A<bP0l+XkKnq&~~ diff --git a/tests/reference/subsurface_z_order-03.png b/tests/reference/subsurface_z_order-03.png index 80c9cf2280f06e1af86dc68eefc7658f82ebce7d..905d86578ea04f5799ffda1bc9e2c3076421f5ce 100644 GIT binary patch literal 233 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKd>+Z$!6vS5kQJRz$e62Be3c}kj?NP2pRtW zf7EdLH;^~g)5S5Qg7NLSPKJg65rz#SK^}^&&(DiEe&7G!Z9}UONCyxk)cOVhfdRWC z7#w`NwX(Sh1b$~XHT~z{;^I2CG!P;J7AA@K{>TzyEW|E`2Mh-8ciLoL)_Z^iJYD@< J);T3K0RT(dRFVJy literal 903 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX5Ps$$$P@Hb9Ck$=lt9;Xep2*t>i(0|T>- zr;B4q#hkZyHx?dp5O4^5vQT2DOdwxhp=ChdRbIXr#zTjHM!$b*p7SYvvaI3R(?1zZ z_V&s#-0(Xfpq9WPY|z3fafpQ{k*Upq(RoILq7VIr-&j;NOj>{Y`3pg#d(%I^5G*^$ zMwVmFm~V_rvAW~l{~>DoTmR=zAAf)L<9QBFKieErp^ishe% rG|sfNtny{78lye#=oo;M_?wNPMCbxfR^kz0&SLO%^>bP0l+XkKHT4$5 diff --git a/tests/reference/subsurface_z_order-04.png b/tests/reference/subsurface_z_order-04.png index e02ce2ae03f78308f113f6bc28d82d5eb85c8455..1c415d59c8be04e5fc038a8eb7077444557dd6db 100644 GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKd>+Z$!6vS5kQJRz$e62Be3c}kj?NP2pRtW zf7EdLH;^~e)5S5Qg7NJ+L&gRM4h945h=W{V3+mPE_4pqgIIMfHPZp#H2oBs-(*S}W zsR0o1!RtRS7YN+*EO0CE0}2|%afoq%h42>!lo?z#HK^!lg}M0I`kX0Q0=;^Jfe$ R7w3ZnJYD@<);T3K0RWzeIwJr8 literal 902 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX5Ps$$$P@Hb9Ck$=lt9;Xep2*t>i(0|T?Q zr;B4q#hkZyHx?dp5O4^5vQT2DOdwxhp=ChdRbG~L2O2EvU+;}EUj5*_ntA!AE zmv6sdZ1~M7afpQ{k*Upq(RoILqR#;VwFC}fgO&jhe(TO3@Tv5tjIGnL-$gREhtzJ6 z=a`+s-SHda{L1UC_2vENJgMK@h|60!#p)ZceP-6@@jE}8dEP-bT!FCR?A#-dT-SZC z-}O#zhyUvhYkzOa8K_VqIbyyoX4jaxZn1jB*0X;L`go}y*V}ep=X`#+@C%3iwt4=G u-5>OC!X4nVrr$fM@?bU%TtR-aH9yKGqT9D%rwA}(F?hQAxvX Date: Tue, 24 Jul 2018 14:05:37 -0700 Subject: [PATCH 0818/1642] compositor-drm: Add support for drm plane FB_DAMAGE_CLIPS property The plane property FB_DAMAGE_CLIPS provides a way to mark damaged regions on the plane in framebuffer coordinates of the framebuffer attached to the plane. This patch adds a new member "damage" to compositor version of drm_plane_state and set FB_DAMAGE_CLIPS property whenever damage is available. v2: Rebase, check if plane support FB_DAMAGE_CLIPS property before setting it. Signed-off-by: Deepak Rawat --- libweston/compositor-drm.c | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c1101105d..4171467cb 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -173,6 +173,7 @@ enum wdrm_plane_property { WDRM_PLANE_CRTC_ID, WDRM_PLANE_IN_FORMATS, WDRM_PLANE_IN_FENCE_FD, + WDRM_PLANE_FB_DAMAGE_CLIPS, WDRM_PLANE__COUNT }; @@ -216,6 +217,7 @@ static const struct drm_property_info plane_props[] = { [WDRM_PLANE_CRTC_ID] = { .name = "CRTC_ID", }, [WDRM_PLANE_IN_FORMATS] = { .name = "IN_FORMATS" }, [WDRM_PLANE_IN_FENCE_FD] = { .name = "IN_FENCE_FD" }, + [WDRM_PLANE_FB_DAMAGE_CLIPS] = { .name = "FB_DAMAGE_CLIPS" }, }; /** @@ -463,6 +465,8 @@ struct drm_plane_state { /* We don't own the fd, so we shouldn't close it */ int in_fence_fd; + pixman_region32_t damage; /* damage to kernel */ + struct wl_list link; /* drm_output_state::plane_list */ }; @@ -1396,6 +1400,7 @@ drm_plane_state_alloc(struct drm_output_state *state_output, state->output_state = state_output; state->plane = plane; state->in_fence_fd = -1; + pixman_region32_init(&state->damage); /* Here we only add the plane state to the desired link, and not * set the member. Having an output pointer set means that the @@ -1427,6 +1432,7 @@ drm_plane_state_free(struct drm_plane_state *state, bool force) wl_list_init(&state->link); state->output_state = NULL; state->in_fence_fd = -1; + pixman_region32_fini(&state->damage); if (force || state != state->plane->state_cur) { drm_fb_unref(state->fb); @@ -1464,6 +1470,7 @@ drm_plane_state_duplicate(struct drm_output_state *state_output, if (src->fb) dst->fb = drm_fb_ref(src->fb); dst->output_state = state_output; + pixman_region32_init(&dst->damage); dst->complete = false; return dst; @@ -2515,6 +2522,42 @@ drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode) return ret; } +static int +plane_add_damage(drmModeAtomicReq *req, struct drm_backend *backend, + struct drm_plane_state *plane_state) +{ + struct drm_plane *plane = plane_state->plane; + struct drm_property_info *info = + &plane->props[WDRM_PLANE_FB_DAMAGE_CLIPS]; + pixman_box32_t *rects; + uint32_t blob_id; + int n_rects; + int ret; + + if (!pixman_region32_not_empty(&plane_state->damage)) + return 0; + + /* + * If a plane doesn't support fb damage blob property, kernel will + * perform full plane update. + */ + if (info->prop_id == 0) + return 0; + + rects = pixman_region32_rectangles(&plane_state->damage, &n_rects); + + ret = drmModeCreatePropertyBlob(backend->drm.fd, rects, + sizeof(*rects) * n_rects, &blob_id); + if (ret != 0) + return ret; + + ret = plane_add_prop(req, plane, WDRM_PLANE_FB_DAMAGE_CLIPS, blob_id); + if (ret != 0) + return ret; + + return 0; +} + static int drm_output_apply_state_atomic(struct drm_output_state *state, drmModeAtomicReq *req, @@ -2590,6 +2633,7 @@ drm_output_apply_state_atomic(struct drm_output_state *state, plane_state->dest_w); ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_H, plane_state->dest_h); + ret |= plane_add_damage(req, b, plane_state); if (plane_state->fb && plane_state->fb->format) pinfo = plane_state->fb->format; From 46a1c729b8eb9b323c4dbfd1da425d0b1daf5c2d Mon Sep 17 00:00:00 2001 From: Deepak Rawat Date: Tue, 24 Jul 2018 14:13:34 -0700 Subject: [PATCH 0819/1642] compositor-drm: Set damage for scanout plane Copy the damage region to scanout drm_plane_state which will be sent to kernel during atomic state update. Signed-off-by: Deepak Rawat --- libweston/compositor-drm.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 4171467cb..4387ec0e2 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -2191,6 +2191,21 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) scanout_state->dest_w = scanout_state->src_w >> 16; scanout_state->dest_h = scanout_state->src_h >> 16; + pixman_region32_copy(&scanout_state->damage, damage); + if (output->base.zoom.active) { + weston_matrix_transform_region(&scanout_state->damage, + &output->base.matrix, + &scanout_state->damage); + } else { + pixman_region32_translate(&scanout_state->damage, + -output->base.x, -output->base.y); + weston_transformed_region(output->base.width, + output->base.height, + output->base.transform, + output->base.current_scale, + &scanout_state->damage, + &scanout_state->damage); + } pixman_region32_subtract(&c->primary_plane.damage, &c->primary_plane.damage, damage); From 779db046b91f28458b6bd79e07818ae20b49ba8b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 28 Mar 2019 12:26:47 +0200 Subject: [PATCH 0820/1642] meson: dep fix for compositor.h needing xkbcommon.h This fixes: [ 5s] cc -Ilibweston/2b98b6d@@session-helper@sta -Ilibweston -I../libweston -Ilibweston/.. -I../libweston/.. -Ilibwes ton/../shared -I../libweston/../shared -I/usr/include/pixman-1 -I/usr/include/libdrm -I/usr/include/dbus-1.0 -I/usr/lib6 4/dbus-1.0/include -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -std=gnu99 -Wno-unused-parameter -Wno-shift-n egative-value -Wno-missing-field-initializers -fvisibility=hidden -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -g -fPIC -MD -MQ 'libweston/2b98b6d@@session-hel per@sta/launcher-util.c.o' -MF 'libweston/2b98b6d@@session-helper@sta/launcher-util.c.o.d' -o 'libweston/2b98b6d@@sessio n-helper@sta/launcher-util.c.o' -c ../libweston/launcher-util.c [ 5s] In file included from ../libweston/launcher-util.c:29: [ 5s] ../libweston/compositor.h:39:10: fatal error: xkbcommon/xkbcommon.h: No such file or directory [ 5s] #include For completeness, also add the same for wayland-server.h. Reported-by: Jan Engelhardt Signed-off-by: Pekka Paalanen --- libweston/meson.build | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libweston/meson.build b/libweston/meson.build index 5d7bfa275..8b887afbc 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -121,7 +121,11 @@ srcs_session_helper = [ ] deps_session_helper = [ # for compositor.h needing pixman.h - dep_pixman.partial_dependency(compile_args: true) + dep_pixman.partial_dependency(compile_args: true), + # for compositor.h needing xkbcommon.h + dep_xkbcommon.partial_dependency(compile_args: true), + # for compositor.h needing wayland-server.h + dep_wayland_server.partial_dependency(compile_args: true) ] if get_option('backend-drm') From fc76388fa199e57684f956ff2d8f95539d2f540a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 14 Mar 2019 16:25:14 +0200 Subject: [PATCH 0821/1642] Remove autotools build Weston 6.0.0 was released with both autotools and Meson build systems. That should be enough for downstream to migrate to Meson build on their on pace. Maintaining two build systems is a hassle, keep the one that is easier to work with and let the other one go. doc/dozygen/tool*.doxygen.in are not deleted, because they have not been integrated with Meson yet. Signed-off-by: Pekka Paalanen --- .gitlab-ci.yml | 20 - Makefile.am | 1766 ----------------- autogen.sh | 9 - compositor/weston.pc.in | 12 - configure.ac | 793 -------- .../libweston-desktop-uninstalled.pc.in | 9 - libweston-desktop/libweston-desktop.pc.in | 11 - libweston/libweston-protocols.pc.in | 7 - libweston/libweston-uninstalled.pc.in | 9 - libweston/libweston.pc.in | 11 - m4/.gitignore | 5 - m4/ax_pthread.m4 | 485 ----- m4/weston.m4 | 37 - tests/weston-tests-env | 113 -- 14 files changed, 3287 deletions(-) delete mode 100644 Makefile.am delete mode 100755 autogen.sh delete mode 100644 compositor/weston.pc.in delete mode 100644 configure.ac delete mode 100644 libweston-desktop/libweston-desktop-uninstalled.pc.in delete mode 100644 libweston-desktop/libweston-desktop.pc.in delete mode 100644 libweston/libweston-protocols.pc.in delete mode 100644 libweston/libweston-uninstalled.pc.in delete mode 100644 libweston/libweston.pc.in delete mode 100644 m4/.gitignore delete mode 100644 m4/ax_pthread.m4 delete mode 100644 m4/weston.m4 delete mode 100755 tests/weston-tests-env diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 129e99060..1b738e873 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -46,26 +46,6 @@ container_prep: - export BUILDDIR="$(pwd)/build-$BUILD_ID" - mkdir "$BUILDDIR" "$PREFIX" - -build-native-autotools: - extends: .build-native - script: - - cd "$BUILDDIR" - - ../autogen.sh --prefix="$PREFIX" --disable-setuid-install --enable-xwayland --enable-x11-compositor --enable-drm-compositor --enable-wayland-compositor --enable-headless-compositor --enable-fbdev-compositor --enable-rdp-compositor --enable-screen-sharing --enable-vaapi-recorder --enable-simple-clients --enable-simple-egl-clients --enable-simple-dmabuf-drm-client --enable-simple-dmabuf-v4l-client --enable-clients --enable-resize-optimization --enable-weston-launch --enable-fullscreen-shell --enable-colord --enable-dbus --enable-systemd-login --enable-junit-xml --enable-ivi-shell --enable-wcap-tools --disable-libunwind --enable-demo-clients-install --enable-lcms --with-cairo=image --enable-remoting --enable-autotools - - make all - - make check - - make install - - make distcheck - artifacts: - name: weston-$CI_COMMIT_SHA-$CI_JOB_ID - when: always - paths: - - build-*/weston-*.tar.xz - - build-*/*.log - - build-*/logs - - prefix-* - - build-native-meson: extends: .build-native script: diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index 5407b593e..000000000 --- a/Makefile.am +++ /dev/null @@ -1,1766 +0,0 @@ -ACLOCAL_AMFLAGS = -I m4 - -bin_PROGRAMS = -noinst_PROGRAMS = -libexec_PROGRAMS = -moduledir = $(libdir)/weston -module_LTLIBRARIES = -libweston_moduledir = $(libdir)/libweston-$(LIBWESTON_MAJOR) -libweston_module_LTLIBRARIES = -noinst_LTLIBRARIES = -BUILT_SOURCES = - -AM_DISTCHECK_CONFIGURE_FLAGS = --disable-setuid-install --enable-autotools - -EXTRA_DIST = weston.ini.in ivi-shell/weston.ini.in - -weston.ini : $(srcdir)/weston.ini.in - $(AM_V_GEN)$(SED) \ - -e 's|@bindir[@]|$(bindir)|g' \ - -e 's|@abs_top_builddir[@]|$(abs_top_builddir)|g' \ - -e 's|@libexecdir[@]|$(libexecdir)|g' \ - $< > $@ - -ivi-shell/weston.ini : $(srcdir)/ivi-shell/weston.ini.in - $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(SED) \ - -e 's|@bindir[@]|$(bindir)|g' \ - -e 's|@libexecdir[@]|$(libexecdir)|g' \ - -e 's|@westondatadir[@]|$(westondatadir)|g' \ - $< > $@ - -all-local : weston.ini ivi-shell/weston.ini - -AM_CFLAGS = $(GCC_CFLAGS) - -AM_CPPFLAGS = \ - -I$(top_builddir)/libweston \ - -I$(top_srcdir)/libweston \ - -I$(top_builddir)/clients \ - -I$(top_builddir)/tests \ - -I$(top_srcdir)/shared \ - -I$(top_builddir)/protocol \ - -DLIBWESTON_MODULEDIR='"$(libweston_moduledir)"' \ - -DLIBEXECDIR='"$(libexecdir)"' \ - -DBINDIR='"$(bindir)"' - -CLEANFILES = weston.ini \ - ivi-shell/weston.ini \ - internal-screenshot-00.png \ - $(BUILT_SOURCES) - -# Libtool race fix -# libweston-desktop depends on libweston, and desktop-shell depends on both. -# This leads to a libtool race at installation, because libtool re-links -# everything. -# If you add more fixes, you may need a workaround to keep automake generated -# targets. See http://debbugs.gnu.org/cgi/bugreport.cgi?bug=7328 -install-libweston_moduleLTLIBRARIES install-moduleLTLIBRARIES: install-libLTLIBRARIES - -lib_LTLIBRARIES = libweston-@LIBWESTON_MAJOR@.la -libweston_@LIBWESTON_MAJOR@_la_CPPFLAGS = $(AM_CPPFLAGS) -libweston_@LIBWESTON_MAJOR@_la_CFLAGS = $(AM_CFLAGS) \ - $(COMPOSITOR_CFLAGS) $(EGL_CFLAGS) $(LIBDRM_CFLAGS) -libweston_@LIBWESTON_MAJOR@_la_LIBADD = $(COMPOSITOR_LIBS) \ - $(DL_LIBS) -lm $(CLOCK_GETTIME_LIBS) \ - $(LIBINPUT_BACKEND_LIBS) libshared.la -libweston_@LIBWESTON_MAJOR@_la_LDFLAGS = -version-info $(LT_VERSION_INFO) - -libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ - libweston/git-version.h \ - libweston/log.c \ - libweston/compositor.c \ - libweston/compositor.h \ - libweston/compositor-drm.h \ - libweston/compositor-fbdev.h \ - libweston/compositor-headless.h \ - libweston/compositor-rdp.h \ - libweston/compositor-wayland.h \ - libweston/compositor-x11.h \ - libweston/input.c \ - libweston/data-device.c \ - libweston/screenshooter.c \ - libweston/touch-calibration.c \ - libweston/clipboard.c \ - libweston/zoom.c \ - libweston/bindings.c \ - libweston/animation.c \ - libweston/noop-renderer.c \ - libweston/pixman-renderer.c \ - libweston/pixman-renderer.h \ - libweston/plugin-registry.c \ - libweston/plugin-registry.h \ - libweston/timeline.c \ - libweston/timeline.h \ - libweston/timeline-object.h \ - libweston/linux-dmabuf.c \ - libweston/linux-dmabuf.h \ - libweston/linux-explicit-synchronization.c \ - libweston/linux-explicit-synchronization.h \ - libweston/linux-sync-file.c \ - libweston/linux-sync-file.h \ - libweston/pixel-formats.c \ - libweston/pixel-formats.h \ - libweston/weston-debug.c \ - libweston/weston-debug.h \ - shared/fd-util.h \ - shared/helpers.h \ - shared/matrix.c \ - shared/matrix.h \ - shared/timespec-util.h \ - shared/zalloc.h \ - shared/platform.h \ - shared/weston-egl-ext.h - -libweston_@LIBWESTON_MAJOR@_datadir = $(datadir)/weston/protocols -dist_libweston_@LIBWESTON_MAJOR@_data_DATA = \ - protocol/weston-debug.xml - -lib_LTLIBRARIES += libweston-desktop-@LIBWESTON_MAJOR@.la -libweston_desktop_@LIBWESTON_MAJOR@_la_CPPFLAGS = $(AM_CPPFLAGS) -libweston_desktop_@LIBWESTON_MAJOR@_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -libweston_desktop_@LIBWESTON_MAJOR@_la_LIBADD = \ - libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) -libweston_desktop_@LIBWESTON_MAJOR@_la_LDFLAGS = -version-info $(LT_VERSION_INFO) - -libweston_desktop_@LIBWESTON_MAJOR@_la_SOURCES = \ - libweston-desktop/client.c \ - libweston-desktop/internal.h \ - libweston-desktop/libweston-desktop.c \ - libweston-desktop/libweston-desktop.h \ - libweston-desktop/seat.c \ - libweston-desktop/surface.c \ - libweston-desktop/wl-shell.c \ - libweston-desktop/xdg-shell-v6.c \ - libweston-desktop/xdg-shell.c \ - libweston-desktop/xwayland.c - -nodist_libweston_desktop_@LIBWESTON_MAJOR@_la_SOURCES = \ - protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-server-protocol.h \ - protocol/xdg-shell-protocol.c \ - protocol/xdg-shell-server-protocol.h - -BUILT_SOURCES += $(nodist_libweston_desktop_@LIBWESTON_MAJOR@_la_SOURCES) - -libweston-desktop-@LIBWESTON_MAJOR@.la libweston-desktop/libweston_desktop_@LIBWESTON_MAJOR@_la-xdg-shell-v6.lo: protocol/xdg-shell-unstable-v6-server-protocol.h -libweston-desktop-@LIBWESTON_MAJOR@.la libweston-desktop/libweston_desktop_@LIBWESTON_MAJOR@_la-xdg-wm-shell.lo: protocol/xdg-shell-server-protocol.h - -if SYSTEMD_NOTIFY_SUPPORT -module_LTLIBRARIES += systemd-notify.la -systemd_notify_la_LDFLAGS = -module -avoid-version -systemd_notify_la_LIBADD = libweston-@LIBWESTON_MAJOR@.la $(SYSTEMD_DAEMON_LIBS) -systemd_notify_la_CFLAGS = \ - $(COMPOSITOR_CFLAGS) \ - $(SYSTEMD_DAEMON_CFLAGS) \ - $(PIXMAN_CFLAGS) \ - $(AM_CFLAGS) -systemd_notify_la_SOURCES = \ - compositor/systemd-notify.c \ - shared/helpers.h \ - shared/zalloc.h \ - libweston/compositor.h -endif - -nodist_libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ - protocol/weston-screenshooter-protocol.c \ - protocol/weston-screenshooter-server-protocol.h \ - protocol/weston-debug-protocol.c \ - protocol/weston-debug-server-protocol.h \ - protocol/text-cursor-position-protocol.c \ - protocol/text-cursor-position-server-protocol.h \ - protocol/text-input-unstable-v1-protocol.c \ - protocol/text-input-unstable-v1-server-protocol.h \ - protocol/input-method-unstable-v1-protocol.c \ - protocol/input-method-unstable-v1-server-protocol.h \ - protocol/presentation-time-protocol.c \ - protocol/presentation-time-server-protocol.h \ - protocol/viewporter-protocol.c \ - protocol/viewporter-server-protocol.h \ - protocol/linux-dmabuf-unstable-v1-protocol.c \ - protocol/linux-dmabuf-unstable-v1-server-protocol.h \ - protocol/relative-pointer-unstable-v1-protocol.c \ - protocol/relative-pointer-unstable-v1-server-protocol.h \ - protocol/pointer-constraints-unstable-v1-protocol.c \ - protocol/pointer-constraints-unstable-v1-server-protocol.h \ - protocol/input-timestamps-unstable-v1-protocol.c \ - protocol/input-timestamps-unstable-v1-server-protocol.h \ - protocol/weston-touch-calibration-protocol.c \ - protocol/weston-touch-calibration-server-protocol.h \ - protocol/linux-explicit-synchronization-unstable-v1-protocol.c \ - protocol/linux-explicit-synchronization-unstable-v1-server-protocol.h - -BUILT_SOURCES += $(nodist_libweston_@LIBWESTON_MAJOR@_la_SOURCES) - -bin_PROGRAMS += weston - -weston_LDFLAGS = -export-dynamic -weston_CPPFLAGS = $(AM_CPPFLAGS) -DMODULEDIR='"$(moduledir)"' \ - -DXSERVER_PATH='"@XSERVER_PATH@"' -weston_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBINPUT_BACKEND_CFLAGS) \ - $(PTHREAD_CFLAGS) $(LIBEVDEV_CFLAGS) -weston_LDADD = libshared.la libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) \ - $(DL_LIBS) $(LIBINPUT_BACKEND_LIBS) \ - $(CLOCK_GETRES_LIBS) \ - $(PTHREAD_LIBS) \ - $(LIBEVDEV_LIBS) \ - -lm - -weston_SOURCES = \ - compositor/main.c \ - compositor/weston-screenshooter.c \ - compositor/text-backend.c -if ENABLE_XWAYLAND -weston_SOURCES += \ - compositor/xwayland.c -endif - -# Track this dependency explicitly instead of using BUILT_SOURCES. We -# add BUILT_SOURCES to CLEANFILES, but we want to keep git-version.h -# in case we're building from tarballs. - -compositor/main.c : $(top_builddir)/libweston/git-version.h -libweston/compositor.c : $(top_builddir)/libweston/git-version.h - -noinst_LTLIBRARIES += \ - libsession-helper.la - -libsession_helper_la_SOURCES = \ - libweston/launcher-util.c \ - libweston/launcher-util.h \ - libweston/launcher-impl.h \ - libweston/weston-launch.h \ - libweston/launcher-weston-launch.c \ - libweston/launcher-direct.c -libsession_helper_la_CFLAGS = $(AM_CFLAGS) $(LIBDRM_CFLAGS) $(PIXMAN_CFLAGS) $(COMPOSITOR_CFLAGS) -libsession_helper_la_LIBADD = libweston-@LIBWESTON_MAJOR@.la - -if ENABLE_DRM_COMPOSITOR -libsession_helper_la_LIBADD += $(LIBDRM_LIBS) -endif - -if ENABLE_DBUS -if HAVE_SYSTEMD_LOGIN -libsession_helper_la_SOURCES += \ - libweston/dbus.h \ - libweston/dbus.c \ - libweston/launcher-logind.c -libsession_helper_la_CFLAGS += $(SYSTEMD_LOGIN_CFLAGS) $(DBUS_CFLAGS) -libsession_helper_la_LIBADD += $(SYSTEMD_LOGIN_LIBS) $(DBUS_LIBS) -endif -endif - -if HAVE_GIT_REPO -libweston/git-version.h : $(top_srcdir)/.git/logs/HEAD - $(AM_V_GEN)echo "#define BUILD_ID \"$(shell git --git-dir=$(top_srcdir)/.git describe --always --dirty) $(shell git --git-dir=$(top_srcdir)/.git log -1 --format='%s (%ci)')\"" > $@ -else -libweston/git-version.h : - $(AM_V_GEN)echo "#define BUILD_ID \"unknown (not built from git or tarball)\"" > $@ - -endif - -.FORCE : - -if BUILD_WESTON_LAUNCH -bin_PROGRAMS += weston-launch -weston_launch_SOURCES = libweston/weston-launch.c libweston/weston-launch.h -weston_launch_CPPFLAGS = -DBINDIR='"$(bindir)"' -weston_launch_CFLAGS= \ - $(AM_CFLAGS) \ - $(PAM_CFLAGS) \ - $(SYSTEMD_LOGIN_CFLAGS) \ - $(LIBDRM_CFLAGS) -weston_launch_LDADD = $(PAM_LIBS) $(SYSTEMD_LOGIN_LIBS) - -if ENABLE_DRM_COMPOSITOR -weston_launch_LDADD += $(LIBDRM_LIBS) -endif - -if ENABLE_SETUID_INSTALL -install-exec-hook: - can_suid_files=no; \ - chown root $(DESTDIR)$(bindir)/weston-launch \ - && chmod u+s $(DESTDIR)$(bindir)/weston-launch \ - && can_suid_files=yes;\ - if test $$can_suid_files = no; then \ - echo 'Error: unable to unable to change ownership/setuid on weston-launch.'; \ - echo 'To skip this step, re-run ./configure using --disable-setuid-install'; \ - false; \ - fi -endif - -endif # BUILD_WESTON_LAUNCH - -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = \ - libweston/libweston-${LIBWESTON_MAJOR}.pc \ - libweston-desktop/libweston-desktop-${LIBWESTON_MAJOR}.pc \ - compositor/weston.pc - -noarch_pkgconfigdir = $(datadir)/pkgconfig -noarch_pkgconfig_DATA = \ - libweston/libweston-${LIBWESTON_MAJOR}-protocols.pc - -wayland_sessiondir = $(datadir)/wayland-sessions -dist_wayland_session_DATA = compositor/weston.desktop - -libwestonincludedir = $(includedir)/libweston-${LIBWESTON_MAJOR} -libwestoninclude_HEADERS = \ - libweston/version.h \ - libweston/compositor.h \ - libweston/compositor-drm.h \ - libweston/compositor-fbdev.h \ - libweston/compositor-headless.h \ - libweston/compositor-rdp.h \ - libweston/compositor-wayland.h \ - libweston/compositor-x11.h \ - libweston/windowed-output-api.h \ - libweston/plugin-registry.h \ - libweston/timeline-object.h \ - shared/matrix.h \ - shared/config-parser.h \ - shared/zalloc.h - -libwestoninclude_HEADERS += \ - libweston-desktop/libweston-desktop.h - -westonincludedir = $(includedir)/weston -westoninclude_HEADERS = compositor/weston.h - -if ENABLE_IVI_SHELL -westoninclude_HEADERS += \ - ivi-shell/ivi-layout-export.h -endif - -if ENABLE_EGL -libweston_module_LTLIBRARIES += gl-renderer.la -gl_renderer_la_LDFLAGS = -module -avoid-version -gl_renderer_la_LIBADD = \ - libweston-@LIBWESTON_MAJOR@.la \ - $(EGL_LIBS) \ - $(COMPOSITOR_LIBS) -gl_renderer_la_CFLAGS = \ - $(COMPOSITOR_CFLAGS) \ - $(EGL_CFLAGS) \ - $(LIBDRM_CFLAGS) \ - $(AM_CFLAGS) -gl_renderer_la_SOURCES = \ - libweston/gl-renderer.h \ - libweston/gl-renderer.c \ - libweston/vertex-clipping.c \ - libweston/vertex-clipping.h \ - libweston/linux-sync-file.c \ - libweston/linux-sync-file.h \ - libweston/linux-sync-file-uapi.h \ - shared/helpers.h -endif - -if ENABLE_X11_COMPOSITOR -libweston_module_LTLIBRARIES += x11-backend.la -x11_backend_la_LDFLAGS = -module -avoid-version -x11_backend_la_LIBADD = \ - libshared-cairo.la \ - libweston-@LIBWESTON_MAJOR@.la \ - $(X11_COMPOSITOR_LIBS) \ - $(COMPOSITOR_LIBS) -x11_backend_la_CFLAGS = \ - $(AM_CFLAGS) \ - $(COMPOSITOR_CFLAGS) \ - $(EGL_CFLAGS) \ - $(PIXMAN_CFLAGS) \ - $(CAIRO_CFLAGS) \ - $(X11_COMPOSITOR_CFLAGS) -x11_backend_la_SOURCES = \ - libweston/compositor-x11.c \ - libweston/compositor-x11.h \ - shared/helpers.h -endif - -INPUT_BACKEND_CFLAGS = $(LIBINPUT_BACKEND_CFLAGS) -INPUT_BACKEND_LIBS = $(LIBINPUT_BACKEND_LIBS) -INPUT_BACKEND_SOURCES = \ - libweston/libinput-seat.c \ - libweston/libinput-seat.h \ - libweston/libinput-device.c \ - libweston/libinput-device.h \ - shared/helpers.h - -if ENABLE_DRM_COMPOSITOR -libweston_module_LTLIBRARIES += drm-backend.la -drm_backend_la_LDFLAGS = -module -avoid-version -drm_backend_la_LIBADD = \ - libsession-helper.la \ - libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) \ - $(DRM_COMPOSITOR_LIBS) \ - $(INPUT_BACKEND_LIBS) \ - libshared.la \ - $(CLOCK_GETTIME_LIBS) -drm_backend_la_CFLAGS = \ - $(COMPOSITOR_CFLAGS) \ - $(EGL_CFLAGS) \ - $(DRM_COMPOSITOR_CFLAGS) \ - $(INPUT_BACKEND_CFLAGS) \ - $(AM_CFLAGS) -drm_backend_la_SOURCES = \ - libweston/compositor-drm.c \ - libweston/compositor-drm.h \ - $(INPUT_BACKEND_SOURCES) \ - shared/helpers.h \ - shared/timespec-util.h \ - libweston/libbacklight.c \ - libweston/libbacklight.h - -if ENABLE_VAAPI_RECORDER -drm_backend_la_SOURCES += libweston/vaapi-recorder.c libweston/vaapi-recorder.h -drm_backend_la_LIBADD += $(LIBVA_LIBS) -drm_backend_la_LDFLAGS += -pthread -drm_backend_la_CFLAGS += $(LIBVA_CFLAGS) -endif - -# remoting -if ENABLE_REMOTING -libweston_module_LTLIBRARIES += remoting-plugin.la -remoting_plugin_la_LDFLAGS = -module -avoid-version -remoting_plugin_la_LIBADD = \ - $(COMPOSITOR_LIBS) \ - $(REMOTING_GST_LIBS) -remoting_plugin_la_CFLAGS = \ - $(COMPOSITOR_CFLAGS) \ - $(REMOTING_GST_CFLAGS) \ - $(AM_CFLAGS) -remoting_plugin_la_SOURCES = \ - remoting/remoting-plugin.c \ - remoting/remoting-plugin.h -endif - -endif - -if ENABLE_WAYLAND_COMPOSITOR -libweston_module_LTLIBRARIES += wayland-backend.la -wayland_backend_la_LDFLAGS = -module -avoid-version -wayland_backend_la_LIBADD = \ - libshared-cairo.la \ - libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) \ - $(WAYLAND_COMPOSITOR_EGL_LIBS) \ - $(WAYLAND_COMPOSITOR_LIBS) -wayland_backend_la_CFLAGS = \ - $(COMPOSITOR_CFLAGS) \ - $(EGL_CFLAGS) \ - $(PIXMAN_CFLAGS) \ - $(CAIRO_CFLAGS) \ - $(WAYLAND_COMPOSITOR_CFLAGS) \ - $(AM_CFLAGS) -wayland_backend_la_SOURCES = \ - libweston/compositor-wayland.c \ - libweston/compositor-wayland.h \ - shared/helpers.h -nodist_wayland_backend_la_SOURCES = \ - protocol/fullscreen-shell-unstable-v1-protocol.c \ - protocol/fullscreen-shell-unstable-v1-client-protocol.h \ - protocol/xdg-shell-protocol.c \ - protocol/xdg-shell-client-protocol.h -endif - -if ENABLE_HEADLESS_COMPOSITOR -libweston_module_LTLIBRARIES += headless-backend.la -headless_backend_la_LDFLAGS = -module -avoid-version -headless_backend_la_LIBADD = \ - libshared.la \ - libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) -headless_backend_la_CFLAGS = $(COMPOSITOR_CFLAGS) $(AM_CFLAGS) -headless_backend_la_SOURCES = \ - libweston/compositor-headless.c \ - libweston/compositor-headless.h \ - shared/helpers.h -endif - -if ENABLE_FBDEV_COMPOSITOR -libweston_module_LTLIBRARIES += fbdev-backend.la -fbdev_backend_la_LDFLAGS = -module -avoid-version -fbdev_backend_la_LIBADD = \ - libshared.la \ - libsession-helper.la \ - libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) \ - $(FBDEV_COMPOSITOR_LIBS) \ - $(INPUT_BACKEND_LIBS) -fbdev_backend_la_CFLAGS = \ - $(COMPOSITOR_CFLAGS) \ - $(EGL_CFLAGS) \ - $(FBDEV_COMPOSITOR_CFLAGS) \ - $(PIXMAN_CFLAGS) \ - $(INPUT_BACKEND_CFLAGS) \ - $(AM_CFLAGS) -fbdev_backend_la_SOURCES = \ - libweston/compositor-fbdev.c \ - libweston/compositor-fbdev.h \ - shared/helpers.h \ - $(INPUT_BACKEND_SOURCES) -endif - -if ENABLE_RDP_COMPOSITOR -libweston_module_LTLIBRARIES += rdp-backend.la -rdp_backend_la_LDFLAGS = -module -avoid-version -rdp_backend_la_LIBADD = \ - libshared.la \ - libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) \ - $(RDP_COMPOSITOR_LIBS) -rdp_backend_la_CFLAGS = \ - $(COMPOSITOR_CFLAGS) \ - $(RDP_COMPOSITOR_CFLAGS) \ - $(AM_CFLAGS) -rdp_backend_la_SOURCES = \ - libweston/compositor-rdp.c \ - libweston/compositor-rdp.h \ - shared/helpers.h -endif - -if HAVE_LCMS -module_LTLIBRARIES += cms-static.la -cms_static_la_LDFLAGS = -module -avoid-version -cms_static_la_LIBADD = \ - libshared.la \ - libweston-@LIBWESTON_MAJOR@.la \ - $(LCMS_LIBS) \ - $(COMPOSITOR_LIBS) -cms_static_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) $(LCMS_CFLAGS) -cms_static_la_SOURCES = \ - compositor/cms-static.c \ - compositor/cms-helper.c \ - compositor/cms-helper.h \ - shared/helpers.h -if ENABLE_COLORD -module_LTLIBRARIES += cms-colord.la -cms_colord_la_LDFLAGS = -module -avoid-version -cms_colord_la_LIBADD = \ - libshared.la \ - libweston-@LIBWESTON_MAJOR@.la \ - $(COLORD_LIBS) \ - $(LCMS_LIBS) \ - $(COMPOSITOR_LIBS) -cms_colord_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) $(COLORD_CFLAGS) -cms_colord_la_SOURCES = \ - compositor/cms-colord.c \ - compositor/cms-helper.c \ - compositor/cms-helper.h \ - shared/helpers.h -endif -endif - -noinst_PROGRAMS += spring-tool -spring_tool_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -spring_tool_LDADD = $(COMPOSITOR_LIBS) -lm -spring_tool_SOURCES = \ - libweston/spring-tool.c \ - libweston/animation.c \ - shared/matrix.c \ - shared/matrix.h \ - libweston/compositor.h - -if BUILD_CLIENTS - -bin_PROGRAMS += weston-terminal weston-info weston-debug weston-screenshooter - -libexec_PROGRAMS += \ - weston-desktop-shell \ - weston-keyboard \ - weston-simple-im - -if ENABLE_IVI_SHELL -libexec_PROGRAMS += \ - weston-ivi-shell-user-interface -endif - -demo_clients = \ - weston-flower \ - weston-image \ - weston-cliptest \ - weston-dnd \ - weston-smoke \ - weston-resizor \ - weston-eventdemo \ - weston-clickdot \ - weston-confine \ - weston-transformed \ - weston-fullscreen \ - weston-stacking \ - weston-calibrator \ - weston-touch-calibrator \ - weston-scaler - -if INSTALL_DEMO_CLIENTS -bin_PROGRAMS += $(demo_clients) -else -noinst_PROGRAMS += $(demo_clients) -endif - - -if BUILD_SIMPLE_CLIENTS -demo_clients += \ - weston-simple-shm \ - weston-simple-damage \ - weston-simple-touch \ - weston-presentation-shm \ - weston-multi-resource - -weston_simple_shm_SOURCES = clients/simple-shm.c -nodist_weston_simple_shm_SOURCES = \ - protocol/xdg-shell-protocol.c \ - protocol/xdg-shell-client-protocol.h \ - protocol/fullscreen-shell-unstable-v1-protocol.c \ - protocol/fullscreen-shell-unstable-v1-client-protocol.h -weston_simple_shm_CFLAGS = $(AM_CFLAGS) $(SIMPLE_CLIENT_CFLAGS) -weston_simple_shm_LDADD = $(SIMPLE_CLIENT_LIBS) libshared.la - -weston_simple_damage_SOURCES = clients/simple-damage.c -nodist_weston_simple_damage_SOURCES = \ - protocol/viewporter-protocol.c \ - protocol/viewporter-client-protocol.h \ - protocol/xdg-shell-protocol.c \ - protocol/xdg-shell-client-protocol.h \ - protocol/fullscreen-shell-unstable-v1-protocol.c \ - protocol/fullscreen-shell-unstable-v1-client-protocol.h -weston_simple_damage_CFLAGS = $(AM_CFLAGS) $(SIMPLE_CLIENT_CFLAGS) -weston_simple_damage_LDADD = $(SIMPLE_CLIENT_LIBS) libshared.la - -weston_simple_touch_SOURCES = \ - clients/simple-touch.c \ - shared/helpers.h -weston_simple_touch_CFLAGS = $(AM_CFLAGS) $(SIMPLE_CLIENT_CFLAGS) -weston_simple_touch_LDADD = $(SIMPLE_CLIENT_LIBS) libshared.la - -weston_presentation_shm_SOURCES = \ - clients/presentation-shm.c \ - shared/helpers.h -nodist_weston_presentation_shm_SOURCES = \ - protocol/presentation-time-protocol.c \ - protocol/presentation-time-client-protocol.h -weston_presentation_shm_CFLAGS = $(AM_CFLAGS) $(SIMPLE_CLIENT_CFLAGS) -weston_presentation_shm_LDADD = $(SIMPLE_CLIENT_LIBS) libshared.la -lm $(CLOCK_GETTIME_LIBS) - -weston_multi_resource_SOURCES = clients/multi-resource.c -weston_multi_resource_CFLAGS = $(AM_CFLAGS) $(SIMPLE_CLIENT_CFLAGS) -weston_multi_resource_LDADD = $(SIMPLE_CLIENT_LIBS) libshared.la $(CLOCK_GETTIME_LIBS) -lm -endif - -if BUILD_SIMPLE_EGL_CLIENTS -demo_clients += weston-simple-egl -weston_simple_egl_SOURCES = clients/simple-egl.c -nodist_weston_simple_egl_SOURCES = \ - protocol/xdg-shell-protocol.c \ - protocol/xdg-shell-client-protocol.h -weston_simple_egl_CFLAGS = $(AM_CFLAGS) $(SIMPLE_EGL_CLIENT_CFLAGS) -weston_simple_egl_LDADD = $(SIMPLE_EGL_CLIENT_LIBS) -lm -endif - -if BUILD_SIMPLE_DMABUF_DRM_CLIENT -demo_clients += weston-simple-dmabuf-drm -weston_simple_dmabuf_drm_SOURCES = \ - clients/simple-dmabuf-drm.c \ - clients/simple-dmabuf-drm-data.h -nodist_weston_simple_dmabuf_drm_SOURCES = \ - protocol/xdg-shell-protocol.c \ - protocol/xdg-shell-client-protocol.h \ - protocol/fullscreen-shell-unstable-v1-protocol.c \ - protocol/fullscreen-shell-unstable-v1-client-protocol.h \ - protocol/linux-dmabuf-unstable-v1-protocol.c \ - protocol/linux-dmabuf-unstable-v1-client-protocol.h -weston_simple_dmabuf_drm_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_DRM_CLIENT_CFLAGS) -weston_simple_dmabuf_drm_LDADD = $(SIMPLE_DMABUF_DRM_CLIENT_LIBS) \ - $(LIBDRM_PLATFORM_FREEDRENO_LIBS) \ - $(LIBDRM_PLATFORM_ETNAVIV_LIBS) \ - $(LIBDRM_PLATFORM_INTEL_LIBS) \ - libshared.la -endif - -if BUILD_SIMPLE_DMABUF_V4L_CLIENT -demo_clients += weston-simple-dmabuf-v4l -weston_simple_dmabuf_v4l_SOURCES = clients/simple-dmabuf-v4l.c -nodist_weston_simple_dmabuf_v4l_SOURCES = \ - protocol/xdg-shell-protocol.c \ - protocol/xdg-shell-client-protocol.h \ - protocol/fullscreen-shell-unstable-v1-protocol.c \ - protocol/fullscreen-shell-unstable-v1-client-protocol.h \ - protocol/linux-dmabuf-unstable-v1-protocol.c \ - protocol/linux-dmabuf-unstable-v1-client-protocol.h -weston_simple_dmabuf_v4l_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_V4L_CLIENT_CFLAGS) -weston_simple_dmabuf_v4l_LDADD = $(SIMPLE_DMABUF_V4L_CLIENT_LIBS) libshared.la -endif - -if BUILD_SIMPLE_DMABUF_EGL_CLIENT -demo_clients += weston-simple-dmabuf-egl -weston_simple_dmabuf_egl_SOURCES = clients/simple-dmabuf-egl.c -nodist_weston_simple_dmabuf_egl_SOURCES = \ - protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-client-protocol.h \ - protocol/fullscreen-shell-unstable-v1-protocol.c \ - protocol/fullscreen-shell-unstable-v1-client-protocol.h \ - protocol/linux-dmabuf-unstable-v1-protocol.c \ - protocol/linux-dmabuf-unstable-v1-client-protocol.h \ - protocol/linux-explicit-synchronization-unstable-v1-protocol.c \ - protocol/linux-explicit-synchronization-unstable-v1-client-protocol.h -weston_simple_dmabuf_egl_CFLAGS = $(AM_CFLAGS) $(SIMPLE_DMABUF_EGL_CLIENT_CFLAGS) -weston_simple_dmabuf_egl_LDADD = $(SIMPLE_DMABUF_EGL_CLIENT_LIBS) libshared.la -lm -endif - -noinst_LTLIBRARIES += libtoytoolkit.la - -libtoytoolkit_la_SOURCES = \ - clients/window.c \ - clients/window.h \ - shared/helpers.h - -nodist_libtoytoolkit_la_SOURCES = \ - protocol/text-cursor-position-protocol.c \ - protocol/text-cursor-position-client-protocol.h \ - protocol/viewporter-protocol.c \ - protocol/viewporter-client-protocol.h \ - protocol/xdg-shell-protocol.c \ - protocol/xdg-shell-client-protocol.h \ - protocol/pointer-constraints-unstable-v1-protocol.c \ - protocol/pointer-constraints-unstable-v1-client-protocol.h \ - protocol/relative-pointer-unstable-v1-protocol.c \ - protocol/relative-pointer-unstable-v1-client-protocol.h - -BUILT_SOURCES += $(nodist_libtoytoolkit_la_SOURCES) - - -libtoytoolkit_la_LIBADD = \ - $(CLIENT_LIBS) \ - $(CAIRO_EGL_LIBS) \ - libshared-cairo.la $(CLOCK_GETTIME_LIBS) -lm -libtoytoolkit_la_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) $(CAIRO_EGL_CFLAGS) - -weston_flower_SOURCES = clients/flower.c -weston_flower_LDADD = libtoytoolkit.la -weston_flower_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_screenshooter_SOURCES = \ - clients/screenshot.c -nodist_weston_screenshooter_SOURCES = \ - protocol/weston-screenshooter-protocol.c \ - protocol/weston-screenshooter-client-protocol.h -weston_screenshooter_LDADD = $(CLIENT_LIBS) libshared.la -weston_screenshooter_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_terminal_SOURCES = \ - clients/terminal.c \ - shared/helpers.h -weston_terminal_LDADD = libtoytoolkit.la -lutil -weston_terminal_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_image_SOURCES = clients/image.c -weston_image_LDADD = libtoytoolkit.la -weston_image_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_cliptest_SOURCES = \ - clients/cliptest.c \ - libweston/vertex-clipping.c \ - libweston/vertex-clipping.h -weston_cliptest_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) -weston_cliptest_LDADD = libtoytoolkit.la - -weston_dnd_SOURCES = \ - clients/dnd.c \ - shared/helpers.h -weston_dnd_LDADD = libtoytoolkit.la -weston_dnd_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_smoke_SOURCES = clients/smoke.c -weston_smoke_LDADD = libtoytoolkit.la -weston_smoke_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_resizor_SOURCES = clients/resizor.c -weston_resizor_LDADD = libtoytoolkit.la -weston_resizor_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_scaler_SOURCES = clients/scaler.c -weston_scaler_LDADD = libtoytoolkit.la -weston_scaler_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -if HAVE_CAIRO_GLESV2 -demo_clients += weston-nested weston-nested-client - -weston_nested_SOURCES = \ - clients/nested.c \ - shared/helpers.h -weston_nested_LDADD = libtoytoolkit.la $(SERVER_LIBS) -weston_nested_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_nested_client_SOURCES = clients/nested-client.c -weston_nested_client_LDADD = $(SIMPLE_EGL_CLIENT_LIBS) -lm -weston_nested_client_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) -endif - -weston_eventdemo_SOURCES = \ - clients/eventdemo.c \ - shared/helpers.h -weston_eventdemo_LDADD = libtoytoolkit.la -weston_eventdemo_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_clickdot_SOURCES = \ - clients/clickdot.c \ - shared/helpers.h -weston_clickdot_LDADD = libtoytoolkit.la -weston_clickdot_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_confine_SOURCES = \ - clients/confine.c \ - shared/helpers.h -weston_confine_LDADD = libtoytoolkit.la -weston_confine_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_transformed_SOURCES = clients/transformed.c -weston_transformed_LDADD = libtoytoolkit.la -weston_transformed_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_fullscreen_SOURCES = clients/fullscreen.c -nodist_weston_fullscreen_SOURCES = \ - protocol/fullscreen-shell-unstable-v1-protocol.c \ - protocol/fullscreen-shell-unstable-v1-client-protocol.h -weston_fullscreen_LDADD = libtoytoolkit.la -weston_fullscreen_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_stacking_SOURCES = \ - clients/stacking.c \ - shared/helpers.h -weston_stacking_LDADD = libtoytoolkit.la -weston_stacking_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_calibrator_SOURCES = \ - clients/calibrator.c \ - shared/helpers.h \ - shared/matrix.c \ - shared/matrix.h -weston_calibrator_LDADD = libtoytoolkit.la -weston_calibrator_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_touch_calibrator_SOURCES = \ - clients/touch-calibrator.c \ - shared/helpers.h \ - shared/matrix.c \ - shared/matrix.h -nodist_weston_touch_calibrator_SOURCES = \ - protocol/weston-touch-calibration-protocol.c \ - protocol/weston-touch-calibration-client-protocol.h -weston_touch_calibrator_LDADD = libtoytoolkit.la -weston_touch_calibrator_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -if BUILD_SUBSURFACES_CLIENT -demo_clients += weston-subsurfaces -weston_subsurfaces_SOURCES = \ - clients/subsurfaces.c \ - shared/helpers.h -weston_subsurfaces_CFLAGS = \ - $(AM_CFLAGS) \ - $(SIMPLE_EGL_CLIENT_CFLAGS) \ - $(CLIENT_CFLAGS) -weston_subsurfaces_LDADD = libtoytoolkit.la $(SIMPLE_EGL_CLIENT_LIBS) -lm -endif - -if HAVE_PANGO -demo_clients += weston-editor -weston_editor_SOURCES = \ - clients/editor.c \ - shared/helpers.h -nodist_weston_editor_SOURCES = \ - protocol/text-input-unstable-v1-protocol.c \ - protocol/text-input-unstable-v1-client-protocol.h -weston_editor_LDADD = libtoytoolkit.la $(PANGO_LIBS) -weston_editor_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) $(PANGO_CFLAGS) -endif - -weston_keyboard_SOURCES = clients/keyboard.c -nodist_weston_keyboard_SOURCES = \ - protocol/weston-desktop-shell-client-protocol.h \ - protocol/weston-desktop-shell-protocol.c \ - protocol/input-method-unstable-v1-protocol.c \ - protocol/input-method-unstable-v1-client-protocol.h -weston_keyboard_LDADD = libtoytoolkit.la -weston_keyboard_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_simple_im_SOURCES = clients/simple-im.c -nodist_weston_simple_im_SOURCES = \ - protocol/input-method-unstable-v1-protocol.c \ - protocol/input-method-unstable-v1-client-protocol.h -weston_simple_im_LDADD = $(CLIENT_LIBS) -weston_simple_im_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_info_SOURCES = \ - clients/weston-info.c \ - shared/helpers.h -nodist_weston_info_SOURCES = \ - protocol/presentation-time-protocol.c \ - protocol/presentation-time-client-protocol.h \ - protocol/linux-dmabuf-unstable-v1-protocol.c \ - protocol/linux-dmabuf-unstable-v1-client-protocol.h \ - protocol/tablet-unstable-v2-protocol.c \ - protocol/tablet-unstable-v2-client-protocol.h \ - protocol/xdg-output-unstable-v1-protocol.c \ - protocol/xdg-output-unstable-v1-client-protocol.h -weston_info_LDADD = $(WESTON_INFO_LIBS) libshared.la -weston_info_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_debug_SOURCES = \ - clients/weston-debug.c \ - shared/helpers.h -nodist_weston_debug_SOURCES = \ - protocol/weston-debug-protocol.c \ - protocol/weston-debug-client-protocol.h -weston_debug_LDADD = $(WESTON_INFO_LIBS) libshared.la -weston_debug_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -weston_desktop_shell_SOURCES = \ - clients/desktop-shell.c \ - shared/helpers.h -nodist_weston_desktop_shell_SOURCES = \ - protocol/weston-desktop-shell-client-protocol.h \ - protocol/weston-desktop-shell-protocol.c -weston_desktop_shell_LDADD = libtoytoolkit.la -weston_desktop_shell_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) - -if ENABLE_IVI_SHELL -weston_ivi_shell_user_interface_SOURCES = \ - clients/ivi-shell-user-interface.c \ - shared/helpers.h -nodist_weston_ivi_shell_user_interface_SOURCES = \ - protocol/ivi-hmi-controller-client-protocol.h \ - protocol/ivi-hmi-controller-protocol.c \ - protocol/ivi-application-client-protocol.h \ - protocol/ivi-application-protocol.c -weston_ivi_shell_user_interface_LDADD = libtoytoolkit.la -weston_ivi_shell_user_interface_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) -endif - -if BUILD_FULL_GL_CLIENTS -demo_clients += weston-gears -weston_gears_SOURCES = clients/gears.c -weston_gears_LDADD = libtoytoolkit.la -weston_gears_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS) -endif - -endif - -BUILT_SOURCES += \ - protocol/weston-screenshooter-protocol.c \ - protocol/weston-screenshooter-client-protocol.h \ - protocol/weston-touch-calibration-protocol.c \ - protocol/weston-touch-calibration-client-protocol.h \ - protocol/weston-debug-protocol.c \ - protocol/weston-debug-client-protocol.h \ - protocol/text-cursor-position-client-protocol.h \ - protocol/text-cursor-position-protocol.c \ - protocol/text-input-unstable-v1-protocol.c \ - protocol/text-input-unstable-v1-client-protocol.h \ - protocol/input-method-unstable-v1-protocol.c \ - protocol/input-method-unstable-v1-client-protocol.h \ - protocol/weston-desktop-shell-client-protocol.h \ - protocol/weston-desktop-shell-protocol.c \ - protocol/viewporter-client-protocol.h \ - protocol/viewporter-protocol.c \ - protocol/presentation-time-protocol.c \ - protocol/presentation-time-client-protocol.h \ - protocol/fullscreen-shell-unstable-v1-protocol.c \ - protocol/fullscreen-shell-unstable-v1-client-protocol.h \ - protocol/xdg-shell-unstable-v6-protocol.c \ - protocol/xdg-shell-unstable-v6-client-protocol.h \ - protocol/xdg-shell-protocol.c \ - protocol/xdg-shell-client-protocol.h \ - protocol/ivi-hmi-controller-protocol.c \ - protocol/ivi-hmi-controller-client-protocol.h \ - protocol/ivi-application-protocol.c \ - protocol/ivi-application-client-protocol.h \ - protocol/linux-dmabuf-unstable-v1-protocol.c \ - protocol/linux-dmabuf-unstable-v1-client-protocol.h \ - protocol/tablet-unstable-v2-protocol.c \ - protocol/tablet-unstable-v2-client-protocol.h \ - protocol/input-timestamps-unstable-v1-protocol.c \ - protocol/input-timestamps-unstable-v1-client-protocol.h \ - protocol/xdg-output-unstable-v1-protocol.c \ - protocol/xdg-output-unstable-v1-client-protocol.h \ - protocol/linux-explicit-synchronization-unstable-v1-protocol.c \ - protocol/linux-explicit-synchronization-unstable-v1-client-protocol.h - -westondatadir = $(datadir)/weston -dist_westondata_DATA = \ - data/wayland.svg \ - data/wayland.png \ - data/pattern.png \ - data/terminal.png \ - data/border.png \ - data/icon_editor.png \ - data/icon_flower.png \ - data/icon_terminal.png \ - data/icon_window.png \ - data/sign_close.png \ - data/sign_maximize.png \ - data/sign_minimize.png - -if ENABLE_IVI_SHELL -dist_westondata_DATA += \ - data/background.png \ - data/tiling.png \ - data/fullscreen.png \ - data/panel.png \ - data/random.png \ - data/sidebyside.png \ - data/home.png \ - data/icon_ivi_clickdot.png \ - data/icon_ivi_flower.png \ - data/icon_ivi_simple-egl.png \ - data/icon_ivi_simple-shm.png \ - data/icon_ivi_smoke.png -endif - - -if BUILD_WCAP_TOOLS -bin_PROGRAMS += wcap-decode - -wcap_decode_SOURCES = \ - wcap/main.c \ - wcap/wcap-decode.c \ - wcap/wcap-decode.h - -wcap_decode_CFLAGS = $(AM_CFLAGS) $(WCAP_CFLAGS) -wcap_decode_LDADD = $(WCAP_LIBS) -endif - - -if ENABLE_DESKTOP_SHELL - -module_LTLIBRARIES += desktop-shell.la - -desktop_shell_la_CPPFLAGS = \ - -I$(top_builddir)/protocol \ - -I$(top_srcdir)/shared \ - -I$(top_builddir)/libweston \ - -I$(top_srcdir)/libweston \ - -I$(top_builddir)/desktop-shell \ - -DMODULEDIR='"$(moduledir)"' \ - -DLIBEXECDIR='"$(libexecdir)"' - -desktop_shell_la_LDFLAGS = -module -avoid-version -desktop_shell_la_LIBADD = libshared.la libweston-desktop-@LIBWESTON_MAJOR@.la $(COMPOSITOR_LIBS) -desktop_shell_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -desktop_shell_la_SOURCES = \ - desktop-shell/shell.h \ - desktop-shell/shell.c \ - desktop-shell/exposay.c \ - desktop-shell/input-panel.c \ - shared/helpers.h -nodist_desktop_shell_la_SOURCES = \ - protocol/weston-desktop-shell-protocol.c \ - protocol/weston-desktop-shell-server-protocol.h - -BUILT_SOURCES += $(nodist_desktop_shell_la_SOURCES) -endif - -if ENABLE_FULLSCREEN_SHELL - -module_LTLIBRARIES += fullscreen-shell.la - -fullscreen_shell_la_CPPFLAGS = \ - -I$(top_builddir)/protocol \ - -I$(top_srcdir)/shared \ - -I$(top_builddir)/libweston \ - -I$(top_srcdir)/libweston - -fullscreen_shell_la_LDFLAGS = -module -avoid-version -fullscreen_shell_la_LIBADD = \ - libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) -fullscreen_shell_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -fullscreen_shell_la_SOURCES = \ - fullscreen-shell/fullscreen-shell.c \ - shared/helpers.h -nodist_fullscreen_shell_la_SOURCES = \ - protocol/fullscreen-shell-unstable-v1-protocol.c \ - protocol/fullscreen-shell-unstable-v1-server-protocol.h - -BUILT_SOURCES += $(nodist_fullscreen_shell_la_SOURCES) -endif - -if ENABLE_IVI_SHELL - -module_LTLIBRARIES += \ - $(ivi_shell) \ - $(hmi_controller) - -ivi_shell = ivi-shell.la -ivi_shell_la_LDFLAGS = -module -avoid-version -ivi_shell_la_LIBADD = \ - libshared.la \ - libweston-@LIBWESTON_MAJOR@.la \ - libweston-desktop-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) -ivi_shell_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -ivi_shell_la_SOURCES = \ - ivi-shell/ivi-layout-export.h \ - ivi-shell/ivi-layout-private.h \ - ivi-shell/ivi-layout-shell.h \ - ivi-shell/ivi-layout.c \ - ivi-shell/ivi-layout-transition.c \ - ivi-shell/ivi-shell.h \ - ivi-shell/ivi-shell.c \ - shared/helpers.h -nodist_ivi_shell_la_SOURCES = \ - protocol/ivi-application-protocol.c \ - protocol/ivi-application-server-protocol.h - -BUILT_SOURCES += $(nodist_ivi_shell_la_SOURCES) - -hmi_controller = hmi-controller.la -hmi_controller_la_LDFLAGS = -module -avoid-version -hmi_controller_la_LIBADD = \ - libshared.la \ - libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) -hmi_controller_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -hmi_controller_la_SOURCES = \ - ivi-shell/ivi-layout-export.h \ - ivi-shell/hmi-controller.c \ - shared/helpers.h -nodist_hmi_controller_la_SOURCES = \ - protocol/ivi-hmi-controller-protocol.c \ - protocol/ivi-hmi-controller-server-protocol.h - -BUILT_SOURCES += $(nodist_hmi_controller_la_SOURCES) - -endif - - -if ENABLE_SCREEN_SHARING - -module_LTLIBRARIES += screen-share.la - -screen_share_la_CPPFLAGS = $(AM_CPPFLAGS) -DBINDIR='"$(bindir)"' -screen_share_la_LDFLAGS = -module -avoid-version -screen_share_la_LIBADD = \ - libshared-cairo.la \ - libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) \ - $(SCREEN_SHARE_LIBS) -screen_share_la_CFLAGS = \ - $(COMPOSITOR_CFLAGS) \ - $(SCREEN_SHARE_CFLAGS) \ - $(AM_CFLAGS) -screen_share_la_SOURCES = \ - compositor/screen-share.c \ - shared/helpers.h -nodist_screen_share_la_SOURCES = \ - protocol/fullscreen-shell-unstable-v1-protocol.c \ - protocol/fullscreen-shell-unstable-v1-client-protocol.h - -endif - -if ENABLE_XWAYLAND - -libweston_module_LTLIBRARIES += xwayland.la - -xwayland_la_CPPFLAGS = \ - -I$(top_builddir)/protocol \ - -I$(top_srcdir)/shared \ - -I$(top_builddir)/libweston \ - -I$(top_srcdir)/libweston \ - -I$(top_builddir)/xwayland \ - -DMODULEDIR='"$(moduledir)"' \ - -DLIBEXECDIR='"$(libexecdir)"' - -xwayland_la_LDFLAGS = -module -avoid-version -xwayland_la_LIBADD = \ - libshared-cairo.la \ - libweston-@LIBWESTON_MAJOR@.la \ - $(XWAYLAND_LIBS) -xwayland_la_CFLAGS = \ - $(AM_CFLAGS) \ - $(COMPOSITOR_CFLAGS) \ - $(PIXMAN_CFLAGS) \ - $(CAIRO_CFLAGS) -xwayland_la_SOURCES = \ - xwayland/xwayland.h \ - xwayland/xwayland-internal-interface.h \ - xwayland/window-manager.c \ - xwayland/selection.c \ - xwayland/dnd.c \ - xwayland/launcher.c \ - xwayland/hash.c \ - xwayland/hash.h \ - shared/helpers.h - -libwestoninclude_HEADERS += xwayland/xwayland-api.h - -endif - - -# -# Shared utilities -# - -noinst_LTLIBRARIES += libshared.la libshared-cairo.la \ - libzunitc.la libzunitcmain.la - -libshared_la_CPPFLAGS = \ - -DDATADIR='"$(datadir)"' \ - $(AM_CPPFLAGS) - -libshared_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) - -libshared_la_SOURCES = \ - shared/config-parser.c \ - shared/option-parser.c \ - shared/config-parser.h \ - shared/file-util.c \ - shared/file-util.h \ - shared/helpers.h \ - shared/os-compatibility.c \ - shared/os-compatibility.h \ - shared/xalloc.c \ - shared/xalloc.h - -libshared_cairo_la_CPPFLAGS = $(libshared_la_CPPFLAGS) - -libshared_cairo_la_CFLAGS = \ - $(AM_CFLAGS) \ - $(COMPOSITOR_CFLAGS) \ - $(PIXMAN_CFLAGS) \ - $(CAIRO_CFLAGS) \ - $(PANGO_CFLAGS) \ - $(PNG_CFLAGS) \ - $(WEBP_CFLAGS) - -libshared_cairo_la_LIBADD = \ - $(PIXMAN_LIBS) \ - $(CAIRO_LIBS) \ - $(PANGO_LIBS) \ - $(PNG_LIBS) \ - $(WEBP_LIBS) \ - $(JPEG_LIBS) - -libshared_cairo_la_SOURCES = \ - $(libshared_la_SOURCES) \ - shared/helpers.h \ - shared/image-loader.c \ - shared/image-loader.h \ - shared/cairo-util.c \ - shared/frame.c \ - shared/cairo-util.h - -libzunitc_la_SOURCES = \ - tools/zunitc/inc/zunitc/zunitc.h \ - tools/zunitc/inc/zunitc/zunitc_impl.h \ - tools/zunitc/src/zuc_base_logger.c \ - tools/zunitc/src/zuc_base_logger.h \ - tools/zunitc/src/zuc_collector.c \ - tools/zunitc/src/zuc_collector.h \ - tools/zunitc/src/zuc_context.h \ - tools/zunitc/src/zuc_event.h \ - tools/zunitc/src/zuc_event_listener.h \ - tools/zunitc/src/zuc_junit_reporter.c \ - tools/zunitc/src/zuc_junit_reporter.h \ - tools/zunitc/src/zuc_types.h \ - tools/zunitc/src/zunitc_impl.c \ - shared/helpers.h - -libzunitc_la_CFLAGS = \ - $(AM_CFLAGS) \ - -I$(top_srcdir)/tools/zunitc/inc - -libzunitc_la_LIBADD = \ - libshared.la \ - $(CLOCK_GETTIME_LIBS) - -if ENABLE_JUNIT_XML -libzunitc_la_CFLAGS += \ - $(LIBXML2_CFLAGS) -libzunitc_la_LIBADD += \ - $(LIBXML2_LIBS) -endif - -libzunitcmain_la_SOURCES = \ - tools/zunitc/src/main.c - -libzunitcmain_la_CFLAGS = \ - $(AM_CFLAGS) \ - -I$(top_srcdir)/tools/zunitc/inc - -libzunitcmain_la_LIBADD = \ - libzunitc.la \ - libshared.la - -# -# tests subdirectory -# - -TESTS = $(internal_tests) $(shared_tests) $(module_tests) $(weston_tests) $(ivi_tests) - -internal_tests = \ - internal-screenshot.weston - -shared_tests = \ - config-parser.test \ - timespec.test \ - string.test \ - vertex-clip.test \ - zuctest - -module_tests = \ - plugin-registry-test.la \ - surface-test.la \ - surface-global-test.la - -weston_tests = \ - bad_buffer.weston \ - keyboard.weston \ - event.weston \ - pointer.weston \ - text.weston \ - presentation.weston \ - viewporter.weston \ - roles.weston \ - subsurface.weston \ - subsurface-shot.weston \ - devices.weston \ - touch.weston \ - linux-explicit-synchronization.weston - -AM_TESTS_ENVIRONMENT = \ - abs_builddir='$(abs_builddir)'; export abs_builddir; \ - abs_top_srcdir='$(abs_top_srcdir)'; export abs_top_srcdir; - -TEST_EXTENSIONS = .la .weston -LA_LOG_COMPILER = $(srcdir)/tests/weston-tests-env -WESTON_LOG_COMPILER = $(srcdir)/tests/weston-tests-env - -clean-local: - -rm -rf logs - -rm -rf $(DOCDIRS) - -# To remove when automake 1.11 support is dropped -export abs_builddir - -noinst_LTLIBRARIES += \ - weston-test.la \ - weston-test-desktop-shell.la \ - $(module_tests) \ - libtest-runner.la \ - libtest-client.la - -noinst_PROGRAMS += \ - $(setbacklight) \ - $(internal_tests) \ - $(shared_tests) \ - $(weston_tests) \ - $(ivi_tests) \ - matrix-test - -test_module_ldflags = -module -avoid-version -rpath $(libdir) -test_module_libadd = \ - libweston-@LIBWESTON_MAJOR@.la \ - $(COMPOSITOR_LIBS) - -plugin_registry_test_la_SOURCES = tests/plugin-registry-test.c -plugin_registry_test_la_LIBADD = $(test_module_libadd) -plugin_registry_test_la_LDFLAGS = $(test_module_ldflags) -plugin_registry_test_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) - -surface_global_test_la_SOURCES = tests/surface-global-test.c -surface_global_test_la_LIBADD = $(test_module_libadd) -surface_global_test_la_LDFLAGS = $(test_module_ldflags) -surface_global_test_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) - -surface_test_la_SOURCES = tests/surface-test.c -surface_test_la_LIBADD = $(test_module_libadd) -surface_test_la_LDFLAGS = $(test_module_ldflags) -surface_test_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) - -weston_test_la_LIBADD = libshared.la $(test_module_libadd) -weston_test_la_LDFLAGS = $(test_module_ldflags) -weston_test_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -weston_test_la_SOURCES = \ - tests/weston-test.c \ - shared/helpers.h -nodist_weston_test_la_SOURCES = \ - protocol/weston-test-protocol.c \ - protocol/weston-test-server-protocol.h - -weston_test_desktop_shell_la_LIBADD = libshared.la libweston-desktop-@LIBWESTON_MAJOR@.la $(COMPOSITOR_LIBS) -weston_test_desktop_shell_la_LDFLAGS = $(test_module_ldflags) -weston_test_desktop_shell_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -weston_test_desktop_shell_la_SOURCES = \ - tests/weston-test-desktop-shell.c \ - shared/helpers.h - -libtest_runner_la_SOURCES = \ - tests/weston-test-runner.c \ - tests/weston-test-runner.h -libtest_runner_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) - -config_parser_test_SOURCES = tests/config-parser-test.c -config_parser_test_LDADD = \ - libshared.la \ - $(COMPOSITOR_LIBS) \ - libzunitc.la \ - libzunitcmain.la -config_parser_test_CFLAGS = \ - $(AM_CFLAGS) \ - -I$(top_srcdir)/tools/zunitc/inc - -timespec_test_SOURCES = tests/timespec-test.c -timespec_test_LDADD = \ - libshared.la \ - libzunitc.la \ - libzunitcmain.la -timespec_test_CFLAGS = \ - $(AM_CFLAGS) \ - -I$(top_srcdir)/tools/zunitc/inc - -string_test_SOURCES = \ - tests/string-test.c \ - shared/string-helpers.h -string_test_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -string_test_LDADD = libtest-client.la - -vertex_clip_test_SOURCES = \ - tests/vertex-clip-test.c \ - shared/helpers.h \ - libweston/vertex-clipping.c \ - libweston/vertex-clipping.h -vertex_clip_test_LDADD = libtest-runner.la -lm $(CLOCK_GETTIME_LIBS) - -libtest_client_la_SOURCES = \ - tests/weston-test-client-helper.c \ - tests/weston-test-client-helper.h \ - tests/input-timestamps-helper.c \ - tests/input-timestamps-helper.h -nodist_libtest_client_la_SOURCES = \ - protocol/weston-test-protocol.c \ - protocol/weston-test-client-protocol.h \ - protocol/input-timestamps-unstable-v1-protocol.c \ - protocol/input-timestamps-unstable-v1-client-protocol.h -libtest_client_la_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) $(CAIRO_CFLAGS) -libtest_client_la_LIBADD = libshared.la libtest-runner.la $(TEST_CLIENT_LIBS) $(CAIRO_LIBS) - - -# -# Internal tests - tests functionality of the testsuite itself -# - -internal_screenshot_weston_SOURCES = tests/internal-screenshot-test.c -internal_screenshot_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -internal_screenshot_weston_LDADD = libtest-client.la - - -# -# Weston Tests -# - -bad_buffer_weston_SOURCES = tests/bad-buffer-test.c -bad_buffer_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -bad_buffer_weston_LDADD = libtest-client.la - -keyboard_weston_SOURCES = tests/keyboard-test.c -keyboard_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -keyboard_weston_LDADD = libtest-client.la - -event_weston_SOURCES = tests/event-test.c -event_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -event_weston_LDADD = libtest-client.la - -pointer_weston_SOURCES = tests/pointer-test.c -pointer_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -pointer_weston_LDADD = libtest-client.la - -devices_weston_SOURCES = tests/devices-test.c -devices_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -devices_weston_LDADD = libtest-client.la - -text_weston_SOURCES = tests/text-test.c -nodist_text_weston_SOURCES = \ - protocol/text-input-unstable-v1-protocol.c \ - protocol/text-input-unstable-v1-client-protocol.h -text_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -text_weston_LDADD = libtest-client.la - -subsurface_weston_SOURCES = tests/subsurface-test.c -subsurface_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -subsurface_weston_LDADD = libtest-client.la - -subsurface_shot_weston_SOURCES = tests/subsurface-shot-test.c -subsurface_shot_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -subsurface_shot_weston_LDADD = libtest-client.la - -presentation_weston_SOURCES = \ - tests/presentation-test.c \ - shared/helpers.h -nodist_presentation_weston_SOURCES = \ - protocol/presentation-time-protocol.c \ - protocol/presentation-time-client-protocol.h -presentation_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -presentation_weston_LDADD = libtest-client.la - -roles_weston_SOURCES = tests/roles-test.c -roles_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -roles_weston_LDADD = libtest-client.la - -viewporter_weston_SOURCES = \ - tests/viewporter-test.c \ - shared/helpers.h -nodist_viewporter_weston_SOURCES = \ - protocol/viewporter-protocol.c \ - protocol/viewporter-client-protocol.h -viewporter_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -viewporter_weston_LDADD = libtest-client.la - -touch_weston_SOURCES = tests/touch-test.c -touch_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -touch_weston_LDADD = libtest-client.la - -linux_explicit_synchronization_weston_SOURCES = \ - tests/linux-explicit-synchronization-test.c -nodist_linux_explicit_synchronization_weston_SOURCES = \ - protocol/linux-explicit-synchronization-unstable-v1-protocol.c \ - protocol/linux-explicit-synchronization-unstable-v1-client-protocol.h -linux_explicit_synchronization_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -linux_explicit_synchronization_weston_LDADD = libtest-client.la - -if ENABLE_XWAYLAND_TEST -weston_tests += xwayland-test.weston -xwayland_test_weston_SOURCES = tests/xwayland-test.c -xwayland_test_weston_CFLAGS = \ - $(AM_CFLAGS) $(XWAYLAND_TEST_CFLAGS) -DXSERVER_PATH='"@XSERVER_PATH@"' -xwayland_test_weston_LDADD = libtest-client.la $(XWAYLAND_TEST_LIBS) -endif - -matrix_test_SOURCES = \ - tests/matrix-test.c \ - shared/matrix.c \ - shared/matrix.h -matrix_test_CPPFLAGS = -DUNIT_TEST -matrix_test_LDADD = -lm $(CLOCK_GETTIME_LIBS) - -if ENABLE_IVI_SHELL -module_tests += \ - ivi-layout-internal-test.la \ - ivi-layout-test.la - -ivi_layout_internal_test_la_LIBADD = $(test_module_libadd) -ivi_layout_internal_test_la_LDFLAGS = $(test_module_ldflags) -ivi_layout_internal_test_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -ivi_layout_internal_test_la_SOURCES = \ - tests/ivi-layout-internal-test.c - -ivi_layout_test_la_LIBADD = $(test_module_libadd) -ivi_layout_test_la_LDFLAGS = $(test_module_ldflags) -ivi_layout_test_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -ivi_layout_test_la_SOURCES = \ - tests/ivi-layout-test-plugin.c \ - tests/ivi-test.h \ - shared/helpers.h -nodist_ivi_layout_test_la_SOURCES = \ - protocol/weston-test-protocol.c \ - protocol/weston-test-server-protocol.h - -ivi_tests = \ - ivi-shell-app.weston - -ivi_shell_app_weston_SOURCES = tests/ivi-shell-app-test.c -nodist_ivi_shell_app_weston_SOURCES = \ - protocol/ivi-application-protocol.c \ - protocol/ivi-application-client-protocol.h -ivi_shell_app_weston_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -ivi_shell_app_weston_LDADD = libtest-client.la - -noinst_PROGRAMS += ivi-layout-test-client.ivi - -ivi_layout_test_client_ivi_SOURCES = \ - tests/ivi-layout-test-client.c \ - tests/ivi-test.h \ - shared/helpers.h -nodist_ivi_layout_test_client_ivi_SOURCES = \ - protocol/ivi-application-protocol.c \ - protocol/ivi-application-client-protocol.h -ivi_layout_test_client_ivi_CFLAGS = $(AM_CFLAGS) $(TEST_CLIENT_CFLAGS) -ivi_layout_test_client_ivi_LDADD = libtest-client.la -endif - -if BUILD_SETBACKLIGHT -noinst_PROGRAMS += setbacklight -setbacklight_SOURCES = \ - tests/setbacklight.c \ - libweston/libbacklight.c \ - libweston/libbacklight.h -setbacklight_CFLAGS = $(AM_CFLAGS) $(SETBACKLIGHT_CFLAGS) -setbacklight_LDADD = $(SETBACKLIGHT_LIBS) -endif - -all-local: zuctest$(EXEEXT) - -noinst_PROGRAMS += zuctest$(EXEEXT) - -zuctest_LDADD = \ - libzunitc.la \ - libzunitcmain.la - -zuctest_CFLAGS = \ - $(AM_CFLAGS) \ - -I$(top_srcdir)/tools/zunitc/inc - -zuctest_SOURCES = \ - tools/zunitc/test/fixtures_test.c \ - tools/zunitc/test/zunitc_test.c - -EXTRA_DIST += \ - tests/internal-screenshot.ini \ - tests/reference/internal-screenshot-bad-00.png \ - tests/reference/internal-screenshot-good-00.png \ - tests/reference/subsurface_z_order-00.png \ - tests/reference/subsurface_z_order-01.png \ - tests/reference/subsurface_z_order-02.png \ - tests/reference/subsurface_z_order-03.png \ - tests/reference/subsurface_z_order-04.png \ - tests/weston-tests-env - -BUILT_SOURCES += \ - protocol/weston-test-protocol.c \ - protocol/weston-test-server-protocol.h \ - protocol/weston-test-client-protocol.h \ - protocol/text-input-unstable-v1-protocol.c \ - protocol/text-input-unstable-v1-client-protocol.h - -EXTRA_DIST += \ - protocol/weston-desktop-shell.xml \ - protocol/weston-screenshooter.xml \ - protocol/text-cursor-position.xml \ - protocol/weston-test.xml \ - protocol/weston-touch-calibration.xml \ - protocol/ivi-application.xml \ - protocol/ivi-hmi-controller.xml - -# -# manual test modules in tests subdirectory -# - -noinst_LTLIBRARIES += \ - surface-screenshot.la - -surface_screenshot_la_LIBADD = libshared.la $(test_module_libadd) -surface_screenshot_la_LDFLAGS = $(test_module_ldflags) -surface_screenshot_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) -surface_screenshot_la_SOURCES = tests/surface-screenshot-test.c - - -# -# Documentation -# - -man_MANS = weston.1 weston.ini.5 weston-debug.1 - -if ENABLE_DRM_COMPOSITOR -man_MANS += weston-drm.7 -endif - -if ENABLE_RDP_COMPOSITOR -man_MANS += weston-rdp.7 -endif - -MAN_SUBSTS = \ - -e 's|@weston_native_backend@|$(WESTON_NATIVE_BACKEND)|g' \ - -e 's|@weston_modules_dir@|$(moduledir)|g' \ - -e 's|@libweston_modules_dir@|$(libweston_moduledir)|g' \ - -e 's|@weston_shell_client@|$(WESTON_SHELL_CLIENT)|g' \ - -e 's|@weston_libexecdir@|$(libexecdir)|g' \ - -e 's|@weston_bindir@|$(bindir)|g' \ - -e 's|@xserver_path@|$(XSERVER_PATH)|g' \ - -e 's|@version@|$(PACKAGE_VERSION)|g' - -SUFFIXES = .1 .5 .7 .man - -%.1 %.5 %.7 : man/%.man - $(AM_V_GEN)$(SED) $(MAN_SUBSTS) < $< > $@ - -EXTRA_DIST += \ - CONTRIBUTING.md \ - README.md \ - doc/wayland-screenshot.jpg \ - doc/calibration-helper.bash \ - man/weston.man \ - man/weston-debug.man \ - man/weston-drm.man \ - man/weston-rdp.man \ - man/weston.ini.man - -CLEANFILES += $(man_MANS) - -if ENABLE_DEVDOCS -DOXYGEN_INDICES = docs/developer/html/index.html docs/tools/html/index.html - -docs/developer/html/index.html: doc/doxygen/tooldev.doxygen | docs/developer - cd doc/doxygen && $(DOXYGEN) tooldev.doxygen - -docs/tools/html/index.html: doc/doxygen/tools.doxygen | docs/tools - cd doc/doxygen && $(DOXYGEN) tools.doxygen -endif - -DOCDIRS = \ - docs/developer \ - docs/tools - -$(DOCDIRS): - $(MKDIR_P) $@ - -.PHONY: doc $(DOXYGEN_INDICES) - -doc: $(DOXYGEN_INDICES) - -.SECONDEXPANSION: - -define protostability -$(if $(findstring unstable,$1),unstable,stable) -endef - -define protoname -$(shell echo $1 | $(SED) 's/\([a-z\-]\+\)-[a-z]\+-v[0-9]\+/\1/') -endef - -protocol/%-protocol.c : $(WAYLAND_PROTOCOLS_DATADIR)/$$(call protostability,$$*)/$$(call protoname,$$*)/$$*.xml - $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(wayland_scanner) code < $< > $@ - -protocol/%-server-protocol.h : $(WAYLAND_PROTOCOLS_DATADIR)/$$(call protostability,$$*)/$$(call protoname,$$*)/$$*.xml - $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(wayland_scanner) server-header < $< > $@ - -protocol/%-client-protocol.h : $(WAYLAND_PROTOCOLS_DATADIR)/$$(call protostability,$$*)/$$(call protoname,$$*)/$$*.xml - $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(wayland_scanner) client-header < $< > $@ - -protocol/%-protocol.c : $(top_srcdir)/protocol/%.xml - $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(wayland_scanner) code < $< > $@ - -protocol/%-server-protocol.h : $(top_srcdir)/protocol/%.xml - $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(wayland_scanner) server-header < $< > $@ - -protocol/%-client-protocol.h : $(top_srcdir)/protocol/%.xml - $(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(wayland_scanner) client-header < $< > $@ - -EXTRA_DIST += \ - clients/meson.build \ - compositor/meson.build \ - data/meson.build \ - desktop-shell/meson.build \ - fullscreen-shell/meson.build \ - ivi-shell/meson.build \ - libweston-desktop/meson.build \ - libweston/git-version.h.meson \ - libweston/meson.build \ - man/meson.build \ - meson.build \ - meson_options.txt \ - protocol/meson.build \ - remoting/meson.build \ - shared/meson.build \ - tests/meson.build \ - wcap/meson.build \ - xwayland/meson.build diff --git a/autogen.sh b/autogen.sh deleted file mode 100755 index 916169a42..000000000 --- a/autogen.sh +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/sh - -test -n "$srcdir" || srcdir=`dirname "$0"` -test -n "$srcdir" || srcdir=. -( - cd "$srcdir" && - autoreconf --force -v --install -) || exit -test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" diff --git a/compositor/weston.pc.in b/compositor/weston.pc.in deleted file mode 100644 index 6890a77cb..000000000 --- a/compositor/weston.pc.in +++ /dev/null @@ -1,12 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ -libexecdir=@libexecdir@ -pkglibexecdir=${libexecdir}/@PACKAGE@ - -Name: Weston Plugin API -Description: Header files for Weston plugin development -Version: @WESTON_VERSION@ -Requires.private: libweston-@LIBWESTON_MAJOR@ -Cflags: -I${includedir}/weston diff --git a/configure.ac b/configure.ac deleted file mode 100644 index 5d50c3855..000000000 --- a/configure.ac +++ /dev/null @@ -1,793 +0,0 @@ -m4_define([weston_major_version], [6]) -m4_define([weston_minor_version], [0]) -m4_define([weston_micro_version], [90]) -m4_define([weston_version], - [weston_major_version.weston_minor_version.weston_micro_version]) -m4_define([libweston_major_version], [6]) -m4_define([libweston_minor_version], [0]) -m4_define([libweston_patch_version], [90]) - -AC_PREREQ([2.64]) -AC_INIT([weston], - [weston_version], - [https://gitlab.freedesktop.org/wayland/weston/issues/], - [weston], - [https://wayland.freedesktop.org]) - -WAYLAND_PREREQ_VERSION="1.12.0" - -AC_SUBST([WESTON_VERSION_MAJOR], [weston_major_version]) -AC_SUBST([WESTON_VERSION_MINOR], [weston_minor_version]) -AC_SUBST([WESTON_VERSION_MICRO], [weston_micro_version]) -AC_SUBST([WESTON_VERSION], [weston_version]) -AC_SUBST([LIBWESTON_MAJOR], [libweston_major_version]) -# We use minor as current and age since on ABI/API break/removal we bump major -# so minor will be reset to 0. -m4_define([lt_current], [libweston_minor_version]) -m4_define([lt_revision], [libweston_patch_version]) -m4_define([lt_age], [libweston_minor_version]) -AC_SUBST([LT_VERSION_INFO], [lt_current:lt_revision:lt_age]) - -AC_CONFIG_AUX_DIR([build-aux]) -AC_CONFIG_HEADERS([config.h]) -AC_CONFIG_MACRO_DIR([m4]) - -AC_USE_SYSTEM_EXTENSIONS -AC_SYS_LARGEFILE - -save_CFLAGS="$CFLAGS" -export CFLAGS="$CFLAGS -Werror" -AC_HEADER_MAJOR -CFLAGS="$save_CFLAGS" - -AM_INIT_AUTOMAKE([1.11 parallel-tests foreign no-dist-gzip dist-xz color-tests subdir-objects]) - -AM_SILENT_RULES([yes]) - -# Check Weston and libweston version consistency -m4_if(m4_cmp(weston_micro_version, [90]), [-1], - [ - dnl micro < 90 - dnl A final or stable release, not a pre-release: - dnl Weston and libweston versions must match. - m4_if(weston_version, libweston_major_version[.]libweston_minor_version[.]libweston_patch_version, - [], - [AC_MSG_ERROR([Weston and libweston version mismatch for a final release])]) - ], - [ - dnl A pre-release: - dnl libweston must be equal or greater than weston. - m4_case(m4_list_cmp([weston_major_version, weston_minor_version, weston_micro_version], - [libweston_major_version, libweston_minor_version, libweston_patch_version]), - [-1], [ - dnl weston < libweston - dnl libweston must be weston_major+1.0.0 - m4_if(m4_eval(weston_major_version+1)[.0.0], - libweston_major_version[.]libweston_minor_version[.]libweston_patch_version, - [], - [AC_MSG_ERROR([libweston version is greater but not (weston_major+1).0.0])]) - ], - [0], [ - dnl weston == libweston, all ok - ], - [1], [ - dnl weston > libweston, wrong - AC_MSG_ERROR([Weston version is greater than libweston.]) - ]) - ]) - -# Check for programs -AC_PROG_CC -AC_PROG_SED - -# Initialize libtool -LT_PREREQ([2.2]) -LT_INIT([disable-static]) - -AC_ARG_ENABLE(autotools, - AS_HELP_STRING([--enable-autotools], - [Allow building with autotools]),, - enable_autotools=no) -if test "x$enable_autotools" = "xno"; then - AC_ERROR([ - *** Autotools support will be removed after the 6.0.0 release *** - - Please, try the Meson based build and report any problems you might have - with it. Instructions and references can be found in README.md. - If you still want to continue building with autotools, - use --enable-autotools configure option. - ]) -fi - -AC_ARG_VAR([WESTON_NATIVE_BACKEND], - [Set the native backend to use, if Weston is not running under Wayland nor X11. @<:@default=drm-backend.so@:>@]) -AC_ARG_VAR([WESTON_SHELL_CLIENT], - [Set the default desktop shell client to load if none is specified in weston.ini. @<:@default=weston-desktop-shell@:>@]) - -PKG_PROG_PKG_CONFIG() - -# Check pthread -AX_PTHREAD - -# Check for dlsym instead of dlopen because ASAN hijacks the latter -WESTON_SEARCH_LIBS([DL], [dl], [dlsym]) - -# In old glibc versions (< 2.17) clock_gettime() and clock_getres() are in librt -WESTON_SEARCH_LIBS([CLOCK_GETTIME], [rt], [clock_gettime]) -WESTON_SEARCH_LIBS([CLOCK_GETRES], [rt], [clock_getres]) - -AC_CHECK_DECL(SFD_CLOEXEC,[], - [AC_MSG_ERROR("SFD_CLOEXEC is needed to compile weston")], - [[#include ]]) -AC_CHECK_DECL(TFD_CLOEXEC,[], - [AC_MSG_ERROR("TFD_CLOEXEC is needed to compile weston")], - [[#include ]]) -AC_CHECK_DECL(CLOCK_MONOTONIC,[], - [AC_MSG_ERROR("CLOCK_MONOTONIC is needed to compile weston")], - [[#include ]]) - -AC_CHECK_FUNCS([mkostemp strchrnul initgroups posix_fallocate]) - -# check for libdrm as a build-time dependency only -# libdrm 2.4.30 introduced drm_fourcc.h. -PKG_CHECK_MODULES(LIBDRM, [libdrm >= 2.4.68], [], [AC_MSG_ERROR([ - libdrm is a hard build-time dependency for libweston core, - but a sufficient version was not found. However, libdrm - is not a runtime dependency unless you have features - enabled that require it.])]) - -COMPOSITOR_MODULES="wayland-server >= $WAYLAND_PREREQ_VERSION pixman-1 >= 0.25.2" - -AC_CONFIG_FILES([doc/doxygen/tools.doxygen doc/doxygen/tooldev.doxygen]) - -AC_ARG_ENABLE(devdocs, - AS_HELP_STRING([--disable-devdocs], - [do not enable building of developer documentation]),, - enable_devdocs=auto) -if test "x$enable_devdocs" != "xno"; then - AC_CHECK_PROGS([DOXYGEN], [doxygen]) - if test "x$DOXYGEN" = "x" -a "x$enable_devdocs" = "xyes"; then - AC_MSG_ERROR([Developer documentation explicitly requested, but Doxygen couldn't be found]) - fi - if test "x$DOXYGEN" != "x"; then - enable_devdocs=yes - else - enable_devdocs=no - fi -fi -AM_CONDITIONAL(ENABLE_DEVDOCS, test "x$enable_devdocs" = "xyes") - -AC_ARG_ENABLE(egl, [ --disable-egl],, - enable_egl=yes) -AM_CONDITIONAL(ENABLE_EGL, test x$enable_egl = xyes) -if test x$enable_egl = xyes; then - AC_DEFINE([ENABLE_EGL], [1], [Build Weston with EGL support]) - PKG_CHECK_MODULES(EGL, [egl glesv2]) - AC_CHECK_HEADERS([linux/sync_file.h]) -fi - -COMPOSITOR_MODULES="$COMPOSITOR_MODULES xkbcommon >= 0.3.0" - -PKG_CHECK_MODULES(XKBCOMMON_COMPOSE, [xkbcommon >= 0.5.0], - [AC_DEFINE(HAVE_XKBCOMMON_COMPOSE, 1, - [Define if xkbcommon is 0.5.0 or newer])],true) - -AC_ARG_ENABLE(setuid-install, [ --enable-setuid-install],, - enable_setuid_install=yes) -AM_CONDITIONAL(ENABLE_SETUID_INSTALL, test x$enable_setuid_install = xyes) - - -AC_ARG_ENABLE(xwayland, [ --enable-xwayland],, - enable_xwayland=yes) -AC_ARG_ENABLE(xwayland-test, [ --enable-xwayland-test],, - enable_xwayland_test=yes) -AM_CONDITIONAL(ENABLE_XWAYLAND, test x$enable_xwayland = xyes) -AM_CONDITIONAL(ENABLE_XWAYLAND_TEST, test x$enable_xwayland = xyes -a x$enable_xwayland_test = xyes) -if test x$enable_xwayland = xyes; then - PKG_CHECK_MODULES([XWAYLAND], xcb xcb-xfixes xcb-composite xcb-shape xcursor cairo-xcb) - AC_DEFINE([BUILD_XWAYLAND], [1], [Build the X server launcher]) - - AC_ARG_WITH(xserver-path, AS_HELP_STRING([--with-xserver-path=PATH], - [Path to X server]), [XSERVER_PATH="$withval"], - [XSERVER_PATH="/usr/bin/Xwayland"]) - AC_SUBST([XSERVER_PATH]) - if test x$enable_xwayland_test = xyes; then - PKG_CHECK_MODULES([XWAYLAND_TEST], x11) - fi -fi - -AC_ARG_ENABLE(x11-compositor, [ --enable-x11-compositor],, - enable_x11_compositor=yes) -AM_CONDITIONAL(ENABLE_X11_COMPOSITOR, test x$enable_x11_compositor = xyes) -have_xcb_xkb=no -if test x$enable_x11_compositor = xyes; then - PKG_CHECK_MODULES([XCB], xcb >= 1.8) - X11_COMPOSITOR_MODULES="x11 x11-xcb xcb-shm" - - PKG_CHECK_MODULES(X11_COMPOSITOR_XKB, [xcb-xkb >= 1.9], - [have_xcb_xkb="yes"], [have_xcb_xkb="no"]) - if test "x$have_xcb_xkb" = xyes; then - X11_COMPOSITOR_MODULES="$X11_COMPOSITOR_MODULES xcb-xkb" - AC_DEFINE([HAVE_XCB_XKB], [1], [libxcb supports XKB protocol]) - fi - - PKG_CHECK_MODULES(X11_COMPOSITOR, [$X11_COMPOSITOR_MODULES]) - AC_DEFINE([BUILD_X11_COMPOSITOR], [1], [Build the X11 compositor]) -fi - - -AC_ARG_ENABLE(drm-compositor, [ --enable-drm-compositor],, - enable_drm_compositor=yes) -AM_CONDITIONAL(ENABLE_DRM_COMPOSITOR, test x$enable_drm_compositor = xyes) -if test x$enable_drm_compositor = xyes; then - AC_DEFINE([BUILD_DRM_COMPOSITOR], [1], [Build the DRM compositor]) - PKG_CHECK_MODULES(DRM_COMPOSITOR, [libudev >= 136 libdrm >= 2.4.30 gbm]) - PKG_CHECK_MODULES(DRM_COMPOSITOR_MODIFIERS, [libdrm >= 2.4.71], - [AC_DEFINE([HAVE_DRM_ADDFB2_MODIFIERS], 1, [libdrm supports modifiers])], - [AC_MSG_WARN([libdrm does not support AddFB2 with modifiers])]) - PKG_CHECK_MODULES(DRM_COMPOSITOR_ATOMIC, [libdrm >= 2.4.78], - [AC_DEFINE([HAVE_DRM_ATOMIC], 1, [libdrm supports atomic API])], - [AC_MSG_WARN([libdrm does not support atomic modesetting, will omit that capability])]) - PKG_CHECK_MODULES(DRM_COMPOSITOR_FORMATS_BLOB, [libdrm >= 2.4.83], - [AC_DEFINE([HAVE_DRM_FORMATS_BLOB], 1, [libdrm supports modifier advertisement])], - [AC_MSG_WARN([libdrm does not support modifier advertisement])]) - PKG_CHECK_MODULES(DRM_COMPOSITOR_GBM_MODIFIERS, [gbm >= 17.1], - [AC_DEFINE([HAVE_GBM_MODIFIERS], 1, [GBM supports modifiers])], - [AC_MSG_WARN([GBM does not support modifiers])]) - PKG_CHECK_MODULES(DRM_COMPOSITOR_GBM, [gbm >= 17.2], - [AC_DEFINE([HAVE_GBM_FD_IMPORT], 1, [gbm supports import with modifiers])], - [AC_MSG_WARN([GBM does not support dmabuf import, will omit that capability])]) -fi - -AC_ARG_ENABLE(remoting, [ --enable-remoting],, - enable_remoting=no) -AM_CONDITIONAL(ENABLE_REMOTING, test x$enable_remoting = xyes) -if test x$enable_remoting = xyes; then - if test x$enable_drm_compositor != xyes; then - AC_MSG_WARN([The remoting-plugin.so module requires the DRM backend.]) - fi - PKG_CHECK_MODULES(REMOTING_GST, [gstreamer-1.0 gstreamer-allocators-1.0 gstreamer-app-1.0 gstreamer-video-1.0]) -fi - - -PKG_CHECK_MODULES(LIBEVDEV, [libevdev]) -PKG_CHECK_MODULES(LIBINPUT_BACKEND, [libinput >= 0.8.0]) -PKG_CHECK_MODULES(COMPOSITOR, [$COMPOSITOR_MODULES]) - -# XXX: For minor version 2 of zwp_linux_explicit_synchronization_v1, we -# actually need a development version after 1.17, but there is no way to -# express such a requirement at the moment. -PKG_CHECK_MODULES(WAYLAND_PROTOCOLS, [wayland-protocols >= 1.17], - [ac_wayland_protocols_pkgdatadir=`$PKG_CONFIG --variable=pkgdatadir wayland-protocols`]) -AC_SUBST(WAYLAND_PROTOCOLS_DATADIR, $ac_wayland_protocols_pkgdatadir) - -AC_ARG_ENABLE(wayland-compositor, [ --enable-wayland-compositor],, - enable_wayland_compositor=yes) -AM_CONDITIONAL(ENABLE_WAYLAND_COMPOSITOR, - test x$enable_wayland_compositor = xyes) -if test x$enable_wayland_compositor = xyes; then - AC_DEFINE([BUILD_WAYLAND_COMPOSITOR], [1], - [Build the Wayland (nested) compositor]) - PKG_CHECK_MODULES(WAYLAND_COMPOSITOR, [wayland-client >= $WAYLAND_PREREQ_VERSION wayland-cursor]) - if test x$enable_egl = xyes; then - PKG_CHECK_MODULES(WAYLAND_COMPOSITOR_EGL, [wayland-egl]) - fi -fi - - -AC_ARG_ENABLE(headless-compositor, [ --enable-headless-compositor],, - enable_headless_compositor=yes) -AM_CONDITIONAL(ENABLE_HEADLESS_COMPOSITOR, - test x$enable_headless_compositor = xyes) -if test x$enable_headless_compositor = xyes; then - AC_DEFINE([BUILD_HEADLESS_COMPOSITOR], [1], [Build the headless compositor]) -fi - - -AC_ARG_ENABLE([fbdev-compositor], [ --enable-fbdev-compositor],, - enable_fbdev_compositor=yes) -AM_CONDITIONAL([ENABLE_FBDEV_COMPOSITOR], - [test x$enable_fbdev_compositor = xyes]) -AS_IF([test x$enable_fbdev_compositor = xyes], [ - AC_DEFINE([BUILD_FBDEV_COMPOSITOR], [1], [Build the fbdev compositor]) - PKG_CHECK_MODULES([FBDEV_COMPOSITOR], [libudev >= 136]) -]) - -AC_ARG_ENABLE([rdp-compositor], [ --enable-rdp-compositor],, - enable_rdp_compositor=no) -AM_CONDITIONAL([ENABLE_RDP_COMPOSITOR], - [test x$enable_rdp_compositor = xyes]) -if test x$enable_rdp_compositor = xyes; then - AC_DEFINE([BUILD_RDP_COMPOSITOR], [1], [Build the RDP compositor]) - PKG_CHECK_MODULES(RDP_COMPOSITOR, [freerdp2 >= 2.0.0], - [], - [PKG_CHECK_MODULES(RDP_COMPOSITOR, [freerdp >= 1.1.0],[])] - ) - SAVED_CPPFLAGS="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $RDP_COMPOSITOR_CFLAGS" - - AC_CHECK_HEADERS([freerdp/version.h]) - AC_CHECK_MEMBER([SURFACE_BITS_COMMAND.bmp], - [AC_DEFINE([HAVE_SURFACE_BITS_BMP], [1], [SURFACE_BITS_CMD has bmp field])], - [], - [[#include ]] - ) - - - CPPFLAGS="$SAVED_CPPFLAGS" -fi - -AC_ARG_ENABLE([screen-sharing], [ --enable-screen-sharing],, - enable_screen_sharing=no) -AM_CONDITIONAL([ENABLE_SCREEN_SHARING], - [test x$enable_screen_sharing = xyes]) -if test x$enable_screen_sharing = xyes; then - PKG_CHECK_MODULES(SCREEN_SHARE, [wayland-client]) - - if test x$enable_rdp_compositor != xyes; then - AC_MSG_WARN([The screen-share.so module requires the RDP backend.]) - fi -fi - -AC_ARG_WITH(cairo, - AS_HELP_STRING([--with-cairo=@<:@image|gl|glesv2@:>@] - [Which Cairo renderer to use for the clients]), - [],[with_cairo="image"]) - -if test "x$with_cairo" = "ximage"; then - cairo_modules="cairo" -else -if test "x$with_cairo" = "xgl"; then - cairo_modules="cairo-gl" - AC_MSG_WARN([The --with-cairo=gl option can cause increased resource usage and potential instability, and thus is not recommended. It is needed only for a few special demo programs.]) -else -if test "x$with_cairo" = "xglesv2"; then - cairo_modules="cairo-glesv2" - AC_MSG_WARN([The --with-cairo=gles2 option can cause increased resource usage and potential instability, and thus is not recommended. It is needed only for a few special demo programs.]) -else - AC_ERROR([Unknown cairo renderer requested]) -fi -fi -fi - -# Included for legacy compat -AC_ARG_WITH(cairo-glesv2, - AS_HELP_STRING([--with-cairo-glesv2], - [Use GLESv2 cairo])) -if test "x$with_cairo_glesv2" = "xyes"; then - cairo_modules="cairo-glesv2" - with_cairo="glesv2" -fi - -if test "x$cairo_modules" = "xcairo-glesv2"; then -AC_DEFINE([USE_CAIRO_GLESV2], [1], [Use the GLESv2 GL cairo backend]) -fi - -PKG_CHECK_MODULES(PIXMAN, [pixman-1]) -PKG_CHECK_MODULES(PNG, [libpng]) - -AC_ARG_WITH([jpeg], - AS_HELP_STRING([--without-jpeg], - [Use jpeglib for JPEG decoding support [default=auto]])) -AS_IF([test "x$with_jpeg" != "xno"], - [WESTON_SEARCH_LIBS([JPEG], [jpeg], [jpeg_CreateDecompress], [have_jpeglib=yes], [have_jpeglib=no])], - [have_jpeglib=no]) -AS_IF([test "x$have_jpeglib" = "xyes"], - [AC_DEFINE([HAVE_JPEG], [1], [Have jpeglib])], - [AS_IF([test "x$with_jpeg" = "xyes"], - [AC_MSG_ERROR([JPEG support explicitly requested, but jpeglib couldn't be found])])]) - -AC_ARG_WITH([webp], - AS_HELP_STRING([--without-webp], - [Use libwebp for WebP decoding support [default=auto]])) -AS_IF([test "x$with_webp" != "xno"], - [PKG_CHECK_MODULES(WEBP, [libwebp], [have_webp=yes], [have_webp=no])], - [have_webp=no]) -AS_IF([test "x$have_webp" = "xyes"], - [AC_DEFINE([HAVE_WEBP], [1], [Have webp])], - [AS_IF([test "x$with_webp" = "xyes"], - [AC_MSG_ERROR([WebP support explicitly requested, but libwebp couldn't be found])])]) - -AC_ARG_ENABLE(vaapi-recorder, [ --enable-vaapi-recorder],, - enable_vaapi_recorder=auto) -have_libva=no -if test x$enable_vaapi_recorder != xno; then - PKG_CHECK_MODULES(LIBVA, [libva >= 0.34.0 libva-drm >= 0.34.0], - [have_libva=yes], [have_libva=no]) - if test "x$have_libva" = "xno" -a "x$enable_vaapi_recorder" = "xyes"; then - AC_MSG_ERROR([vaapi-recorder explicitly enabled, but libva couldn't be found]) - fi - AS_IF([test "x$have_libva" = "xyes"], - [AC_DEFINE([BUILD_VAAPI_RECORDER], [1], [Build the vaapi recorder])]) -fi -AM_CONDITIONAL(ENABLE_VAAPI_RECORDER, test "x$have_libva" = xyes) - -PKG_CHECK_MODULES(CAIRO, [cairo]) - -PKG_CHECK_MODULES(TEST_CLIENT, [wayland-client >= $WAYLAND_PREREQ_VERSION pixman-1]) - -AC_ARG_ENABLE(simple-clients, - AS_HELP_STRING([--disable-simple-clients], - [do not build the simple wl_shm clients]),, - enable_simple_clients=yes) -AM_CONDITIONAL(BUILD_SIMPLE_CLIENTS, test "x$enable_simple_clients" = "xyes") -if test x$enable_simple_clients = xyes; then - PKG_CHECK_MODULES(SIMPLE_CLIENT, [wayland-client]) -fi - -AC_ARG_ENABLE(simple-egl-clients, - AS_HELP_STRING([--disable-simple-egl-clients], - [do not build the simple EGL clients]),, - enable_simple_egl_clients="$enable_egl") -AM_CONDITIONAL(BUILD_SIMPLE_EGL_CLIENTS, test "x$enable_simple_egl_clients" = "xyes") -if test x$enable_simple_egl_clients = xyes; then - PKG_CHECK_MODULES(SIMPLE_EGL_CLIENT, - [egl glesv2 wayland-client wayland-egl wayland-cursor]) -fi - -AC_ARG_ENABLE(simple-dmabuf-drm-client, - AS_HELP_STRING([--disable-simple-dmabuf-drm-client], - [do not build the simple dmabuf drm client]),, - enable_simple_dmabuf_drm_client="auto") -if ! test "x$enable_simple_dmabuf_drm_client" = "xno"; then - PKG_CHECK_MODULES(SIMPLE_DMABUF_DRM_CLIENT, [wayland-client libdrm], [have_simple_dmabuf_libs=yes], - [have_simple_dmabuf_libs=no]) - - PKG_CHECK_MODULES(LIBDRM_PLATFORM_FREEDRENO, [libdrm_freedreno], - AC_DEFINE([HAVE_LIBDRM_FREEDRENO], [1], [Build freedreno dmabuf client]) have_simple_dmabuf_drm_client=yes, - [true]) - PKG_CHECK_MODULES(LIBDRM_PLATFORM_INTEL, [libdrm_intel], - AC_DEFINE([HAVE_LIBDRM_INTEL], [1], [Build intel dmabuf client]) have_simple_dmabuf_drm_client=yes, - [true]) - PKG_CHECK_MODULES(LIBDRM_PLATFORM_ETNAVIV, [libdrm_etnaviv], - AC_DEFINE([HAVE_LIBDRM_ETNAVIV], [1], [Build etnaviv dmabuf client]) have_simple_dmabuf_drm_client=yes, - [true]) - - if test "x$have_simple_dmabuf_drm_client" != "xyes" -o \ - "x$have_simple_dmabuf_libs" = "xno" && \ - test "x$enable_simple_dmabuf_drm_client" = "xyes"; then - AC_MSG_ERROR([DRM dmabuf client explicitly enabled, but none of libdrm_{intel,freedreno,etnaviv} found]) - fi - - if test "x$have_simple_dmabuf_drm_client" = "xyes" -a "x$have_simple_dmabuf_libs" = "xyes"; then - enable_simple_dmabuf_drm_client="yes" - fi -fi -AM_CONDITIONAL(BUILD_SIMPLE_DMABUF_DRM_CLIENT, test "x$enable_simple_dmabuf_drm_client" = "xyes") - -AC_ARG_ENABLE(simple-dmabuf-v4l-client, - AS_HELP_STRING([--disable-simple-dmabuf-v4l-client], - [do not build the simple dmabuf v4l client]),, - enable_simple_dmabuf_v4l_client="auto") -if ! test "x$enable_simple_dmabuf_v4l_client" = "xno"; then - PKG_CHECK_MODULES(SIMPLE_DMABUF_V4L_CLIENT, [wayland-client libdrm], - have_simple_dmabuf_v4l_client=yes, have_simple_dmabuf_v4l_client=no) - if test "x$have_simple_dmabuf_v4l_client" = "xno" -a "x$enable_simple_dmabuf_v4l_client" = "xyes"; then - AC_MSG_ERROR([V4L dmabuf client explicitly enabled, but libdrm couldn't be found]) - fi - enable_simple_dmabuf_v4l_client="$have_simple_dmabuf_v4l_client" -fi -AM_CONDITIONAL(BUILD_SIMPLE_DMABUF_V4L_CLIENT, test "x$enable_simple_dmabuf_v4l_client" = "xyes") - -AC_ARG_ENABLE(simple-dmabuf-egl-client, - AS_HELP_STRING([--disable-simple-dmabuf-egl-client], - [do not build the simple dmabuf egl client]),, - enable_simple_dmabuf_egl_client="auto") -if ! test "x$enable_simple_dmabuf_egl_client" = "xno"; then - PKG_CHECK_MODULES(SIMPLE_DMABUF_EGL_CLIENT, [wayland-client libdrm gbm egl glesv2], - [have_simple_dmabuf_egl_client=yes], [have_simple_dmabuf_egl_client=no]) - - if test "x$have_simple_dmabuf_egl_client" = "xno" -a "x$enable_simple_dmabuf_egl_client" = "xyes"; then - AC_MSG_ERROR([EGL dmabuf client explicitly enabled, but libdrm/egl/glev2 couldn't be found]) - fi - enable_simple_dmabuf_egl_client="$have_simple_dmabuf_egl_client" -fi -AM_CONDITIONAL(BUILD_SIMPLE_DMABUF_EGL_CLIENT, test "x$enable_simple_dmabuf_egl_client" = "xyes") - -AC_ARG_ENABLE(clients, [ --enable-clients],, enable_clients=yes) -AM_CONDITIONAL(BUILD_CLIENTS, test x$enable_clients = xyes) -have_cairo_egl=no -if test x$enable_clients = xyes; then - AC_DEFINE([BUILD_CLIENTS], [1], [Build the Wayland clients]) - - PKG_CHECK_MODULES(CLIENT, [wayland-client >= $WAYLAND_PREREQ_VERSION cairo >= 1.10.0 xkbcommon wayland-cursor]) - PKG_CHECK_MODULES(SERVER, [wayland-server]) - PKG_CHECK_MODULES(WESTON_INFO, [wayland-client >= $WAYLAND_PREREQ_VERSION]) - - # Only check for cairo-egl if a GL or GLES renderer requested - AS_IF([test "x$cairo_modules" = "xcairo-gl" -o "x$cairo_modules" = "xcairo-glesv2"], [ - PKG_CHECK_MODULES(CAIRO_EGL, [wayland-egl egl cairo-egl >= 1.11.3 $cairo_modules], - [have_cairo_egl=yes], [have_cairo_egl=no]) - AS_IF([test "x$have_cairo_egl" = "xyes"], - [AC_DEFINE([HAVE_CAIRO_EGL], [1], [Have cairo-egl])], - [AC_ERROR([cairo-egl not used because $CAIRO_EGL_PKG_ERRORS])])], - [have_cairo_egl=no]) - - PKG_CHECK_MODULES(PANGO, [pangocairo pango glib-2.0 >= 2.36], [have_pango=yes], [have_pango=no]) -fi - -AC_ARG_ENABLE(resize-optimization, - AS_HELP_STRING([--disable-resize-optimization], - [disable resize optimization allocating a big buffer in toytoolkit]),, - enable_resize_optimization=yes) -AS_IF([test "x$enable_resize_optimization" = "xyes"], - [AC_DEFINE([USE_RESIZE_POOL], [1], [Use resize memory pool as a performance optimization])]) - -AC_ARG_ENABLE(weston-launch, [ --enable-weston-launch],, enable_weston_launch=yes) -AM_CONDITIONAL(BUILD_WESTON_LAUNCH, test x$enable_weston_launch = xyes) -if test x$enable_weston_launch = xyes; then - WESTON_SEARCH_LIBS([PAM], [pam], [pam_open_session], [have_pam=yes], [have_pam=no]) - if test x$have_pam = xno; then - AC_ERROR([weston-launch requires pam]) - fi -fi - -AM_CONDITIONAL(HAVE_PANGO, test "x$have_pango" = "xyes") -if test "x$have_pango" = "xyes"; then - AC_DEFINE([HAVE_PANGO], [1], [Have pango]) -fi - -AM_CONDITIONAL(HAVE_CAIRO_GLESV2, - [test "x$have_cairo_egl" = "xyes" -a "x$cairo_modules" = "xcairo-glesv2" -a "x$enable_egl" = "xyes"]) - -AM_CONDITIONAL(BUILD_FULL_GL_CLIENTS, - test x$cairo_modules = "xcairo-gl" -a "x$have_cairo_egl" = "xyes" -a "x$enable_egl" = "xyes") - -AM_CONDITIONAL(BUILD_SUBSURFACES_CLIENT, - [test '(' "x$have_cairo_egl" != "xyes" -o "x$cairo_modules" = "xcairo-glesv2" ')' -a "x$enable_simple_egl_clients" = "xyes"]) - -AM_CONDITIONAL(ENABLE_DESKTOP_SHELL, true) - -AC_ARG_ENABLE(fullscreen-shell, - AS_HELP_STRING([--disable-fullscreen-shell], - [do not build fullscreen-shell server plugin]),, - enable_fullscreen_shell=yes) -AM_CONDITIONAL(ENABLE_FULLSCREEN_SHELL, - test "x$enable_fullscreen_shell" = "xyes") - -# CMS modules -AC_ARG_ENABLE(colord, - AS_HELP_STRING([--disable-colord], - [do not build colord CMS support]),, - enable_colord=auto) -have_colord=no -if test "x$enable_colord" != "xno"; then - PKG_CHECK_MODULES(COLORD, - colord >= 0.1.27, - have_colord=yes, - have_colord=no) - if test "x$have_colord" = "xno" -a "x$enable_colord" = "xyes"; then - AC_MSG_ERROR([colord support explicitly requested, but colord couldn't be found]) - fi - if test "x$have_colord" = "xyes"; then - enable_colord=yes - fi -fi -AM_CONDITIONAL(ENABLE_COLORD, test "x$enable_colord" = "xyes") - -# dbus support -AC_ARG_ENABLE(dbus, - AS_HELP_STRING([--disable-dbus], - [do not build with dbus support]),, - enable_dbus=auto) -have_dbus=no -if test "x$enable_dbus" != "xno"; then - PKG_CHECK_MODULES(DBUS, - dbus-1 >= 1.6, - have_dbus=yes, - have_dbus=no) - if test "x$have_dbus" = "xno" -a "x$enable_dbus" = "xyes"; then - AC_MSG_ERROR([dbus support explicitly requested, but libdbus couldn't be found]) - fi - if test "x$have_dbus" = "xyes"; then - enable_dbus=yes - AC_DEFINE([HAVE_DBUS], [1], [Build with dbus support]) - else - enable_dbus=no - fi -fi -AM_CONDITIONAL(ENABLE_DBUS, test "x$enable_dbus" = "xyes") - -# systemd-login support -AC_ARG_ENABLE(systemd-login, - AS_HELP_STRING([--enable-systemd-login], - [Enable logind support]),, - enable_systemd_login=auto) -if test x$enable_systemd_login != xno -a x$have_dbus != xno; then - PKG_CHECK_MODULES(SYSTEMD_LOGIN, - [libsystemd >= 209], - [have_systemd_login_209=yes;have_systemd_login=yes], - [have_systemd_login_209=no;have_systemd_login=no]) - - # Older versions of systemd package systemd-login separately. Fall back on that - AS_IF([test x$have_systemd_login != xyes],[ - PKG_CHECK_MODULES(SYSTEMD_LOGIN, - [libsystemd-login >= 198], - [have_systemd_login=yes], - [have_systemd_login=no]) - ]) -else - have_systemd_login=no -fi - -if test "x$have_systemd_login" = "xno" -a "x$enable_systemd_login" = "xyes"; then - AC_MSG_ERROR([systemd-login support explicitly enabled, but can't find libsystemd>=209, libsystemd-login or dbus]) -fi - -AS_IF([test "x$have_systemd_login" = "xyes"], - [AC_DEFINE([HAVE_SYSTEMD_LOGIN], [1], [Have systemd-login])]) -AM_CONDITIONAL(HAVE_SYSTEMD_LOGIN, test "x$have_systemd_login" = "xyes") - -AS_IF([test "x$have_systemd_login_209" = "xyes"], - [AC_DEFINE([HAVE_SYSTEMD_LOGIN_209], [1], [Have systemd-login >= 209])]) - - -# Note that other features might want libxml2, or this feature might use -# alternative xml libraries at some point. Therefore the feature and -# pre-requisite concepts are split. -AC_ARG_ENABLE(junit_xml, - AS_HELP_STRING([--disable-junit-xml], - [do not build with JUnit XML output]),, - enable_junit_xml=auto) -if test "x$enable_junit_xml" != "xno"; then - PKG_CHECK_MODULES(LIBXML2, - [libxml-2.0 >= 2.6], - have_libxml2=yes, - have_libxml2=no) - if test "x$have_libxml2" = "xno" -a "x$enable_junit_xml" = "xyes"; then - AC_MSG_ERROR([JUnit XML support explicitly requested, but libxml2 couldn't be found]) - fi - if test "x$have_libxml2" = "xyes"; then - enable_junit_xml=yes - AC_DEFINE(ENABLE_JUNIT_XML, [1], [Build Weston with JUnit output support]) - else - enable_junit_xml=no - fi -fi -AM_CONDITIONAL(ENABLE_JUNIT_XML, test "x$enable_junit_xml" = "xyes") - -# ivi-shell support -AC_ARG_ENABLE(ivi-shell, - AS_HELP_STRING([--disable-ivi-shell], - [do not build ivi-shell server plugin and client]),, - enable_ivi_shell=yes) -AM_CONDITIONAL(ENABLE_IVI_SHELL, test "x$enable_ivi_shell" = "xyes") - -AC_ARG_ENABLE(wcap-tools, [ --disable-wcap-tools],, enable_wcap_tools=yes) -AM_CONDITIONAL(BUILD_WCAP_TOOLS, test x$enable_wcap_tools = xyes) -if test x$enable_wcap_tools = xyes; then - AC_DEFINE([BUILD_WCAP_TOOLS], [1], [Build the wcap tools]) - PKG_CHECK_MODULES(WCAP, [cairo]) - WCAP_LIBS="$WCAP_LIBS -lm" -fi - -PKG_CHECK_MODULES(SETBACKLIGHT, [libudev libdrm], enable_setbacklight=yes, enable_setbacklight=no) -AM_CONDITIONAL(BUILD_SETBACKLIGHT, test "x$enable_setbacklight" = "xyes") - -if test "x$GCC" = "xyes"; then - GCC_CFLAGS="-Wall -Wextra -Wno-unused-parameter \ - -Wno-shift-negative-value -Wno-missing-field-initializers \ - -g -fvisibility=hidden \ - -Wstrict-prototypes -Wmissing-prototypes -Wsign-compare" -fi -AC_SUBST(GCC_CFLAGS) - - -if test "x$WESTON_NATIVE_BACKEND" = "x"; then - WESTON_NATIVE_BACKEND="drm-backend.so" -fi -AC_MSG_NOTICE([Weston's native backend: $WESTON_NATIVE_BACKEND]) -AC_DEFINE_UNQUOTED([WESTON_NATIVE_BACKEND], ["$WESTON_NATIVE_BACKEND"], - [The default backend to load, if not wayland nor x11.]) - -if test "x$WESTON_SHELL_CLIENT" = "x"; then - WESTON_SHELL_CLIENT="weston-desktop-shell" -fi -AC_MSG_NOTICE([Weston's default desktop shell client: $WESTON_SHELL_CLIENT]) -AC_DEFINE_UNQUOTED([WESTON_SHELL_CLIENT], ["$WESTON_SHELL_CLIENT"], - [The default desktop shell client to load.]) - -AC_ARG_ENABLE(demo-clients-install, - AS_HELP_STRING([--enable-demo-clients-install], - [Install demo clients built with weston]),, - enable_demo_clients_install=no) -AM_CONDITIONAL(INSTALL_DEMO_CLIENTS, [test "x$enable_demo_clients_install" = "xyes"]) - -AC_ARG_ENABLE(lcms, - AS_HELP_STRING([--disable-lcms], - [Disable lcms support]),, - enable_lcms=auto) -have_lcms=no -if test "x$enable_lcms" != "xno"; then - PKG_CHECK_MODULES(LCMS, - lcms2, - have_lcms=yes, - have_lcms=no) - if test "x$have_lcms" = "xno" -a "x$enable_lcms" = "xyes"; then - AC_MSG_ERROR([lcms support explicitly requested, but lcms couldn't be found]) - fi - if test "x$have_lcms" = "xyes"; then - enable_lcms=yes - AC_DEFINE(HAVE_LCMS, 1, [Have lcms support]) - fi -fi -AM_CONDITIONAL(HAVE_LCMS, [test "x$enable_lcms" = xyes]) - -AC_PATH_PROG([wayland_scanner], [wayland-scanner]) -if test x$wayland_scanner = x; then - PKG_CHECK_MODULES(WAYLAND_SCANNER, [wayland-scanner]) - wayland_scanner=`$PKG_CONFIG --variable=wayland_scanner wayland-scanner` -fi - -AC_ARG_ENABLE(systemd_notify, - AS_HELP_STRING([--enable-systemd-notify], - [Enables systemd notifications to - notify systemd about weston state - and update watchdog. - Also sockets provided by systemd - in case of socket-base activation - are added to wayland display]),, - enable_systemd_notify=no) -AM_CONDITIONAL(SYSTEMD_NOTIFY_SUPPORT, test x$enable_systemd_notify = xyes) -if test "x$enable_systemd_notify" = "xyes"; then - AC_DEFINE([SYSTEMD_NOTIFY_SUPPORT], [1], [Build the systemd sd_notify support]) - PKG_CHECK_MODULES(SYSTEMD_DAEMON, [libsystemd]) -fi - -AC_CONFIG_FILES([Makefile libweston/version.h compositor/weston.pc]) - -# AC_CONFIG_FILES needs the full name when running autoconf, so we need to use -# libweston_abi_version here, and outside [] because of m4 quoting rules -AC_CONFIG_FILES([libweston/libweston-]libweston_major_version[.pc:libweston/libweston.pc.in]) -AC_CONFIG_FILES([libweston/libweston-]libweston_major_version[-uninstalled.pc:libweston/libweston-uninstalled.pc.in]) -AC_CONFIG_FILES([libweston/libweston-]libweston_major_version[-protocols.pc:libweston/libweston-protocols.pc.in]) -AC_CONFIG_FILES([libweston-desktop/libweston-desktop-]libweston_major_version[.pc:libweston-desktop/libweston-desktop.pc.in]) -AC_CONFIG_FILES([libweston-desktop/libweston-desktop-]libweston_major_version[-uninstalled.pc:libweston-desktop/libweston-desktop-uninstalled.pc.in]) - -AM_CONDITIONAL([HAVE_GIT_REPO], [test -f $srcdir/.git/logs/HEAD]) - -AC_OUTPUT - -AC_MSG_RESULT([ - Native Backend ${WESTON_NATIVE_BACKEND} - setuid Install ${enable_setuid_install} - - Cairo Renderer ${with_cairo} - EGL ${enable_egl} - xcb_xkb ${have_xcb_xkb} - XWayland ${enable_xwayland} - dbus ${enable_dbus} - - ivi-shell ${enable_ivi_shell} - - Build wcap utility ${enable_wcap_tools} - Build Fullscreen Shell ${enable_fullscreen_shell} - Enable developer documentation ${enable_devdocs} - - weston-launch utility ${enable_weston_launch} - systemd-login support ${have_systemd_login} - systemd notify support ${enable_systemd_notify} - - DRM Compositor ${enable_drm_compositor} - Remoting ${enable_remoting} - X11 Compositor ${enable_x11_compositor} - Wayland Compositor ${enable_wayland_compositor} - Headless Compositor ${enable_headless_compositor} - FBDEV Compositor ${enable_fbdev_compositor} - RDP Compositor ${enable_rdp_compositor} - Screen Sharing ${enable_screen_sharing} - JUnit XML output ${enable_junit_xml} - - Build Clients ${enable_clients} - Build EGL Clients ${have_cairo_egl} - Build Simple Clients ${enable_simple_clients} - Build Simple EGL Clients ${enable_simple_egl_clients} - - Install Demo Clients ${enable_demo_clients_install} - - Colord Support ${have_colord} - LCMS2 Support ${have_lcms} - libjpeg Support ${have_jpeglib} - libwebp Support ${have_webp} - VA H.264 encoding Support ${have_libva} -]) diff --git a/libweston-desktop/libweston-desktop-uninstalled.pc.in b/libweston-desktop/libweston-desktop-uninstalled.pc.in deleted file mode 100644 index 4fb798d58..000000000 --- a/libweston-desktop/libweston-desktop-uninstalled.pc.in +++ /dev/null @@ -1,9 +0,0 @@ -libdir=@abs_top_builddir@/.libs -includedir=@abs_top_srcdir@ - -Name: libweston-desktop, uninstalled -Description: Desktop shells abstraction library for libweston compositors (not installed) -Version: @WESTON_VERSION@ -Requires.private: libweston-@LIBWESTON_MAJOR@-uninstalled wayland-server -Cflags: -I${includedir}/libweston-desktop -I${includedir}/shared -Libs: -L${libdir} -lweston-desktop-@LIBWESTON_MAJOR@ diff --git a/libweston-desktop/libweston-desktop.pc.in b/libweston-desktop/libweston-desktop.pc.in deleted file mode 100644 index ac59c357c..000000000 --- a/libweston-desktop/libweston-desktop.pc.in +++ /dev/null @@ -1,11 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: libweston-desktop -Description: Desktop shells abstraction library for libweston compositors -Version: @WESTON_VERSION@ -Requires.private: libweston-@LIBWESTON_MAJOR@ wayland-server -Cflags: -I${includedir}/libweston-@LIBWESTON_MAJOR@ -Libs: -L${libdir} -lweston-desktop-@LIBWESTON_MAJOR@ diff --git a/libweston/libweston-protocols.pc.in b/libweston/libweston-protocols.pc.in deleted file mode 100644 index 6547a0d5a..000000000 --- a/libweston/libweston-protocols.pc.in +++ /dev/null @@ -1,7 +0,0 @@ -prefix=@prefix@ -datarootdir=@datarootdir@ -pkgdatadir=${pc_sysrootdir}@datadir@/@PACKAGE@/protocols - -Name: libWeston Protocols -Description: libWeston protocol files -Version: @WESTON_VERSION@ diff --git a/libweston/libweston-uninstalled.pc.in b/libweston/libweston-uninstalled.pc.in deleted file mode 100644 index b6efd8a4d..000000000 --- a/libweston/libweston-uninstalled.pc.in +++ /dev/null @@ -1,9 +0,0 @@ -libdir=@abs_top_builddir@/.libs -includedir=@abs_top_srcdir@ - -Name: libweston API, uninstalled -Description: Header files for libweston compositors development (not installed) -Version: @WESTON_VERSION@ -Requires.private: wayland-server pixman-1 xkbcommon -Cflags: -I${includedir}/libweston -I${includedir}/shared -Libs: -L${libdir} -lweston-@LIBWESTON_MAJOR@ diff --git a/libweston/libweston.pc.in b/libweston/libweston.pc.in deleted file mode 100644 index 23910031b..000000000 --- a/libweston/libweston.pc.in +++ /dev/null @@ -1,11 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: libweston API -Description: Header files for libweston compositors development -Version: @WESTON_VERSION@ -Requires.private: wayland-server pixman-1 xkbcommon -Cflags: -I${includedir}/libweston-@LIBWESTON_MAJOR@ -Libs: -L${libdir} -lweston-@LIBWESTON_MAJOR@ diff --git a/m4/.gitignore b/m4/.gitignore deleted file mode 100644 index 38066ddf7..000000000 --- a/m4/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -libtool.m4 -ltoptions.m4 -ltsugar.m4 -ltversion.m4 -lt~obsolete.m4 diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 deleted file mode 100644 index 5fbf9fe0d..000000000 --- a/m4/ax_pthread.m4 +++ /dev/null @@ -1,485 +0,0 @@ -# =========================================================================== -# https://www.gnu.org/software/autoconf-archive/ax_pthread.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) -# -# DESCRIPTION -# -# This macro figures out how to build C programs using POSIX threads. It -# sets the PTHREAD_LIBS output variable to the threads library and linker -# flags, and the PTHREAD_CFLAGS output variable to any special C compiler -# flags that are needed. (The user can also force certain compiler -# flags/libs to be tested by setting these environment variables.) -# -# Also sets PTHREAD_CC to any special C compiler that is needed for -# multi-threaded programs (defaults to the value of CC otherwise). (This -# is necessary on AIX to use the special cc_r compiler alias.) -# -# NOTE: You are assumed to not only compile your program with these flags, -# but also to link with them as well. For example, you might link with -# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS -# -# If you are only building threaded programs, you may wish to use these -# variables in your default LIBS, CFLAGS, and CC: -# -# LIBS="$PTHREAD_LIBS $LIBS" -# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" -# CC="$PTHREAD_CC" -# -# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant -# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to -# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). -# -# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the -# PTHREAD_PRIO_INHERIT symbol is defined when compiling with -# PTHREAD_CFLAGS. -# -# ACTION-IF-FOUND is a list of shell commands to run if a threads library -# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it -# is not found. If ACTION-IF-FOUND is not specified, the default action -# will define HAVE_PTHREAD. -# -# Please let the authors know if this macro fails on any platform, or if -# you have any other suggestions or comments. This macro was based on work -# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help -# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by -# Alejandro Forero Cuervo to the autoconf macro repository. We are also -# grateful for the helpful feedback of numerous users. -# -# Updated for Autoconf 2.68 by Daniel Richard G. -# -# LICENSE -# -# Copyright (c) 2008 Steven G. Johnson -# Copyright (c) 2011 Daniel Richard G. -# -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see . -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -#serial 24 - -AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) -AC_DEFUN([AX_PTHREAD], [ -AC_REQUIRE([AC_CANONICAL_HOST]) -AC_REQUIRE([AC_PROG_CC]) -AC_REQUIRE([AC_PROG_SED]) -AC_LANG_PUSH([C]) -ax_pthread_ok=no - -# We used to check for pthread.h first, but this fails if pthread.h -# requires special compiler flags (e.g. on Tru64 or Sequent). -# It gets checked for in the link test anyway. - -# First of all, check if the user has set any of the PTHREAD_LIBS, -# etcetera environment variables, and if threads linking works using -# them: -if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then - ax_pthread_save_CC="$CC" - ax_pthread_save_CFLAGS="$CFLAGS" - ax_pthread_save_LIBS="$LIBS" - AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) - CFLAGS="$CFLAGS $PTHREAD_CFLAGS" - LIBS="$PTHREAD_LIBS $LIBS" - AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) - AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) - AC_MSG_RESULT([$ax_pthread_ok]) - if test "x$ax_pthread_ok" = "xno"; then - PTHREAD_LIBS="" - PTHREAD_CFLAGS="" - fi - CC="$ax_pthread_save_CC" - CFLAGS="$ax_pthread_save_CFLAGS" - LIBS="$ax_pthread_save_LIBS" -fi - -# We must check for the threads library under a number of different -# names; the ordering is very important because some systems -# (e.g. DEC) have both -lpthread and -lpthreads, where one of the -# libraries is broken (non-POSIX). - -# Create a list of thread flags to try. Items starting with a "-" are -# C compiler flags, and other items are library names, except for "none" -# which indicates that we try without any flags at all, and "pthread-config" -# which is a program returning the flags for the Pth emulation library. - -ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" - -# The ordering *is* (sometimes) important. Some notes on the -# individual items follow: - -# pthreads: AIX (must check this before -lpthread) -# none: in case threads are in libc; should be tried before -Kthread and -# other compiler flags to prevent continual compiler warnings -# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) -# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 -# (Note: HP C rejects this with "bad form for `-t' option") -# -pthreads: Solaris/gcc (Note: HP C also rejects) -# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it -# doesn't hurt to check since this sometimes defines pthreads and -# -D_REENTRANT too), HP C (must be checked before -lpthread, which -# is present but should not be used directly; and before -mthreads, -# because the compiler interprets this as "-mt" + "-hreads") -# -mthreads: Mingw32/gcc, Lynx/gcc -# pthread: Linux, etcetera -# --thread-safe: KAI C++ -# pthread-config: use pthread-config program (for GNU Pth library) - -case $host_os in - - freebsd*) - - # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) - # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) - - ax_pthread_flags="-kthread lthread $ax_pthread_flags" - ;; - - hpux*) - - # From the cc(1) man page: "[-mt] Sets various -D flags to enable - # multi-threading and also sets -lpthread." - - ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" - ;; - - openedition*) - - # IBM z/OS requires a feature-test macro to be defined in order to - # enable POSIX threads at all, so give the user a hint if this is - # not set. (We don't define these ourselves, as they can affect - # other portions of the system API in unpredictable ways.) - - AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], - [ -# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) - AX_PTHREAD_ZOS_MISSING -# endif - ], - [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) - ;; - - solaris*) - - # On Solaris (at least, for some versions), libc contains stubbed - # (non-functional) versions of the pthreads routines, so link-based - # tests will erroneously succeed. (N.B.: The stubs are missing - # pthread_cleanup_push, or rather a function called by this macro, - # so we could check for that, but who knows whether they'll stub - # that too in a future libc.) So we'll check first for the - # standard Solaris way of linking pthreads (-mt -lpthread). - - ax_pthread_flags="-mt,pthread pthread $ax_pthread_flags" - ;; -esac - -# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) - -AS_IF([test "x$GCC" = "xyes"], - [ax_pthread_flags="-pthread -pthreads $ax_pthread_flags"]) - -# The presence of a feature test macro requesting re-entrant function -# definitions is, on some systems, a strong hint that pthreads support is -# correctly enabled - -case $host_os in - darwin* | hpux* | linux* | osf* | solaris*) - ax_pthread_check_macro="_REENTRANT" - ;; - - aix*) - ax_pthread_check_macro="_THREAD_SAFE" - ;; - - *) - ax_pthread_check_macro="--" - ;; -esac -AS_IF([test "x$ax_pthread_check_macro" = "x--"], - [ax_pthread_check_cond=0], - [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) - -# Are we compiling with Clang? - -AC_CACHE_CHECK([whether $CC is Clang], - [ax_cv_PTHREAD_CLANG], - [ax_cv_PTHREAD_CLANG=no - # Note that Autoconf sets GCC=yes for Clang as well as GCC - if test "x$GCC" = "xyes"; then - AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], - [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ -# if defined(__clang__) && defined(__llvm__) - AX_PTHREAD_CC_IS_CLANG -# endif - ], - [ax_cv_PTHREAD_CLANG=yes]) - fi - ]) -ax_pthread_clang="$ax_cv_PTHREAD_CLANG" - -ax_pthread_clang_warning=no - -# Clang needs special handling, because older versions handle the -pthread -# option in a rather... idiosyncratic way - -if test "x$ax_pthread_clang" = "xyes"; then - - # Clang takes -pthread; it has never supported any other flag - - # (Note 1: This will need to be revisited if a system that Clang - # supports has POSIX threads in a separate library. This tends not - # to be the way of modern systems, but it's conceivable.) - - # (Note 2: On some systems, notably Darwin, -pthread is not needed - # to get POSIX threads support; the API is always present and - # active. We could reasonably leave PTHREAD_CFLAGS empty. But - # -pthread does define _REENTRANT, and while the Darwin headers - # ignore this macro, third-party headers might not.) - - PTHREAD_CFLAGS="-pthread" - PTHREAD_LIBS= - - ax_pthread_ok=yes - - # However, older versions of Clang make a point of warning the user - # that, in an invocation where only linking and no compilation is - # taking place, the -pthread option has no effect ("argument unused - # during compilation"). They expect -pthread to be passed in only - # when source code is being compiled. - # - # Problem is, this is at odds with the way Automake and most other - # C build frameworks function, which is that the same flags used in - # compilation (CFLAGS) are also used in linking. Many systems - # supported by AX_PTHREAD require exactly this for POSIX threads - # support, and in fact it is often not straightforward to specify a - # flag that is used only in the compilation phase and not in - # linking. Such a scenario is extremely rare in practice. - # - # Even though use of the -pthread flag in linking would only print - # a warning, this can be a nuisance for well-run software projects - # that build with -Werror. So if the active version of Clang has - # this misfeature, we search for an option to squash it. - - AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], - [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], - [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown - # Create an alternate version of $ac_link that compiles and - # links in two steps (.c -> .o, .o -> exe) instead of one - # (.c -> exe), because the warning occurs only in the second - # step - ax_pthread_save_ac_link="$ac_link" - ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' - ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"` - ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" - ax_pthread_save_CFLAGS="$CFLAGS" - for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do - AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) - CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" - ac_link="$ax_pthread_save_ac_link" - AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], - [ac_link="$ax_pthread_2step_ac_link" - AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], - [break]) - ]) - done - ac_link="$ax_pthread_save_ac_link" - CFLAGS="$ax_pthread_save_CFLAGS" - AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) - ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" - ]) - - case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in - no | unknown) ;; - *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; - esac - -fi # $ax_pthread_clang = yes - -if test "x$ax_pthread_ok" = "xno"; then -for ax_pthread_try_flag in $ax_pthread_flags; do - - case $ax_pthread_try_flag in - none) - AC_MSG_CHECKING([whether pthreads work without any flags]) - ;; - - -mt,pthread) - AC_MSG_CHECKING([whether pthreads work with -mt -lpthread]) - PTHREAD_CFLAGS="-mt" - PTHREAD_LIBS="-lpthread" - ;; - - -*) - AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) - PTHREAD_CFLAGS="$ax_pthread_try_flag" - ;; - - pthread-config) - AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) - AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) - PTHREAD_CFLAGS="`pthread-config --cflags`" - PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" - ;; - - *) - AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) - PTHREAD_LIBS="-l$ax_pthread_try_flag" - ;; - esac - - ax_pthread_save_CFLAGS="$CFLAGS" - ax_pthread_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $PTHREAD_CFLAGS" - LIBS="$PTHREAD_LIBS $LIBS" - - # Check for various functions. We must include pthread.h, - # since some functions may be macros. (On the Sequent, we - # need a special flag -Kthread to make this header compile.) - # We check for pthread_join because it is in -lpthread on IRIX - # while pthread_create is in libc. We check for pthread_attr_init - # due to DEC craziness with -lpthreads. We check for - # pthread_cleanup_push because it is one of the few pthread - # functions on Solaris that doesn't have a non-functional libc stub. - # We try pthread_create on general principles. - - AC_LINK_IFELSE([AC_LANG_PROGRAM([#include -# if $ax_pthread_check_cond -# error "$ax_pthread_check_macro must be defined" -# endif - static void routine(void *a) { a = 0; } - static void *start_routine(void *a) { return a; }], - [pthread_t th; pthread_attr_t attr; - pthread_create(&th, 0, start_routine, 0); - pthread_join(th, 0); - pthread_attr_init(&attr); - pthread_cleanup_push(routine, 0); - pthread_cleanup_pop(0) /* ; */])], - [ax_pthread_ok=yes], - []) - - CFLAGS="$ax_pthread_save_CFLAGS" - LIBS="$ax_pthread_save_LIBS" - - AC_MSG_RESULT([$ax_pthread_ok]) - AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) - - PTHREAD_LIBS="" - PTHREAD_CFLAGS="" -done -fi - -# Various other checks: -if test "x$ax_pthread_ok" = "xyes"; then - ax_pthread_save_CFLAGS="$CFLAGS" - ax_pthread_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $PTHREAD_CFLAGS" - LIBS="$PTHREAD_LIBS $LIBS" - - # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. - AC_CACHE_CHECK([for joinable pthread attribute], - [ax_cv_PTHREAD_JOINABLE_ATTR], - [ax_cv_PTHREAD_JOINABLE_ATTR=unknown - for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do - AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], - [int attr = $ax_pthread_attr; return attr /* ; */])], - [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], - []) - done - ]) - AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ - test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ - test "x$ax_pthread_joinable_attr_defined" != "xyes"], - [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], - [$ax_cv_PTHREAD_JOINABLE_ATTR], - [Define to necessary symbol if this constant - uses a non-standard name on your system.]) - ax_pthread_joinable_attr_defined=yes - ]) - - AC_CACHE_CHECK([whether more special flags are required for pthreads], - [ax_cv_PTHREAD_SPECIAL_FLAGS], - [ax_cv_PTHREAD_SPECIAL_FLAGS=no - case $host_os in - solaris*) - ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" - ;; - esac - ]) - AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ - test "x$ax_pthread_special_flags_added" != "xyes"], - [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" - ax_pthread_special_flags_added=yes]) - - AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], - [ax_cv_PTHREAD_PRIO_INHERIT], - [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], - [[int i = PTHREAD_PRIO_INHERIT;]])], - [ax_cv_PTHREAD_PRIO_INHERIT=yes], - [ax_cv_PTHREAD_PRIO_INHERIT=no]) - ]) - AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ - test "x$ax_pthread_prio_inherit_defined" != "xyes"], - [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) - ax_pthread_prio_inherit_defined=yes - ]) - - CFLAGS="$ax_pthread_save_CFLAGS" - LIBS="$ax_pthread_save_LIBS" - - # More AIX lossage: compile with *_r variant - if test "x$GCC" != "xyes"; then - case $host_os in - aix*) - AS_CASE(["x/$CC"], - [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], - [#handle absolute path differently from PATH based program lookup - AS_CASE(["x$CC"], - [x/*], - [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], - [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) - ;; - esac - fi -fi - -test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" - -AC_SUBST([PTHREAD_LIBS]) -AC_SUBST([PTHREAD_CFLAGS]) -AC_SUBST([PTHREAD_CC]) - -# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: -if test "x$ax_pthread_ok" = "xyes"; then - ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) - : -else - ax_pthread_ok=no - $2 -fi -AC_LANG_POP -])dnl AX_PTHREAD diff --git a/m4/weston.m4 b/m4/weston.m4 deleted file mode 100644 index 636f9fb02..000000000 --- a/m4/weston.m4 +++ /dev/null @@ -1,37 +0,0 @@ -dnl -dnl Copyright © 2016 Quentin “Sardem FF7” Glidic -dnl -dnl Permission is hereby granted, free of charge, to any person obtaining a -dnl copy of this software and associated documentation files (the "Software"), -dnl to deal in the Software without restriction, including without limitation -dnl the rights to use, copy, modify, merge, publish, distribute, sublicense, -dnl and/or sell copies of the Software, and to permit persons to whom the -dnl Software is furnished to do so, subject to the following conditions: -dnl -dnl The above copyright notice and this permission notice (including the next -dnl paragraph) shall be included in all copies or substantial portions of the -dnl Software. -dnl -dnl THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -dnl IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -dnl FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -dnl THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -dnl LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -dnl FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -dnl DEALINGS IN THE SOFTWARE. -dnl - -dnl WESTON_SEARCH_LIBS(PREFIX, search-libs, function, [action-if-found], [action-if-not-found], [other-libraries]) -dnl WESTON_SEARCH_LIBS is a wrapper around AC_SEARCH_LIBS with a little difference: -dnl action-if-found is called even if no library is required -AC_DEFUN([WESTON_SEARCH_LIBS], [ - weston_save_LIBS=${LIBS} - AC_SEARCH_LIBS([$3], [$2], [$4], [$5], [$6]) - AS_CASE([${ac_cv_search_][$3][}], - ['none required'], [$4], - [no], [], - [$1][_LIBS=${ac_cv_search_][$3][}] - ) - AC_SUBST([$1][_LIBS]) - LIBS=${weston_save_LIBS} -]) diff --git a/tests/weston-tests-env b/tests/weston-tests-env deleted file mode 100755 index 4a352ff0d..000000000 --- a/tests/weston-tests-env +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/bash - -TEST_FILE=${1##*/} -TEST_NAME=${TEST_FILE%.*} - -if [ -z "$TEST_NAME" ]; then - echo "usage: $(basename $0) " - exit 1; -fi - -WESTON=$abs_builddir/weston -LOGDIR=$abs_builddir/logs - -mkdir -p "$LOGDIR" || exit - -SERVERLOG="$LOGDIR/${TEST_NAME}-serverlog.txt" -OUTLOG="$LOGDIR/${TEST_NAME}-log.txt" - -rm -f "$SERVERLOG" || exit - -BACKEND=${BACKEND:-headless-backend.so} - -MODDIR=$abs_builddir/.libs - -SHELL_PLUGIN=$MODDIR/desktop-shell.so -TEST_PLUGIN=$MODDIR/weston-test.so - -WESTON_MODULE_MAP= -for mod in cms-colord cms-static desktop-shell drm-backend fbdev-backend \ - fullscreen-shell gl-renderer headless-backend hmi-controller \ - ivi-shell rdp-compositor screen-share wayland-backend \ - weston-test-desktop-shell x11-backend xwayland; do - WESTON_MODULE_MAP="${WESTON_MODULE_MAP}${mod}.so=${abs_builddir}/.libs/${mod}.so;" -done - -for exe in weston-desktop-shell weston-keyboard weston-screenshooter \ - weston-simple-im ivi-layout-test-client.ivi; do \ - WESTON_MODULE_MAP="${WESTON_MODULE_MAP}${exe}=${abs_builddir}/${exe};" -done - -CONFIG_FILE="${TEST_NAME}.ini" - -if [ -e "${abs_builddir}/${CONFIG_FILE}" ]; then - CONFIG="--config=${abs_builddir}/${CONFIG_FILE}" -elif [ -e "${abs_top_srcdir}/tests/${CONFIG_FILE}" ]; then - CONFIG="--config=${abs_top_srcdir}/tests/${CONFIG_FILE}" -else - CONFIG="--no-config" -fi - -case $TEST_FILE in - ivi-*.la|ivi-*.so) - SHELL_PLUGIN=$MODDIR/ivi-shell.so - - set -x - WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ - WESTON_DATA_DIR=$abs_top_srcdir/data \ - WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ - $WESTON --backend=$MODDIR/$BACKEND \ - --no-config \ - --shell=$SHELL_PLUGIN \ - --socket=test-${TEST_NAME} \ - --modules=$TEST_PLUGIN,$MODDIR/${TEST_FILE/.la/.so}\ - --log="$SERVERLOG" \ - &> "$OUTLOG" - ;; - *.la|*.so) - set -x - WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ - WESTON_DATA_DIR=$abs_top_srcdir/data \ - WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ - $WESTON --backend=$MODDIR/$BACKEND \ - ${CONFIG} \ - --shell=$SHELL_PLUGIN \ - --socket=test-${TEST_NAME} \ - --xwayland \ - --modules=$MODDIR/${TEST_FILE/.la/.so} \ - --log="$SERVERLOG" \ - &> "$OUTLOG" - ;; - ivi-*.weston) - SHELL_PLUGIN=$MODDIR/ivi-shell.so - - set -x - WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ - WESTON_DATA_DIR=$abs_top_srcdir/data \ - WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ - WESTON_TEST_CLIENT_PATH=$abs_builddir/$TEST_FILE \ - $WESTON --backend=$MODDIR/$BACKEND \ - --no-config \ - --shell=$SHELL_PLUGIN \ - --socket=test-${TEST_NAME} \ - --modules=$TEST_PLUGIN \ - --log="$SERVERLOG" \ - $($abs_builddir/$TESTNAME --params) \ - &> "$OUTLOG" - ;; - *) - set -x - WESTON_MODULE_MAP="${WESTON_MODULE_MAP}" \ - WESTON_DATA_DIR=$abs_top_srcdir/data \ - WESTON_TEST_REFERENCE_PATH=$abs_top_srcdir/tests/reference \ - WESTON_TEST_CLIENT_PATH=$abs_builddir/$TEST_FILE \ - $WESTON --backend=$MODDIR/$BACKEND \ - ${CONFIG} \ - --shell=$SHELL_PLUGIN \ - --socket=test-${TEST_NAME} \ - --xwayland \ - --modules=$TEST_PLUGIN \ - --log="$SERVERLOG" \ - $($abs_builddir/$TEST_FILE --params) \ - &> "$OUTLOG" -esac From 5dc2ddf9c665b27c8b0c01c787a68a78113479f9 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 14 Mar 2019 16:43:19 +0200 Subject: [PATCH 0822/1642] Clean up .gitignore after autotools removal There is no such thing as building in-tree anymore, so no need to ignore build artifacts. Signed-off-by: Pekka Paalanen --- .gitignore | 102 ------------------------------------------- ivi-shell/.gitignore | 1 - man/.gitignore | 4 -- tests/.gitignore | 9 ---- wcap/.gitignore | 3 -- 5 files changed, 119 deletions(-) delete mode 100644 ivi-shell/.gitignore delete mode 100644 man/.gitignore delete mode 100644 tests/.gitignore delete mode 100644 wcap/.gitignore diff --git a/.gitignore b/.gitignore index 69c5d61ba..b81eee661 100644 --- a/.gitignore +++ b/.gitignore @@ -1,113 +1,11 @@ -*.announce -*.deps -*.jpg -*.la -*.lo -*.log -*.o -*.pc -*.sig -*.so *.swp .*.sw? .sw? *.sublime-project *.sublime-workspace -*.tar.xz -*.trs *~ ctags cscope.out -.libs -.dirstamp -/aclocal.m4 -/autom4te.cache -/build-aux/ -/config.guess -/config.h -/config.h.in -/config.log -/config.mk -/config.status -/config.sub -/configure -/depcomp -/doc/doxygen/*.doxygen -/docs/developer -/docs/tools -/install-sh -/libtool -/ltmain.sh -/logs -/missing -/stamp-h1 -/test-driver -/weston.ini -Makefile -Makefile.in TAGS -protocol/.*.valid -protocol/*.[ch] 00*.patch - -weston-calibrator -weston-clickdot -weston-cliptest -weston-confine -weston-dnd -weston-editor -weston-eventdemo -weston-flower -weston-fullscreen -weston-gears -weston-image -weston-nested -weston-nested-client -weston-presentation-shm -weston-resizor -weston-scaler -weston-simple-dmabuf-drm -weston-simple-dmabuf-v4l -weston-simple-egl -weston-simple-shm -weston-simple-touch -weston-simple-damage -weston-smoke -weston-stacking -weston-subsurfaces -weston-touch-calibrator -weston-transformed -weston-view - -weston-keyboard -libtoytoolkit.a -weston-desktop-shell -weston-ivi-shell-user-interface -weston-info -weston-screenshooter -weston-tablet-shell -weston-terminal -weston-multi-resource -weston-simple-im -weston -weston-launch -spring-tool - -*.weston -*.test -*.ivi -wcap-decode -matrix-test -setbacklight -weston.1 -weston-drm.7 -weston.ini.5 - -/libweston/git-version.h -/libweston/version.h - -/tests/weston-ivi.ini -internal-screenshot-00.png - -/zuctest diff --git a/ivi-shell/.gitignore b/ivi-shell/.gitignore deleted file mode 100644 index e690c591e..000000000 --- a/ivi-shell/.gitignore +++ /dev/null @@ -1 +0,0 @@ -weston.ini diff --git a/man/.gitignore b/man/.gitignore deleted file mode 100644 index 6138c7d53..000000000 --- a/man/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -weston.1 -weston-drm.7 -weston.ini.5 - diff --git a/tests/.gitignore b/tests/.gitignore deleted file mode 100644 index 25e7344f7..000000000 --- a/tests/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -*.log -*.test -*.trs -*.weston -logs -matrix-test -setbacklight -test-client -test-text-client diff --git a/wcap/.gitignore b/wcap/.gitignore deleted file mode 100644 index 67b722d1e..000000000 --- a/wcap/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -wcap-decode -wcap-snapshot - From be61a1ff502108eb5b1aca1c689b67f1afe03d6e Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 14 Mar 2019 16:56:27 +0200 Subject: [PATCH 0823/1642] tests: fix references to automake There is no automake anymore, I suppose it is ninja that handles it now. There are still a couple references to automake left to point out where the conventions originated, e.g. the exit code 77. Signed-off-by: Pekka Paalanen --- tests/ivi-layout-test-plugin.c | 4 ++-- tests/weston-test-client-helper.c | 2 +- tests/weston-test.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ivi-layout-test-plugin.c b/tests/ivi-layout-test-plugin.c index aa88a712f..fa7695357 100644 --- a/tests/ivi-layout-test-plugin.c +++ b/tests/ivi-layout-test-plugin.c @@ -181,7 +181,7 @@ test_client_sigchld(struct weston_process *process, int status) container_of(process, struct test_launcher, process); struct weston_compositor *c = launcher->compositor; - /* Chain up from weston-test-runner's exit code so that automake + /* Chain up from weston-test-runner's exit code so that ninja * knows the exit status and can report e.g. skipped tests. */ if (WIFEXITED(status)) weston_compositor_exit_with_code(c, WEXITSTATUS(status)); @@ -297,7 +297,7 @@ runner_assert_fail(const char *cond, const char *file, int line, * This module is specially written to execute tests that target the * ivi_layout API. * - * This module is listed in TESTS in Makefile.am. weston-tests-env handles + * This module is listed in meson.build which handles * this module specially by loading it in ivi-shell. * * Once Weston init completes, this module launches one test program: diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index ea202c0c7..27e6b86af 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -850,7 +850,7 @@ skip(const char *fmt, ...) /* automake tests uses exit code 77. weston-test-runner will see * this and use it, and then weston-test's sigchld handler (in the * weston process) will use that as an exit status, which is what - * automake will see in the end. */ + * ninja will see in the end. */ exit(77); } diff --git a/tests/weston-test.c b/tests/weston-test.c index 12ef54ee5..c018cee54 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -64,7 +64,7 @@ test_client_sigchld(struct weston_process *process, int status) struct weston_test *test = container_of(process, struct weston_test, process); - /* Chain up from weston-test-runner's exit code so that automake + /* Chain up from weston-test-runner's exit code so that ninja * knows the exit status and can report e.g. skipped tests. */ if (WIFEXITED(status) && WEXITSTATUS(status) != 0) exit(WEXITSTATUS(status)); From 66581d245eb1bbc9a6bf23b45003a8f68de154b7 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 14 Mar 2019 17:08:30 +0200 Subject: [PATCH 0824/1642] README: refer to version numbers in meson.build configure.ac is no more, and in meson.build there are no minor or patch versions for libweston, only major. Signed-off-by: Pekka Paalanen --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7a6aec33c..7f686d99f 100644 --- a/README.md +++ b/README.md @@ -165,14 +165,14 @@ The document provides the full details, with the gist summed below: - Minor - new backward compatible features. - Patch - internal (implementation specific) fixes. -Weston and libweston have separate version numbers in configure.ac. All +Weston and libweston have separate version numbers in meson.build. All releases are made by the Weston version number. Libweston version number matches the Weston version number in all releases except maybe pre-releases. Pre-releases have the Weston micro version 91 or greater. A pre-release is allowed to install a libweston version greater than the Weston version in case libweston major was bumped. In that case, the libweston version -must be Weston major + 1 and with minor and patch versions zero. +must be Weston major + 1. Pkg-config files are named after libweston major, but carry the Weston version number. This means that Weston pre-release 2.1.91 may install libweston-3.pc From a6acfa83460d4f1e9f59fd5e52a8e39e1b8d4167 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Sun, 17 Mar 2019 18:10:09 +0200 Subject: [PATCH 0825/1642] compositor: Fix invalid view numbering in scene-graph With the addition of patch 433f4e77b7729 we display the same view id (0) for every view as we're modifying the local variable. Displaying sub-surfaces based views is also problematic. The caller need to modify the view number as well, so we instead we pass the address as to allow that to happen. Otherwise we end up repeating the same number for views without sub-subrfaces once those have been printed. Signed-off-by: Marius Vlad --- libweston/compositor.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index d87522e73..4c096ae30 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6647,7 +6647,7 @@ debug_scene_view_print(FILE *fp, struct weston_view *view, int view_idx) static void debug_scene_view_print_tree(struct weston_view *view, - FILE *fp, int view_idx) + FILE *fp, int *view_idx) { struct weston_subsurface *sub; struct weston_view *ev; @@ -6656,7 +6656,7 @@ debug_scene_view_print_tree(struct weston_view *view, * print the view first, then we recursively go on printing * sub-surfaces. We bail out once no more sub-surfaces are available. */ - debug_scene_view_print(fp, view, view_idx++); + debug_scene_view_print(fp, view, *view_idx); /* no more sub-surfaces */ if (wl_list_empty(&view->surface->subsurface_list)) @@ -6667,6 +6667,8 @@ debug_scene_view_print_tree(struct weston_view *view, /* do not print again the parent view */ if (view == ev) continue; + + (*view_idx)++; debug_scene_view_print_tree(ev, fp, view_idx); } } @@ -6741,8 +6743,10 @@ weston_compositor_print_scene_graph(struct weston_compositor *ec) layer->mask.x2, layer->mask.y2); } - wl_list_for_each(view, &layer->view_list.link, layer_link.link) - debug_scene_view_print_tree(view, fp, view_idx); + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { + debug_scene_view_print_tree(view, fp, &view_idx); + view_idx++; + } if (wl_list_empty(&layer->view_list.link)) fprintf(fp, "\t[no views]\n"); From 253ba9a6db38aaf92955d32aac044d4d7d3a076a Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Sun, 17 Mar 2019 18:22:21 +0200 Subject: [PATCH 0826/1642] compositor: Fix missing new line when displaying buffer type for EGL buffer Signed-off-by: Marius Vlad --- libweston/compositor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 4c096ae30..172ea3dc3 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6578,7 +6578,7 @@ debug_scene_view_print_buffer(FILE *fp, struct weston_view *view) return; } - fprintf(fp, "\t\tEGL buffer"); + fprintf(fp, "\t\tEGL buffer\n"); } static void From 651566af2d937a8889ddda0ecc71e93b4091842a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 29 Mar 2019 13:37:56 +0200 Subject: [PATCH 0827/1642] build: add missing dep to x11 backend All other backends already link to libweston, x11 backend should too. This fixes a build failure: [1/50] Compiling C object 'libweston/2b98b6d@@x11-backend@sha/compositor-x11.c.o'. FAILED: libweston/2b98b6d@@x11-backend@sha/compositor-x11.c.o cc -Ilibweston/2b98b6d@@x11-backend@sha -Ilibweston -I../../git/weston/libweston -Ilibweston/.. -I../../git/weston/libweston/.. -Ilibweston/../shared -I../../git/weston/libweston/../shared -Iprotocol -I/home/pq/local/include -I/usr/include/cairo -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/pixman-1 -I/usr/include/uuid -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/pango-1.0 -I/usr/include/fribidi -I/usr/include/harfbuzz -I/usr/include/libdrm -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wextra -std=gnu99 -g -Wno-unused-parameter -Wno-shift-negative-value -Wno-missing-field-initializers -fvisibility=hidden -fPIC -MD -MQ 'libweston/2b98b6d@@x11-backend@sha/compositor-x11.c.o' -MF 'libweston/2b98b6d@@x11-backend@sha/compositor-x11.c.o.d' -o 'libweston/2b98b6d@@x11-backend@sha/compositor-x11.c.o' -c ../../git/weston/libweston/compositor-x11.c ../../git/weston/libweston/compositor-x11.c:51:10: fatal error: xkbcommon/xkbcommon.h: No such file or directory #include Signed-off-by: Pekka Paalanen --- libweston/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/meson.build b/libweston/meson.build index 8b887afbc..dbbf2b924 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -365,6 +365,7 @@ if get_option('backend-x11') endif deps_x11 = [ + dep_libweston, dep_x11_xcb, dep_lib_cairo_shared, dep_pixman, From 6f9db6c4a16d853bbc5889ad5ff0d9c75e21d69c Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Mon, 1 Apr 2019 17:51:35 +0900 Subject: [PATCH 0828/1642] cairo-util: Don't set title string to Pango layout if the title is NULL If buttons list isn't empty and title is NULL, SEGV is occured in pango_layout_set_text(). This patch fixes this problem. Signed-off-by: Tomohito Esaki --- shared/cairo-util.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/shared/cairo-util.c b/shared/cairo-util.c index d71e0ed48..15cbf82ca 100644 --- a/shared/cairo-util.c +++ b/shared/cairo-util.c @@ -463,10 +463,12 @@ create_layout(cairo_t *cr, const char *title) PangoFontDescription *desc; layout = pango_cairo_create_layout(cr); - pango_layout_set_text(layout, title, -1); - desc = pango_font_description_from_string("Sans Bold 10"); - pango_layout_set_font_description(layout, desc); - pango_font_description_free(desc); + if (title) { + pango_layout_set_text(layout, title, -1); + desc = pango_font_description_from_string("Sans Bold 10"); + pango_layout_set_font_description(layout, desc); + pango_font_description_free(desc); + } pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); pango_layout_set_auto_dir (layout, FALSE); From 53d7c243ba0baa052081735b8effff4e2679ce65 Mon Sep 17 00:00:00 2001 From: Kamal Pandey Date: Thu, 4 Apr 2019 19:45:45 +0530 Subject: [PATCH 0829/1642] FIX: weston: clients: typo in simple-dmabuf-egl.c Fix variable EGL_NO_IMAGE to EGL_NO_IMAGE_KHR in clients/simple-dmabuf-egl.c Signed-off-by: Kamal Pandey --- clients/simple-dmabuf-egl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c index 75d880e08..2d99c4709 100644 --- a/clients/simple-dmabuf-egl.c +++ b/clients/simple-dmabuf-egl.c @@ -293,7 +293,7 @@ create_fbo_for_buffer(struct display *display, struct buffer *buffer) EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); - if (buffer->egl_image == EGL_NO_IMAGE) { + if (buffer->egl_image == EGL_NO_IMAGE_KHR) { fprintf(stderr, "EGLImageKHR creation failed\n"); return false; } From 250f1066ffe0aa76b515725f46c2dba121482ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Or=C3=B3n=20Mart=C3=ADnez?= Date: Thu, 28 Mar 2019 16:24:42 +0100 Subject: [PATCH 0830/1642] support byte-by-byte reproducible build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build path ends in the final binary package causing the build not to be reproducible byte-by-byte. Reference: https://bugs.debian.org/899358 Signed-off-by: Héctor Orón Martínez --- compositor/meson.build | 2 -- ivi-shell/meson.build | 2 -- weston.ini.in | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/compositor/meson.build b/compositor/meson.build index 3824d6ffb..61860a7a8 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -160,8 +160,6 @@ endif weston_ini_config = configuration_data() weston_ini_config.set('bindir', dir_bin) weston_ini_config.set('libexecdir', dir_libexec) -weston_ini_config.set('abs_top_srcdir', meson.source_root()) -weston_ini_config.set('abs_top_builddir', meson.build_root()) configure_file( input: '../weston.ini.in', output: 'weston.ini', diff --git a/ivi-shell/meson.build b/ivi-shell/meson.build index fceaf8041..17ab5ea5b 100644 --- a/ivi-shell/meson.build +++ b/ivi-shell/meson.build @@ -40,8 +40,6 @@ if get_option('shell-ivi') ivi_test_config = configuration_data() ivi_test_config.set('bindir', dir_bin) ivi_test_config.set('libexecdir', dir_libexec) - ivi_test_config.set('abs_top_srcdir', meson.current_source_dir()) - ivi_test_config.set('abs_top_builddir', meson.current_build_dir()) ivi_test_config.set('plugin_prefix', meson.current_build_dir()) ivi_test_config.set('westondatadir', join_paths(dir_data, 'weston')) ivi_test_ini = configure_file( diff --git a/weston.ini.in b/weston.ini.in index 846ef746b..74bcb5120 100644 --- a/weston.ini.in +++ b/weston.ini.in @@ -38,7 +38,7 @@ path=/usr/bin/google-chrome [launcher] icon=/usr/share/icons/gnome/24x24/apps/arts.png -path=@abs_top_builddir@/weston-flower +path=@bindir@/weston-flower [input-method] path=@libexecdir@/weston-keyboard From 923a1e9688c7f9a16cb5195e1bc22deff3f43e39 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Sun, 14 Apr 2019 10:38:25 +0000 Subject: [PATCH 0831/1642] Fix incorrect include In file included from ../clients/multi-resource.c:38: /usr/include/sys/poll.h:1:2: warning: #warning redirecting incorrect #include to [-Wcpp] #warning redirecting incorrect #include to ^~~~~~~ --- clients/multi-resource.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/multi-resource.c b/clients/multi-resource.c index 2be0a7e30..1d5e4e0c3 100644 --- a/clients/multi-resource.c +++ b/clients/multi-resource.c @@ -35,7 +35,7 @@ #include #include #include -#include +#include #include #include From b81fc517d87eecf2df6ec80c0a317656d611f9b3 Mon Sep 17 00:00:00 2001 From: Harish Krupo Date: Tue, 16 Apr 2019 10:41:01 +0530 Subject: [PATCH 0832/1642] meson.build: Fix warning for configure_file We claim to support meson versions >= 0.47 but the `install:` argument in configure_file was introduced in version 0.50. This produces the following meson warning: WARNING: Project specifies a minimum meson_version '>= 0.47' but uses features which were added in newer versions: * 0.50.0: {'install arg in configure_file'} From the documentation for the install argument [1]: " When omitted it (install) defaults to true when install_dir is set and not empty, false otherwise." So, remove the `install:` argument and just depend on `install_dir` for installing. Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/225 [1] https://mesonbuild.com/Reference-manual.html#configure_file Signed-off-by: Harish Krupo --- ivi-shell/meson.build | 1 - man/meson.build | 5 ----- meson.build | 3 ++- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/ivi-shell/meson.build b/ivi-shell/meson.build index 17ab5ea5b..5c866fcf5 100644 --- a/ivi-shell/meson.build +++ b/ivi-shell/meson.build @@ -45,7 +45,6 @@ if get_option('shell-ivi') ivi_test_ini = configure_file( input: '../ivi-shell/weston.ini.in', output: 'weston-ivi-test.ini', - install: false, configuration: ivi_test_config ) endif diff --git a/man/meson.build b/man/meson.build index ced569449..80e231203 100644 --- a/man/meson.build +++ b/man/meson.build @@ -11,7 +11,6 @@ man_conf.set('version', version_weston) configure_file( input: 'weston.man', output: 'weston.1', - install: true, install_dir: join_paths(dir_man, 'man1'), configuration: man_conf ) @@ -19,7 +18,6 @@ configure_file( configure_file( input: 'weston-debug.man', output: 'weston-debug.1', - install: true, install_dir: join_paths(dir_man, 'man1'), configuration: man_conf ) @@ -27,7 +25,6 @@ configure_file( configure_file( input: 'weston.ini.man', output: 'weston.ini.5', - install: true, install_dir: join_paths(dir_man, 'man5'), configuration: man_conf ) @@ -36,7 +33,6 @@ if get_option('backend-drm') configure_file( input: 'weston-drm.man', output: 'weston-drm.7', - install: true, install_dir: join_paths(dir_man, 'man7'), configuration: man_conf ) @@ -46,7 +42,6 @@ if get_option('backend-rdp') configure_file( input: 'weston-rdp.man', output: 'weston-rdp.7', - install: true, install_dir: join_paths(dir_man, 'man7'), configuration: man_conf ) diff --git a/meson.build b/meson.build index aae962614..df312b82c 100644 --- a/meson.build +++ b/meson.build @@ -166,6 +166,7 @@ subdir('tests') subdir('data') subdir('man') -configure_file(output: 'config.h', install: false, configuration: config_h) +configure_file(output: 'config.h', configuration: config_h) + # TODO: process doc/doxygen/*.doxygen.in From 3a28bd66ff35c75e174bde1fe9dce0791f90a087 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Tue, 16 Apr 2019 17:37:10 +0300 Subject: [PATCH 0833/1642] meson.build/libweston: Fix clang warning for export-dynamic Identical to 8a8558dd, where we need to pass `-Wl` as linker args. Signed-off-by: Marius Vlad --- libweston/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/meson.build b/libweston/meson.build index dbbf2b924..e6c90ad62 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -79,7 +79,7 @@ lib_weston = shared_library( 'weston-@0@'.format(libweston_major), srcs_libweston, include_directories: include_directories('..', '../shared'), - link_args: [ '-export-dynamic' ], + link_args: [ '-Wl,-export-dynamic' ], install: true, version: '0.0.@0@'.format(libweston_revision), link_whole: lib_libshared, From 4ab901ebb0cc18aab8af34263e91c352b6a4e29f Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 28 Mar 2019 14:45:40 +0200 Subject: [PATCH 0834/1642] libweston: fix protocol install path These protocols are from libweston, not weston. Even the pkg-config files is called libweston-6-protocols.pc. Signed-off-by: Pekka Paalanen --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index df312b82c..8b179cfd5 100644 --- a/meson.build +++ b/meson.build @@ -39,7 +39,7 @@ dir_module_libweston = join_paths(dir_lib, 'libweston-@0@'.format(libweston_majo dir_data_pc = join_paths(dir_data, 'pkgconfig') dir_lib_pc = join_paths(dir_lib, 'pkgconfig') dir_man = join_paths(dir_prefix, get_option('mandir')) -dir_protocol_libweston = 'weston/protocols' # XXX: this should be 'libweston' +dir_protocol_libweston = 'libweston/protocols' pkgconfig = import('pkgconfig') From 53c37fa347afe97ee4c6443c40044e69ac1ab1ff Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 28 Mar 2019 15:49:54 +0200 Subject: [PATCH 0835/1642] build: remove dir_include It was unused. Signed-off-by: Pekka Paalanen --- meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/meson.build b/meson.build index 8b179cfd5..8962c87ee 100644 --- a/meson.build +++ b/meson.build @@ -30,7 +30,6 @@ endif dir_prefix = get_option('prefix') dir_bin = join_paths(dir_prefix, get_option('bindir')) dir_data = join_paths(dir_prefix, get_option('datadir')) -dir_include = join_paths(dir_prefix, get_option('includedir')) dir_include_libweston = 'libweston-@0@'.format(libweston_major) dir_lib = join_paths(dir_prefix, get_option('libdir')) dir_libexec = join_paths(dir_prefix, get_option('libexecdir')) From cb74afd482a5edaeb1daf1a1b82a7dbe88cb4345 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 29 Mar 2019 13:35:15 +0200 Subject: [PATCH 0836/1642] build: declare separate dependency for compositor.h These are not specific to the launchers but to compositor.h, so name them that way. Once we can rely on the mentioned Meson PR, we can simplify this further. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/libweston/meson.build b/libweston/meson.build index e6c90ad62..4a9a7c043 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -92,6 +92,19 @@ dep_libweston = declare_dependency( dependencies: deps_libweston ) +# XXX: We should be able to use dep_libweston.partial_dependency() instead +# of this, but a Meson bug makes it not work. It will be fixed with +# https://github.com/mesonbuild/meson/pull/5167 +# in hopefully Meson 0.51. +dep_libweston_h = declare_dependency( + include_directories: include_directories('.'), + dependencies: [ + dep_pixman.partial_dependency(compile_args: true), + dep_xkbcommon.partial_dependency(compile_args: true), + dep_wayland_server.partial_dependency(compile_args: true) + ] +) + pkgconfig.generate( lib_weston, filebase: 'libweston-@0@'.format(libweston_major), @@ -119,14 +132,7 @@ srcs_session_helper = [ 'launcher-util.c', 'launcher-weston-launch.c', ] -deps_session_helper = [ - # for compositor.h needing pixman.h - dep_pixman.partial_dependency(compile_args: true), - # for compositor.h needing xkbcommon.h - dep_xkbcommon.partial_dependency(compile_args: true), - # for compositor.h needing wayland-server.h - dep_wayland_server.partial_dependency(compile_args: true) -] +deps_session_helper = [ dep_libweston_h ] if get_option('backend-drm') deps_session_helper += dep_libdrm From a78cf77582aa946e9671ffdd5a0a4f988d656588 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 28 Mar 2019 16:35:56 +0200 Subject: [PATCH 0837/1642] Rename timeline-object.h to libweston/timeline-object.h This patch sets up the stage for similarly renaming compositor.h which will justify this. That patch will be big, so moving timeline-object.h first makes it easy to see the changes to the build and install directives. This and all the following moves essentially break the API, so libweston major is bumped. Signed-off-by: Pekka Paalanen --- include/libweston/meson.build | 4 ++++ {libweston => include/libweston}/timeline-object.h | 0 include/meson.build | 1 + libweston/compositor.h | 2 +- libweston/meson.build | 7 +++---- meson.build | 6 +++++- 6 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 include/libweston/meson.build rename {libweston => include/libweston}/timeline-object.h (100%) create mode 100644 include/meson.build diff --git a/include/libweston/meson.build b/include/libweston/meson.build new file mode 100644 index 000000000..5cc7bc5bb --- /dev/null +++ b/include/libweston/meson.build @@ -0,0 +1,4 @@ +install_headers( + 'timeline-object.h', + subdir: dir_include_libweston_install +) diff --git a/libweston/timeline-object.h b/include/libweston/timeline-object.h similarity index 100% rename from libweston/timeline-object.h rename to include/libweston/timeline-object.h diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 000000000..ef8298fb4 --- /dev/null +++ b/include/meson.build @@ -0,0 +1 @@ +subdir('libweston') diff --git a/libweston/compositor.h b/libweston/compositor.h index a5223c285..16ad495ba 100644 --- a/libweston/compositor.h +++ b/libweston/compositor.h @@ -44,7 +44,7 @@ extern "C" { #include "matrix.h" #include "config-parser.h" #include "zalloc.h" -#include "timeline-object.h" +#include struct weston_geometry { int32_t x, y; diff --git a/libweston/meson.build b/libweston/meson.build index 4a9a7c043..de4c6ffe8 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -59,7 +59,6 @@ srcs_libweston = [ install_headers( 'compositor.h', 'plugin-registry.h', - 'timeline-object.h', 'windowed-output-api.h', '../shared/config-parser.h', '../shared/matrix.h', @@ -78,7 +77,7 @@ endif lib_weston = shared_library( 'weston-@0@'.format(libweston_major), srcs_libweston, - include_directories: include_directories('..', '../shared'), + include_directories: [ include_directories('..', '../shared'), public_inc ], link_args: [ '-Wl,-export-dynamic' ], install: true, version: '0.0.@0@'.format(libweston_revision), @@ -88,7 +87,7 @@ lib_weston = shared_library( dep_libweston = declare_dependency( link_with: lib_weston, - include_directories: include_directories('.'), + include_directories: [ include_directories('.'), public_inc ], dependencies: deps_libweston ) @@ -97,7 +96,7 @@ dep_libweston = declare_dependency( # https://github.com/mesonbuild/meson/pull/5167 # in hopefully Meson 0.51. dep_libweston_h = declare_dependency( - include_directories: include_directories('.'), + include_directories: [ include_directories('.'), public_inc ], dependencies: [ dep_pixman.partial_dependency(compile_args: true), dep_xkbcommon.partial_dependency(compile_args: true), diff --git a/meson.build b/meson.build index 8962c87ee..7e4fc2366 100644 --- a/meson.build +++ b/meson.build @@ -10,7 +10,7 @@ project('weston', license: 'MIT/Expat', ) -libweston_major = 6 +libweston_major = 7 # libweston_revision is manufactured to follow the autotools build's # library file naming, thanks to libtool @@ -31,6 +31,7 @@ dir_prefix = get_option('prefix') dir_bin = join_paths(dir_prefix, get_option('bindir')) dir_data = join_paths(dir_prefix, get_option('datadir')) dir_include_libweston = 'libweston-@0@'.format(libweston_major) +dir_include_libweston_install = join_paths(dir_include_libweston, 'libweston') dir_lib = join_paths(dir_prefix, get_option('libdir')) dir_libexec = join_paths(dir_prefix, get_option('libexecdir')) dir_module_weston = join_paths(dir_lib, 'weston') @@ -40,6 +41,8 @@ dir_lib_pc = join_paths(dir_lib, 'pkgconfig') dir_man = join_paths(dir_prefix, get_option('mandir')) dir_protocol_libweston = 'libweston/protocols' +public_inc = include_directories('include') + pkgconfig = import('pkgconfig') libweston_version_h = configuration_data() @@ -149,6 +152,7 @@ dep_libdrm = dependency('libdrm', version: '>= 2.4.68') dep_libdrm_headers = dep_libdrm.partial_dependency(compile_args: true) dep_threads = dependency('threads') +subdir('include') subdir('protocol') subdir('shared') subdir('libweston') From 3d5d9476e3d184ae1fe7d36996e78d788a2922a8 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 28 Mar 2019 16:28:47 +0200 Subject: [PATCH 0838/1642] Rename compositor.h to libweston/libweston.h The main idea is to make libweston users use the form #include instead of the plain #include which is prone to name conflicts. This is reflected both in the installed files, and the internal header search paths so that Weston would use the exact same form as an external project using libweston would. The public headers are moved under a new top-level directory include/ to make them clearly stand out as special (public API). Signed-off-by: Pekka Paalanen --- compositor/cms-colord.c | 2 +- compositor/cms-helper.c | 2 +- compositor/cms-helper.h | 2 +- compositor/cms-static.c | 2 +- compositor/main.c | 2 +- compositor/screen-share.c | 2 +- compositor/systemd-notify.c | 2 +- compositor/text-backend.c | 2 +- compositor/weston-screenshooter.c | 2 +- compositor/weston.h | 2 +- compositor/xwayland.c | 2 +- desktop-shell/shell.h | 2 +- fullscreen-shell/fullscreen-shell.c | 2 +- libweston/compositor.h => include/libweston/libweston.h | 0 include/libweston/meson.build | 1 + ivi-shell/ivi-layout-export.h | 2 +- ivi-shell/ivi-layout-private.h | 2 +- ivi-shell/ivi-layout.c | 2 +- ivi-shell/ivi-shell.h | 2 +- libweston-desktop/client.c | 2 +- libweston-desktop/internal.h | 2 +- libweston-desktop/libweston-desktop.c | 2 +- libweston-desktop/libweston-desktop.h | 2 +- libweston-desktop/seat.c | 2 +- libweston-desktop/surface.c | 2 +- libweston-desktop/wl-shell.c | 2 +- libweston-desktop/xdg-shell-v6.c | 2 +- libweston-desktop/xdg-shell.c | 2 +- libweston-desktop/xwayland.c | 2 +- libweston/animation.c | 2 +- libweston/bindings.c | 2 +- libweston/clipboard.c | 2 +- libweston/compositor-drm.c | 2 +- libweston/compositor-drm.h | 2 +- libweston/compositor-fbdev.c | 2 +- libweston/compositor-fbdev.h | 2 +- libweston/compositor-headless.c | 2 +- libweston/compositor-headless.h | 2 +- libweston/compositor-rdp.c | 2 +- libweston/compositor-rdp.h | 2 +- libweston/compositor-wayland.c | 2 +- libweston/compositor-wayland.h | 2 +- libweston/compositor-x11.c | 2 +- libweston/compositor-x11.h | 2 +- libweston/compositor.c | 2 +- libweston/data-device.c | 2 +- libweston/dbus.c | 2 +- libweston/dbus.h | 2 +- libweston/gl-renderer.h | 2 +- libweston/input.c | 2 +- libweston/launcher-direct.c | 2 +- libweston/launcher-impl.h | 2 +- libweston/launcher-logind.c | 2 +- libweston/launcher-util.c | 2 +- libweston/launcher-util.h | 2 +- libweston/launcher-weston-launch.c | 2 +- libweston/libinput-device.c | 2 +- libweston/libinput-device.h | 2 +- libweston/libinput-seat.c | 2 +- libweston/libinput-seat.h | 2 +- libweston/linux-dmabuf.c | 2 +- libweston/linux-explicit-synchronization.c | 2 +- libweston/log.c | 2 +- libweston/meson.build | 1 - libweston/noop-renderer.c | 2 +- libweston/pixman-renderer.h | 2 +- libweston/plugin-registry.c | 2 +- libweston/screenshooter.c | 2 +- libweston/spring-tool.c | 2 +- libweston/timeline.c | 2 +- libweston/touch-calibration.c | 2 +- libweston/vaapi-recorder.c | 2 +- libweston/weston-debug.c | 2 +- libweston/zoom.c | 2 +- remoting/remoting-plugin.h | 2 +- tests/ivi-layout-internal-test.c | 2 +- tests/ivi-layout-test-plugin.c | 2 +- tests/plugin-registry-test.c | 2 +- tests/surface-global-test.c | 2 +- tests/surface-screenshot-test.c | 2 +- tests/surface-test.c | 2 +- tests/weston-test.c | 2 +- xwayland/dnd.c | 2 +- xwayland/window-manager.c | 2 +- xwayland/xwayland.h | 2 +- 85 files changed, 83 insertions(+), 83 deletions(-) rename libweston/compositor.h => include/libweston/libweston.h (100%) diff --git a/compositor/cms-colord.c b/compositor/cms-colord.c index b68e4921c..d68e377db 100644 --- a/compositor/cms-colord.c +++ b/compositor/cms-colord.c @@ -33,7 +33,7 @@ #include #include -#include "compositor.h" +#include #include "weston.h" #include "cms-helper.h" #include "shared/helpers.h" diff --git a/compositor/cms-helper.c b/compositor/cms-helper.c index 1784c46d3..bc56a9dca 100644 --- a/compositor/cms-helper.c +++ b/compositor/cms-helper.c @@ -34,7 +34,7 @@ #include #endif -#include "compositor.h" +#include #include "cms-helper.h" #ifdef HAVE_LCMS diff --git a/compositor/cms-helper.h b/compositor/cms-helper.h index 402652e69..4a5b711e5 100644 --- a/compositor/cms-helper.h +++ b/compositor/cms-helper.h @@ -28,7 +28,7 @@ #include "config.h" -#include "compositor.h" +#include /* General overview on how to be a CMS plugin: * diff --git a/compositor/cms-static.c b/compositor/cms-static.c index e24501b6c..2f357c41c 100644 --- a/compositor/cms-static.c +++ b/compositor/cms-static.c @@ -28,7 +28,7 @@ #include #include -#include "compositor.h" +#include #include "cms-helper.h" #include "shared/helpers.h" #include "weston.h" diff --git a/compositor/main.c b/compositor/main.c index 57e3de89f..03482c537 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -47,7 +47,7 @@ #include #include "weston.h" -#include "compositor.h" +#include #include "../shared/os-compatibility.h" #include "../shared/helpers.h" #include "../shared/string-helpers.h" diff --git a/compositor/screen-share.c b/compositor/screen-share.c index 33de2b1f1..69689d73a 100644 --- a/compositor/screen-share.c +++ b/compositor/screen-share.c @@ -40,7 +40,7 @@ #include -#include "compositor.h" +#include #include "weston.h" #include "shared/helpers.h" #include "shared/os-compatibility.h" diff --git a/compositor/systemd-notify.c b/compositor/systemd-notify.c index e53a9d287..b2dcce1fc 100644 --- a/compositor/systemd-notify.c +++ b/compositor/systemd-notify.c @@ -33,7 +33,7 @@ #include "shared/helpers.h" #include "shared/string-helpers.h" #include "shared/zalloc.h" -#include "compositor.h" +#include #include "weston.h" struct systemd_notifier { diff --git a/compositor/text-backend.c b/compositor/text-backend.c index 030195845..722dcb2c1 100644 --- a/compositor/text-backend.c +++ b/compositor/text-backend.c @@ -33,7 +33,7 @@ #include #include -#include "compositor.h" +#include #include "weston.h" #include "text-input-unstable-v1-server-protocol.h" #include "input-method-unstable-v1-server-protocol.h" diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c index 13dea3f18..55fbf8dfc 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -28,7 +28,7 @@ #include #include -#include "compositor.h" +#include #include "weston.h" #include "weston-screenshooter-server-protocol.h" #include "shared/helpers.h" diff --git a/compositor/weston.h b/compositor/weston.h index 5a2225d18..84bc67060 100644 --- a/compositor/weston.h +++ b/compositor/weston.h @@ -30,7 +30,7 @@ extern "C" { #endif -#include +#include void screenshooter_create(struct weston_compositor *ec); diff --git a/compositor/xwayland.c b/compositor/xwayland.c index 61580c7cd..36d5cf31e 100644 --- a/compositor/xwayland.c +++ b/compositor/xwayland.c @@ -29,7 +29,7 @@ #include #include -#include "compositor.h" +#include #include "compositor/weston.h" #include "xwayland/xwayland-api.h" #include "shared/helpers.h" diff --git a/desktop-shell/shell.h b/desktop-shell/shell.h index fb8c2bf0b..bbf562654 100644 --- a/desktop-shell/shell.h +++ b/desktop-shell/shell.h @@ -27,7 +27,7 @@ #include #include -#include "compositor.h" +#include #include "xwayland/xwayland-api.h" #include "weston-desktop-shell-server-protocol.h" diff --git a/fullscreen-shell/fullscreen-shell.c b/fullscreen-shell/fullscreen-shell.c index 99e9dbbc2..6c04cf3dc 100644 --- a/fullscreen-shell/fullscreen-shell.c +++ b/fullscreen-shell/fullscreen-shell.c @@ -33,7 +33,7 @@ #include #include -#include "compositor.h" +#include #include "compositor/weston.h" #include "fullscreen-shell-unstable-v1-server-protocol.h" #include "shared/helpers.h" diff --git a/libweston/compositor.h b/include/libweston/libweston.h similarity index 100% rename from libweston/compositor.h rename to include/libweston/libweston.h diff --git a/include/libweston/meson.build b/include/libweston/meson.build index 5cc7bc5bb..74480838f 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -1,4 +1,5 @@ install_headers( + 'libweston.h', 'timeline-object.h', subdir: dir_include_libweston_install ) diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index 77b06de94..b5e92c044 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -59,7 +59,7 @@ extern "C" { #include #include "stdbool.h" -#include "compositor.h" +#include #include "plugin-registry.h" #define IVI_SUCCEEDED (0) diff --git a/ivi-shell/ivi-layout-private.h b/ivi-shell/ivi-layout-private.h index c054130bc..4b6cd6a3a 100644 --- a/ivi-shell/ivi-layout-private.h +++ b/ivi-shell/ivi-layout-private.h @@ -28,7 +28,7 @@ #include -#include "compositor.h" +#include #include "ivi-layout-export.h" #include "libweston-desktop/libweston-desktop.h" diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 2c450f31d..bf0bccc7c 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -62,7 +62,7 @@ #include #include "compositor/weston.h" -#include "compositor.h" +#include #include "ivi-shell.h" #include "ivi-layout-export.h" #include "ivi-layout-private.h" diff --git a/ivi-shell/ivi-shell.h b/ivi-shell/ivi-shell.h index 3aeddcab6..8762113b1 100644 --- a/ivi-shell/ivi-shell.h +++ b/ivi-shell/ivi-shell.h @@ -29,7 +29,7 @@ #include #include -#include "compositor.h" +#include #include "libweston-desktop/libweston-desktop.h" struct ivi_shell diff --git a/libweston-desktop/client.c b/libweston-desktop/client.c index 29c3c9861..a3e9a31ef 100644 --- a/libweston-desktop/client.c +++ b/libweston-desktop/client.c @@ -25,7 +25,7 @@ #include -#include "compositor.h" +#include #include "zalloc.h" #include "libweston-desktop.h" diff --git a/libweston-desktop/internal.h b/libweston-desktop/internal.h index ce853ba98..e4ab2701b 100644 --- a/libweston-desktop/internal.h +++ b/libweston-desktop/internal.h @@ -24,7 +24,7 @@ #ifndef WESTON_DESKTOP_INTERNAL_H #define WESTON_DESKTOP_INTERNAL_H -#include "compositor.h" +#include struct weston_desktop_seat; struct weston_desktop_client; diff --git a/libweston-desktop/libweston-desktop.c b/libweston-desktop/libweston-desktop.c index 49cd21027..a22f67d4e 100644 --- a/libweston-desktop/libweston-desktop.c +++ b/libweston-desktop/libweston-desktop.c @@ -28,7 +28,7 @@ #include #include -#include "compositor.h" +#include #include "zalloc.h" #include "helpers.h" diff --git a/libweston-desktop/libweston-desktop.h b/libweston-desktop/libweston-desktop.h index a0fb9381b..3e7ac738e 100644 --- a/libweston-desktop/libweston-desktop.h +++ b/libweston-desktop/libweston-desktop.h @@ -24,7 +24,7 @@ #ifndef WESTON_DESKTOP_H #define WESTON_DESKTOP_H -#include "compositor.h" +#include #include #include diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c index ae1c5e9f1..1c2380dc9 100644 --- a/libweston-desktop/seat.c +++ b/libweston-desktop/seat.c @@ -30,7 +30,7 @@ #include -#include "compositor.h" +#include #include "zalloc.h" #include "libweston-desktop.h" diff --git a/libweston-desktop/surface.c b/libweston-desktop/surface.c index cbfa5ee00..eec9806da 100644 --- a/libweston-desktop/surface.c +++ b/libweston-desktop/surface.c @@ -28,7 +28,7 @@ #include -#include "compositor.h" +#include #include "zalloc.h" #include "libweston-desktop.h" diff --git a/libweston-desktop/wl-shell.c b/libweston-desktop/wl-shell.c index 37720acbb..0067b63af 100644 --- a/libweston-desktop/wl-shell.c +++ b/libweston-desktop/wl-shell.c @@ -30,7 +30,7 @@ #include -#include "compositor.h" +#include #include "zalloc.h" #include "libweston-desktop.h" diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index b6cb599c7..80c3f0879 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -31,7 +31,7 @@ #include -#include "compositor.h" +#include #include "zalloc.h" #include "xdg-shell-unstable-v6-server-protocol.h" diff --git a/libweston-desktop/xdg-shell.c b/libweston-desktop/xdg-shell.c index 58a1ecdb8..d5bed385f 100644 --- a/libweston-desktop/xdg-shell.c +++ b/libweston-desktop/xdg-shell.c @@ -31,7 +31,7 @@ #include -#include "compositor.h" +#include #include "zalloc.h" #include "xdg-shell-server-protocol.h" diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index 4b4407b9c..ea9ff34e7 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -30,7 +30,7 @@ #include -#include "compositor.h" +#include #include "zalloc.h" #include "libweston-desktop.h" diff --git a/libweston/animation.c b/libweston/animation.c index c2f8b9bad..a81a8c194 100644 --- a/libweston/animation.c +++ b/libweston/animation.c @@ -35,7 +35,7 @@ #include #include -#include "compositor.h" +#include #include "shared/helpers.h" #include "shared/timespec-util.h" diff --git a/libweston/bindings.c b/libweston/bindings.c index d9e280e4d..68c07a22f 100644 --- a/libweston/bindings.c +++ b/libweston/bindings.c @@ -29,7 +29,7 @@ #include #include -#include "compositor.h" +#include #include "shared/helpers.h" #include "shared/timespec-util.h" diff --git a/libweston/clipboard.c b/libweston/clipboard.c index f37508cbd..c8296b01d 100644 --- a/libweston/clipboard.c +++ b/libweston/clipboard.c @@ -33,7 +33,7 @@ #include #include -#include "compositor.h" +#include #include "shared/helpers.h" struct clipboard_source { diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 4387ec0e2..21ee2c1a7 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -50,7 +50,7 @@ #include #include -#include "compositor.h" +#include #include "compositor-drm.h" #include "weston-debug.h" #include "shared/helpers.h" diff --git a/libweston/compositor-drm.h b/libweston/compositor-drm.h index 71a306fda..76c84c8ab 100644 --- a/libweston/compositor-drm.h +++ b/libweston/compositor-drm.h @@ -28,7 +28,7 @@ #ifndef WESTON_COMPOSITOR_DRM_H #define WESTON_COMPOSITOR_DRM_H -#include "compositor.h" +#include #include "plugin-registry.h" #ifdef __cplusplus diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index 6031be522..a31ee999b 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -45,7 +45,7 @@ #include #include "shared/helpers.h" -#include "compositor.h" +#include #include "compositor-fbdev.h" #include "launcher-util.h" #include "pixman-renderer.h" diff --git a/libweston/compositor-fbdev.h b/libweston/compositor-fbdev.h index 29c21828e..4dbdce721 100644 --- a/libweston/compositor-fbdev.h +++ b/libweston/compositor-fbdev.h @@ -32,7 +32,7 @@ extern "C" { #include -#include "compositor.h" +#include #define WESTON_FBDEV_BACKEND_CONFIG_VERSION 2 diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index 5a0e46c8d..098e08e9b 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -33,7 +33,7 @@ #include #include -#include "compositor.h" +#include #include "compositor-headless.h" #include "shared/helpers.h" #include "linux-explicit-synchronization.h" diff --git a/libweston/compositor-headless.h b/libweston/compositor-headless.h index 039b50e3d..ee116ec3b 100644 --- a/libweston/compositor-headless.h +++ b/libweston/compositor-headless.h @@ -32,7 +32,7 @@ extern "C" { #include -#include "compositor.h" +#include #define WESTON_HEADLESS_BACKEND_CONFIG_VERSION 2 diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 871a0a3e8..8ece5e97c 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -98,7 +98,7 @@ #include "shared/helpers.h" #include "shared/timespec-util.h" -#include "compositor.h" +#include #include "compositor-rdp.h" #include "pixman-renderer.h" diff --git a/libweston/compositor-rdp.h b/libweston/compositor-rdp.h index bd0a6a909..ebdd72f2c 100644 --- a/libweston/compositor-rdp.h +++ b/libweston/compositor-rdp.h @@ -30,7 +30,7 @@ extern "C" { #endif -#include "compositor.h" +#include #include "plugin-registry.h" #define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v1" diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index e1485ca63..4e2257159 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -44,7 +44,7 @@ #include #endif -#include "compositor.h" +#include #include "compositor-wayland.h" #include "gl-renderer.h" #include "weston-egl-ext.h" diff --git a/libweston/compositor-wayland.h b/libweston/compositor-wayland.h index d5c29f0a6..7fe513a7f 100644 --- a/libweston/compositor-wayland.h +++ b/libweston/compositor-wayland.h @@ -26,7 +26,7 @@ #ifndef WESTON_COMPOSITOR_WAYLAND_H #define WESTON_COMPOSITOR_WAYLAND_H -#include "compositor.h" +#include #ifdef __cplusplus extern "C" { diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 922e3c80b..d8513b596 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -50,7 +50,7 @@ #include -#include "compositor.h" +#include #include "compositor-x11.h" #include "shared/config-parser.h" #include "shared/helpers.h" diff --git a/libweston/compositor-x11.h b/libweston/compositor-x11.h index 8989fc26e..1556e8e77 100644 --- a/libweston/compositor-x11.h +++ b/libweston/compositor-x11.h @@ -32,7 +32,7 @@ extern "C" { #include -#include "compositor.h" +#include #define WESTON_X11_BACKEND_CONFIG_VERSION 2 diff --git a/libweston/compositor.c b/libweston/compositor.c index 172ea3dc3..428b87f4a 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -55,7 +55,7 @@ #include "timeline.h" -#include "compositor.h" +#include #include "weston-debug.h" #include "linux-dmabuf.h" #include "viewporter-server-protocol.h" diff --git a/libweston/data-device.c b/libweston/data-device.c index e3dbee3ef..b0fb77605 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -32,7 +32,7 @@ #include #include -#include "compositor.h" +#include #include "shared/helpers.h" #include "shared/timespec-util.h" diff --git a/libweston/dbus.c b/libweston/dbus.c index 36815dbd6..d2530aca3 100644 --- a/libweston/dbus.c +++ b/libweston/dbus.c @@ -44,7 +44,7 @@ #include #include -#include "compositor.h" +#include #include "dbus.h" /* diff --git a/libweston/dbus.h b/libweston/dbus.h index 9bbfa380f..639946ce5 100644 --- a/libweston/dbus.h +++ b/libweston/dbus.h @@ -31,7 +31,7 @@ #include #include -#include "compositor.h" +#include #ifdef HAVE_DBUS diff --git a/libweston/gl-renderer.h b/libweston/gl-renderer.h index 202702b5a..24af9256f 100644 --- a/libweston/gl-renderer.h +++ b/libweston/gl-renderer.h @@ -27,7 +27,7 @@ #include -#include "compositor.h" +#include #ifdef ENABLE_EGL diff --git a/libweston/input.c b/libweston/input.c index 6579592ba..93ac0f6c8 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -41,7 +41,7 @@ #include "shared/helpers.h" #include "shared/os-compatibility.h" #include "shared/timespec-util.h" -#include "compositor.h" +#include #include "relative-pointer-unstable-v1-server-protocol.h" #include "pointer-constraints-unstable-v1-server-protocol.h" #include "input-timestamps-unstable-v1-server-protocol.h" diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c index e0ce6d632..e5f1bcfbf 100644 --- a/libweston/launcher-direct.c +++ b/libweston/launcher-direct.c @@ -26,7 +26,7 @@ #include "config.h" -#include "compositor.h" +#include #include #include diff --git a/libweston/launcher-impl.h b/libweston/launcher-impl.h index b601baa9e..4161caffa 100644 --- a/libweston/launcher-impl.h +++ b/libweston/launcher-impl.h @@ -25,7 +25,7 @@ #include "config.h" -#include "compositor.h" +#include struct weston_launcher; diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c index 34e6e5ca4..bcbde16c1 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -38,7 +38,7 @@ #include #include -#include "compositor.h" +#include #include "dbus.h" #include "launcher-impl.h" diff --git a/libweston/launcher-util.c b/libweston/launcher-util.c index 41ac79507..5cbb0abb5 100644 --- a/libweston/launcher-util.c +++ b/libweston/launcher-util.c @@ -26,7 +26,7 @@ #include "config.h" -#include "compositor.h" +#include #include "launcher-util.h" #include "launcher-impl.h" diff --git a/libweston/launcher-util.h b/libweston/launcher-util.h index 242e1cc83..dd7b77022 100644 --- a/libweston/launcher-util.h +++ b/libweston/launcher-util.h @@ -28,7 +28,7 @@ #include "config.h" -#include "compositor.h" +#include struct weston_launcher; diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c index 65beb3256..c811a5008 100644 --- a/libweston/launcher-weston-launch.c +++ b/libweston/launcher-weston-launch.c @@ -44,7 +44,7 @@ #include #include -#include "compositor.h" +#include #include "weston-launch.h" #include "launcher-impl.h" diff --git a/libweston/libinput-device.c b/libweston/libinput-device.c index e25df1440..140e87a45 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -38,7 +38,7 @@ #include #include -#include "compositor.h" +#include #include "libinput-device.h" #include "shared/helpers.h" #include "shared/timespec-util.h" diff --git a/libweston/libinput-device.h b/libweston/libinput-device.h index 6147a5137..d3fc645dc 100644 --- a/libweston/libinput-device.h +++ b/libweston/libinput-device.h @@ -33,7 +33,7 @@ #include #include -#include "compositor.h" +#include enum evdev_device_seat_capability { EVDEV_SEAT_POINTER = (1 << 0), diff --git a/libweston/libinput-seat.c b/libweston/libinput-seat.c index 6625daff5..9d3887bc1 100644 --- a/libweston/libinput-seat.c +++ b/libweston/libinput-seat.c @@ -34,7 +34,7 @@ #include #include -#include "compositor.h" +#include #include "launcher-util.h" #include "libinput-seat.h" #include "libinput-device.h" diff --git a/libweston/libinput-seat.h b/libweston/libinput-seat.h index 8c6a5bf7f..315980dcc 100644 --- a/libweston/libinput-seat.h +++ b/libweston/libinput-seat.h @@ -31,7 +31,7 @@ #include -#include "compositor.h" +#include struct libinput_device; diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index 148c61fb6..5f7515b5c 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -30,7 +30,7 @@ #include #include -#include "compositor.h" +#include #include "linux-dmabuf.h" #include "linux-dmabuf-unstable-v1-server-protocol.h" diff --git a/libweston/linux-explicit-synchronization.c b/libweston/linux-explicit-synchronization.c index 99e30a1fc..ec2f015e0 100644 --- a/libweston/linux-explicit-synchronization.c +++ b/libweston/linux-explicit-synchronization.c @@ -28,7 +28,7 @@ #include #include -#include "compositor.h" +#include #include "linux-explicit-synchronization.h" #include "linux-explicit-synchronization-unstable-v1-server-protocol.h" #include "linux-sync-file.h" diff --git a/libweston/log.c b/libweston/log.c index d9bdbf8ca..6b9840de2 100644 --- a/libweston/log.c +++ b/libweston/log.c @@ -34,7 +34,7 @@ #include -#include "compositor.h" +#include static int default_log_handler(const char *fmt, va_list ap); diff --git a/libweston/meson.build b/libweston/meson.build index de4c6ffe8..1421993bf 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -57,7 +57,6 @@ srcs_libweston = [ ] install_headers( - 'compositor.h', 'plugin-registry.h', 'windowed-output-api.h', '../shared/config-parser.h', diff --git a/libweston/noop-renderer.c b/libweston/noop-renderer.c index 6eed40ee5..aef93002c 100644 --- a/libweston/noop-renderer.c +++ b/libweston/noop-renderer.c @@ -28,7 +28,7 @@ #include #include -#include "compositor.h" +#include static int noop_renderer_read_pixels(struct weston_output *output, diff --git a/libweston/pixman-renderer.h b/libweston/pixman-renderer.h index 7a5f72901..83a430106 100644 --- a/libweston/pixman-renderer.h +++ b/libweston/pixman-renderer.h @@ -25,7 +25,7 @@ #include "config.h" -#include "compositor.h" +#include int pixman_renderer_init(struct weston_compositor *ec); diff --git a/libweston/plugin-registry.c b/libweston/plugin-registry.c index 48c42200e..63d89145b 100644 --- a/libweston/plugin-registry.c +++ b/libweston/plugin-registry.c @@ -29,7 +29,7 @@ #include #include -#include "compositor.h" +#include #include "plugin-registry.h" struct weston_plugin_api { diff --git a/libweston/screenshooter.c b/libweston/screenshooter.c index 2bde4f1df..5199d2cb3 100644 --- a/libweston/screenshooter.c +++ b/libweston/screenshooter.c @@ -34,7 +34,7 @@ #include #include -#include "compositor.h" +#include #include "shared/helpers.h" #include "shared/timespec-util.h" diff --git a/libweston/spring-tool.c b/libweston/spring-tool.c index a6ce055dc..b032d737b 100644 --- a/libweston/spring-tool.c +++ b/libweston/spring-tool.c @@ -28,7 +28,7 @@ #include "config.h" -#include "compositor.h" +#include #include "shared/timespec-util.h" WL_EXPORT void diff --git a/libweston/timeline.c b/libweston/timeline.c index f5a39cbad..2e7d8981d 100644 --- a/libweston/timeline.c +++ b/libweston/timeline.c @@ -33,7 +33,7 @@ #include #include "timeline.h" -#include "compositor.h" +#include #include "file-util.h" struct timeline_log { diff --git a/libweston/touch-calibration.c b/libweston/touch-calibration.c index 6e09fe022..d6f21604b 100644 --- a/libweston/touch-calibration.c +++ b/libweston/touch-calibration.c @@ -33,7 +33,7 @@ #include "shared/string-helpers.h" #include "shared/zalloc.h" #include "shared/timespec-util.h" -#include "compositor.h" +#include #include "weston-touch-calibration-server-protocol.h" diff --git a/libweston/vaapi-recorder.c b/libweston/vaapi-recorder.c index 7d6d8cc9c..0a743570c 100644 --- a/libweston/vaapi-recorder.c +++ b/libweston/vaapi-recorder.c @@ -45,7 +45,7 @@ #include #include -#include "compositor.h" +#include #include "vaapi-recorder.h" #define NAL_REF_IDC_NONE 0 diff --git a/libweston/weston-debug.c b/libweston/weston-debug.c index b1349bc38..0536562a9 100644 --- a/libweston/weston-debug.c +++ b/libweston/weston-debug.c @@ -28,7 +28,7 @@ #include "weston-debug.h" #include "helpers.h" -#include "compositor.h" +#include #include "weston-debug-server-protocol.h" diff --git a/libweston/zoom.c b/libweston/zoom.c index b89264f7b..8d513c394 100644 --- a/libweston/zoom.c +++ b/libweston/zoom.c @@ -30,7 +30,7 @@ #include #include -#include "compositor.h" +#include #include "text-cursor-position-server-protocol.h" #include "shared/helpers.h" diff --git a/remoting/remoting-plugin.h b/remoting/remoting-plugin.h index 9a7ca365c..bfad20d5e 100644 --- a/remoting/remoting-plugin.h +++ b/remoting/remoting-plugin.h @@ -28,7 +28,7 @@ #ifndef REMOTING_PLUGIN_H #define REMOTING_PLUGIN_H -#include "compositor.h" +#include #include "plugin-registry.h" #define WESTON_REMOTING_API_NAME "weston_remoting_api_v1" diff --git a/tests/ivi-layout-internal-test.c b/tests/ivi-layout-internal-test.c index 1054d9700..9a552681d 100644 --- a/tests/ivi-layout-internal-test.c +++ b/tests/ivi-layout-internal-test.c @@ -32,7 +32,7 @@ #include #include -#include "compositor.h" +#include #include "compositor/weston.h" #include "ivi-shell/ivi-layout-export.h" #include "ivi-shell/ivi-layout-private.h" diff --git a/tests/ivi-layout-test-plugin.c b/tests/ivi-layout-test-plugin.c index fa7695357..4799919cd 100644 --- a/tests/ivi-layout-test-plugin.c +++ b/tests/ivi-layout-test-plugin.c @@ -34,7 +34,7 @@ #include #include -#include "compositor.h" +#include #include "compositor/weston.h" #include "weston-test-server-protocol.h" #include "ivi-test.h" diff --git a/tests/plugin-registry-test.c b/tests/plugin-registry-test.c index 738ccd315..30faa85d1 100644 --- a/tests/plugin-registry-test.c +++ b/tests/plugin-registry-test.c @@ -27,7 +27,7 @@ #include -#include "compositor.h" +#include #include "compositor/weston.h" #include "plugin-registry.h" diff --git a/tests/surface-global-test.c b/tests/surface-global-test.c index 59d4152db..548f5523f 100644 --- a/tests/surface-global-test.c +++ b/tests/surface-global-test.c @@ -28,7 +28,7 @@ #include #include -#include "compositor.h" +#include #include "compositor/weston.h" static void diff --git a/tests/surface-screenshot-test.c b/tests/surface-screenshot-test.c index 908022dec..3c6aa06cd 100644 --- a/tests/surface-screenshot-test.c +++ b/tests/surface-screenshot-test.c @@ -33,7 +33,7 @@ #include #include -#include "compositor.h" +#include #include "compositor/weston.h" #include "file-util.h" diff --git a/tests/surface-test.c b/tests/surface-test.c index 0661cc950..1dce42b87 100644 --- a/tests/surface-test.c +++ b/tests/surface-test.c @@ -28,7 +28,7 @@ #include #include -#include "compositor.h" +#include #include "compositor/weston.h" static void diff --git a/tests/weston-test.c b/tests/weston-test.c index c018cee54..34c1089df 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -32,7 +32,7 @@ #include #include -#include "compositor.h" +#include #include "compositor/weston.h" #include "weston-test-server-protocol.h" diff --git a/xwayland/dnd.c b/xwayland/dnd.c index bb3b8cc69..0324a6167 100644 --- a/xwayland/dnd.c +++ b/xwayland/dnd.c @@ -37,7 +37,7 @@ #include #include -#include "compositor.h" +#include #include "xwayland.h" #include "cairo-util.h" diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 77260b0fc..f5ca5ed9e 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -41,7 +41,7 @@ #include #include -#include "compositor.h" +#include #include "xwayland.h" #include "xwayland-internal-interface.h" diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index 507d534d3..150410113 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -30,7 +30,7 @@ #include #include -#include "compositor.h" +#include #include "compositor/weston.h" #include "xwayland-api.h" #include "weston-debug.h" From 7571027f177ec038874ae261d79b672acae1f4ae Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 29 Mar 2019 16:39:12 +0200 Subject: [PATCH 0839/1642] Rename public backend headers The backend headers are renamed from compositor-foo.h to backend-foo.h to better describe their purpose. These headers are public libweston API for each specific backend. The headers will also be used like #include instead of #include to give them a more explicit namespace. Signed-off-by: Pekka Paalanen --- compositor/main.c | 12 ++++++------ .../libweston/backend-drm.h | 0 .../libweston/backend-fbdev.h | 0 .../libweston/backend-headless.h | 0 .../libweston/backend-rdp.h | 0 .../libweston/backend-wayland.h | 0 .../libweston/backend-x11.h | 0 include/libweston/meson.build | 7 +++++++ libweston/compositor-drm.c | 2 +- libweston/compositor-fbdev.c | 2 +- libweston/compositor-headless.c | 2 +- libweston/compositor-rdp.c | 2 +- libweston/compositor-wayland.c | 2 +- libweston/compositor-x11.c | 2 +- libweston/meson.build | 12 ++++++------ remoting/remoting-plugin.c | 2 +- 16 files changed, 26 insertions(+), 19 deletions(-) rename libweston/compositor-drm.h => include/libweston/backend-drm.h (100%) rename libweston/compositor-fbdev.h => include/libweston/backend-fbdev.h (100%) rename libweston/compositor-headless.h => include/libweston/backend-headless.h (100%) rename libweston/compositor-rdp.h => include/libweston/backend-rdp.h (100%) rename libweston/compositor-wayland.h => include/libweston/backend-wayland.h (100%) rename libweston/compositor-x11.h => include/libweston/backend-x11.h (100%) diff --git a/compositor/main.c b/compositor/main.c index 03482c537..9aef92884 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -55,12 +55,12 @@ #include "version.h" #include "weston.h" -#include "compositor-drm.h" -#include "compositor-headless.h" -#include "compositor-rdp.h" -#include "compositor-fbdev.h" -#include "compositor-x11.h" -#include "compositor-wayland.h" +#include +#include +#include +#include +#include +#include #include "windowed-output-api.h" #include "weston-debug.h" #include "../remoting/remoting-plugin.h" diff --git a/libweston/compositor-drm.h b/include/libweston/backend-drm.h similarity index 100% rename from libweston/compositor-drm.h rename to include/libweston/backend-drm.h diff --git a/libweston/compositor-fbdev.h b/include/libweston/backend-fbdev.h similarity index 100% rename from libweston/compositor-fbdev.h rename to include/libweston/backend-fbdev.h diff --git a/libweston/compositor-headless.h b/include/libweston/backend-headless.h similarity index 100% rename from libweston/compositor-headless.h rename to include/libweston/backend-headless.h diff --git a/libweston/compositor-rdp.h b/include/libweston/backend-rdp.h similarity index 100% rename from libweston/compositor-rdp.h rename to include/libweston/backend-rdp.h diff --git a/libweston/compositor-wayland.h b/include/libweston/backend-wayland.h similarity index 100% rename from libweston/compositor-wayland.h rename to include/libweston/backend-wayland.h diff --git a/libweston/compositor-x11.h b/include/libweston/backend-x11.h similarity index 100% rename from libweston/compositor-x11.h rename to include/libweston/backend-x11.h diff --git a/include/libweston/meson.build b/include/libweston/meson.build index 74480838f..6e1aee7ed 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -3,3 +3,10 @@ install_headers( 'timeline-object.h', subdir: dir_include_libweston_install ) + +backend_drm_h = files('backend-drm.h') +backend_fbdev_h = files('backend-fbdev.h') +backend_headless_h = files('backend-headless.h') +backend_rdp_h = files('backend-rdp.h') +backend_wayland_h = files('backend-wayland.h') +backend_x11_h = files('backend-x11.h') diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 21ee2c1a7..c563a57c7 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -51,7 +51,7 @@ #include #include -#include "compositor-drm.h" +#include #include "weston-debug.h" #include "shared/helpers.h" #include "shared/timespec-util.h" diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c index a31ee999b..bcda7d0ed 100644 --- a/libweston/compositor-fbdev.c +++ b/libweston/compositor-fbdev.c @@ -46,7 +46,7 @@ #include "shared/helpers.h" #include -#include "compositor-fbdev.h" +#include #include "launcher-util.h" #include "pixman-renderer.h" #include "libinput-seat.h" diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index 098e08e9b..2b3137a2b 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -34,7 +34,7 @@ #include #include -#include "compositor-headless.h" +#include #include "shared/helpers.h" #include "linux-explicit-synchronization.h" #include "pixman-renderer.h" diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c index 8ece5e97c..1f39d03ca 100644 --- a/libweston/compositor-rdp.c +++ b/libweston/compositor-rdp.c @@ -99,7 +99,7 @@ #include "shared/helpers.h" #include "shared/timespec-util.h" #include -#include "compositor-rdp.h" +#include #include "pixman-renderer.h" #define MAX_FREERDP_FDS 32 diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 4e2257159..9881712aa 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -45,7 +45,7 @@ #endif #include -#include "compositor-wayland.h" +#include #include "gl-renderer.h" #include "weston-egl-ext.h" #include "pixman-renderer.h" diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index d8513b596..903cf137c 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -51,7 +51,7 @@ #include #include -#include "compositor-x11.h" +#include #include "shared/config-parser.h" #include "shared/helpers.h" #include "shared/image-loader.h" diff --git a/libweston/meson.build b/libweston/meson.build index 1421993bf..aaa95fd58 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -249,7 +249,7 @@ if get_option('backend-drm') ) env_modmap += 'drm-backend.so=@0@;'.format(plugin_drm.full_path()) - install_headers('compositor-drm.h', subdir: dir_include_libweston) + install_headers(backend_drm_h, subdir: dir_include_libweston_install) endif @@ -270,7 +270,7 @@ if get_option('backend-headless') install_dir: dir_module_libweston, ) env_modmap += 'headless-backend.so=@0@;'.format(plugin_headless.full_path()) - install_headers('compositor-headless.h', subdir: dir_include_libweston) + install_headers(backend_headless_h, subdir: dir_include_libweston_install) endif @@ -308,7 +308,7 @@ if get_option('backend-rdp') install_dir: dir_module_libweston ) env_modmap += 'rdp-backend.so=@0@;'.format(plugin_rdp.full_path()) - install_headers('compositor-rdp.h', subdir: dir_include_libweston) + install_headers(backend_rdp_h, subdir: dir_include_libweston_install) endif @@ -351,7 +351,7 @@ if get_option('backend-wayland') install_dir: dir_module_libweston ) env_modmap += 'wayland-backend.so=@0@;'.format(plugin_wlwl.full_path()) - install_headers('compositor-wayland.h', subdir: dir_include_libweston) + install_headers(backend_wayland_h, subdir: dir_include_libweston_install) endif @@ -407,7 +407,7 @@ if get_option('backend-x11') ) env_modmap += 'x11-backend.so=@0@;'.format(plugin_x11.full_path()) - install_headers('compositor-x11.h', subdir: dir_include_libweston) + install_headers(backend_x11_h, subdir: dir_include_libweston_install) endif @@ -439,7 +439,7 @@ if get_option('backend-fbdev') ) env_modmap += 'fbdev-backend.so=@0@;'.format(plugin_fbdev.full_path()) - install_headers('compositor-fbdev.h', subdir: dir_include_libweston) + install_headers(backend_fbdev_h, subdir: dir_include_libweston_install) endif diff --git a/remoting/remoting-plugin.c b/remoting/remoting-plugin.c index e99d61e15..00d1ac773 100644 --- a/remoting/remoting-plugin.c +++ b/remoting/remoting-plugin.c @@ -44,7 +44,7 @@ #include #include "remoting-plugin.h" -#include "compositor-drm.h" +#include #include "shared/helpers.h" #include "shared/timespec-util.h" From 27b377f51f3ee96a81936032bf803a49638c3691 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 29 Mar 2019 17:07:34 +0200 Subject: [PATCH 0840/1642] Rename plugin-registry.h to libweston/plugin-registry.h See "Rename compositor.h to libweston/libweston.h" for rationale. Signed-off-by: Pekka Paalanen --- include/libweston/backend-drm.h | 2 +- include/libweston/backend-rdp.h | 2 +- include/libweston/meson.build | 1 + {libweston => include/libweston}/plugin-registry.h | 0 ivi-shell/ivi-layout-export.h | 2 +- libweston/compositor.c | 2 +- libweston/meson.build | 1 - libweston/plugin-registry.c | 2 +- libweston/windowed-output-api.h | 2 +- remoting/remoting-plugin.h | 2 +- tests/plugin-registry-test.c | 2 +- xwayland/xwayland-api.h | 2 +- 12 files changed, 10 insertions(+), 10 deletions(-) rename {libweston => include/libweston}/plugin-registry.h (100%) diff --git a/include/libweston/backend-drm.h b/include/libweston/backend-drm.h index 76c84c8ab..f6647e286 100644 --- a/include/libweston/backend-drm.h +++ b/include/libweston/backend-drm.h @@ -29,7 +29,7 @@ #define WESTON_COMPOSITOR_DRM_H #include -#include "plugin-registry.h" +#include #ifdef __cplusplus extern "C" { diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index ebdd72f2c..7a63643f5 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -31,7 +31,7 @@ extern "C" { #endif #include -#include "plugin-registry.h" +#include #define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v1" diff --git a/include/libweston/meson.build b/include/libweston/meson.build index 6e1aee7ed..2e0dfc164 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -1,5 +1,6 @@ install_headers( 'libweston.h', + 'plugin-registry.h', 'timeline-object.h', subdir: dir_include_libweston_install ) diff --git a/libweston/plugin-registry.h b/include/libweston/plugin-registry.h similarity index 100% rename from libweston/plugin-registry.h rename to include/libweston/plugin-registry.h diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index b5e92c044..740e1ac0f 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -60,7 +60,7 @@ extern "C" { #include "stdbool.h" #include -#include "plugin-registry.h" +#include #define IVI_SUCCEEDED (0) #define IVI_FAILED (-1) diff --git a/libweston/compositor.c b/libweston/compositor.c index 428b87f4a..faaf5b2ac 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -69,7 +69,7 @@ #include "shared/timespec-util.h" #include "git-version.h" #include "version.h" -#include "plugin-registry.h" +#include #include "pixel-formats.h" #define DEFAULT_REPAINT_WINDOW 7 /* milliseconds */ diff --git a/libweston/meson.build b/libweston/meson.build index aaa95fd58..2196aff85 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -57,7 +57,6 @@ srcs_libweston = [ ] install_headers( - 'plugin-registry.h', 'windowed-output-api.h', '../shared/config-parser.h', '../shared/matrix.h', diff --git a/libweston/plugin-registry.c b/libweston/plugin-registry.c index 63d89145b..f5ec6fa55 100644 --- a/libweston/plugin-registry.c +++ b/libweston/plugin-registry.c @@ -30,7 +30,7 @@ #include #include -#include "plugin-registry.h" +#include struct weston_plugin_api { struct wl_list link; /**< in weston_compositor::plugin_api_list */ diff --git a/libweston/windowed-output-api.h b/libweston/windowed-output-api.h index 388413f30..be4dec67e 100644 --- a/libweston/windowed-output-api.h +++ b/libweston/windowed-output-api.h @@ -30,7 +30,7 @@ extern "C" { #endif -#include "plugin-registry.h" +#include struct weston_compositor; struct weston_output; diff --git a/remoting/remoting-plugin.h b/remoting/remoting-plugin.h index bfad20d5e..8c82ac716 100644 --- a/remoting/remoting-plugin.h +++ b/remoting/remoting-plugin.h @@ -29,7 +29,7 @@ #define REMOTING_PLUGIN_H #include -#include "plugin-registry.h" +#include #define WESTON_REMOTING_API_NAME "weston_remoting_api_v1" diff --git a/tests/plugin-registry-test.c b/tests/plugin-registry-test.c index 30faa85d1..4ffc69e55 100644 --- a/tests/plugin-registry-test.c +++ b/tests/plugin-registry-test.c @@ -29,7 +29,7 @@ #include #include "compositor/weston.h" -#include "plugin-registry.h" +#include static void dummy_func(void) diff --git a/xwayland/xwayland-api.h b/xwayland/xwayland-api.h index 317533ef8..ff71fddd0 100644 --- a/xwayland/xwayland-api.h +++ b/xwayland/xwayland-api.h @@ -32,7 +32,7 @@ extern "C" { #include -#include "plugin-registry.h" +#include struct weston_compositor; struct weston_xwayland; From 9eda0ea82595bbf5a82bca7cec68ba596008f41b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 29 Mar 2019 17:14:29 +0200 Subject: [PATCH 0841/1642] Rename windowed-output-api.h to libweston/windowed-output-api.h See "Rename compositor.h to libweston/libweston.h" for rationale. Signed-off-by: Pekka Paalanen --- compositor/main.c | 2 +- include/libweston/meson.build | 1 + {libweston => include/libweston}/windowed-output-api.h | 0 libweston/compositor-headless.c | 2 +- libweston/compositor-wayland.c | 2 +- libweston/compositor-x11.c | 2 +- libweston/meson.build | 1 - 7 files changed, 5 insertions(+), 5 deletions(-) rename {libweston => include/libweston}/windowed-output-api.h (100%) diff --git a/compositor/main.c b/compositor/main.c index 9aef92884..ea35b2764 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -61,7 +61,7 @@ #include #include #include -#include "windowed-output-api.h" +#include #include "weston-debug.h" #include "../remoting/remoting-plugin.h" diff --git a/include/libweston/meson.build b/include/libweston/meson.build index 2e0dfc164..f6b55feba 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -2,6 +2,7 @@ install_headers( 'libweston.h', 'plugin-registry.h', 'timeline-object.h', + 'windowed-output-api.h', subdir: dir_include_libweston_install ) diff --git a/libweston/windowed-output-api.h b/include/libweston/windowed-output-api.h similarity index 100% rename from libweston/windowed-output-api.h rename to include/libweston/windowed-output-api.h diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c index 2b3137a2b..04c33d150 100644 --- a/libweston/compositor-headless.c +++ b/libweston/compositor-headless.c @@ -39,7 +39,7 @@ #include "linux-explicit-synchronization.h" #include "pixman-renderer.h" #include "presentation-time-server-protocol.h" -#include "windowed-output-api.h" +#include struct headless_backend { struct weston_backend base; diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 9881712aa..7e2fee176 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -58,7 +58,7 @@ #include "xdg-shell-client-protocol.h" #include "presentation-time-server-protocol.h" #include "linux-dmabuf.h" -#include "windowed-output-api.h" +#include #define WINDOW_TITLE "Weston Compositor" diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 903cf137c..a62fe80ca 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -63,7 +63,7 @@ #include "presentation-time-server-protocol.h" #include "linux-dmabuf.h" #include "linux-explicit-synchronization.h" -#include "windowed-output-api.h" +#include #define DEFAULT_AXIS_STEP_DISTANCE 10 diff --git a/libweston/meson.build b/libweston/meson.build index 2196aff85..31ad923fd 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -57,7 +57,6 @@ srcs_libweston = [ ] install_headers( - 'windowed-output-api.h', '../shared/config-parser.h', '../shared/matrix.h', '../shared/zalloc.h', From 96dc449259e9e59eee241a0ce36a123b4aaef9f6 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 4 Apr 2019 13:47:40 +0300 Subject: [PATCH 0842/1642] Rename matrix.h to libweston/matrix.h matrix.h is a public installed header and even used by libweston.h. See "Rename compositor.h to libweston/libweston.h" for rationale. Signed-off-by: Pekka Paalanen --- clients/calibrator.c | 4 ++-- clients/touch-calibrator.c | 2 +- include/libweston/libweston.h | 2 +- {shared => include/libweston}/matrix.h | 0 include/libweston/meson.build | 1 + libweston/meson.build | 1 - shared/matrix.c | 2 +- shared/meson.build | 1 + tests/matrix-test.c | 2 +- tests/meson.build | 2 +- 10 files changed, 9 insertions(+), 8 deletions(-) rename {shared => include/libweston}/matrix.h (100%) diff --git a/clients/calibrator.c b/clients/calibrator.c index 778c23cfa..2439fc94c 100644 --- a/clients/calibrator.c +++ b/clients/calibrator.c @@ -38,7 +38,7 @@ #include "window.h" #include "shared/helpers.h" -#include "shared/matrix.h" +#include /* Our points for the calibration must be not be on a line */ static const struct { @@ -286,7 +286,7 @@ main(int argc, char *argv[]) break; } } - + display = display_create(&argc, argv); if (display == NULL) { diff --git a/clients/touch-calibrator.c b/clients/touch-calibrator.c index 3c7e6ece6..96f8c5a56 100644 --- a/clients/touch-calibrator.c +++ b/clients/touch-calibrator.c @@ -40,7 +40,7 @@ #include "clients/window.h" #include "shared/helpers.h" -#include "shared/matrix.h" +#include #include "weston-touch-calibration-client-protocol.h" diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 16ad495ba..e4ebc71d0 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -41,7 +41,7 @@ extern "C" { #define WL_HIDE_DEPRECATED #include -#include "matrix.h" +#include #include "config-parser.h" #include "zalloc.h" #include diff --git a/shared/matrix.h b/include/libweston/matrix.h similarity index 100% rename from shared/matrix.h rename to include/libweston/matrix.h diff --git a/include/libweston/meson.build b/include/libweston/meson.build index f6b55feba..e631e1d13 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -1,5 +1,6 @@ install_headers( 'libweston.h', + 'matrix.h', 'plugin-registry.h', 'timeline-object.h', 'windowed-output-api.h', diff --git a/libweston/meson.build b/libweston/meson.build index 31ad923fd..de6c93975 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -58,7 +58,6 @@ srcs_libweston = [ install_headers( '../shared/config-parser.h', - '../shared/matrix.h', '../shared/zalloc.h', subdir: dir_include_libweston ) diff --git a/shared/matrix.c b/shared/matrix.c index 605db83c9..4e8d6b40b 100644 --- a/shared/matrix.c +++ b/shared/matrix.c @@ -37,7 +37,7 @@ #include #endif -#include "matrix.h" +#include /* diff --git a/shared/meson.build b/shared/meson.build index 5b0d8d130..c3fd932ae 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -17,6 +17,7 @@ lib_libshared = static_library( ) dep_libshared = declare_dependency( link_with: lib_libshared, + include_directories: public_inc, dependencies: deps_libshared ) diff --git a/tests/matrix-test.c b/tests/matrix-test.c index 520e12eb1..03b92162b 100644 --- a/tests/matrix-test.c +++ b/tests/matrix-test.c @@ -32,7 +32,7 @@ #include #include -#include "shared/matrix.h" +#include struct inverse_matrix { double LU[16]; /* column-major */ diff --git a/tests/meson.build b/tests/meson.build index 03692f477..5274d8dd3 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -97,7 +97,7 @@ dep_zucmain = declare_dependency( tests_standalone = [ ['config-parser', [], [ dep_zucmain ]], - ['matrix', [ '../shared/matrix.c' ], [ dep_libm ]], + ['matrix', [ '../shared/matrix.c' ], [ dep_libm, dep_libshared.partial_dependency(includes: true) ]], ['string'], [ 'vertex-clip', From 91b1010de97653d3bbe72f08107c4d9f2e5c4ece Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 4 Apr 2019 14:27:31 +0300 Subject: [PATCH 0843/1642] Rename config-parser.h to libweston/config-parser.h It is a public installed header used by libweston.h. See "Rename compositor.h to libweston/libweston.h" for rationale. Signed-off-by: Pekka Paalanen --- clients/desktop-shell.c | 2 +- clients/editor.c | 2 +- clients/ivi-shell-user-interface.c | 2 +- clients/terminal.c | 2 +- clients/window.h | 2 +- desktop-shell/shell.c | 2 +- {shared => include/libweston}/config-parser.h | 0 include/libweston/libweston.h | 2 +- include/libweston/meson.build | 1 + libweston/compositor-x11.c | 1 - libweston/meson.build | 1 - shared/cairo-util.c | 4 ++-- shared/config-parser.c | 2 +- shared/meson.build | 2 +- shared/option-parser.c | 2 +- tests/config-parser-test.c | 2 +- tests/weston-test-desktop-shell.c | 2 +- tools/zunitc/src/zunitc_impl.c | 2 +- 18 files changed, 16 insertions(+), 17 deletions(-) rename {shared => include/libweston}/config-parser.h (100%) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index 0dc0f0ba3..8e4fae948 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -43,7 +43,7 @@ #include #include "window.h" #include "shared/cairo-util.h" -#include "shared/config-parser.h" +#include #include "shared/helpers.h" #include "shared/xalloc.h" #include "shared/zalloc.h" diff --git a/clients/editor.c b/clients/editor.c index 8f9eb6322..03e6b4913 100644 --- a/clients/editor.c +++ b/clients/editor.c @@ -38,7 +38,7 @@ #include -#include "shared/config-parser.h" +#include #include "shared/helpers.h" #include "shared/xalloc.h" #include "window.h" diff --git a/clients/ivi-shell-user-interface.c b/clients/ivi-shell-user-interface.c index a38d6af86..44baa5420 100644 --- a/clients/ivi-shell-user-interface.c +++ b/clients/ivi-shell-user-interface.c @@ -38,7 +38,7 @@ #include #include #include "shared/cairo-util.h" -#include "shared/config-parser.h" +#include #include "shared/helpers.h" #include "shared/os-compatibility.h" #include "shared/xalloc.h" diff --git a/clients/terminal.c b/clients/terminal.c index ebc5bbe29..f7546ede8 100644 --- a/clients/terminal.c +++ b/clients/terminal.c @@ -44,7 +44,7 @@ #include -#include "shared/config-parser.h" +#include #include "shared/helpers.h" #include "shared/xalloc.h" #include "window.h" diff --git a/clients/window.h b/clients/window.h index fde5c2f05..03a1fe671 100644 --- a/clients/window.h +++ b/clients/window.h @@ -31,7 +31,7 @@ #include #include #include -#include "shared/config-parser.h" +#include #include "shared/zalloc.h" #include "shared/platform.h" diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 93b1c70b5..270c95435 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -39,7 +39,7 @@ #include "shell.h" #include "compositor/weston.h" #include "weston-desktop-shell-server-protocol.h" -#include "shared/config-parser.h" +#include #include "shared/helpers.h" #include "shared/timespec-util.h" #include "libweston-desktop/libweston-desktop.h" diff --git a/shared/config-parser.h b/include/libweston/config-parser.h similarity index 100% rename from shared/config-parser.h rename to include/libweston/config-parser.h diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index e4ebc71d0..7632382cc 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -42,7 +42,7 @@ extern "C" { #include #include -#include "config-parser.h" +#include #include "zalloc.h" #include diff --git a/include/libweston/meson.build b/include/libweston/meson.build index e631e1d13..659e135b2 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -1,4 +1,5 @@ install_headers( + 'config-parser.h', 'libweston.h', 'matrix.h', 'plugin-registry.h', diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index a62fe80ca..3fdeb1142 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -52,7 +52,6 @@ #include #include -#include "shared/config-parser.h" #include "shared/helpers.h" #include "shared/image-loader.h" #include "shared/timespec-util.h" diff --git a/libweston/meson.build b/libweston/meson.build index de6c93975..b165ddb66 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -57,7 +57,6 @@ srcs_libweston = [ ] install_headers( - '../shared/config-parser.h', '../shared/zalloc.h', subdir: dir_include_libweston ) diff --git a/shared/cairo-util.c b/shared/cairo-util.c index 15cbf82ca..f558e1d0c 100644 --- a/shared/cairo-util.c +++ b/shared/cairo-util.c @@ -37,7 +37,7 @@ #include "shared/helpers.h" #include "image-loader.h" -#include "config-parser.h" +#include #ifdef HAVE_PANGO #include @@ -543,7 +543,7 @@ theme_render_frame(struct theme *t, text_height = logical.height; if (text_width < logical.width) pango_layout_set_width (title_layout, text_width * PANGO_SCALE); - + #else cairo_text_extents_t extents; cairo_font_extents_t font_extents; diff --git a/shared/config-parser.c b/shared/config-parser.c index 35f09f006..297b0f70a 100644 --- a/shared/config-parser.c +++ b/shared/config-parser.c @@ -39,7 +39,7 @@ #include #include -#include "config-parser.h" +#include #include "helpers.h" #include "string-helpers.h" diff --git a/shared/meson.build b/shared/meson.build index c3fd932ae..eb897c100 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -10,7 +10,7 @@ deps_libshared = dep_wayland_client lib_libshared = static_library( 'shared', srcs_libshared, - include_directories: include_directories('..'), + include_directories: [ include_directories('..'), public_inc ], dependencies: deps_libshared, pic: true, install: false diff --git a/shared/option-parser.c b/shared/option-parser.c index 0f93464c6..795195f5c 100644 --- a/shared/option-parser.c +++ b/shared/option-parser.c @@ -33,7 +33,7 @@ #include #include -#include "config-parser.h" +#include #include "string-helpers.h" static bool diff --git a/tests/config-parser-test.c b/tests/config-parser-test.c index 1cdffffc7..44ee73593 100644 --- a/tests/config-parser-test.c +++ b/tests/config-parser-test.c @@ -33,7 +33,7 @@ #include #include -#include "config-parser.h" +#include #include "shared/helpers.h" #include "zunitc/zunitc.h" diff --git a/tests/weston-test-desktop-shell.c b/tests/weston-test-desktop-shell.c index c780316d9..b580ffd6f 100644 --- a/tests/weston-test-desktop-shell.c +++ b/tests/weston-test-desktop-shell.c @@ -37,7 +37,7 @@ #include #include "compositor/weston.h" -#include "shared/config-parser.h" +#include #include "shared/helpers.h" #include "libweston-desktop/libweston-desktop.h" diff --git a/tools/zunitc/src/zunitc_impl.c b/tools/zunitc/src/zunitc_impl.c index 58a5b1744..72f08df67 100644 --- a/tools/zunitc/src/zunitc_impl.c +++ b/tools/zunitc/src/zunitc_impl.c @@ -46,7 +46,7 @@ #include "zuc_event_listener.h" #include "zuc_junit_reporter.h" -#include "shared/config-parser.h" +#include #include "shared/helpers.h" #include "shared/zalloc.h" From ecbdcfd373025be6454a0aee7d870ca096fb7d52 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 4 Apr 2019 14:46:00 +0300 Subject: [PATCH 0844/1642] Rename zalloc.h to libweston/zalloc.h It is a public installed header used by libweston.h. See "Rename compositor.h to libweston/libweston.h" for rationale. Signed-off-by: Pekka Paalanen --- clients/desktop-shell.c | 2 +- clients/fullscreen.c | 2 +- clients/ivi-shell-user-interface.c | 2 +- clients/meson.build | 6 +++--- clients/multi-resource.c | 2 +- clients/presentation-shm.c | 2 +- clients/simple-damage.c | 2 +- clients/simple-dmabuf-drm.c | 2 +- clients/simple-dmabuf-egl.c | 2 +- clients/simple-dmabuf-v4l.c | 2 +- clients/simple-shm.c | 2 +- clients/subsurfaces.c | 2 +- clients/weston-debug.c | 2 +- clients/weston-info.c | 2 +- clients/window.c | 2 +- clients/window.h | 2 +- compositor/systemd-notify.c | 2 +- include/libweston/libweston.h | 2 +- include/libweston/meson.build | 1 + {shared => include/libweston}/zalloc.h | 0 libweston-desktop/client.c | 2 +- libweston-desktop/libweston-desktop.c | 2 +- libweston-desktop/seat.c | 2 +- libweston-desktop/surface.c | 2 +- libweston-desktop/wl-shell.c | 2 +- libweston-desktop/xdg-shell-v6.c | 2 +- libweston-desktop/xdg-shell.c | 2 +- libweston-desktop/xwayland.c | 2 +- libweston/meson.build | 5 ----- libweston/touch-calibration.c | 2 +- shared/xalloc.h | 2 +- tests/input-timestamps-helper.c | 2 +- tests/meson.build | 8 +++++--- tests/weston-test-client-helper.c | 2 +- tools/zunitc/src/zuc_base_logger.c | 2 +- tools/zunitc/src/zuc_collector.c | 2 +- tools/zunitc/src/zuc_junit_reporter.c | 3 +-- tools/zunitc/src/zunitc_impl.c | 2 +- 38 files changed, 42 insertions(+), 45 deletions(-) rename {shared => include/libweston}/zalloc.h (100%) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index 8e4fae948..c60e74d2f 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -46,7 +46,7 @@ #include #include "shared/helpers.h" #include "shared/xalloc.h" -#include "shared/zalloc.h" +#include #include "shared/file-util.h" #include "weston-desktop-shell-client-protocol.h" diff --git a/clients/fullscreen.c b/clients/fullscreen.c index e2e6477f1..22fcec11d 100644 --- a/clients/fullscreen.c +++ b/clients/fullscreen.c @@ -36,7 +36,7 @@ #include #include "window.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" -#include "shared/zalloc.h" +#include struct fs_output { struct wl_list link; diff --git a/clients/ivi-shell-user-interface.c b/clients/ivi-shell-user-interface.c index 44baa5420..445892a6d 100644 --- a/clients/ivi-shell-user-interface.c +++ b/clients/ivi-shell-user-interface.c @@ -42,7 +42,7 @@ #include "shared/helpers.h" #include "shared/os-compatibility.h" #include "shared/xalloc.h" -#include "shared/zalloc.h" +#include #include "shared/file-util.h" #include "ivi-application-client-protocol.h" #include "ivi-hmi-controller-client-protocol.h" diff --git a/clients/meson.build b/clients/meson.build index 53a94212f..3f180f77b 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -132,7 +132,7 @@ foreach t : simple_clients executable( t_name, t.get('sources'), - include_directories: include_directories('..'), + include_directories: [ include_directories('..'), public_inc ], dependencies: t_deps, install: true ) @@ -215,7 +215,7 @@ foreach t : tools_list executable( 'weston-@0@'.format(t.get('name')), t.get('sources'), - include_directories: include_directories('..', '../shared'), + include_directories: [ include_directories('..', '../shared'), public_inc ], dependencies: t.get('deps', []), install: true ) @@ -328,7 +328,7 @@ if simple_dmabuf_drm_deps.length() > 0 fullscreen_shell_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_client_protocol_h, linux_dmabuf_unstable_v1_protocol_c, - include_directories: include_directories('..'), + include_directories: [ include_directories('..'), public_inc ], dependencies: [ dep_wayland_client, dep_libdrm, diff --git a/clients/multi-resource.c b/clients/multi-resource.c index 1d5e4e0c3..eb4fd4406 100644 --- a/clients/multi-resource.c +++ b/clients/multi-resource.c @@ -42,7 +42,7 @@ #include #include "shared/os-compatibility.h" #include "shared/xalloc.h" -#include "shared/zalloc.h" +#include struct device { enum { KEYBOARD, POINTER } type; diff --git a/clients/presentation-shm.c b/clients/presentation-shm.c index d6a939e5b..9f4790ae5 100644 --- a/clients/presentation-shm.c +++ b/clients/presentation-shm.c @@ -38,7 +38,7 @@ #include #include "shared/helpers.h" -#include "shared/zalloc.h" +#include #include "shared/timespec-util.h" #include "shared/os-compatibility.h" #include "presentation-time-client-protocol.h" diff --git a/clients/simple-damage.c b/clients/simple-damage.c index f33b9790c..72ab6dd65 100644 --- a/clients/simple-damage.c +++ b/clients/simple-damage.c @@ -38,7 +38,7 @@ #include #include "shared/os-compatibility.h" -#include "shared/zalloc.h" +#include #include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "viewporter-client-protocol.h" diff --git a/clients/simple-dmabuf-drm.c b/clients/simple-dmabuf-drm.c index 001614911..98f7f0f26 100644 --- a/clients/simple-dmabuf-drm.c +++ b/clients/simple-dmabuf-drm.c @@ -56,7 +56,7 @@ #include #include -#include "shared/zalloc.h" +#include #include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c index 2d99c4709..13b00191c 100644 --- a/clients/simple-dmabuf-egl.c +++ b/clients/simple-dmabuf-egl.c @@ -46,7 +46,7 @@ #include #include "shared/helpers.h" #include "shared/platform.h" -#include "shared/zalloc.h" +#include #include "xdg-shell-unstable-v6-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c index a91a07309..ecdc6c3cc 100644 --- a/clients/simple-dmabuf-v4l.c +++ b/clients/simple-dmabuf-v4l.c @@ -46,7 +46,7 @@ #include #include -#include "shared/zalloc.h" +#include #include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" diff --git a/clients/simple-shm.c b/clients/simple-shm.c index 979d79882..ff6613f88 100644 --- a/clients/simple-shm.c +++ b/clients/simple-shm.c @@ -36,7 +36,7 @@ #include #include "shared/os-compatibility.h" -#include "shared/zalloc.h" +#include #include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" diff --git a/clients/subsurfaces.c b/clients/subsurfaces.c index d88b8617c..23c22e8fd 100644 --- a/clients/subsurfaces.c +++ b/clients/subsurfaces.c @@ -43,7 +43,7 @@ #include "shared/helpers.h" #include "shared/xalloc.h" -#include "shared/zalloc.h" +#include #include "window.h" #if 0 diff --git a/clients/weston-debug.c b/clients/weston-debug.c index 563c64ea5..d7ff081e6 100644 --- a/clients/weston-debug.c +++ b/clients/weston-debug.c @@ -42,7 +42,7 @@ #include #include "shared/helpers.h" -#include "shared/zalloc.h" +#include #include "weston-debug-client-protocol.h" struct debug_app { diff --git a/clients/weston-info.c b/clients/weston-info.c index 8d7d79887..4134191bc 100644 --- a/clients/weston-info.c +++ b/clients/weston-info.c @@ -38,7 +38,7 @@ #include "shared/helpers.h" #include "shared/os-compatibility.h" #include "shared/xalloc.h" -#include "shared/zalloc.h" +#include #include "presentation-time-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "tablet-unstable-v2-client-protocol.h" diff --git a/clients/window.c b/clients/window.c index bb9c708fe..dfde55eea 100644 --- a/clients/window.c +++ b/clients/window.c @@ -73,7 +73,7 @@ typedef void *EGLContext; #include "shared/cairo-util.h" #include "shared/helpers.h" #include "shared/xalloc.h" -#include "shared/zalloc.h" +#include #include "xdg-shell-client-protocol.h" #include "text-cursor-position-client-protocol.h" #include "pointer-constraints-unstable-v1-client-protocol.h" diff --git a/clients/window.h b/clients/window.h index 03a1fe671..57e24331a 100644 --- a/clients/window.h +++ b/clients/window.h @@ -32,7 +32,7 @@ #include #include #include -#include "shared/zalloc.h" +#include #include "shared/platform.h" struct window; diff --git a/compositor/systemd-notify.c b/compositor/systemd-notify.c index b2dcce1fc..6284a6d9f 100644 --- a/compositor/systemd-notify.c +++ b/compositor/systemd-notify.c @@ -32,7 +32,7 @@ #include "shared/helpers.h" #include "shared/string-helpers.h" -#include "shared/zalloc.h" +#include #include #include "weston.h" diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 7632382cc..965831878 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -43,7 +43,7 @@ extern "C" { #include #include -#include "zalloc.h" +#include #include struct weston_geometry { diff --git a/include/libweston/meson.build b/include/libweston/meson.build index 659e135b2..a3ac20c26 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -5,6 +5,7 @@ install_headers( 'plugin-registry.h', 'timeline-object.h', 'windowed-output-api.h', + 'zalloc.h', subdir: dir_include_libweston_install ) diff --git a/shared/zalloc.h b/include/libweston/zalloc.h similarity index 100% rename from shared/zalloc.h rename to include/libweston/zalloc.h diff --git a/libweston-desktop/client.c b/libweston-desktop/client.c index a3e9a31ef..8188d3336 100644 --- a/libweston-desktop/client.c +++ b/libweston-desktop/client.c @@ -26,7 +26,7 @@ #include #include -#include "zalloc.h" +#include #include "libweston-desktop.h" #include "internal.h" diff --git a/libweston-desktop/libweston-desktop.c b/libweston-desktop/libweston-desktop.c index a22f67d4e..decce52ab 100644 --- a/libweston-desktop/libweston-desktop.c +++ b/libweston-desktop/libweston-desktop.c @@ -29,7 +29,7 @@ #include #include -#include "zalloc.h" +#include #include "helpers.h" #include "libweston-desktop.h" diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c index 1c2380dc9..0c5b50dac 100644 --- a/libweston-desktop/seat.c +++ b/libweston-desktop/seat.c @@ -31,7 +31,7 @@ #include #include -#include "zalloc.h" +#include #include "libweston-desktop.h" #include "internal.h" diff --git a/libweston-desktop/surface.c b/libweston-desktop/surface.c index eec9806da..09b4176ba 100644 --- a/libweston-desktop/surface.c +++ b/libweston-desktop/surface.c @@ -29,7 +29,7 @@ #include #include -#include "zalloc.h" +#include #include "libweston-desktop.h" #include "internal.h" diff --git a/libweston-desktop/wl-shell.c b/libweston-desktop/wl-shell.c index 0067b63af..6361097ef 100644 --- a/libweston-desktop/wl-shell.c +++ b/libweston-desktop/wl-shell.c @@ -31,7 +31,7 @@ #include #include -#include "zalloc.h" +#include #include "libweston-desktop.h" #include "internal.h" diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index 80c3f0879..383867a0e 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -32,7 +32,7 @@ #include #include -#include "zalloc.h" +#include #include "xdg-shell-unstable-v6-server-protocol.h" #include "libweston-desktop.h" diff --git a/libweston-desktop/xdg-shell.c b/libweston-desktop/xdg-shell.c index d5bed385f..8e0a6ac4d 100644 --- a/libweston-desktop/xdg-shell.c +++ b/libweston-desktop/xdg-shell.c @@ -32,7 +32,7 @@ #include #include -#include "zalloc.h" +#include #include "xdg-shell-server-protocol.h" #include "libweston-desktop.h" diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index ea9ff34e7..ee319b31b 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -31,7 +31,7 @@ #include #include -#include "zalloc.h" +#include #include "libweston-desktop.h" #include "internal.h" diff --git a/libweston/meson.build b/libweston/meson.build index b165ddb66..20b7aa890 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -56,11 +56,6 @@ srcs_libweston = [ weston_debug_server_protocol_h, ] -install_headers( - '../shared/zalloc.h', - subdir: dir_include_libweston -) - if get_option('renderer-gl') dep_egl = dependency('egl', required: false) if not dep_egl.found() diff --git a/libweston/touch-calibration.c b/libweston/touch-calibration.c index d6f21604b..b346d90c7 100644 --- a/libweston/touch-calibration.c +++ b/libweston/touch-calibration.c @@ -31,7 +31,7 @@ #include "shared/helpers.h" #include "shared/string-helpers.h" -#include "shared/zalloc.h" +#include #include "shared/timespec-util.h" #include diff --git a/shared/xalloc.h b/shared/xalloc.h index 484de2d8f..cd39dd8b3 100644 --- a/shared/xalloc.h +++ b/shared/xalloc.h @@ -34,7 +34,7 @@ extern "C" { #include #include -#include "zalloc.h" +#include void * fail_on_null(void *p, size_t size, char *file, int32_t line); diff --git a/tests/input-timestamps-helper.c b/tests/input-timestamps-helper.c index 3dd8805f3..2ed1dd440 100644 --- a/tests/input-timestamps-helper.c +++ b/tests/input-timestamps-helper.c @@ -33,7 +33,7 @@ #include "input-timestamps-helper.h" #include "input-timestamps-unstable-v1-client-protocol.h" #include "shared/timespec-util.h" -#include "shared/zalloc.h" +#include #include "weston-test-client-helper.h" struct input_timestamps { diff --git a/tests/meson.build b/tests/meson.build index 5274d8dd3..b494d38d8 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -221,7 +221,7 @@ if get_option('shell-ivi') weston_test_protocol_c, ivi_application_client_protocol_h, ivi_application_protocol_c, - include_directories: include_directories('..', '../shared'), + include_directories: [ include_directories('..', '../shared'), public_inc ], dependencies: dep_test_client, install: false ) @@ -261,7 +261,7 @@ foreach t : tests_standalone srcs_t, c_args: [ '-DUNIT_TEST' ], build_by_default: true, - include_directories: include_directories('..', '../shared', '../libweston'), + include_directories: [ include_directories('..', '../shared', '../libweston'), public_inc ], dependencies: deps_t, install: false, ) @@ -325,8 +325,10 @@ foreach t : tests_weston 'test-@0@'.format(t.get(0)), srcs_t, c_args: [ '-DUNIT_TEST' ], - include_directories: + include_directories: [ include_directories('..', '../shared'), + public_inc, + ], dependencies: deps_t, install: false, ) diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 27e6b86af..f7d7df519 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -36,7 +36,7 @@ #include "shared/os-compatibility.h" #include "shared/xalloc.h" -#include "shared/zalloc.h" +#include #include "weston-test-client-helper.h" #define max(a, b) (((a) > (b)) ? (a) : (b)) diff --git a/tools/zunitc/src/zuc_base_logger.c b/tools/zunitc/src/zuc_base_logger.c index ffa127f03..4b5434419 100644 --- a/tools/zunitc/src/zuc_base_logger.c +++ b/tools/zunitc/src/zuc_base_logger.c @@ -37,7 +37,7 @@ #include "zuc_event_listener.h" #include "zuc_types.h" -#include "shared/zalloc.h" +#include /* a few sequences for rudimentary ANSI graphics. */ #define CSI_GRN "\x1b[0;32m" diff --git a/tools/zunitc/src/zuc_collector.c b/tools/zunitc/src/zuc_collector.c index e17231cd8..035c9ce72 100644 --- a/tools/zunitc/src/zuc_collector.c +++ b/tools/zunitc/src/zuc_collector.c @@ -32,7 +32,7 @@ #include #include -#include "shared/zalloc.h" +#include #include "zuc_event_listener.h" #include "zunitc/zunitc_impl.h" diff --git a/tools/zunitc/src/zuc_junit_reporter.c b/tools/zunitc/src/zuc_junit_reporter.c index df906401e..c0457f210 100644 --- a/tools/zunitc/src/zuc_junit_reporter.c +++ b/tools/zunitc/src/zuc_junit_reporter.c @@ -42,7 +42,7 @@ #include "zuc_event_listener.h" #include "zuc_types.h" -#include "shared/zalloc.h" +#include /** * Hardcoded output name. @@ -460,7 +460,6 @@ zuc_junit_reporter_create(void) #else /* ENABLE_JUNIT_XML */ -#include "shared/zalloc.h" #include "zuc_event_listener.h" /* diff --git a/tools/zunitc/src/zunitc_impl.c b/tools/zunitc/src/zunitc_impl.c index 72f08df67..05db02297 100644 --- a/tools/zunitc/src/zunitc_impl.c +++ b/tools/zunitc/src/zunitc_impl.c @@ -48,7 +48,7 @@ #include #include "shared/helpers.h" -#include "shared/zalloc.h" +#include /* * If CLOCK_MONOTONIC is present on the system it will give us reliable From eebb7dc9ce8b42624802002b5a4ee94b5e3b4b7c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 4 Apr 2019 15:52:47 +0300 Subject: [PATCH 0845/1642] Rename xwayland-api.h to libweston/xwayland-api.h See "Rename compositor.h to libweston/libweston.h" for rationale. Signed-off-by: Pekka Paalanen --- compositor/xwayland.c | 2 +- desktop-shell/shell.h | 2 +- include/libweston/meson.build | 2 ++ {xwayland => include/libweston}/xwayland-api.h | 0 xwayland/launcher.c | 2 +- xwayland/meson.build | 2 +- xwayland/xwayland.h | 2 +- 7 files changed, 7 insertions(+), 5 deletions(-) rename {xwayland => include/libweston}/xwayland-api.h (100%) diff --git a/compositor/xwayland.c b/compositor/xwayland.c index 36d5cf31e..50b8dcc1b 100644 --- a/compositor/xwayland.c +++ b/compositor/xwayland.c @@ -31,7 +31,7 @@ #include #include "compositor/weston.h" -#include "xwayland/xwayland-api.h" +#include #include "shared/helpers.h" struct wet_xwayland { diff --git a/desktop-shell/shell.h b/desktop-shell/shell.h index bbf562654..c82fd28c7 100644 --- a/desktop-shell/shell.h +++ b/desktop-shell/shell.h @@ -28,7 +28,7 @@ #include #include -#include "xwayland/xwayland-api.h" +#include #include "weston-desktop-shell-server-protocol.h" diff --git a/include/libweston/meson.build b/include/libweston/meson.build index a3ac20c26..340cd255c 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -15,3 +15,5 @@ backend_headless_h = files('backend-headless.h') backend_rdp_h = files('backend-rdp.h') backend_wayland_h = files('backend-wayland.h') backend_x11_h = files('backend-x11.h') + +xwayland_api_h = files('xwayland-api.h') diff --git a/xwayland/xwayland-api.h b/include/libweston/xwayland-api.h similarity index 100% rename from xwayland/xwayland-api.h rename to include/libweston/xwayland-api.h diff --git a/xwayland/launcher.c b/xwayland/launcher.c index c5b993851..44083111c 100644 --- a/xwayland/launcher.c +++ b/xwayland/launcher.c @@ -37,7 +37,7 @@ #include #include "xwayland.h" -#include "xwayland-api.h" +#include #include "shared/helpers.h" #include "shared/string-helpers.h" #include "compositor/weston.h" diff --git a/xwayland/meson.build b/xwayland/meson.build index 6eb7e20c2..de3eeb2ef 100644 --- a/xwayland/meson.build +++ b/xwayland/meson.build @@ -41,4 +41,4 @@ plugin_xwayland = shared_library( ) env_modmap += 'xwayland.so=@0@;'.format(plugin_xwayland.full_path()) -install_headers('xwayland-api.h', subdir: dir_include_libweston) +install_headers(xwayland_api_h, subdir: dir_include_libweston_install) diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index 150410113..d474a511b 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -32,7 +32,7 @@ #include #include "compositor/weston.h" -#include "xwayland-api.h" +#include #include "weston-debug.h" #define SEND_EVENT_MASK (0x80) From cda1488ce0851de050a6df72abaa4b799038d836 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 4 Apr 2019 15:41:02 +0300 Subject: [PATCH 0846/1642] Rename version.h to libweston/version.h This is an installed public header, and without the subdir would surely conflict with something else. include/libweston/meson.build is necessary for putting the generated header in the right subdirectory so that '#include ' can work. Signed-off-by: Pekka Paalanen --- compositor/main.c | 2 +- include/libweston/meson.build | 12 ++++++++++++ {libweston => include/libweston}/version.h.in | 0 libweston/compositor.c | 2 +- meson.build | 11 ----------- 5 files changed, 14 insertions(+), 13 deletions(-) rename {libweston => include/libweston}/version.h.in (100%) diff --git a/compositor/main.c b/compositor/main.c index ea35b2764..4855cdbb5 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -52,7 +52,7 @@ #include "../shared/helpers.h" #include "../shared/string-helpers.h" #include "git-version.h" -#include "version.h" +#include #include "weston.h" #include diff --git a/include/libweston/meson.build b/include/libweston/meson.build index 340cd255c..8e8f81604 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -17,3 +17,15 @@ backend_wayland_h = files('backend-wayland.h') backend_x11_h = files('backend-x11.h') xwayland_api_h = files('xwayland-api.h') + +libweston_version_h = configuration_data() +libweston_version_h.set('WESTON_VERSION_MAJOR', version_weston_arr[0]) +libweston_version_h.set('WESTON_VERSION_MINOR', version_weston_arr[1]) +libweston_version_h.set('WESTON_VERSION_MICRO', version_weston_arr[2]) +libweston_version_h.set('WESTON_VERSION', version_weston) +version_h = configure_file( + input: 'version.h.in', + output: 'version.h', + configuration: libweston_version_h +) +install_headers(version_h, subdir: dir_include_libweston_install) diff --git a/libweston/version.h.in b/include/libweston/version.h.in similarity index 100% rename from libweston/version.h.in rename to include/libweston/version.h.in diff --git a/libweston/compositor.c b/libweston/compositor.c index faaf5b2ac..6a7c4cedc 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -68,7 +68,7 @@ #include "shared/string-helpers.h" #include "shared/timespec-util.h" #include "git-version.h" -#include "version.h" +#include #include #include "pixel-formats.h" diff --git a/meson.build b/meson.build index 7e4fc2366..551900282 100644 --- a/meson.build +++ b/meson.build @@ -45,17 +45,6 @@ public_inc = include_directories('include') pkgconfig = import('pkgconfig') -libweston_version_h = configuration_data() -libweston_version_h.set('WESTON_VERSION_MAJOR', version_weston_arr[0]) -libweston_version_h.set('WESTON_VERSION_MINOR', version_weston_arr[1]) -libweston_version_h.set('WESTON_VERSION_MICRO', version_weston_arr[2]) -libweston_version_h.set('WESTON_VERSION', version_weston) -version_h = configure_file( - input: 'libweston/version.h.in', - output: 'version.h', - configuration: libweston_version_h -) -install_headers(version_h, subdir: dir_include_libweston) git_version_h = vcs_tag( input: 'libweston/git-version.h.meson', output: 'git-version.h', From e04159b238d0f9b787ad3a25643a40ae9cf2a734 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 4 Apr 2019 15:54:07 +0300 Subject: [PATCH 0847/1642] xwayland: do not include weston.h weston.h is a Weston frontend header, while this is a libweston plugin. A libweston plugin cannot depend on Weston. Luckily the header is not actually needed. Signed-off-by: Pekka Paalanen --- xwayland/launcher.c | 1 - 1 file changed, 1 deletion(-) diff --git a/xwayland/launcher.c b/xwayland/launcher.c index 44083111c..2c7da1138 100644 --- a/xwayland/launcher.c +++ b/xwayland/launcher.c @@ -40,7 +40,6 @@ #include #include "shared/helpers.h" #include "shared/string-helpers.h" -#include "compositor/weston.h" static int weston_xserver_handle_event(int listen_fd, uint32_t mask, void *data) From 8ebd9817e722d9c04d3c4335e6f5571152e7b121 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 4 Apr 2019 16:02:14 +0300 Subject: [PATCH 0848/1642] Move libweston-desktop.h This too is a public installed header. The public headers are moved under a new top-level directory include/ to make them clearly stand out as special (public API). Signed-off-by: Pekka Paalanen --- desktop-shell/shell.c | 2 +- .../libweston-desktop}/libweston-desktop.h | 0 include/meson.build | 5 +++++ ivi-shell/ivi-layout-private.h | 2 +- ivi-shell/ivi-shell.h | 2 +- libweston-desktop/client.c | 2 +- libweston-desktop/libweston-desktop.c | 2 +- libweston-desktop/meson.build | 2 -- libweston-desktop/seat.c | 2 +- libweston-desktop/surface.c | 2 +- libweston-desktop/wl-shell.c | 2 +- libweston-desktop/xdg-shell-v6.c | 2 +- libweston-desktop/xdg-shell.c | 2 +- libweston-desktop/xwayland.c | 2 +- tests/weston-test-desktop-shell.c | 2 +- 15 files changed, 17 insertions(+), 14 deletions(-) rename {libweston-desktop => include/libweston-desktop}/libweston-desktop.h (100%) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 270c95435..33fda6d0f 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -42,7 +42,7 @@ #include #include "shared/helpers.h" #include "shared/timespec-util.h" -#include "libweston-desktop/libweston-desktop.h" +#include #define DEFAULT_NUM_WORKSPACES 1 #define DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH 200 diff --git a/libweston-desktop/libweston-desktop.h b/include/libweston-desktop/libweston-desktop.h similarity index 100% rename from libweston-desktop/libweston-desktop.h rename to include/libweston-desktop/libweston-desktop.h diff --git a/include/meson.build b/include/meson.build index ef8298fb4..1ea6dd3d2 100644 --- a/include/meson.build +++ b/include/meson.build @@ -1 +1,6 @@ subdir('libweston') + +install_headers( + 'libweston-desktop/libweston-desktop.h', + subdir: join_paths(dir_include_libweston, 'libweston-desktop') +) diff --git a/ivi-shell/ivi-layout-private.h b/ivi-shell/ivi-layout-private.h index 4b6cd6a3a..1898a177f 100644 --- a/ivi-shell/ivi-layout-private.h +++ b/ivi-shell/ivi-layout-private.h @@ -30,7 +30,7 @@ #include #include "ivi-layout-export.h" -#include "libweston-desktop/libweston-desktop.h" +#include struct ivi_layout_view { struct wl_list link; /* ivi_layout::view_list */ diff --git a/ivi-shell/ivi-shell.h b/ivi-shell/ivi-shell.h index 8762113b1..d7f1cdbde 100644 --- a/ivi-shell/ivi-shell.h +++ b/ivi-shell/ivi-shell.h @@ -30,7 +30,7 @@ #include #include -#include "libweston-desktop/libweston-desktop.h" +#include struct ivi_shell { diff --git a/libweston-desktop/client.c b/libweston-desktop/client.c index 8188d3336..56413f713 100644 --- a/libweston-desktop/client.c +++ b/libweston-desktop/client.c @@ -28,7 +28,7 @@ #include #include -#include "libweston-desktop.h" +#include #include "internal.h" struct weston_desktop_client { diff --git a/libweston-desktop/libweston-desktop.c b/libweston-desktop/libweston-desktop.c index decce52ab..5d0a4a792 100644 --- a/libweston-desktop/libweston-desktop.c +++ b/libweston-desktop/libweston-desktop.c @@ -32,7 +32,7 @@ #include #include "helpers.h" -#include "libweston-desktop.h" +#include #include "internal.h" diff --git a/libweston-desktop/meson.build b/libweston-desktop/meson.build index 837d4edd4..455f17b3c 100644 --- a/libweston-desktop/meson.build +++ b/libweston-desktop/meson.build @@ -25,8 +25,6 @@ dep_lib_desktop = declare_dependency( dependencies: dep_libweston ) -install_headers('libweston-desktop.h', subdir: dir_include_libweston) - pkgconfig.generate( lib_desktop, filebase: 'libweston-desktop-@0@'.format(libweston_major), diff --git a/libweston-desktop/seat.c b/libweston-desktop/seat.c index 0c5b50dac..b92546dff 100644 --- a/libweston-desktop/seat.c +++ b/libweston-desktop/seat.c @@ -33,7 +33,7 @@ #include #include -#include "libweston-desktop.h" +#include #include "internal.h" #include "shared/timespec-util.h" diff --git a/libweston-desktop/surface.c b/libweston-desktop/surface.c index 09b4176ba..433f08aef 100644 --- a/libweston-desktop/surface.c +++ b/libweston-desktop/surface.c @@ -31,7 +31,7 @@ #include #include -#include "libweston-desktop.h" +#include #include "internal.h" struct weston_desktop_view { diff --git a/libweston-desktop/wl-shell.c b/libweston-desktop/wl-shell.c index 6361097ef..9efec89b4 100644 --- a/libweston-desktop/wl-shell.c +++ b/libweston-desktop/wl-shell.c @@ -33,7 +33,7 @@ #include #include -#include "libweston-desktop.h" +#include #include "internal.h" #define WD_WL_SHELL_PROTOCOL_VERSION 1 diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston-desktop/xdg-shell-v6.c index 383867a0e..955fccad3 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston-desktop/xdg-shell-v6.c @@ -35,7 +35,7 @@ #include #include "xdg-shell-unstable-v6-server-protocol.h" -#include "libweston-desktop.h" +#include #include "internal.h" #define WD_XDG_SHELL_PROTOCOL_VERSION 1 diff --git a/libweston-desktop/xdg-shell.c b/libweston-desktop/xdg-shell.c index 8e0a6ac4d..d1fc2ec12 100644 --- a/libweston-desktop/xdg-shell.c +++ b/libweston-desktop/xdg-shell.c @@ -35,7 +35,7 @@ #include #include "xdg-shell-server-protocol.h" -#include "libweston-desktop.h" +#include #include "internal.h" /************************************************************************************ diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index ee319b31b..711c8a30c 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -33,7 +33,7 @@ #include #include -#include "libweston-desktop.h" +#include #include "internal.h" #include "xwayland/xwayland-internal-interface.h" diff --git a/tests/weston-test-desktop-shell.c b/tests/weston-test-desktop-shell.c index b580ffd6f..c88455c40 100644 --- a/tests/weston-test-desktop-shell.c +++ b/tests/weston-test-desktop-shell.c @@ -39,7 +39,7 @@ #include "compositor/weston.h" #include #include "shared/helpers.h" -#include "libweston-desktop/libweston-desktop.h" +#include struct desktest_shell { struct wl_listener compositor_destroy_listener; From 4e952328ca117a912efb68088c8516fa1fb97b0b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 4 Apr 2019 16:53:11 +0300 Subject: [PATCH 0849/1642] build: turn vertex-clipping.c into a dependency Making this into a dependency object not only carries the .c files with it, but it also brings the include directories as well, which means the users can simply use the object without guessing the paths. This should help with moving GL-renderer into a new subdirectory. Signed-off-by: Pekka Paalanen --- clients/meson.build | 4 ++-- libweston/meson.build | 6 +++++- tests/meson.build | 7 +------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/clients/meson.build b/clients/meson.build index 3f180f77b..3d36efe48 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -226,7 +226,7 @@ demo_clients = [ { 'basename': 'clickdot' }, { 'basename': 'cliptest', - 'add_sources': [ '../libweston/vertex-clipping.c' ] + 'dep_objs': dep_vertex_clipping }, { 'basename': 'confine' }, { 'basename': 'dnd' }, @@ -277,7 +277,7 @@ if get_option('demo-clients') foreach t : demo_clients t_name = 'weston-' + t.get('basename') t_srcs = [ t.get('basename') + '.c' ] + t.get('add_sources', []) - t_deps = [ dep_toytoolkit ] + t_deps = [ dep_toytoolkit, t.get('dep_objs', []) ] foreach depname : t.get('deps', []) dep = dependency(depname, required: false) if not dep.found() diff --git a/libweston/meson.build b/libweston/meson.build index 20b7aa890..76686f22d 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -433,6 +433,10 @@ if get_option('backend-fbdev') install_headers(backend_fbdev_h, subdir: dir_include_libweston_install) endif +dep_vertex_clipping = declare_dependency( + sources: 'vertex-clipping.c', + include_directories: include_directories('.') +) if get_option('renderer-gl') config_h.set('ENABLE_EGL', '1') @@ -440,7 +444,6 @@ if get_option('renderer-gl') srcs_renderer_gl = [ 'gl-renderer.c', 'linux-sync-file.c', - 'vertex-clipping.c', '../shared/matrix.c', linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_server_protocol_h, @@ -450,6 +453,7 @@ if get_option('renderer-gl') dep_pixman, dep_libweston, dep_libdrm_headers, + dep_vertex_clipping ] foreach name : [ 'egl', 'glesv2' ] diff --git a/tests/meson.build b/tests/meson.build index b494d38d8..704ab54df 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -99,12 +99,7 @@ tests_standalone = [ ['config-parser', [], [ dep_zucmain ]], ['matrix', [ '../shared/matrix.c' ], [ dep_libm, dep_libshared.partial_dependency(includes: true) ]], ['string'], - [ - 'vertex-clip', - [ - '../libweston/vertex-clipping.c' - ] - ], + [ 'vertex-clip', [], [ dep_test_client, dep_vertex_clipping ]], ['timespec', [], [ dep_zucmain ]], ['zuc', [ From 4b5727375c79e9a561b6a518e688954d8cbe87ce Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 4 Apr 2019 17:11:53 +0300 Subject: [PATCH 0850/1642] libweston: export weston_linux_sync_file_read_timestamp() This is an internal export for GL-renderer, so that it does not need to build linux-sync-file.c a second time. This follows the example of linux-explicit-synchronization.c which is also used by GL-renderer. Signed-off-by: Pekka Paalanen --- libweston/gl-renderer.c | 3 ++- libweston/linux-sync-file.c | 5 +++-- libweston/linux-sync-file.h | 2 +- libweston/meson.build | 1 - 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libweston/gl-renderer.c b/libweston/gl-renderer.c index a03e45eaa..d160a5d88 100644 --- a/libweston/gl-renderer.c +++ b/libweston/gl-renderer.c @@ -339,7 +339,8 @@ timeline_render_point_handler(int fd, uint32_t mask, void *data) if (mask & WL_EVENT_READABLE) { struct timespec tspec = { 0 }; - if (linux_sync_file_read_timestamp(trp->fd, &tspec) == 0) { + if (weston_linux_sync_file_read_timestamp(trp->fd, + &tspec) == 0) { TL_POINT(tp_name, TLP_GPU(&tspec), TLP_OUTPUT(trp->output), TLP_END); } diff --git a/libweston/linux-sync-file.c b/libweston/linux-sync-file.c index 913fb8da8..9f5313ccf 100644 --- a/libweston/linux-sync-file.c +++ b/libweston/linux-sync-file.c @@ -30,6 +30,7 @@ #include #include #include +#include #ifdef HAVE_LINUX_SYNC_FILE_H #include @@ -62,8 +63,8 @@ linux_sync_file_is_valid(int fd) * \param ts[out] the timespec struct to fill with the timestamp * \return 0 if a timestamp was read, -1 on error */ -int -linux_sync_file_read_timestamp(int fd, struct timespec *ts) +WL_EXPORT int +weston_linux_sync_file_read_timestamp(int fd, struct timespec *ts) { struct sync_file_info file_info = { { 0 } }; struct sync_fence_info fence_info = { { 0 } }; diff --git a/libweston/linux-sync-file.h b/libweston/linux-sync-file.h index b831fa1ea..9746d7bac 100644 --- a/libweston/linux-sync-file.h +++ b/libweston/linux-sync-file.h @@ -33,6 +33,6 @@ bool linux_sync_file_is_valid(int fd); int -linux_sync_file_read_timestamp(int fd, struct timespec *ts); +weston_linux_sync_file_read_timestamp(int fd, struct timespec *ts); #endif /* WESTON_LINUX_SYNC_FILE_H */ diff --git a/libweston/meson.build b/libweston/meson.build index 76686f22d..4bc96f1e7 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -443,7 +443,6 @@ if get_option('renderer-gl') srcs_renderer_gl = [ 'gl-renderer.c', - 'linux-sync-file.c', '../shared/matrix.c', linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_server_protocol_h, From 3a2c67aa51cc2a2e0b83a07d58f53fa20ee6b42d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 4 Apr 2019 17:19:54 +0300 Subject: [PATCH 0851/1642] gl-renderer: does not need matrix.c The symbols of matrix.c are already exported by libweston, no need to build them again. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/libweston/meson.build b/libweston/meson.build index 4bc96f1e7..c9c9166f5 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -443,7 +443,6 @@ if get_option('renderer-gl') srcs_renderer_gl = [ 'gl-renderer.c', - '../shared/matrix.c', linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_server_protocol_h, ] From 81475a5c15fece1cece088238edab4fe2a4f1408 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 4 Apr 2019 17:29:27 +0300 Subject: [PATCH 0852/1642] libweston: move gl-renderer into a subdir GL-renderer is expected to grow more files, both by addition and by splitting. Moving them into a new subdirectory helps people to understand which files are relevant. Signed-off-by: Pekka Paalanen --- libweston/compositor-drm.c | 2 +- libweston/compositor-wayland.c | 2 +- libweston/compositor-x11.c | 2 +- libweston/meson.build | 38 ++--------------------- libweston/{ => renderer-gl}/gl-renderer.c | 0 libweston/{ => renderer-gl}/gl-renderer.h | 0 libweston/renderer-gl/meson.build | 37 ++++++++++++++++++++++ 7 files changed, 42 insertions(+), 39 deletions(-) rename libweston/{ => renderer-gl}/gl-renderer.c (100%) rename libweston/{ => renderer-gl}/gl-renderer.h (100%) create mode 100644 libweston/renderer-gl/meson.build diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index c563a57c7..5c1ccdd7c 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -55,7 +55,7 @@ #include "weston-debug.h" #include "shared/helpers.h" #include "shared/timespec-util.h" -#include "gl-renderer.h" +#include "renderer-gl/gl-renderer.h" #include "weston-egl-ext.h" #include "pixman-renderer.h" #include "pixel-formats.h" diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 7e2fee176..337ea1058 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -46,7 +46,7 @@ #include #include -#include "gl-renderer.h" +#include "renderer-gl/gl-renderer.h" #include "weston-egl-ext.h" #include "pixman-renderer.h" #include "shared/helpers.h" diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c index 3fdeb1142..854d053f7 100644 --- a/libweston/compositor-x11.c +++ b/libweston/compositor-x11.c @@ -56,7 +56,7 @@ #include "shared/image-loader.h" #include "shared/timespec-util.h" #include "shared/file-util.h" -#include "gl-renderer.h" +#include "renderer-gl/gl-renderer.h" #include "weston-egl-ext.h" #include "pixman-renderer.h" #include "presentation-time-server-protocol.h" diff --git a/libweston/meson.build b/libweston/meson.build index c9c9166f5..3c9640887 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -438,42 +438,6 @@ dep_vertex_clipping = declare_dependency( include_directories: include_directories('.') ) -if get_option('renderer-gl') - config_h.set('ENABLE_EGL', '1') - - srcs_renderer_gl = [ - 'gl-renderer.c', - linux_dmabuf_unstable_v1_protocol_c, - linux_dmabuf_unstable_v1_server_protocol_h, - ] - - deps_renderer_gl = [ - dep_pixman, - dep_libweston, - dep_libdrm_headers, - dep_vertex_clipping - ] - - foreach name : [ 'egl', 'glesv2' ] - d = dependency(name, required: false) - if not d.found() - error('gl-renderer requires @0@ which was not found. Or, you can use \'-Drenderer-gl=false\'.'.format(name)) - endif - deps_renderer_gl += d - endforeach - - plugin_gl = shared_library( - 'gl-renderer', - srcs_renderer_gl, - include_directories: include_directories('..', '../shared'), - dependencies: deps_renderer_gl, - name_prefix: '', - install: true, - install_dir: dir_module_libweston - ) - env_modmap += 'gl-renderer.so=@0@;'.format(plugin_gl.full_path()) -endif - if get_option('weston-launch') dep_pam = cc.find_library('pam') @@ -491,3 +455,5 @@ if get_option('weston-launch') meson.add_install_script('echo', 'REMINDER: You are installing weston-launch, please make it setuid-root.') endif + +subdir('renderer-gl') diff --git a/libweston/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c similarity index 100% rename from libweston/gl-renderer.c rename to libweston/renderer-gl/gl-renderer.c diff --git a/libweston/gl-renderer.h b/libweston/renderer-gl/gl-renderer.h similarity index 100% rename from libweston/gl-renderer.h rename to libweston/renderer-gl/gl-renderer.h diff --git a/libweston/renderer-gl/meson.build b/libweston/renderer-gl/meson.build new file mode 100644 index 000000000..b5d77d587 --- /dev/null +++ b/libweston/renderer-gl/meson.build @@ -0,0 +1,37 @@ +if not get_option('renderer-gl') + subdir_done() +endif + +config_h.set('ENABLE_EGL', '1') + +srcs_renderer_gl = [ + 'gl-renderer.c', + linux_dmabuf_unstable_v1_protocol_c, + linux_dmabuf_unstable_v1_server_protocol_h, +] + +deps_renderer_gl = [ + dep_pixman, + dep_libweston, + dep_libdrm_headers, + dep_vertex_clipping +] + +foreach name : [ 'egl', 'glesv2' ] + d = dependency(name, required: false) + if not d.found() + error('gl-renderer requires @0@ which was not found. Or, you can use \'-Drenderer-gl=false\'.'.format(name)) + endif + deps_renderer_gl += d +endforeach + +plugin_gl = shared_library( + 'gl-renderer', + srcs_renderer_gl, + include_directories: include_directories('../..', '../../shared'), + dependencies: deps_renderer_gl, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'gl-renderer.so=@0@;'.format(plugin_gl.full_path()) From ee4c7a24dd8e2f52edc3ee4a9245f316e8261367 Mon Sep 17 00:00:00 2001 From: Harish Krupo Date: Fri, 19 Apr 2019 01:53:27 +0530 Subject: [PATCH 0853/1642] window.c: Don't assume registry advertisement order The toytoolkit assumes that wl_seats are advertised after wl_data_device_manager and creates a data_device during wl_seat registry binding. This patch removes this assumption by creating data_devices for all the wl_seats created up until then. Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/201 Signed-off-by: Harish Krupo --- clients/window.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/clients/window.c b/clients/window.c index dfde55eea..92b545dd9 100644 --- a/clients/window.c +++ b/clients/window.c @@ -5828,6 +5828,29 @@ display_add_input(struct display *d, uint32_t id, int display_seat_version) keyboard_repeat_func); } +static void +display_add_data_device(struct display *d, uint32_t id, int ddm_version) +{ + struct input *input; + + d->data_device_manager_version = MIN(ddm_version, 3); + d->data_device_manager = + wl_registry_bind(d->registry, id, + &wl_data_device_manager_interface, + d->data_device_manager_version); + + wl_list_for_each(input, &d->input_list, link) { + if (!input->data_device) { + input->data_device = + wl_data_device_manager_get_data_device(d->data_device_manager, + input->seat); + wl_data_device_add_listener(input->data_device, + &data_device_listener, + input); + } + } +} + static void input_destroy(struct input *input) { @@ -5932,11 +5955,7 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); wl_shm_add_listener(d->shm, &shm_listener, d); } else if (strcmp(interface, "wl_data_device_manager") == 0) { - d->data_device_manager_version = MIN(version, 3); - d->data_device_manager = - wl_registry_bind(registry, id, - &wl_data_device_manager_interface, - d->data_device_manager_version); + display_add_data_device(d, id, version); } else if (strcmp(interface, "xdg_wm_base") == 0) { d->xdg_shell = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); From 737ac0d4b3f7b40c345b61ff62dea0550217f063 Mon Sep 17 00:00:00 2001 From: Harish Krupo Date: Fri, 19 Apr 2019 22:06:37 +0530 Subject: [PATCH 0854/1642] data-device: send INVALID_FINISH when operation != dnd The documentation of wl_data_offer::finish states that it should be used to signify that a drag and drop operation is completed. So send WL_DATA_OFFER_ERROR_INVALID_FINISH when the client calls the finish request but the operation isn't dnd. Signed-off-by: Harish Krupo --- include/libweston/libweston.h | 1 + libweston/data-device.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 965831878..f30b66386 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -427,6 +427,7 @@ struct weston_data_source { struct weston_seat *seat; bool accepted; bool actions_set; + bool set_selection; uint32_t dnd_actions; enum wl_data_device_manager_dnd_action current_dnd_action; enum wl_data_device_manager_dnd_action compositor_action; diff --git a/libweston/data-device.c b/libweston/data-device.c index b0fb77605..e19409fc4 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -220,6 +220,13 @@ data_offer_finish(struct wl_client *client, struct wl_resource *resource) if (!offer->source || offer->source->offer != offer) return; + if (offer->source->set_selection) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_FINISH, + "finish only valid for drag n drop"); + return; + } + /* Disallow finish while we have a grab driving drag-and-drop, or * if the negotiation is not at the right stage */ @@ -1145,6 +1152,7 @@ weston_seat_set_selection(struct weston_seat *seat, seat->selection_data_source = source; seat->selection_serial = serial; + source->set_selection = true; if (keyboard) focus = keyboard->focus; @@ -1267,6 +1275,7 @@ create_data_source(struct wl_client *client, source->dnd_actions = 0; source->current_dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; source->compositor_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + source->set_selection = false; wl_array_init(&source->mime_types); From 43152a3a8fa1e3164db33ac7ace105186c5e8ac8 Mon Sep 17 00:00:00 2001 From: Harish Krupo Date: Fri, 19 Apr 2019 22:06:44 +0530 Subject: [PATCH 0855/1642] Fix: clients/window: Premature finish request when copy-pasting As per the wl_data_offer::finish documentation, the request is only valid for drag n drop operations and signifies that a dnd is completed. Send finish request only when we have a dnd operation active. Signed-off-by: Harish Krupo --- clients/window.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clients/window.c b/clients/window.c index 92b545dd9..d4dc96e15 100644 --- a/clients/window.c +++ b/clients/window.c @@ -3922,8 +3922,9 @@ offer_io_func(struct task *task, uint32_t events) offer->x, offer->y, offer->user_data); if (len == 0) { - if (display->data_device_manager_version >= - WL_DATA_OFFER_FINISH_SINCE_VERSION) + if ((offer != offer->input->selection_offer) && + (display->data_device_manager_version >= + WL_DATA_OFFER_FINISH_SINCE_VERSION)) wl_data_offer_finish(offer->offer); close(offer->fd); data_offer_destroy(offer); From 1bdf36329557e6c846d5e51856eb8da00504212a Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Tue, 9 Apr 2019 12:18:25 +0200 Subject: [PATCH 0856/1642] weston-terminal: Fix weston-terminal crash on mutter Set up handlers for wl_data_source v3 events Signed-off-by: Sebastian Wick --- clients/terminal.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/clients/terminal.c b/clients/terminal.c index f7546ede8..f7e9ba955 100644 --- a/clients/terminal.c +++ b/clients/terminal.c @@ -2207,10 +2207,29 @@ data_source_cancelled(void *data, struct wl_data_source *source) wl_data_source_destroy(source); } +static void +data_source_dnd_drop_performed(void *data, struct wl_data_source *source) +{ +} + +static void +data_source_dnd_finished(void *data, struct wl_data_source *source) +{ +} + +static void +data_source_action(void *data, + struct wl_data_source *source, uint32_t dnd_action) +{ +} + static const struct wl_data_source_listener data_source_listener = { data_source_target, data_source_send, - data_source_cancelled + data_source_cancelled, + data_source_dnd_drop_performed, + data_source_dnd_finished, + data_source_action }; static const char text_mime_type[] = "text/plain;charset=utf-8"; From a1450a8a71ef1adb921421f48bbea552a8e738e3 Mon Sep 17 00:00:00 2001 From: Randy Li Date: Thu, 18 Apr 2019 11:37:12 +0800 Subject: [PATCH 0857/1642] make error() portable error() is not posix but gnu extension so may not be available on all kind of systemsi e.g. musl. Signed-off-by: Randy 'ayaka' Li Signed-off-by: Randy Li --- libweston/weston-launch.c | 157 +++++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 46 deletions(-) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index bf73e0d61..9e5c94297 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -33,7 +33,6 @@ #include #include -#include #include #include @@ -235,11 +234,17 @@ setup_pam(struct weston_launch *wl) static int setup_launcher_socket(struct weston_launch *wl) { - if (socketpair(AF_LOCAL, SOCK_SEQPACKET, 0, wl->sock) < 0) - error(1, errno, "socketpair failed"); + if (socketpair(AF_LOCAL, SOCK_SEQPACKET, 0, wl->sock) < 0) { + fprintf(stderr, "weston: socketpair failed: %s\n", + strerror(errno)); + return -1; + } - if (fcntl(wl->sock[0], F_SETFD, FD_CLOEXEC) < 0) - error(1, errno, "fcntl failed"); + if (fcntl(wl->sock[0], F_SETFD, FD_CLOEXEC) < 0) { + fprintf(stderr, "weston: fcntl failed: %s\n", + strerror(errno)); + return -1; + } return 0; } @@ -474,7 +479,8 @@ handle_signal(struct weston_launch *wl) int pid, status, ret; if (read(wl->signalfd, &sig, sizeof sig) != sizeof sig) { - error(0, errno, "reading signalfd failed"); + fprintf(stderr, "weston: reading signalfd failed: %s\n", + strerror(errno)); return -1; } @@ -540,49 +546,83 @@ setup_tty(struct weston_launch *wl, const char *tty) int tty0 = open("/dev/tty0", O_WRONLY | O_CLOEXEC); char filename[16]; - if (tty0 < 0) - error(1, errno, "could not open tty0"); + if (tty0 < 0) { + fprintf(stderr, "weston: could not open tty0: %s\n", + strerror(errno)); + return -1; + } if (ioctl(tty0, VT_OPENQRY, &wl->ttynr) < 0 || wl->ttynr == -1) - error(1, errno, "failed to find non-opened console"); + { + fprintf(stderr, "weston: failed to find non-opened console: %s\n", + strerror(errno)); + return -1; + } snprintf(filename, sizeof filename, "/dev/tty%d", wl->ttynr); wl->tty = open(filename, O_RDWR | O_NOCTTY); close(tty0); } - if (wl->tty < 0) - error(1, errno, "failed to open tty"); + if (wl->tty < 0) { + fprintf(stderr, "weston: failed to open tty: %s\n", + strerror(errno)); + return -1; + } if (fstat(wl->tty, &buf) == -1 || - major(buf.st_rdev) != TTY_MAJOR || minor(buf.st_rdev) == 0) - error(1, 0, "weston-launch must be run from a virtual terminal"); + major(buf.st_rdev) != TTY_MAJOR || minor(buf.st_rdev) == 0) { + fprintf(stderr, "weston: weston-launch must be run from a virtual terminal\n"); + return -1; + } if (tty) { - if (fstat(wl->tty, &buf) < 0) - error(1, errno, "stat %s failed", tty); + if (fstat(wl->tty, &buf) < 0) { + fprintf(stderr, "weston: stat %s failed: %s\n", tty, + strerror(errno)); + return -1; + } - if (major(buf.st_rdev) != TTY_MAJOR) - error(1, 0, "invalid tty device: %s", tty); + if (major(buf.st_rdev) != TTY_MAJOR) { + fprintf(stderr, + "weston: invalid tty device: %s\n", tty); + return -1; + } wl->ttynr = minor(buf.st_rdev); } - if (ioctl(wl->tty, KDGKBMODE, &wl->kb_mode)) - error(1, errno, "failed to get current keyboard mode: %m\n"); + if (ioctl(wl->tty, KDGKBMODE, &wl->kb_mode)) { + fprintf(stderr, + "weston: failed to get current keyboard mode: %s", + strerror(errno)); + return -1; + } if (ioctl(wl->tty, KDSKBMUTE, 1) && - ioctl(wl->tty, KDSKBMODE, K_OFF)) - error(1, errno, "failed to set K_OFF keyboard mode: %m\n"); + ioctl(wl->tty, KDSKBMODE, K_OFF)) { + fprintf(stderr, + "weston: failed to set K_OFF keyboard mode: %s\n", + strerror(errno)); + return -1; + } - if (ioctl(wl->tty, KDSETMODE, KD_GRAPHICS)) - error(1, errno, "failed to set KD_GRAPHICS mode on tty: %m\n"); + if (ioctl(wl->tty, KDSETMODE, KD_GRAPHICS)) { + fprintf(stderr, + "weston: failed to set KD_GRAPHICS mode on tty: %s\n", + strerror(errno)); + return -1; + } mode.mode = VT_PROCESS; mode.relsig = SIGUSR1; mode.acqsig = SIGUSR2; - if (ioctl(wl->tty, VT_SETMODE, &mode) < 0) - error(1, errno, "failed to take control of vt handling\n"); + if (ioctl(wl->tty, VT_SETMODE, &mode) < 0) { + fprintf(stderr, + "weston: failed to take control of vt handling %s\n", + strerror(errno)); + return -1; + } return 0; } @@ -595,10 +635,16 @@ setup_session(struct weston_launch *wl, char **child_argv) int i; if (wl->tty != STDIN_FILENO) { - if (setsid() < 0) - error(1, errno, "setsid failed"); - if (ioctl(wl->tty, TIOCSCTTY, 0) < 0) - error(1, errno, "TIOCSCTTY failed - tty is in use"); + if (setsid() < 0) { + fprintf(stderr, "weston: setsid failed %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + if (ioctl(wl->tty, TIOCSCTTY, 0) < 0) { + fprintf(stderr, "TIOCSCTTY failed - tty is in use %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } } term = getenv("TERM"); @@ -614,7 +660,7 @@ setup_session(struct weston_launch *wl, char **child_argv) if (env) { for (i = 0; env[i]; ++i) { if (putenv(env[i]) != 0) - error(0, 0, "putenv %s failed", env[i]); + fprintf(stderr, "putenv %s failed\n", env[i]); } free(env); } @@ -638,8 +684,11 @@ drop_privileges(struct weston_launch *wl) #ifdef HAVE_INITGROUPS initgroups(wl->pw->pw_name, wl->pw->pw_gid) < 0 || #endif - setuid(wl->pw->pw_uid) < 0) - error(1, errno, "dropping privileges failed"); + setuid(wl->pw->pw_uid) < 0) { + fprintf(stderr, "weston: dropping privileges failed %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } } static void @@ -678,7 +727,8 @@ launch_compositor(struct weston_launch *wl, int argc, char *argv[]) execv(child_argv[0], child_argv); - error(1, errno, "exec failed"); + fprintf(stderr, "weston: exec failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); } static void @@ -713,8 +763,10 @@ main(int argc, char *argv[]) switch (c) { case 'u': wl.new_user = optarg; - if (getuid() != 0) - error(1, 0, "Permission denied. -u allowed for root only"); + if (getuid() != 0) { + fprintf(stderr, "weston: Permission denied. -u allowed for root only\n"); + exit(EXIT_FAILURE); + } break; case 't': tty = optarg; @@ -730,27 +782,38 @@ main(int argc, char *argv[]) } } - if ((argc - optind) > (MAX_ARGV_SIZE - 6)) - error(1, E2BIG, "Too many arguments to pass to weston"); + if ((argc - optind) > (MAX_ARGV_SIZE - 6)) { + fprintf(stderr, + "weston: Too many arguments to pass to weston: %s\n", + strerror(E2BIG)); + exit(EXIT_FAILURE); + } - if (tty && !wl.new_user) - error(1, 0, "-t/--tty option requires -u/--user option as well"); + if (tty && !wl.new_user) { + fprintf(stderr, "weston: -t/--tty option requires -u/--user option as well\n"); + exit(EXIT_FAILURE); + } if (wl.new_user) wl.pw = getpwnam(wl.new_user); else wl.pw = getpwuid(getuid()); - if (wl.pw == NULL) - error(1, errno, "failed to get username"); + if (wl.pw == NULL) { + fprintf(stderr, "weston: failed to get username: %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } - if (!weston_launch_allowed(&wl)) - error(1, 0, "Permission denied. You should either:\n" + if (!weston_launch_allowed(&wl)) { + fprintf(stderr, "Permission denied. You should either:\n" #ifdef HAVE_SYSTEMD_LOGIN " - run from an active and local (systemd) session.\n" #else " - enable systemd session support for weston-launch.\n" #endif - " - or add yourself to the 'weston-launch' group."); + " - or add yourself to the 'weston-launch' group.\n"); + exit(EXIT_FAILURE); + } if (setup_tty(&wl, tty) < 0) exit(EXIT_FAILURE); @@ -765,8 +828,10 @@ main(int argc, char *argv[]) exit(EXIT_FAILURE); wl.child = fork(); - if (wl.child == -1) - error(EXIT_FAILURE, errno, "fork failed"); + if (wl.child == -1) { + fprintf(stderr, "weston: fork failed %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } if (wl.child == 0) launch_compositor(&wl, argc - optind, argv + optind); From 38c66ccb21020ccd650a9a6321059303c284086a Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Mon, 22 Apr 2019 17:22:57 +0300 Subject: [PATCH 0858/1642] weston-launch: Fix warning on error() not being avaiable due to removal of header Commit a1450a8a7 removed errno header but forgot to remove all error() calls. Signed-off-by: Marius Vlad --- libweston/weston-launch.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index 9e5c94297..0ae7d0df7 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -850,8 +850,10 @@ main(int argc, char *argv[]) fds[1].events = POLLIN; n = poll(fds, 2, -1); - if (n < 0) - error(0, errno, "poll failed"); + if (n < 0) { + fprintf(stderr, "poll failed: %s\n", strerror(errno)); + return -1; + } if (fds[0].revents & POLLIN) handle_socket_msg(&wl); if (fds[1].revents) From 7bd14029dd393491b28ffe8243b2e2fb270945ef Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 22 Apr 2019 20:02:19 +0300 Subject: [PATCH 0859/1642] ci: run with werror --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1b738e873..fcb13e5a2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -51,7 +51,7 @@ build-native-meson: script: - export PATH=~/.local/bin:$PATH - cd "$BUILDDIR" - - meson --prefix="$PREFIX" -Dsimple-dmabuf-drm=intel .. + - meson --prefix="$PREFIX" -Dsimple-dmabuf-drm=intel -Dwerror=true .. - ninja -k0 - ninja install - ninja test From 4071225cdc12a36a08ddd3102a3dd17d4006c320 Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Mon, 29 Apr 2019 17:54:10 +0200 Subject: [PATCH 0860/1642] clients: close unused keymap fd In the simple examples in which keymap is not handled, the open descriptor has to be properly closed. After each suspend/resume sequence the keymap is send again to every client. On client weston-simple-egl the leak causes a segfault when no more file descriptors can be opened. Close the file descriptor and lazily copy/paste the comment already available in simple-dmabuf-v4l. Signed-off-by: Antonio Borneo --- clients/multi-resource.c | 2 ++ clients/simple-egl.c | 2 ++ clients/weston-info.c | 3 +++ 3 files changed, 7 insertions(+) diff --git a/clients/multi-resource.c b/clients/multi-resource.c index eb4fd4406..f3c42d0df 100644 --- a/clients/multi-resource.c +++ b/clients/multi-resource.c @@ -296,6 +296,8 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { + /* Just so we don’t leak the keymap fd */ + close(fd); } static void diff --git a/clients/simple-egl.c b/clients/simple-egl.c index 8a086ef0d..1afde36a7 100644 --- a/clients/simple-egl.c +++ b/clients/simple-egl.c @@ -640,6 +640,8 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { + /* Just so we don’t leak the keymap fd */ + close(fd); } static void diff --git a/clients/weston-info.c b/clients/weston-info.c index 4134191bc..08c26191a 100644 --- a/clients/weston-info.c +++ b/clients/weston-info.c @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -463,6 +464,8 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { + /* Just so we don’t leak the keymap fd */ + close(fd); } static void From 45d38856c8856b5550bb5656bda60ea674ca52c0 Mon Sep 17 00:00:00 2001 From: - Date: Thu, 2 May 2019 17:35:29 +0000 Subject: [PATCH 0861/1642] zunitc: Fix undeclared identifier 'NULL' Since v6 release, -Dtest-junit-xml=false build regressed because its ifdef branch no longer includes stddef.h and thus NULL, either directly or through another header. Using musl-1.1.22 and llvm-8.0.0. --- tools/zunitc/src/zuc_junit_reporter.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/zunitc/src/zuc_junit_reporter.c b/tools/zunitc/src/zuc_junit_reporter.c index c0457f210..42acad918 100644 --- a/tools/zunitc/src/zuc_junit_reporter.c +++ b/tools/zunitc/src/zuc_junit_reporter.c @@ -460,6 +460,7 @@ zuc_junit_reporter_create(void) #else /* ENABLE_JUNIT_XML */ +#include #include "zuc_event_listener.h" /* From 3957863667c15bc5f1984ddc6c5967a323f41e7a Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Fri, 26 Apr 2019 23:57:31 +0200 Subject: [PATCH 0862/1642] log: remove "%m" from format strings by using strerror(errno) The printf() format specifier "%m" is a glibc extension to print the string returned by strerror(errno). While supported by other libraries (e.g. uClibc and musl), it is not widely portable. In Weston code the format string is often passed to a logging function that calls other syscalls before the conversion of "%m" takes place. If one of such syscall modifies the value in errno, the conversion of "%m" will incorrectly report the error string corresponding to the new value of errno. Remove all the occurrences of the specifier "%m" in Weston code by using directly the string returned by strerror(errno). While there, fix some minor indentation issue. Signed-off-by: Antonio Borneo --- clients/calibrator.c | 4 ++- clients/clickdot.c | 4 ++- clients/cliptest.c | 4 ++- clients/confine.c | 4 ++- clients/desktop-shell.c | 8 ++++-- clients/dnd.c | 4 ++- clients/editor.c | 8 ++++-- clients/eventdemo.c | 8 ++++-- clients/flower.c | 4 ++- clients/fullscreen.c | 4 ++- clients/gears.c | 4 ++- clients/image.c | 4 ++- clients/ivi-shell-user-interface.c | 10 ++++--- clients/keyboard.c | 4 ++- clients/multi-resource.c | 4 +-- clients/nested.c | 18 +++++++----- clients/presentation-shm.c | 9 +++--- clients/resizor.c | 4 ++- clients/scaler.c | 4 ++- clients/screenshot.c | 9 +++--- clients/simple-damage.c | 7 +++-- clients/simple-im.c | 6 ++-- clients/simple-shm.c | 7 +++-- clients/simple-touch.c | 7 +++-- clients/smoke.c | 4 ++- clients/stacking.c | 4 ++- clients/subsurfaces.c | 4 ++- clients/terminal.c | 8 ++++-- clients/transformed.c | 4 ++- clients/weston-debug.c | 10 ++++--- clients/weston-info.c | 3 +- clients/window.c | 20 ++++++++------ compositor/main.c | 24 +++++++++------- compositor/screen-share.c | 27 ++++++++++-------- compositor/xwayland.c | 7 +++-- libweston/compositor-drm.c | 44 ++++++++++++++++++------------ libweston/compositor-wayland.c | 15 ++++++---- libweston/compositor.c | 5 ++-- libweston/input.c | 6 ++-- libweston/launcher-direct.c | 24 ++++++++++------ libweston/launcher-logind.c | 2 +- libweston/launcher-weston-launch.c | 6 ++-- libweston/screenshooter.c | 4 ++- libweston/weston-launch.c | 16 +++++++---- tests/ivi-layout-test-plugin.c | 8 ++++-- tests/weston-test-runner.c | 2 +- tests/weston-test.c | 4 ++- xwayland/launcher.c | 6 ++-- xwayland/selection.c | 8 ++++-- 49 files changed, 265 insertions(+), 149 deletions(-) diff --git a/clients/calibrator.c b/clients/calibrator.c index 2439fc94c..21ca876f7 100644 --- a/clients/calibrator.c +++ b/clients/calibrator.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -290,7 +291,8 @@ main(int argc, char *argv[]) display = display_create(&argc, argv); if (display == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/clickdot.c b/clients/clickdot.c index f9e6e640e..4e8a945e7 100644 --- a/clients/clickdot.c +++ b/clients/clickdot.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -328,7 +329,8 @@ main(int argc, char *argv[]) display = display_create(&argc, argv); if (display == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/cliptest.c b/clients/cliptest.c index 57aefdab4..89983850a 100644 --- a/clients/cliptest.c +++ b/clients/cliptest.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -621,7 +622,8 @@ main(int argc, char *argv[]) d = display_create(&argc, argv); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/confine.c b/clients/confine.c index 255236a35..01caae07b 100644 --- a/clients/confine.c +++ b/clients/confine.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -490,7 +491,8 @@ main(int argc, char *argv[]) display = display_create(&argc, argv); if (display == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index c60e74d2f..8114491df 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -212,7 +212,7 @@ panel_launcher_activate(struct panel_launcher *widget) pid = fork(); if (pid < 0) { - fprintf(stderr, "fork failed: %m\n"); + fprintf(stderr, "fork failed: %s\n", strerror(errno)); return; } @@ -225,7 +225,8 @@ panel_launcher_activate(struct panel_launcher *widget) exit(EXIT_FAILURE); if (execve(argv[0], argv, widget->envp.data) < 0) { - fprintf(stderr, "execl '%s' failed: %m\n", argv[0]); + fprintf(stderr, "execl '%s' failed: %s\n", argv[0], + strerror(errno)); exit(1); } } @@ -1519,7 +1520,8 @@ int main(int argc, char *argv[]) desktop.display = display_create(&argc, argv); if (desktop.display == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/dnd.c b/clients/dnd.c index ec706bffb..8323f4fdc 100644 --- a/clients/dnd.c +++ b/clients/dnd.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -848,7 +849,8 @@ main(int argc, char *argv[]) d = display_create(&argc, argv); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/editor.c b/clients/editor.c index 03e6b4913..c3d9491d6 100644 --- a/clients/editor.c +++ b/clients/editor.c @@ -580,7 +580,7 @@ data_source_send(void *data, struct editor *editor = data; if (write(fd, editor->selected_text, strlen(editor->selected_text) + 1) < 0) - fprintf(stderr, "write failed: %m\n"); + fprintf(stderr, "write failed: %s\n", strerror(errno)); close(fd); } @@ -1609,7 +1609,8 @@ main(int argc, char *argv[]) text_buffer = read_file(argv[1]); if (text_buffer == NULL) { - fprintf(stderr, "could not read file '%s': %m\n", argv[1]); + fprintf(stderr, "could not read file '%s': %s\n", + argv[1], strerror(errno)); return -1; } } @@ -1618,7 +1619,8 @@ main(int argc, char *argv[]) editor.display = display_create(&argc, argv); if (editor.display == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); free(text_buffer); return -1; } diff --git a/clients/eventdemo.c b/clients/eventdemo.c index d8eef5b53..57b67be71 100644 --- a/clients/eventdemo.c +++ b/clients/eventdemo.c @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include @@ -515,14 +517,16 @@ main(int argc, char *argv[]) /* Connect to the display and have the arguments parsed */ d = display_create(&argc, argv); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } /* Create new eventdemo window */ e = eventdemo_create(d); if (e == NULL) { - fprintf(stderr, "failed to create eventdemo: %m\n"); + fprintf(stderr, "failed to create eventdemo: %s\n", + strerror(errno)); return -1; } diff --git a/clients/flower.c b/clients/flower.c index 34287fd8a..e3471ce7e 100644 --- a/clients/flower.c +++ b/clients/flower.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -172,7 +173,8 @@ int main(int argc, char *argv[]) d = display_create(&argc, argv); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/fullscreen.c b/clients/fullscreen.c index 22fcec11d..ff41d418c 100644 --- a/clients/fullscreen.c +++ b/clients/fullscreen.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -518,7 +519,8 @@ int main(int argc, char *argv[]) d = display_create(&argc, argv); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/gears.c b/clients/gears.c index 3c57c4a74..6090a850a 100644 --- a/clients/gears.c +++ b/clients/gears.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -488,7 +489,8 @@ int main(int argc, char *argv[]) d = display_create(&argc, argv); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } gears = gears_create(d); diff --git a/clients/image.c b/clients/image.c index db9ccd647..0a8fb5b5b 100644 --- a/clients/image.c +++ b/clients/image.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -419,7 +420,8 @@ main(int argc, char *argv[]) d = display_create(&argc, argv); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/ivi-shell-user-interface.c b/clients/ivi-shell-user-interface.c index 445892a6d..7d2d1a20f 100644 --- a/clients/ivi-shell-user-interface.c +++ b/clients/ivi-shell-user-interface.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include "shared/cairo-util.h" @@ -806,8 +807,8 @@ createShmBuffer(struct wlContextStruct *p_wlCtx) fd = os_create_anonymous_file(size); if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %m\n", - size); + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); return ; } @@ -815,7 +816,7 @@ createShmBuffer(struct wlContextStruct *p_wlCtx) mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (MAP_FAILED == p_wlCtx->data) { - fprintf(stderr, "mmap failed: %m\n"); + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return; } @@ -828,7 +829,8 @@ createShmBuffer(struct wlContextStruct *p_wlCtx) WL_SHM_FORMAT_ARGB8888); if (NULL == p_wlCtx->wlBuffer) { - fprintf(stderr, "wl_shm_create_buffer failed: %m\n"); + fprintf(stderr, "wl_shm_create_buffer failed: %s\n", + strerror(errno)); close(fd); return; } diff --git a/clients/keyboard.c b/clients/keyboard.c index c9f6f28e7..e39d76dd2 100644 --- a/clients/keyboard.c +++ b/clients/keyboard.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -1019,7 +1020,8 @@ main(int argc, char *argv[]) virtual_keyboard.display = display_create(&argc, argv); if (virtual_keyboard.display == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/multi-resource.c b/clients/multi-resource.c index f3c42d0df..b86503db9 100644 --- a/clients/multi-resource.c +++ b/clients/multi-resource.c @@ -97,8 +97,8 @@ attach_buffer(struct window *window, int width, int height) fd = os_create_anonymous_file(size); if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %m\n", - size); + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); return -1; } diff --git a/clients/nested.c b/clients/nested.c index e2bdf6849..bc51b584e 100644 --- a/clients/nested.c +++ b/clients/nested.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -330,8 +331,8 @@ launch_client(struct nested *nested, const char *path) if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { fprintf(stderr, "launch_client: " - "socketpair failed while launching '%s': %m\n", - path); + "socketpair failed while launching '%s': %s\n", + path, strerror(errno)); free(client); return NULL; } @@ -342,7 +343,8 @@ launch_client(struct nested *nested, const char *path) close(sv[1]); free(client); fprintf(stderr, "launch_client: " - "fork failed while launching '%s': %m\n", path); + "fork failed while launching '%s': %s\n", path, + strerror(errno)); return NULL; } @@ -354,7 +356,8 @@ launch_client(struct nested *nested, const char *path) * get a non-CLOEXEC fd to pass through exec. */ clientfd = dup(sv[1]); if (clientfd == -1) { - fprintf(stderr, "compositor: dup failed: %m\n"); + fprintf(stderr, "compositor: dup failed: %s\n", + strerror(errno)); exit(-1); } @@ -363,8 +366,8 @@ launch_client(struct nested *nested, const char *path) execl(path, path, NULL); - fprintf(stderr, "compositor: executing '%s' failed: %m\n", - path); + fprintf(stderr, "compositor: executing '%s' failed: %s\n", + path, strerror(errno)); exit(-1); } @@ -1116,7 +1119,8 @@ main(int argc, char *argv[]) display = display_create(&argc, argv); if (display == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/presentation-shm.c b/clients/presentation-shm.c index 9f4790ae5..7150eb088 100644 --- a/clients/presentation-shm.c +++ b/clients/presentation-shm.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "shared/helpers.h" @@ -141,14 +142,14 @@ create_shm_buffers(struct display *display, struct buffer **buffers, fd = os_create_anonymous_file(size); if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %m\n", - size); + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); return -1; } data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %m\n"); + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return -1; } @@ -480,7 +481,7 @@ window_emulate_rendering(struct window *window) ret = nanosleep(&delay, NULL); if (ret) - printf("nanosleep failed: %m\n"); + printf("nanosleep failed: %s\n", strerror(errno)); } static void diff --git a/clients/resizor.c b/clients/resizor.c index 600452adc..cfc5d419e 100644 --- a/clients/resizor.c +++ b/clients/resizor.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -439,7 +440,8 @@ main(int argc, char *argv[]) display = display_create(&argc, argv); if (display == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/scaler.c b/clients/scaler.c index 23cc3a495..91736fb36 100644 --- a/clients/scaler.c +++ b/clients/scaler.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -288,7 +289,8 @@ main(int argc, char *argv[]) d = display_create(&argc, argv); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/screenshot.c b/clients/screenshot.c index 96fb16828..bbf2e6bdf 100644 --- a/clients/screenshot.c +++ b/clients/screenshot.c @@ -173,14 +173,14 @@ screenshot_create_shm_buffer(int width, int height, void **data_out, fd = os_create_anonymous_file(size); if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %m\n", - size); + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); return NULL; } data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %m\n"); + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return NULL; } @@ -286,7 +286,8 @@ int main(int argc, char *argv[]) display = wl_display_connect(NULL); if (display == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/simple-damage.c b/clients/simple-damage.c index 72ab6dd65..0458bd064 100644 --- a/clients/simple-damage.c +++ b/clients/simple-damage.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "shared/os-compatibility.h" @@ -123,14 +124,14 @@ create_shm_buffer(struct display *display, struct buffer *buffer, fd = os_create_anonymous_file(size); if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %m\n", - size); + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); return -1; } data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %m\n"); + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return -1; } diff --git a/clients/simple-im.c b/clients/simple-im.c index 280589b18..a5b834ded 100644 --- a/clients/simple-im.c +++ b/clients/simple-im.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -490,7 +491,8 @@ main(int argc, char *argv[]) simple_im.display = wl_display_connect(NULL); if (simple_im.display == NULL) { - fprintf(stderr, "Failed to connect to server: %m\n"); + fprintf(stderr, "Failed to connect to server: %s\n", + strerror(errno)); return -1; } @@ -516,7 +518,7 @@ main(int argc, char *argv[]) ret = wl_display_dispatch(simple_im.display); if (ret == -1) { - fprintf(stderr, "Dispatch error: %m\n"); + fprintf(stderr, "Dispatch error: %s\n", strerror(errno)); return -1; } diff --git a/clients/simple-shm.c b/clients/simple-shm.c index ff6613f88..90892c87f 100644 --- a/clients/simple-shm.c +++ b/clients/simple-shm.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include "shared/os-compatibility.h" @@ -98,14 +99,14 @@ create_shm_buffer(struct display *display, struct buffer *buffer, fd = os_create_anonymous_file(size); if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %m\n", - size); + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); return -1; } data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %m\n"); + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return -1; } diff --git a/clients/simple-touch.c b/clients/simple-touch.c index 4d0114716..385188c3c 100644 --- a/clients/simple-touch.c +++ b/clients/simple-touch.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -70,15 +71,15 @@ create_shm_buffer(struct touch *touch) fd = os_create_anonymous_file(size); if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %m\n", - size); + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); exit(1); } touch->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (touch->data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %m\n"); + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); exit(1); } diff --git a/clients/smoke.c b/clients/smoke.c index cedd17f4d..f1b90ec7a 100644 --- a/clients/smoke.c +++ b/clients/smoke.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -273,7 +274,8 @@ int main(int argc, char *argv[]) d = display_create(&argc, argv); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/stacking.c b/clients/stacking.c index b034cf2ab..5e9084f87 100644 --- a/clients/stacking.c +++ b/clients/stacking.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -290,7 +291,8 @@ main(int argc, char *argv[]) stacking.display = display_create(&argc, argv); if (stacking.display == NULL) { - fprintf(stderr, "Failed to create display: %m\n"); + fprintf(stderr, "Failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/subsurfaces.c b/clients/subsurfaces.c index 23c22e8fd..0e4e52b04 100644 --- a/clients/subsurfaces.c +++ b/clients/subsurfaces.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -788,7 +789,8 @@ main(int argc, char *argv[]) display = display_create(&argc, argv); if (display == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/terminal.c b/clients/terminal.c index f7e9ba955..38e402071 100644 --- a/clients/terminal.c +++ b/clients/terminal.c @@ -3087,11 +3087,12 @@ terminal_run(struct terminal *terminal, const char *path) setenv("TERM", option_term, 1); setenv("COLORTERM", option_term, 1); if (execl(path, path, NULL)) { - printf("exec failed: %m\n"); + printf("exec failed: %s\n", strerror(errno)); exit(EXIT_FAILURE); } } else if (pid < 0) { - fprintf(stderr, "failed to fork and create pty (%m).\n"); + fprintf(stderr, "failed to fork and create pty (%s).\n", + strerror(errno)); return -1; } @@ -3158,7 +3159,8 @@ int main(int argc, char *argv[]) d = display_create(&argc, argv); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/transformed.c b/clients/transformed.c index 6687a4a80..dd88fcd4b 100644 --- a/clients/transformed.c +++ b/clients/transformed.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -263,7 +264,8 @@ int main(int argc, char *argv[]) d = display_create(&argc, argv); if (d == NULL) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/weston-debug.c b/clients/weston-debug.c index d7ff081e6..3060dec97 100644 --- a/clients/weston-debug.c +++ b/clients/weston-debug.c @@ -276,8 +276,8 @@ setup_out_fd(const char *output, const char *outfd) O_WRONLY | O_APPEND | O_CREAT, 0644); if (fd < 0) { fprintf(stderr, - "Error: opening file '%s' failed: %m\n", - output); + "Error: opening file '%s' failed: %s\n", + output, strerror(errno)); } return fd; } @@ -290,7 +290,8 @@ setup_out_fd(const char *output, const char *outfd) flags = fcntl(fd, F_GETFL); if (flags == -1) { fprintf(stderr, - "Error: cannot use file descriptor %d: %m\n", fd); + "Error: cannot use file descriptor %d: %s\n", fd, + strerror(errno)); return -1; } @@ -432,7 +433,8 @@ main(int argc, char **argv) app.dpy = wl_display_connect(NULL); if (!app.dpy) { - fprintf(stderr, "Error: Could not connect to Wayland display: %m\n"); + fprintf(stderr, "Error: Could not connect to Wayland display: %s\n", + strerror(errno)); ret = 1; goto out_parse; } diff --git a/clients/weston-info.c b/clients/weston-info.c index 08c26191a..a26f0bf0a 100644 --- a/clients/weston-info.c +++ b/clients/weston-info.c @@ -1856,7 +1856,8 @@ main(int argc, char **argv) info.display = wl_display_connect(NULL); if (!info.display) { - fprintf(stderr, "failed to create display: %m\n"); + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); return -1; } diff --git a/clients/window.c b/clients/window.c index d4dc96e15..804799765 100644 --- a/clients/window.c +++ b/clients/window.c @@ -740,14 +740,14 @@ make_shm_pool(struct display *display, int size, void **data) fd = os_create_anonymous_file(size); if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %m\n", - size); + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); return NULL; } *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (*data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %m\n"); + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return NULL; } @@ -6168,7 +6168,8 @@ display_create(int *argc, char *argv[]) d->display = wl_display_connect(NULL); if (d->display == NULL) { - fprintf(stderr, "failed to connect to Wayland display: %m\n"); + fprintf(stderr, "failed to connect to Wayland display: %s\n", + strerror(errno)); free(d); return NULL; } @@ -6195,7 +6196,8 @@ display_create(int *argc, char *argv[]) wl_registry_add_listener(d->registry, ®istry_listener, d); if (wl_display_roundtrip(d->display) < 0) { - fprintf(stderr, "Failed to process Wayland connection: %m\n"); + fprintf(stderr, "Failed to process Wayland connection: %s\n", + strerror(errno)); return NULL; } @@ -6531,7 +6533,8 @@ toytimer_fire(struct task *tsk, uint32_t events) * readable and getting here, there'll be nothing to * read and we get EAGAIN. */ if (errno != EAGAIN) - fprintf(stderr, "timer read failed: %m\n"); + fprintf(stderr, "timer read failed: %s\n", + strerror(errno)); return; } @@ -6546,7 +6549,8 @@ toytimer_init(struct toytimer *tt, clockid_t clock, struct display *display, tt->fd = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK); if (tt->fd == -1) { - fprintf(stderr, "creating timer failed: %m\n"); + fprintf(stderr, "creating timer failed: %s\n", + strerror(errno)); abort(); } @@ -6571,7 +6575,7 @@ toytimer_arm(struct toytimer *tt, const struct itimerspec *its) ret = timerfd_settime(tt->fd, 0, its, NULL); if (ret < 0) { - fprintf(stderr, "timer setup failed: %m\n"); + fprintf(stderr, "timer setup failed: %s\n", strerror(errno)); abort(); } } diff --git a/compositor/main.c b/compositor/main.c index 4855cdbb5..280211e32 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -365,7 +365,7 @@ sigchld_handler(int signal_number, void *data) } if (pid < 0 && errno != ECHILD) - weston_log("waitpid error %m\n"); + weston_log("waitpid error %s\n", strerror(errno)); return 1; } @@ -391,7 +391,7 @@ child_client_exec(int sockfd, const char *path) * non-CLOEXEC fd to pass through exec. */ clientfd = dup(sockfd); if (clientfd == -1) { - weston_log("compositor: dup failed: %m\n"); + weston_log("compositor: dup failed: %s\n", strerror(errno)); return; } @@ -399,8 +399,8 @@ child_client_exec(int sockfd, const char *path) setenv("WAYLAND_SOCKET", s, 1); if (execl(path, path, NULL) < 0) - weston_log("compositor: executing '%s' failed: %m\n", - path); + weston_log("compositor: executing '%s' failed: %s\n", + path, strerror(errno)); } WL_EXPORT struct wl_client * @@ -417,8 +417,8 @@ weston_client_launch(struct weston_compositor *compositor, if (os_socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { weston_log("weston_client_launch: " - "socketpair failed while launching '%s': %m\n", - path); + "socketpair failed while launching '%s': %s\n", + path, strerror(errno)); return NULL; } @@ -427,7 +427,8 @@ weston_client_launch(struct weston_compositor *compositor, close(sv[0]); close(sv[1]); weston_log("weston_client_launch: " - "fork failed while launching '%s': %m\n", path); + "fork failed while launching '%s': %s\n", path, + strerror(errno)); return NULL; } @@ -812,13 +813,15 @@ weston_create_listening_socket(struct wl_display *display, const char *socket_na { if (socket_name) { if (wl_display_add_socket(display, socket_name)) { - weston_log("fatal: failed to add socket: %m\n"); + weston_log("fatal: failed to add socket: %s\n", + strerror(errno)); return -1; } } else { socket_name = wl_display_add_socket_auto(display); if (!socket_name) { - weston_log("fatal: failed to add socket: %m\n"); + weston_log("fatal: failed to add socket: %s\n", + strerror(errno)); return -1; } } @@ -3083,7 +3086,8 @@ int main(int argc, char *argv[]) if (fd != -1) { primary_client = wl_client_create(display, fd); if (!primary_client) { - weston_log("fatal: failed to add client: %m\n"); + weston_log("fatal: failed to add client: %s\n", + strerror(errno)); goto out; } primary_client_destroyed.notify = diff --git a/compositor/screen-share.c b/compositor/screen-share.c index 69689d73a..360cd3cdb 100644 --- a/compositor/screen-share.c +++ b/compositor/screen-share.c @@ -207,7 +207,7 @@ ss_seat_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, if (format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (map_str == MAP_FAILED) { - weston_log("mmap failed: %m\n"); + weston_log("mmap failed: %s\n", strerror(errno)); goto error; } @@ -462,13 +462,13 @@ shared_output_get_shm_buffer(struct shared_output *so) fd = os_create_anonymous_file(height * stride); if (fd < 0) { - weston_log("os_create_anonymous_file: %m\n"); + weston_log("os_create_anonymous_file: %s\n", strerror(errno)); return NULL; } data = mmap(NULL, height * stride, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { - weston_log("mmap: %m\n"); + weston_log("mmap: %s\n", strerror(errno)); goto out_close; } @@ -940,7 +940,7 @@ shared_output_create(struct weston_output *output, int parent_fd) so->parent.surface = wl_compositor_create_surface(so->parent.compositor); if (!so->parent.surface) { - weston_log("Screen share failed: %m\n"); + weston_log("Screen share failed: %s\n", strerror(errno)); goto err_display; } @@ -950,7 +950,7 @@ shared_output_create(struct weston_output *output, int parent_fd) so->parent.output, output->current_mode->refresh); if (!so->parent.mode_feedback) { - weston_log("Screen share failed: %m\n"); + weston_log("Screen share failed: %s\n", strerror(errno)); goto err_display; } zwp_fullscreen_shell_mode_feedback_v1_add_listener(so->parent.mode_feedback, @@ -964,7 +964,7 @@ shared_output_create(struct weston_output *output, int parent_fd) wl_event_loop_add_fd(loop, epoll_fd, WL_EVENT_READABLE, shared_output_handle_event, so); if (!so->event_source) { - weston_log("Screen share failed: %m\n"); + weston_log("Screen share failed: %s\n", strerror(errno)); goto err_display; } @@ -1033,7 +1033,8 @@ weston_output_share(struct weston_output *output, const char* command) }; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { - weston_log("weston_output_share: socketpair failed: %m\n"); + weston_log("weston_output_share: socketpair failed: %s\n", + strerror(errno)); return NULL; } @@ -1042,7 +1043,8 @@ weston_output_share(struct weston_output *output, const char* command) if (pid == -1) { close(sv[0]); close(sv[1]); - weston_log("weston_output_share: fork failed: %m\n"); + weston_log("weston_output_share: fork failed: %s\n", + strerror(errno)); return NULL; } @@ -1054,13 +1056,15 @@ weston_output_share(struct weston_output *output, const char* command) /* Launch clients as the user. Do not launch clients with * wrong euid. */ if (seteuid(getuid()) == -1) { - weston_log("weston_output_share: setuid failed: %m\n"); + weston_log("weston_output_share: setuid failed: %s\n", + strerror(errno)); abort(); } sv[1] = dup(sv[1]); if (sv[1] == -1) { - weston_log("weston_output_share: dup failed: %m\n"); + weston_log("weston_output_share: dup failed: %s\n", + strerror(errno)); abort(); } @@ -1068,7 +1072,8 @@ weston_output_share(struct weston_output *output, const char* command) setenv("WAYLAND_SERVER_SOCKET", str, 1); execv(argv[0], argv); - weston_log("weston_output_share: exec failed: %m\n"); + weston_log("weston_output_share: exec failed: %s\n", + strerror(errno)); abort(); } else { close(sv[1]); diff --git a/compositor/xwayland.c b/compositor/xwayland.c index 50b8dcc1b..8eadbe252 100644 --- a/compositor/xwayland.c +++ b/compositor/xwayland.c @@ -27,6 +27,8 @@ #include "config.h" #include +#include +#include #include #include @@ -128,9 +130,10 @@ spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd NULL) < 0) weston_log("exec of '%s %s -rootless " "-listen %s -listen %s -wm %s " - "-terminate' failed: %m\n", + "-terminate' failed: %s\n", xserver, display, - abstract_fd_str, unix_fd_str, wm_fd_str); + abstract_fd_str, unix_fd_str, wm_fd_str, + strerror(errno)); fail: _exit(EXIT_FAILURE); diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 5c1ccdd7c..bbeb1cfbd 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -665,7 +665,8 @@ drm_output_pageflip_timer_create(struct drm_output *output) output); if (output->pageflip_timer == NULL) { - weston_log("creating drm pageflip timer failed: %m\n"); + weston_log("creating drm pageflip timer failed: %s\n", + strerror(errno)); return -1; } @@ -1083,7 +1084,7 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, fb->fd = b->drm.fd; if (drm_fb_addfb(b, fb) != 0) { - weston_log("failed to create kms fb: %m\n"); + weston_log("failed to create kms fb: %s\n", strerror(errno)); goto err_bo; } @@ -1333,7 +1334,8 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, if (drm_fb_addfb(backend, fb) != 0) { if (type == BUFFER_GBM_SURFACE) - weston_log("failed to create kms fb: %m\n"); + weston_log("failed to create kms fb: %s\n", + strerror(errno)); goto err_free; } @@ -2104,7 +2106,8 @@ drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) bo = gbm_surface_lock_front_buffer(output->gbm_surface); if (!bo) { - weston_log("failed to lock front buffer: %m\n"); + weston_log("failed to lock front buffer: %s\n", + strerror(errno)); return NULL; } @@ -2228,7 +2231,7 @@ drm_output_set_gamma(struct weston_output *output_base, output->crtc_id, size, r, g, b); if (rc) - weston_log("set gamma failed: %m\n"); + weston_log("set gamma failed: %s\n", strerror(errno)); } /* Determine the type of vblank synchronization to use for the output. @@ -2302,20 +2305,23 @@ drm_output_apply_state_legacy(struct drm_output_state *state) ret = drmModeSetPlane(backend->drm.fd, p->plane_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (ret) - weston_log("drmModeSetPlane failed disable: %m\n"); + weston_log("drmModeSetPlane failed disable: %s\n", + strerror(errno)); } if (output->cursor_plane) { ret = drmModeSetCursor(backend->drm.fd, output->crtc_id, 0, 0, 0); if (ret) - weston_log("drmModeSetCursor failed disable: %m\n"); + weston_log("drmModeSetCursor failed disable: %s\n", + strerror(errno)); } ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, 0, 0, 0, NULL, 0, NULL); if (ret) - weston_log("drmModeSetCrtc failed disabling: %m\n"); + weston_log("drmModeSetCrtc failed disabling: %s\n", + strerror(errno)); drm_output_assign_state(state, DRM_STATE_APPLY_SYNC); weston_compositor_read_presentation_clock(output->base.compositor, &now); @@ -2356,7 +2362,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) connectors, n_conn, &mode->mode_info); if (ret) { - weston_log("set mode failed: %m\n"); + weston_log("set mode failed: %s\n", strerror(errno)); goto err; } } @@ -2369,7 +2375,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) if (drmModePageFlip(backend->drm.fd, output->crtc_id, scanout_state->fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { - weston_log("queueing pageflip failed: %m\n"); + weston_log("queueing pageflip failed: %s\n", strerror(errno)); goto err; } @@ -2529,7 +2535,8 @@ drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode) sizeof(mode->mode_info), &mode->blob_id); if (ret != 0) - weston_log("failed to create mode property blob: %m\n"); + weston_log("failed to create mode property blob: %s\n", + strerror(errno)); drm_debug(backend, "\t\t\t[atomic] created new mode blob %lu for %s\n", (unsigned long) mode->blob_id, mode->mode_info.name); @@ -2828,7 +2835,8 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, } if (ret != 0) { - weston_log("atomic: couldn't commit new state: %m\n"); + weston_log("atomic: couldn't commit new state: %s\n", + strerror(errno)); goto out; } @@ -3104,7 +3112,8 @@ drm_output_start_repaint_loop(struct weston_output *output_base) ret = drm_pending_state_apply(pending_state); if (ret != 0) { - weston_log("applying repaint-start state failed: %m\n"); + weston_log("applying repaint-start state failed: %s\n", + strerror(errno)); goto finish_frame; } @@ -3462,7 +3471,7 @@ cursor_bo_update(struct drm_plane_state *plane_state, struct weston_view *ev) wl_shm_buffer_end_access(buffer->shm_buffer); if (gbm_bo_write(bo, buf, sizeof buf) < 0) - weston_log("failed update cursor: %m\n"); + weston_log("failed update cursor: %s\n", strerror(errno)); } static struct drm_plane_state * @@ -3608,7 +3617,8 @@ drm_output_set_cursor(struct drm_output_state *output_state) handle = gbm_bo_get_handle(bo).s32; if (drmModeSetCursor(b->drm.fd, output->crtc_id, handle, b->cursor_width, b->cursor_height)) { - weston_log("failed to set cursor: %m\n"); + weston_log("failed to set cursor: %s\n", + strerror(errno)); goto err; } } @@ -3618,7 +3628,7 @@ drm_output_set_cursor(struct drm_output_state *output_state) if (drmModeMoveCursor(b->drm.fd, output->crtc_id, state->dest_x, state->dest_y)) { - weston_log("failed to move cursor: %m\n"); + weston_log("failed to move cursor: %s\n", strerror(errno)); goto err; } @@ -7057,7 +7067,7 @@ recorder_frame_notify(struct wl_listener *listener, void *data) ret = vaapi_recorder_frame(output->recorder, fd, output->scanout_plane->state_cur->fb->strides[0]); if (ret < 0) { - weston_log("[libva recorder] aborted: %m\n"); + weston_log("[libva recorder] aborted: %s\n", strerror(errno)); recorder_destroy(output); } } diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c index 337ea1058..729cbb71f 100644 --- a/libweston/compositor-wayland.c +++ b/libweston/compositor-wayland.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -302,20 +303,23 @@ wayland_output_get_shm_buffer(struct wayland_output *output) fd = os_create_anonymous_file(height * stride); if (fd < 0) { - weston_log("could not create an anonymous file buffer: %m\n"); + weston_log("could not create an anonymous file buffer: %s\n", + strerror(errno)); return NULL; } data = mmap(NULL, height * stride, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { - weston_log("could not mmap %d memory for data: %m\n", height * stride); + weston_log("could not mmap %d memory for data: %s\n", height * stride, + strerror(errno)); close(fd); return NULL; } sb = zalloc(sizeof *sb); if (sb == NULL) { - weston_log("could not zalloc %zu memory for sb: %m\n", sizeof *sb); + weston_log("could not zalloc %zu memory for sb: %s\n", sizeof *sb, + strerror(errno)); close(fd); munmap(data, height * stride); return NULL; @@ -1917,7 +1921,7 @@ input_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, if (format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (map_str == MAP_FAILED) { - weston_log("mmap failed: %m\n"); + weston_log("mmap failed: %s\n", strerror(errno)); goto error; } @@ -2729,7 +2733,8 @@ wayland_backend_create(struct weston_compositor *compositor, b->parent.wl_display = wl_display_connect(new_config->display_name); if (b->parent.wl_display == NULL) { - weston_log("Error: Failed to connect to parent Wayland compositor: %m\n"); + weston_log("Error: Failed to connect to parent Wayland compositor: %s\n", + strerror(errno)); weston_log_continue(STAMP_SPACE "display option: %s, WAYLAND_DISPLAY=%s\n", new_config->display_name ?: "(none)", getenv("WAYLAND_DISPLAY") ?: "(not set)"); diff --git a/libweston/compositor.c b/libweston/compositor.c index 6a7c4cedc..4dc52ec39 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -7020,8 +7020,9 @@ weston_compositor_read_presentation_clock( if (!warned) weston_log("Error: failure to read " - "the presentation clock %#x: '%m' (%d)\n", - compositor->presentation_clock, errno); + "the presentation clock %#x: '%s' (%d)\n", + compositor->presentation_clock, + strerror(errno), errno); warned = true; } } diff --git a/libweston/input.c b/libweston/input.c index 93ac0f6c8..9ecee4b14 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -37,6 +37,7 @@ #include #include #include +#include #include "shared/helpers.h" #include "shared/os-compatibility.h" @@ -2089,8 +2090,9 @@ weston_keyboard_send_keymap(struct weston_keyboard *kbd, struct wl_resource *res fd = os_create_anonymous_file(xkb_info->keymap_size); if (fd < 0) { - weston_log("creating a keymap file for %lu bytes failed: %m\n", - (unsigned long) xkb_info->keymap_size); + weston_log("creating a keymap file for %lu bytes failed: %s\n", + (unsigned long) xkb_info->keymap_size, + strerror(errno)); return; } diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c index e5f1bcfbf..d59de1bd7 100644 --- a/libweston/launcher-direct.c +++ b/libweston/launcher-direct.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -129,14 +130,16 @@ setup_tty(struct launcher_direct *launcher, int tty) if (tty == 0) { launcher->tty = dup(tty); if (launcher->tty == -1) { - weston_log("couldn't dup stdin: %m\n"); + weston_log("couldn't dup stdin: %s\n", + strerror(errno)); return -1; } } else { snprintf(tty_device, sizeof tty_device, "/dev/tty%d", tty); launcher->tty = open(tty_device, O_RDWR | O_CLOEXEC); if (launcher->tty == -1) { - weston_log("couldn't open tty %s: %m\n", tty_device); + weston_log("couldn't open tty %s: %s\n", tty_device, + strerror(errno)); return -1; } } @@ -151,7 +154,7 @@ setup_tty(struct launcher_direct *launcher, int tty) ret = ioctl(launcher->tty, KDGETMODE, &kd_mode); if (ret) { - weston_log("failed to get VT mode: %m\n"); + weston_log("failed to get VT mode: %s\n", strerror(errno)); return -1; } if (kd_mode != KD_TEXT) { @@ -164,19 +167,22 @@ setup_tty(struct launcher_direct *launcher, int tty) ioctl(launcher->tty, VT_WAITACTIVE, minor(buf.st_rdev)); if (ioctl(launcher->tty, KDGKBMODE, &launcher->kb_mode)) { - weston_log("failed to read keyboard mode: %m\n"); + weston_log("failed to read keyboard mode: %s\n", + strerror(errno)); goto err_close; } if (ioctl(launcher->tty, KDSKBMUTE, 1) && ioctl(launcher->tty, KDSKBMODE, K_OFF)) { - weston_log("failed to set K_OFF keyboard mode: %m\n"); + weston_log("failed to set K_OFF keyboard mode: %s\n", + strerror(errno)); goto err_close; } ret = ioctl(launcher->tty, KDSETMODE, KD_GRAPHICS); if (ret) { - weston_log("failed to set KD_GRAPHICS mode on tty: %m\n"); + weston_log("failed to set KD_GRAPHICS mode on tty: %s\n", + strerror(errno)); goto err_close; } @@ -255,10 +261,12 @@ launcher_direct_restore(struct weston_launcher *launcher_base) if (ioctl(launcher->tty, KDSKBMUTE, 0) && ioctl(launcher->tty, KDSKBMODE, launcher->kb_mode)) - weston_log("failed to restore kb mode: %m\n"); + weston_log("failed to restore kb mode: %s\n", + strerror(errno)); if (ioctl(launcher->tty, KDSETMODE, KD_TEXT)) - weston_log("failed to set KD_TEXT mode on tty: %m\n"); + weston_log("failed to set KD_TEXT mode on tty: %s\n", + strerror(errno)); /* We have to drop master before we switch the VT back in * VT_AUTO, so we don't risk switching to a VT with another diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c index bcbde16c1..9893cca14 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -225,7 +225,7 @@ launcher_logind_close(struct weston_launcher *launcher, int fd) r = fstat(fd, &st); close(fd); if (r < 0) { - weston_log("logind: cannot fstat fd: %m\n"); + weston_log("logind: cannot fstat fd: %s\n", strerror(errno)); return; } diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c index c811a5008..e5d828ce2 100644 --- a/libweston/launcher-weston-launch.c +++ b/libweston/launcher-weston-launch.c @@ -169,10 +169,12 @@ launcher_weston_launch_restore(struct weston_launcher *launcher_base) if (ioctl(launcher->tty, KDSKBMUTE, 0) && ioctl(launcher->tty, KDSKBMODE, launcher->kb_mode)) - weston_log("failed to restore kb mode: %m\n"); + weston_log("failed to restore kb mode: %s\n", + strerror(errno)); if (ioctl(launcher->tty, KDSETMODE, KD_TEXT)) - weston_log("failed to set KD_TEXT mode on tty: %m\n"); + weston_log("failed to set KD_TEXT mode on tty: %s\n", + strerror(errno)); /* We have to drop master before we switch the VT back in * VT_AUTO, so we don't risk switching to a VT with another diff --git a/libweston/screenshooter.c b/libweston/screenshooter.c index 5199d2cb3..2c722a66b 100644 --- a/libweston/screenshooter.c +++ b/libweston/screenshooter.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -431,7 +432,8 @@ weston_recorder_create(struct weston_output *output, const char *filename) O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (recorder->fd < 0) { - weston_log("problem opening output file %s: %m\n", filename); + weston_log("problem opening output file %s: %s\n", filename, + strerror(errno)); goto err_recorder; } diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index 0ae7d0df7..4962bd6fa 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -128,7 +128,8 @@ read_groups(int *ngroups) n = getgroups(0, NULL); if (n < 0) { - fprintf(stderr, "Unable to retrieve groups: %m\n"); + fprintf(stderr, "Unable to retrieve groups: %s\n", + strerror(errno)); return NULL; } @@ -137,7 +138,8 @@ read_groups(int *ngroups) return NULL; if (getgroups(n, groups) < 0) { - fprintf(stderr, "Unable to retrieve groups: %m\n"); + fprintf(stderr, "Unable to retrieve groups: %s\n", + strerror(errno)); free(groups); return NULL; } @@ -325,8 +327,8 @@ handle_open(struct weston_launch *wl, struct msghdr *msg, ssize_t len) fd = open(message->path, message->flags); if (fd < 0) { - fprintf(stderr, "Error opening device %s: %m\n", - message->path); + fprintf(stderr, "Error opening device %s: %s\n", + message->path, strerror(errno)); goto err0; } @@ -439,10 +441,12 @@ quit(struct weston_launch *wl, int status) if (ioctl(wl->tty, KDSKBMUTE, 0) && ioctl(wl->tty, KDSKBMODE, wl->kb_mode)) - fprintf(stderr, "failed to restore keyboard mode: %m\n"); + fprintf(stderr, "failed to restore keyboard mode: %s\n", + strerror(errno)); if (ioctl(wl->tty, KDSETMODE, KD_TEXT)) - fprintf(stderr, "failed to set KD_TEXT mode on tty: %m\n"); + fprintf(stderr, "failed to set KD_TEXT mode on tty: %s\n", + strerror(errno)); /* We have to drop master before we switch the VT back in * VT_AUTO, so we don't risk switching to a VT with another diff --git a/tests/ivi-layout-test-plugin.c b/tests/ivi-layout-test-plugin.c index 4799919cd..d7aa17d06 100644 --- a/tests/ivi-layout-test-plugin.c +++ b/tests/ivi-layout-test-plugin.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include "compositor/weston.h" @@ -198,7 +199,8 @@ idle_launch_client(void *data) pid = fork(); if (pid == -1) { - weston_log("fatal: failed to fork '%s': %m\n", launcher->exe); + weston_log("fatal: failed to fork '%s': %s\n", launcher->exe, + strerror(errno)); weston_compositor_exit_with_code(launcher->compositor, EXIT_FAILURE); return; @@ -208,8 +210,8 @@ idle_launch_client(void *data) sigfillset(&allsigs); sigprocmask(SIG_UNBLOCK, &allsigs, NULL); execl(launcher->exe, launcher->exe, NULL); - weston_log("compositor: executing '%s' failed: %m\n", - launcher->exe); + weston_log("compositor: executing '%s' failed: %s\n", + launcher->exe, strerror(errno)); _exit(EXIT_FAILURE); } diff --git a/tests/weston-test-runner.c b/tests/weston-test-runner.c index f0566ac06..9dbe43e30 100644 --- a/tests/weston-test-runner.c +++ b/tests/weston-test-runner.c @@ -104,7 +104,7 @@ exec_and_report_test(const struct weston_test *t, void *test_data, int iteration run_test(t, test_data, iteration); /* never returns */ if (waitid(P_ALL, 0, &info, WEXITED)) { - fprintf(stderr, "waitid failed: %m\n"); + fprintf(stderr, "waitid failed: %s\n", strerror(errno)); abort(); } diff --git a/tests/weston-test.c b/tests/weston-test.c index 34c1089df..eea73fb33 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "compositor/weston.h" @@ -649,7 +650,8 @@ idle_launch_client(void *data) sigfillset(&allsigs); sigprocmask(SIG_UNBLOCK, &allsigs, NULL); execl(path, path, NULL); - weston_log("compositor: executing '%s' failed: %m\n", path); + weston_log("compositor: executing '%s' failed: %s\n", path, + strerror(errno)); exit(EXIT_FAILURE); } diff --git a/xwayland/launcher.c b/xwayland/launcher.c index 2c7da1138..d71be145f 100644 --- a/xwayland/launcher.c +++ b/xwayland/launcher.c @@ -100,7 +100,8 @@ bind_to_abstract_socket(int display) "%c/tmp/.X11-unix/X%d", 0, display); size = offsetof(struct sockaddr_un, sun_path) + name_size; if (bind(fd, (struct sockaddr *) &addr, size) < 0) { - weston_log("failed to bind to @%s: %m\n", addr.sun_path + 1); + weston_log("failed to bind to @%s: %s\n", addr.sun_path + 1, + strerror(errno)); close(fd); return -1; } @@ -130,7 +131,8 @@ bind_to_unix_socket(int display) size = offsetof(struct sockaddr_un, sun_path) + name_size; unlink(addr.sun_path); if (bind(fd, (struct sockaddr *) &addr, size) < 0) { - weston_log("failed to bind to %s: %m\n", addr.sun_path); + weston_log("failed to bind to %s: %s\n", addr.sun_path, + strerror(errno)); close(fd); return -1; } diff --git a/xwayland/selection.c b/xwayland/selection.c index 411fceda8..e4d797aec 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "xwayland.h" #include "shared/helpers.h" @@ -59,7 +60,7 @@ writable_callback(int fd, uint32_t mask, void *data) wl_event_source_remove(wm->property_source); wm->property_source = NULL; close(fd); - weston_log("write error to target fd: %m\n"); + weston_log("write error to target fd: %s\n", strerror(errno)); return 1; } @@ -401,7 +402,8 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) len = read(fd, p, available); if (len == -1) { - weston_log("read error from data source: %m\n"); + weston_log("read error from data source: %s\n", + strerror(errno)); weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); if (wm->property_source) wl_event_source_remove(wm->property_source); @@ -494,7 +496,7 @@ weston_wm_send_data(struct weston_wm *wm, xcb_atom_t target, const char *mime_ty int p[2]; if (pipe2(p, O_CLOEXEC | O_NONBLOCK) == -1) { - weston_log("pipe2 failed: %m\n"); + weston_log("pipe2 failed: %s\n", strerror(errno)); weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); return; } From 78ef426892c1b6c265f6341be307dd917d472d3e Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Mon, 6 May 2019 20:15:06 +0300 Subject: [PATCH 0863/1642] libweston: Remove functions with no implementation/definition Seems that these functions: weston_compositor_fade()/weston_compositor_unlock() lost their implementation a while ago. Signed-off-by: Marius Vlad --- include/libweston/libweston.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index f30b66386..188700729 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -1750,12 +1750,8 @@ weston_output_damage(struct weston_output *output); void weston_compositor_schedule_repaint(struct weston_compositor *compositor); void -weston_compositor_fade(struct weston_compositor *compositor, float tint); -void weston_compositor_damage_all(struct weston_compositor *compositor); void -weston_compositor_unlock(struct weston_compositor *compositor); -void weston_compositor_wake(struct weston_compositor *compositor); void weston_compositor_offscreen(struct weston_compositor *compositor); From bc137e345fe3eccf48671fbc30d48caa180b1a97 Mon Sep 17 00:00:00 2001 From: Harish Krupo Date: Thu, 9 May 2019 15:26:04 +0530 Subject: [PATCH 0864/1642] gitlab-ci: Use gitlab.fdo URL for wayland-protocols Signed-off-by: Harish Krupo --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fcb13e5a2..ca0f61155 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,7 +29,7 @@ container_prep: stage: build image: $DEBIAN_CONTAINER_IMAGE before_script: - - git clone --depth=1 git://anongit.freedesktop.org/git/wayland/wayland-protocols + - git clone --depth=1 https://gitlab.freedesktop.org/wayland/wayland-protocols - export WAYLAND_PROTOCOLS_DIR="$(pwd)/prefix-wayland-protocols" - export PKG_CONFIG_PATH="$WAYLAND_PROTOCOLS_DIR/share/pkgconfig:$PKG_CONFIG_PATH" - export MAKEFLAGS="-j4" From 880b485d76c032877d6197fdde39f2763fa3cd3f Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Sun, 7 Apr 2019 17:07:58 +0300 Subject: [PATCH 0865/1642] libweston: Decouple weston_debug_compositor from weston_compositor This patch allows initialization of weston-debug/log framework much earlier than weston_compositor, which in turn will provide the option start logging before weston_compositor has been created. Signed-off-by: Marius Vlad --- compositor/main.c | 9 ++++++++- include/libweston/libweston.h | 9 +++++++-- libweston/compositor.c | 7 +++++-- libweston/weston-debug.c | 36 +++++++++++++++++++++++++---------- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 280211e32..090bb2514 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2918,6 +2918,7 @@ int main(int argc, char *argv[]) struct wl_listener primary_client_destroyed; struct weston_seat *seat; struct wet_compositor wet = { 0 }; + struct weston_debug_compositor *wdc = NULL; int require_input; sigset_t mask; @@ -2961,6 +2962,12 @@ int main(int argc, char *argv[]) return EXIT_SUCCESS; } + wdc = weston_debug_compositor_create(); + if (!wdc) { + fprintf(stderr, "Failed to initialize weston debug framework.\n"); + return EXIT_FAILURE; + } + weston_log_set_handler(vlog, vlog_continue); weston_log_file_open(log); @@ -3025,7 +3032,7 @@ int main(int argc, char *argv[]) backend = weston_choose_default_backend(); } - wet.compositor = weston_compositor_create(display, &wet); + wet.compositor = weston_compositor_create(display, wdc, &wet); if (wet.compositor == NULL) { weston_log("fatal: failed to create compositor\n"); goto out; diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 188700729..eb8e6be53 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -1982,7 +1982,8 @@ weston_compositor_print_scene_graph(struct weston_compositor *ec); void weston_compositor_destroy(struct weston_compositor *ec); struct weston_compositor * -weston_compositor_create(struct wl_display *display, void *user_data); +weston_compositor_create(struct wl_display *display, + struct weston_debug_compositor *wdc, void *user_data); enum weston_compositor_backend { WESTON_BACKEND_DRM, @@ -2376,8 +2377,12 @@ int weston_compositor_enable_touch_calibrator(struct weston_compositor *compositor, weston_touch_calibration_save_func save); +struct weston_debug_compositor * +weston_debug_compositor_create(void); + int -weston_debug_compositor_create(struct weston_compositor *compositor); +weston_debug_compositor_setup(struct weston_compositor *compositor, + struct weston_debug_compositor *wdc); void weston_debug_compositor_destroy(struct weston_compositor *compositor); diff --git a/libweston/compositor.c b/libweston/compositor.c index 4dc52ec39..050f37f6d 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6782,11 +6782,14 @@ debug_scene_graph_cb(struct weston_debug_stream *stream, void *data) * * \param display The Wayland display to be used. * \param user_data A pointer to an object that can later be retrieved + * \param wdc A pointer to weston_debug_compositor * using the \ref weston_compositor_get_user_data function. * \return The compositor instance on success or NULL on failure. */ WL_EXPORT struct weston_compositor * -weston_compositor_create(struct wl_display *display, void *user_data) +weston_compositor_create(struct wl_display *display, + struct weston_debug_compositor *wdc, + void *user_data) { struct weston_compositor *ec; struct wl_event_loop *loop; @@ -6840,7 +6843,7 @@ weston_compositor_create(struct wl_display *display, void *user_data) ec, bind_presentation)) goto fail; - if (weston_debug_compositor_create(ec) < 0) + if (weston_debug_compositor_setup(ec, wdc) < 0) goto fail; if (weston_input_init(ec) != 0) diff --git a/libweston/weston-debug.c b/libweston/weston-debug.c index 0536562a9..f859ca851 100644 --- a/libweston/weston-debug.c +++ b/libweston/weston-debug.c @@ -253,6 +253,28 @@ bind_weston_debug(struct wl_client *client, } } +/** + * Connect weston-compositor structure to weston-debug structure + * an vice versa. + * + * \param compositor + * \param wdc + * \return 0 on success, -1 on failure + * + */ +int +weston_debug_compositor_setup(struct weston_compositor *compositor, + struct weston_debug_compositor *wdc) +{ + if (compositor->weston_debug) + return -1; + + wdc->compositor = compositor; + compositor->weston_debug = wdc; + + return 0; +} + /** Initialize weston-debug structure * * \param compositor The libweston compositor. @@ -264,24 +286,18 @@ bind_weston_debug(struct wl_client *client, * * \internal */ -int -weston_debug_compositor_create(struct weston_compositor *compositor) +WL_EXPORT struct weston_debug_compositor * +weston_debug_compositor_create(void) { struct weston_debug_compositor *wdc; - if (compositor->weston_debug) - return -1; - wdc = zalloc(sizeof *wdc); if (!wdc) - return -1; + return NULL; - wdc->compositor = compositor; wl_list_init(&wdc->scope_list); - compositor->weston_debug = wdc; - - return 0; + return wdc; } /** Destroy weston_debug_compositor structure From 1e2fda2ea1c8bd998132757b8c2f6eedc743e1dd Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Sun, 7 Apr 2019 19:07:16 +0300 Subject: [PATCH 0866/1642] compositor: Convert weston-debug framework to use weston_debug_compositor Signed-off-by: Marius Vlad --- compositor/main.c | 4 ++-- libweston/compositor-drm.c | 3 ++- libweston/compositor.c | 2 +- libweston/weston-debug.c | 21 +++++++++------------ libweston/weston-debug.h | 4 +++- xwayland/launcher.c | 9 +++++---- 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 090bb2514..b14c8dc38 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -3039,10 +3039,10 @@ int main(int argc, char *argv[]) } segv_compositor = wet.compositor; - log_scope = weston_compositor_add_debug_scope(wet.compositor, "log", + log_scope = weston_compositor_add_debug_scope(wdc, "log", "Weston and Wayland log\n", NULL, NULL); protocol_scope = - weston_compositor_add_debug_scope(wet.compositor, + weston_compositor_add_debug_scope(wdc, "proto", "Wayland protocol dump for all clients.\n", NULL, NULL); diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index bbeb1cfbd..0e5cabef9 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -7499,7 +7499,8 @@ drm_backend_create(struct weston_compositor *compositor, b->pageflip_timeout = config->pageflip_timeout; b->use_pixman_shadow = config->use_pixman_shadow; - b->debug = weston_compositor_add_debug_scope(compositor, "drm-backend", + b->debug = weston_compositor_add_debug_scope(compositor->weston_debug, + "drm-backend", "Debug messages from DRM/KMS backend\n", NULL, NULL); diff --git a/libweston/compositor.c b/libweston/compositor.c index 050f37f6d..bbaaadad5 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6889,7 +6889,7 @@ weston_compositor_create(struct wl_display *display, timeline_key_binding_handler, ec); ec->debug_scene = - weston_compositor_add_debug_scope(ec, "scene-graph", + weston_compositor_add_debug_scope(ec->weston_debug, "scene-graph", "Scene graph details\n", debug_scene_graph_cb, ec); diff --git a/libweston/weston-debug.c b/libweston/weston-debug.c index f859ca851..e183d01cb 100644 --- a/libweston/weston-debug.c +++ b/libweston/weston-debug.c @@ -349,16 +349,15 @@ weston_debug_compositor_destroy(struct weston_compositor *compositor) WL_EXPORT void weston_compositor_enable_debug_protocol(struct weston_compositor *compositor) { - struct weston_debug_compositor *wdc = compositor->weston_debug; - - assert(wdc); - if (wdc->global) + struct weston_log_context *log_ctx = compositor->weston_log_ctx; + assert(log_ctx); + if (log_ctx->global) return; - wdc->global = wl_global_create(compositor->wl_display, + log_ctx->global = wl_global_create(compositor->wl_display, &weston_debug_v1_interface, 1, - wdc, bind_weston_debug); - if (!wdc->global) + log_ctx, bind_weston_debug); + if (!log_ctx->global) return; weston_log("WARNING: debug protocol has been enabled. " @@ -378,7 +377,7 @@ weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) /** Register a new debug stream name, creating a debug scope * - * \param compositor The libweston compositor where to add. + * \param wdc The weston_debug_compositor where to add. * \param name The debug stream/scope name; must not be NULL. * \param desc The debug scope description for humans; must not be NULL. * \param begin_cb Optional callback when a client subscribes to this scope. @@ -411,21 +410,19 @@ weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) * \sa weston_debug_stream, weston_debug_scope_cb */ WL_EXPORT struct weston_debug_scope * -weston_compositor_add_debug_scope(struct weston_compositor *compositor, +weston_compositor_add_debug_scope(struct weston_debug_compositor *wdc, const char *name, const char *description, weston_debug_scope_cb begin_cb, void *user_data) { - struct weston_debug_compositor *wdc; struct weston_debug_scope *scope; - if (!compositor || !name || !description) { + if (!name || !description) { weston_log("Error: cannot add a debug scope without name or description.\n"); return NULL; } - wdc = compositor->weston_debug; if (!wdc) { weston_log("Error: cannot add debug scope '%s', infra not initialized.\n", name); diff --git a/libweston/weston-debug.h b/libweston/weston-debug.h index 157752509..7b8c884eb 100644 --- a/libweston/weston-debug.h +++ b/libweston/weston-debug.h @@ -35,6 +35,8 @@ extern "C" { #endif struct weston_compositor; +struct weston_debug_compositor; +struct wl_display; void weston_compositor_enable_debug_protocol(struct weston_compositor *); @@ -58,7 +60,7 @@ typedef void (*weston_debug_scope_cb)(struct weston_debug_stream *stream, void *user_data); struct weston_debug_scope * -weston_compositor_add_debug_scope(struct weston_compositor *compositor, +weston_compositor_add_debug_scope(struct weston_debug_compositor *compositor, const char *name, const char *description, weston_debug_scope_cb begin_cb, diff --git a/xwayland/launcher.c b/xwayland/launcher.c index d71be145f..f406df002 100644 --- a/xwayland/launcher.c +++ b/xwayland/launcher.c @@ -394,10 +394,11 @@ weston_module_init(struct weston_compositor *compositor) wxs->destroy_listener.notify = weston_xserver_destroy; wl_signal_add(&compositor->destroy_signal, &wxs->destroy_listener); - wxs->wm_debug = weston_compositor_add_debug_scope(wxs->compositor, - "xwm-wm-x11", - "XWM's window management X11 events\n", - NULL, NULL); + wxs->wm_debug = + weston_compositor_add_debug_scope(wxs->compositor->weston_debug, + "xwm-wm-x11", + "XWM's window management X11 events\n", + NULL, NULL); return 0; } From 3d7d978c2135537d13dfeaa273c1b49d321350e2 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Wed, 17 Apr 2019 12:35:38 +0300 Subject: [PATCH 0867/1642] libweston: Rename weston_debug_compositor to weston_log_context As we transition towards a more generic API for weston loggging framework rename weston_debug_compositor to weston_log_context to show the fact that this is not really debug but a logging context. Signed-off-by: Marius Vlad --- compositor/main.c | 12 ++-- include/libweston/libweston.h | 14 ++--- libweston/compositor-drm.c | 2 +- libweston/compositor.c | 8 +-- libweston/weston-debug.c | 102 ++++++++++++++++------------------ libweston/weston-debug.h | 4 +- xwayland/launcher.c | 2 +- 7 files changed, 70 insertions(+), 74 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index b14c8dc38..c7a4fa8ad 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2918,7 +2918,7 @@ int main(int argc, char *argv[]) struct wl_listener primary_client_destroyed; struct weston_seat *seat; struct wet_compositor wet = { 0 }; - struct weston_debug_compositor *wdc = NULL; + struct weston_log_context *log_ctx = NULL; int require_input; sigset_t mask; @@ -2962,8 +2962,8 @@ int main(int argc, char *argv[]) return EXIT_SUCCESS; } - wdc = weston_debug_compositor_create(); - if (!wdc) { + log_ctx = weston_log_ctx_compositor_create(); + if (!log_ctx) { fprintf(stderr, "Failed to initialize weston debug framework.\n"); return EXIT_FAILURE; } @@ -3032,17 +3032,17 @@ int main(int argc, char *argv[]) backend = weston_choose_default_backend(); } - wet.compositor = weston_compositor_create(display, wdc, &wet); + wet.compositor = weston_compositor_create(display, log_ctx, &wet); if (wet.compositor == NULL) { weston_log("fatal: failed to create compositor\n"); goto out; } segv_compositor = wet.compositor; - log_scope = weston_compositor_add_debug_scope(wdc, "log", + log_scope = weston_compositor_add_debug_scope(log_ctx, "log", "Weston and Wayland log\n", NULL, NULL); protocol_scope = - weston_compositor_add_debug_scope(wdc, + weston_compositor_add_debug_scope(log_ctx, "proto", "Wayland protocol dump for all clients.\n", NULL, NULL); diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index eb8e6be53..3e6f7277a 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -1167,7 +1167,7 @@ struct weston_compositor { struct weston_layer calibrator_layer; struct weston_touch_calibrator *touch_calibrator; - struct weston_debug_compositor *weston_debug; + struct weston_log_context *weston_log_ctx; struct weston_debug_scope *debug_scene; }; @@ -1983,7 +1983,7 @@ void weston_compositor_destroy(struct weston_compositor *ec); struct weston_compositor * weston_compositor_create(struct wl_display *display, - struct weston_debug_compositor *wdc, void *user_data); + struct weston_log_context *log_ctx, void *user_data); enum weston_compositor_backend { WESTON_BACKEND_DRM, @@ -2377,15 +2377,15 @@ int weston_compositor_enable_touch_calibrator(struct weston_compositor *compositor, weston_touch_calibration_save_func save); -struct weston_debug_compositor * -weston_debug_compositor_create(void); +struct weston_log_context * +weston_log_ctx_compositor_create(void); int -weston_debug_compositor_setup(struct weston_compositor *compositor, - struct weston_debug_compositor *wdc); +weston_log_ctx_compositor_setup(struct weston_compositor *compositor, + struct weston_log_context *log_ctx); void -weston_debug_compositor_destroy(struct weston_compositor *compositor); +weston_log_ctx_compositor_destroy(struct weston_compositor *compositor); void weston_buffer_send_server_error(struct weston_buffer *buffer, diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 0e5cabef9..b0e0ca536 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -7499,7 +7499,7 @@ drm_backend_create(struct weston_compositor *compositor, b->pageflip_timeout = config->pageflip_timeout; b->use_pixman_shadow = config->use_pixman_shadow; - b->debug = weston_compositor_add_debug_scope(compositor->weston_debug, + b->debug = weston_compositor_add_debug_scope(compositor->weston_log_ctx, "drm-backend", "Debug messages from DRM/KMS backend\n", NULL, NULL); diff --git a/libweston/compositor.c b/libweston/compositor.c index bbaaadad5..aefd99b2a 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6788,7 +6788,7 @@ debug_scene_graph_cb(struct weston_debug_stream *stream, void *data) */ WL_EXPORT struct weston_compositor * weston_compositor_create(struct wl_display *display, - struct weston_debug_compositor *wdc, + struct weston_log_context *log_ctx, void *user_data) { struct weston_compositor *ec; @@ -6843,7 +6843,7 @@ weston_compositor_create(struct wl_display *display, ec, bind_presentation)) goto fail; - if (weston_debug_compositor_setup(ec, wdc) < 0) + if (weston_log_ctx_compositor_setup(ec, log_ctx) < 0) goto fail; if (weston_input_init(ec) != 0) @@ -6889,7 +6889,7 @@ weston_compositor_create(struct wl_display *display, timeline_key_binding_handler, ec); ec->debug_scene = - weston_compositor_add_debug_scope(ec->weston_debug, "scene-graph", + weston_compositor_add_debug_scope(ec->weston_log_ctx, "scene-graph", "Scene graph details\n", debug_scene_graph_cb, ec); @@ -7196,7 +7196,7 @@ weston_compositor_destroy(struct weston_compositor *compositor) weston_debug_scope_destroy(compositor->debug_scene); compositor->debug_scene = NULL; - weston_debug_compositor_destroy(compositor); + weston_log_ctx_compositor_destroy(compositor); free(compositor); } diff --git a/libweston/weston-debug.c b/libweston/weston-debug.c index e183d01cb..3534953e1 100644 --- a/libweston/weston-debug.c +++ b/libweston/weston-debug.c @@ -39,13 +39,13 @@ #include #include -/** Main weston-debug context +/** Main weston-log context * * One per weston_compositor. * * \internal */ -struct weston_debug_compositor { +struct weston_log_context { struct weston_compositor *compositor; struct wl_listener compositor_destroy_listener; struct wl_global *global; @@ -82,11 +82,11 @@ struct weston_debug_stream { }; static struct weston_debug_scope * -get_scope(struct weston_debug_compositor *wdc, const char *name) +get_scope(struct weston_log_context *log_ctx, const char *name) { struct weston_debug_scope *scope; - wl_list_for_each(scope, &wdc->scope_list, compositor_link) + wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) if (strcmp(name, scope->name) == 0) return scope; @@ -128,7 +128,7 @@ stream_close_on_failure(struct weston_debug_stream *stream, } static struct weston_debug_stream * -stream_create(struct weston_debug_compositor *wdc, const char *name, +stream_create(struct weston_log_context *log_ctx, const char *name, int32_t streamfd, struct wl_resource *stream_resource) { struct weston_debug_stream *stream; @@ -141,7 +141,7 @@ stream_create(struct weston_debug_compositor *wdc, const char *name, stream->fd = streamfd; stream->resource = stream_resource; - scope = get_scope(wdc, name); + scope = get_scope(log_ctx, name); if (scope) { wl_list_insert(&scope->stream_list, &stream->scope_link); @@ -196,12 +196,12 @@ weston_debug_subscribe(struct wl_client *client, int32_t streamfd, uint32_t new_stream_id) { - struct weston_debug_compositor *wdc; + struct weston_log_context *log_ctx; struct wl_resource *stream_resource; uint32_t version; struct weston_debug_stream *stream; - wdc = wl_resource_get_user_data(global_resource); + log_ctx = wl_resource_get_user_data(global_resource); version = wl_resource_get_version(global_resource); stream_resource = wl_resource_create(client, @@ -210,7 +210,7 @@ weston_debug_subscribe(struct wl_client *client, if (!stream_resource) goto fail; - stream = stream_create(wdc, name, streamfd, stream_resource); + stream = stream_create(log_ctx, name, streamfd, stream_resource); if (!stream) goto fail; @@ -233,7 +233,7 @@ static void bind_weston_debug(struct wl_client *client, void *data, uint32_t version, uint32_t id) { - struct weston_debug_compositor *wdc = data; + struct weston_log_context *log_ctx = data; struct weston_debug_scope *scope; struct wl_resource *resource; @@ -245,88 +245,84 @@ bind_weston_debug(struct wl_client *client, return; } wl_resource_set_implementation(resource, &weston_debug_impl, - wdc, NULL); + log_ctx, NULL); - wl_list_for_each(scope, &wdc->scope_list, compositor_link) { + wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) { weston_debug_v1_send_available(resource, scope->name, scope->desc); } } /** - * Connect weston-compositor structure to weston-debug structure - * an vice versa. + * Connect weston_compositor structure to weston_log_context structure. * * \param compositor - * \param wdc + * \param log_ctx * \return 0 on success, -1 on failure * + * Sets weston_compositor::weston_log_ctx. */ int -weston_debug_compositor_setup(struct weston_compositor *compositor, - struct weston_debug_compositor *wdc) +weston_log_ctx_compositor_setup(struct weston_compositor *compositor, + struct weston_log_context *log_ctx) { - if (compositor->weston_debug) + if (compositor->weston_log_ctx) return -1; - wdc->compositor = compositor; - compositor->weston_debug = wdc; + log_ctx->compositor = compositor; + compositor->weston_log_ctx = log_ctx; return 0; } -/** Initialize weston-debug structure +/** Initialize weston_log_context structure * * \param compositor The libweston compositor. * \return 0 on success, -1 on failure. * - * weston_debug_compositor is a singleton for each weston_compositor. + * weston_log_context is a singleton for each weston_compositor. * - * Sets weston_compositor::weston_debug. - * - * \internal */ -WL_EXPORT struct weston_debug_compositor * -weston_debug_compositor_create(void) +WL_EXPORT struct weston_log_context * +weston_log_ctx_compositor_create(void) { - struct weston_debug_compositor *wdc; + struct weston_log_context *log_ctx; - wdc = zalloc(sizeof *wdc); - if (!wdc) + log_ctx = zalloc(sizeof *log_ctx); + if (!log_ctx) return NULL; - wl_list_init(&wdc->scope_list); + wl_list_init(&log_ctx->scope_list); - return wdc; + return log_ctx; } -/** Destroy weston_debug_compositor structure +/** Destroy weston_log_context structure * * \param compositor The libweston compositor whose weston-debug to tear down. * - * Clears weston_compositor::weston_debug. + * Clears weston_compositor::weston_log_ctx. * - * \internal */ -void -weston_debug_compositor_destroy(struct weston_compositor *compositor) +WL_EXPORT void +weston_log_ctx_compositor_destroy(struct weston_compositor *compositor) { - struct weston_debug_compositor *wdc = compositor->weston_debug; + struct weston_log_context *log_ctx = compositor->weston_log_ctx; struct weston_debug_scope *scope; - if (wdc->global) - wl_global_destroy(wdc->global); + if (log_ctx->global) + wl_global_destroy(log_ctx->global); - wl_list_for_each(scope, &wdc->scope_list, compositor_link) + wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) weston_log("Internal warning: debug scope '%s' has not been destroyed.\n", scope->name); /* Remove head to not crash if scope removed later. */ - wl_list_remove(&wdc->scope_list); + wl_list_remove(&log_ctx->scope_list); - free(wdc); + free(log_ctx); - compositor->weston_debug = NULL; + compositor->weston_log_ctx = NULL; } /** Enable weston-debug protocol extension @@ -372,12 +368,12 @@ weston_compositor_enable_debug_protocol(struct weston_compositor *compositor) WL_EXPORT bool weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) { - return wc->weston_debug->global != NULL; + return wc->weston_log_ctx->global != NULL; } /** Register a new debug stream name, creating a debug scope * - * \param wdc The weston_debug_compositor where to add. + * \param log_ctx The weston_log_context where to add. * \param name The debug stream/scope name; must not be NULL. * \param desc The debug scope description for humans; must not be NULL. * \param begin_cb Optional callback when a client subscribes to this scope. @@ -410,7 +406,7 @@ weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) * \sa weston_debug_stream, weston_debug_scope_cb */ WL_EXPORT struct weston_debug_scope * -weston_compositor_add_debug_scope(struct weston_debug_compositor *wdc, +weston_compositor_add_debug_scope(struct weston_log_context *log_ctx, const char *name, const char *description, weston_debug_scope_cb begin_cb, @@ -423,13 +419,13 @@ weston_compositor_add_debug_scope(struct weston_debug_compositor *wdc, return NULL; } - if (!wdc) { + if (!log_ctx) { weston_log("Error: cannot add debug scope '%s', infra not initialized.\n", name); return NULL; } - if (get_scope(wdc, name)){ + if (get_scope(log_ctx, name)){ weston_log("Error: debug scope named '%s' is already registered.\n", name); return NULL; @@ -457,16 +453,16 @@ weston_compositor_add_debug_scope(struct weston_debug_compositor *wdc, return NULL; } - wl_list_insert(wdc->scope_list.prev, &scope->compositor_link); + wl_list_insert(log_ctx->scope_list.prev, &scope->compositor_link); return scope; } -/** Destroy a debug scope +/** Destroy a log scope * - * \param scope The debug scope to destroy; may be NULL. + * \param scope The log scope to destroy; may be NULL. * - * Destroys the debug scope, closing all open streams subscribed to it and + * Destroys the log scope, closing all open streams subscribed to it and * sending them each a \c weston_debug_stream_v1.failure event. * * \memberof weston_debug_scope diff --git a/libweston/weston-debug.h b/libweston/weston-debug.h index 7b8c884eb..d63b96881 100644 --- a/libweston/weston-debug.h +++ b/libweston/weston-debug.h @@ -35,7 +35,7 @@ extern "C" { #endif struct weston_compositor; -struct weston_debug_compositor; +struct weston_log_context; struct wl_display; void @@ -60,7 +60,7 @@ typedef void (*weston_debug_scope_cb)(struct weston_debug_stream *stream, void *user_data); struct weston_debug_scope * -weston_compositor_add_debug_scope(struct weston_debug_compositor *compositor, +weston_compositor_add_debug_scope(struct weston_log_context *compositor, const char *name, const char *description, weston_debug_scope_cb begin_cb, diff --git a/xwayland/launcher.c b/xwayland/launcher.c index f406df002..339b6464c 100644 --- a/xwayland/launcher.c +++ b/xwayland/launcher.c @@ -395,7 +395,7 @@ weston_module_init(struct weston_compositor *compositor) wl_signal_add(&compositor->destroy_signal, &wxs->destroy_listener); wxs->wm_debug = - weston_compositor_add_debug_scope(wxs->compositor->weston_debug, + weston_compositor_add_debug_scope(wxs->compositor->weston_log_ctx, "xwm-wm-x11", "XWM's window management X11 events\n", NULL, NULL); From 5d5e3358454eb6eaf6410db688c8a62073093e55 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Wed, 17 Apr 2019 13:05:38 +0300 Subject: [PATCH 0868/1642] libweston: Rename weston_debug_scope to weston_log_scope This is a continuation of the previous patch to align more closely to the weston log framework. Signed-off-by: Marius Vlad --- compositor/main.c | 4 +-- include/libweston/libweston.h | 2 +- libweston/compositor-drm.c | 2 +- libweston/weston-debug.c | 52 +++++++++++++++++------------------ libweston/weston-debug.h | 24 ++++++++-------- xwayland/xwayland.h | 2 +- 6 files changed, 43 insertions(+), 43 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index c7a4fa8ad..82a01dab5 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -122,8 +122,8 @@ struct wet_compositor { }; static FILE *weston_logfile = NULL; -static struct weston_debug_scope *log_scope; -static struct weston_debug_scope *protocol_scope; +static struct weston_log_scope *log_scope; +static struct weston_log_scope *protocol_scope; static int cached_tm_mday = -1; diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 3e6f7277a..ccd662ce7 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -1168,7 +1168,7 @@ struct weston_compositor { struct weston_touch_calibrator *touch_calibrator; struct weston_log_context *weston_log_ctx; - struct weston_debug_scope *debug_scene; + struct weston_log_scope *debug_scene; }; struct weston_buffer { diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index b0e0ca536..542008719 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -354,7 +354,7 @@ struct drm_backend { bool fb_modifiers; - struct weston_debug_scope *debug; + struct weston_log_scope *debug; }; struct drm_mode { diff --git a/libweston/weston-debug.c b/libweston/weston-debug.c index 3534953e1..018ed5da8 100644 --- a/libweston/weston-debug.c +++ b/libweston/weston-debug.c @@ -49,19 +49,19 @@ struct weston_log_context { struct weston_compositor *compositor; struct wl_listener compositor_destroy_listener; struct wl_global *global; - struct wl_list scope_list; /**< weston_debug_scope::compositor_link */ + struct wl_list scope_list; /**< weston_log_scope::compositor_link */ }; -/** weston-debug message scope +/** weston-log message scope * * This is used for scoping debugging messages. Clients can subscribe to * only the scopes they are interested in. A scope is identified by its name * (also referred to as debug stream name). */ -struct weston_debug_scope { +struct weston_log_scope { char *name; char *desc; - weston_debug_scope_cb begin_cb; + weston_log_scope_cb begin_cb; void *user_data; struct wl_list stream_list; /**< weston_debug_stream::scope_link */ struct wl_list compositor_link; @@ -71,7 +71,7 @@ struct weston_debug_scope { * * A client provides a file descriptor for the server to write debug * messages into. A weston_debug_stream is associated to one - * weston_debug_scope via the scope name, and the scope provides the messages. + * weston_log_scope via the scope name, and the scope provides the messages. * There can be several streams for the same scope, all streams getting the * same messages. */ @@ -81,10 +81,10 @@ struct weston_debug_stream { struct wl_list scope_link; }; -static struct weston_debug_scope * +static struct weston_log_scope * get_scope(struct weston_log_context *log_ctx, const char *name) { - struct weston_debug_scope *scope; + struct weston_log_scope *scope; wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) if (strcmp(name, scope->name) == 0) @@ -132,7 +132,7 @@ stream_create(struct weston_log_context *log_ctx, const char *name, int32_t streamfd, struct wl_resource *stream_resource) { struct weston_debug_stream *stream; - struct weston_debug_scope *scope; + struct weston_log_scope *scope; stream = zalloc(sizeof *stream); if (!stream) @@ -234,7 +234,7 @@ bind_weston_debug(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct weston_log_context *log_ctx = data; - struct weston_debug_scope *scope; + struct weston_log_scope *scope; struct wl_resource *resource; resource = wl_resource_create(client, @@ -308,7 +308,7 @@ WL_EXPORT void weston_log_ctx_compositor_destroy(struct weston_compositor *compositor) { struct weston_log_context *log_ctx = compositor->weston_log_ctx; - struct weston_debug_scope *scope; + struct weston_log_scope *scope; if (log_ctx->global) wl_global_destroy(log_ctx->global); @@ -402,17 +402,17 @@ weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) * The debug scope must be destroyed before destroying the * \c weston_compositor. * - * \memberof weston_debug_scope - * \sa weston_debug_stream, weston_debug_scope_cb + * \memberof weston_log_scope + * \sa weston_debug_stream, weston_log_scope_cb */ -WL_EXPORT struct weston_debug_scope * +WL_EXPORT struct weston_log_scope * weston_compositor_add_debug_scope(struct weston_log_context *log_ctx, const char *name, const char *description, - weston_debug_scope_cb begin_cb, + weston_log_scope_cb begin_cb, void *user_data) { - struct weston_debug_scope *scope; + struct weston_log_scope *scope; if (!name || !description) { weston_log("Error: cannot add a debug scope without name or description.\n"); @@ -465,10 +465,10 @@ weston_compositor_add_debug_scope(struct weston_log_context *log_ctx, * Destroys the log scope, closing all open streams subscribed to it and * sending them each a \c weston_debug_stream_v1.failure event. * - * \memberof weston_debug_scope + * \memberof weston_log_scope */ WL_EXPORT void -weston_debug_scope_destroy(struct weston_debug_scope *scope) +weston_debug_scope_destroy(struct weston_log_scope *scope) { struct weston_debug_stream *stream; @@ -507,10 +507,10 @@ weston_debug_scope_destroy(struct weston_debug_scope *scope) * scope is initialized to NULL before creation and set to NULL after * destruction. * - * \memberof weston_debug_scope + * \memberof weston_log_scope */ WL_EXPORT bool -weston_debug_scope_is_enabled(struct weston_debug_scope *scope) +weston_debug_scope_is_enabled(struct weston_log_scope *scope) { if (!scope) return false; @@ -638,10 +638,10 @@ weston_debug_stream_complete(struct weston_debug_stream *stream) * The behavioral details for each stream are the same as for * weston_debug_stream_write(). * - * \memberof weston_debug_scope + * \memberof weston_log_scope */ WL_EXPORT void -weston_debug_scope_write(struct weston_debug_scope *scope, +weston_debug_scope_write(struct weston_log_scope *scope, const char *data, size_t len) { struct weston_debug_stream *stream; @@ -665,10 +665,10 @@ weston_debug_scope_write(struct weston_debug_scope *scope, * The behavioral details for each stream are the same as for * weston_debug_stream_write(). * - * \memberof weston_debug_scope + * \memberof weston_log_scope */ WL_EXPORT void -weston_debug_scope_vprintf(struct weston_debug_scope *scope, +weston_debug_scope_vprintf(struct weston_log_scope *scope, const char *fmt, va_list ap) { static const char oom[] = "Out of memory"; @@ -698,10 +698,10 @@ weston_debug_scope_vprintf(struct weston_debug_scope *scope, * The behavioral details for each stream are the same as for * weston_debug_stream_write(). * - * \memberof weston_debug_scope + * \memberof weston_log_scope */ WL_EXPORT void -weston_debug_scope_printf(struct weston_debug_scope *scope, +weston_debug_scope_printf(struct weston_log_scope *scope, const char *fmt, ...) { va_list ap; @@ -723,7 +723,7 @@ weston_debug_scope_printf(struct weston_debug_scope *scope, * The string is NUL-terminated, even if truncated. */ WL_EXPORT char * -weston_debug_scope_timestamp(struct weston_debug_scope *scope, +weston_debug_scope_timestamp(struct weston_log_scope *scope, char *buf, size_t len) { struct timeval tv; diff --git a/libweston/weston-debug.h b/libweston/weston-debug.h index d63b96881..da29bf4aa 100644 --- a/libweston/weston-debug.h +++ b/libweston/weston-debug.h @@ -44,44 +44,44 @@ weston_compositor_enable_debug_protocol(struct weston_compositor *); bool weston_compositor_is_debug_protocol_enabled(struct weston_compositor *); -struct weston_debug_scope; +struct weston_log_scope; struct weston_debug_stream; -/** weston_debug_scope callback +/** weston_log_scope callback * * \param stream The debug stream. * \param user_data The \c user_data argument given to * weston_compositor_add_debug_scope() * - * \memberof weston_debug_scope + * \memberof weston_log_scope * \sa weston_debug_stream */ -typedef void (*weston_debug_scope_cb)(struct weston_debug_stream *stream, +typedef void (*weston_log_scope_cb)(struct weston_debug_stream *stream, void *user_data); -struct weston_debug_scope * +struct weston_log_scope * weston_compositor_add_debug_scope(struct weston_log_context *compositor, const char *name, const char *description, - weston_debug_scope_cb begin_cb, + weston_log_scope_cb begin_cb, void *user_data); void -weston_debug_scope_destroy(struct weston_debug_scope *scope); +weston_debug_scope_destroy(struct weston_log_scope *scope); bool -weston_debug_scope_is_enabled(struct weston_debug_scope *scope); +weston_debug_scope_is_enabled(struct weston_log_scope *scope); void -weston_debug_scope_write(struct weston_debug_scope *scope, +weston_debug_scope_write(struct weston_log_scope *scope, const char *data, size_t len); void -weston_debug_scope_vprintf(struct weston_debug_scope *scope, +weston_debug_scope_vprintf(struct weston_log_scope *scope, const char *fmt, va_list ap); void -weston_debug_scope_printf(struct weston_debug_scope *scope, +weston_debug_scope_printf(struct weston_log_scope *scope, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); @@ -102,7 +102,7 @@ void weston_debug_stream_complete(struct weston_debug_stream *stream); char * -weston_debug_scope_timestamp(struct weston_debug_scope *scope, +weston_debug_scope_timestamp(struct weston_log_scope *scope, char *buf, size_t len); #ifdef __cplusplus diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index d474a511b..1c3235028 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -54,7 +54,7 @@ struct weston_xserver { weston_xwayland_spawn_xserver_func_t spawn_func; void *user_data; - struct weston_debug_scope *wm_debug; + struct weston_log_scope *wm_debug; }; struct weston_wm { From 7e4db953735b9d47ef8907bd5a0b0823db3f7b1c Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Wed, 17 Apr 2019 13:47:06 +0300 Subject: [PATCH 0869/1642] libweston: Rename weston_debug_scope_ to weston_log_scope_ Rename also the functions which work on weston_log_scope. Signed-off-by: Marius Vlad --- compositor/main.c | 36 +++++++++++++-------------- libweston/compositor-drm.c | 14 +++++------ libweston/compositor.c | 10 ++++---- libweston/weston-debug.c | 50 +++++++++++++++++++------------------- libweston/weston-debug.h | 16 ++++++------ xwayland/launcher.c | 10 ++++---- xwayland/window-manager.c | 12 ++++----- 7 files changed, 74 insertions(+), 74 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 82a01dab5..576809eed 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -163,10 +163,10 @@ custom_handler(const char *fmt, va_list arg) vfprintf(weston_logfile, fmt, arg2); va_end(arg2); - weston_debug_scope_printf(log_scope, "%s libwayland: ", - weston_debug_scope_timestamp(log_scope, + weston_log_scope_printf(log_scope, "%s libwayland: ", + weston_log_scope_timestamp(log_scope, timestr, sizeof timestr)); - weston_debug_scope_vprintf(log_scope, fmt, arg); + weston_log_scope_vprintf(log_scope, fmt, arg); } static void @@ -203,11 +203,11 @@ vlog(const char *fmt, va_list ap) va_copy(ap2, ap); - if (weston_debug_scope_is_enabled(log_scope)) { - weston_debug_scope_printf(log_scope, "%s ", - weston_debug_scope_timestamp(log_scope, + if (weston_log_scope_is_enabled(log_scope)) { + weston_log_scope_printf(log_scope, "%s ", + weston_log_scope_timestamp(log_scope, timestr, sizeof timestr)); - weston_debug_scope_vprintf(log_scope, fmt, ap); + weston_log_scope_vprintf(log_scope, fmt, ap); } l = weston_log_timestamp(); @@ -223,7 +223,7 @@ vlog_continue(const char *fmt, va_list argp) va_list argp2; va_copy(argp2, argp); - weston_debug_scope_vprintf(log_scope, fmt, argp2); + weston_log_scope_vprintf(log_scope, fmt, argp2); va_end(argp2); return vfprintf(weston_logfile, fmt, argp); @@ -264,14 +264,14 @@ protocol_log_fn(void *user_data, int i; char type; - if (!weston_debug_scope_is_enabled(protocol_scope)) + if (!weston_log_scope_is_enabled(protocol_scope)) return; fp = open_memstream(&logstr, &logsize); if (!fp) return; - weston_debug_scope_timestamp(protocol_scope, + weston_log_scope_timestamp(protocol_scope, timestr, sizeof timestr); fprintf(fp, "%s ", timestr); fprintf(fp, "client %p %s ", wl_resource_get_client(res), @@ -334,7 +334,7 @@ protocol_log_fn(void *user_data, fprintf(fp, ")\n"); if (fclose(fp) == 0) - weston_debug_scope_write(protocol_scope, logstr, logsize); + weston_log_scope_write(protocol_scope, logstr, logsize); free(logstr); } @@ -3039,13 +3039,13 @@ int main(int argc, char *argv[]) } segv_compositor = wet.compositor; - log_scope = weston_compositor_add_debug_scope(log_ctx, "log", + log_scope = weston_compositor_add_log_scope(log_ctx, "log", "Weston and Wayland log\n", NULL, NULL); protocol_scope = - weston_compositor_add_debug_scope(log_ctx, - "proto", - "Wayland protocol dump for all clients.\n", - NULL, NULL); + weston_compositor_add_log_scope(log_ctx, + "proto", + "Wayland protocol dump for all clients.\n", + NULL, NULL); if (debug_protocol) { protologger = wl_display_add_protocol_logger(display, @@ -3167,9 +3167,9 @@ int main(int argc, char *argv[]) if (protologger) wl_protocol_logger_destroy(protologger); - weston_debug_scope_destroy(protocol_scope); + weston_compositor_log_scope_destroy(protocol_scope); protocol_scope = NULL; - weston_debug_scope_destroy(log_scope); + weston_compositor_log_scope_destroy(log_scope); log_scope = NULL; weston_compositor_destroy(wet.compositor); diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 542008719..6d40e72b8 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -114,7 +114,7 @@ * possible type and use a matching format specifier. */ #define drm_debug(b, ...) \ - weston_debug_scope_printf((b)->debug, __VA_ARGS__) + weston_log_scope_printf((b)->debug, __VA_ARGS__) #define MAX_CLONED_CONNECTORS 4 @@ -3199,7 +3199,7 @@ drm_repaint_begin(struct weston_compositor *compositor) ret = drm_pending_state_alloc(b); b->repaint_data = ret; - if (weston_debug_scope_is_enabled(b->debug)) { + if (weston_log_scope_is_enabled(b->debug)) { char *dbg = weston_compositor_print_scene_graph(compositor); drm_debug(b, "[repaint] Beginning repaint; pending_state %p\n", ret); @@ -6769,7 +6769,7 @@ drm_destroy(struct weston_compositor *ec) destroy_sprites(b); - weston_debug_scope_destroy(b->debug); + weston_compositor_log_scope_destroy(b->debug); b->debug = NULL; weston_compositor_shutdown(ec); @@ -7499,10 +7499,10 @@ drm_backend_create(struct weston_compositor *compositor, b->pageflip_timeout = config->pageflip_timeout; b->use_pixman_shadow = config->use_pixman_shadow; - b->debug = weston_compositor_add_debug_scope(compositor->weston_log_ctx, - "drm-backend", - "Debug messages from DRM/KMS backend\n", - NULL, NULL); + b->debug = weston_compositor_add_log_scope(compositor->weston_log_ctx, + "drm-backend", + "Debug messages from DRM/KMS backend\n", + NULL, NULL); compositor->backend = &b->base; diff --git a/libweston/compositor.c b/libweston/compositor.c index aefd99b2a..da34ee75a 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6889,10 +6889,10 @@ weston_compositor_create(struct wl_display *display, timeline_key_binding_handler, ec); ec->debug_scene = - weston_compositor_add_debug_scope(ec->weston_log_ctx, "scene-graph", - "Scene graph details\n", - debug_scene_graph_cb, - ec); + weston_compositor_add_log_scope(ec->weston_log_ctx, "scene-graph", + "Scene graph details\n", + debug_scene_graph_cb, + ec); return ec; @@ -7194,7 +7194,7 @@ weston_compositor_destroy(struct weston_compositor *compositor) if (compositor->heads_changed_source) wl_event_source_remove(compositor->heads_changed_source); - weston_debug_scope_destroy(compositor->debug_scene); + weston_compositor_log_scope_destroy(compositor->debug_scene); compositor->debug_scene = NULL; weston_log_ctx_compositor_destroy(compositor); diff --git a/libweston/weston-debug.c b/libweston/weston-debug.c index 018ed5da8..b18d596ff 100644 --- a/libweston/weston-debug.c +++ b/libweston/weston-debug.c @@ -54,8 +54,8 @@ struct weston_log_context { /** weston-log message scope * - * This is used for scoping debugging messages. Clients can subscribe to - * only the scopes they are interested in. A scope is identified by its name + * This is used for scoping logging/debugging messages. Clients can subscribe + * to only the scopes they are interested in. A scope is identified by its name * (also referred to as debug stream name). */ struct weston_log_scope { @@ -371,7 +371,7 @@ weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) return wc->weston_log_ctx->global != NULL; } -/** Register a new debug stream name, creating a debug scope +/** Register a new debug stream name, creating a log scope * * \param log_ctx The weston_log_context where to add. * \param name The debug stream/scope name; must not be NULL. @@ -406,11 +406,11 @@ weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) * \sa weston_debug_stream, weston_log_scope_cb */ WL_EXPORT struct weston_log_scope * -weston_compositor_add_debug_scope(struct weston_log_context *log_ctx, - const char *name, - const char *description, - weston_log_scope_cb begin_cb, - void *user_data) +weston_compositor_add_log_scope(struct weston_log_context *log_ctx, + const char *name, + const char *description, + weston_log_scope_cb begin_cb, + void *user_data) { struct weston_log_scope *scope; @@ -468,7 +468,7 @@ weston_compositor_add_debug_scope(struct weston_log_context *log_ctx, * \memberof weston_log_scope */ WL_EXPORT void -weston_debug_scope_destroy(struct weston_log_scope *scope) +weston_compositor_log_scope_destroy(struct weston_log_scope *scope) { struct weston_debug_stream *stream; @@ -490,7 +490,7 @@ weston_debug_scope_destroy(struct weston_log_scope *scope) /** Are there any active subscriptions to the scope? * - * \param scope The debug scope to check; may be NULL. + * \param scope The log scope to check; may be NULL. * \return True if any streams are open for this scope, false otherwise. * * As printing some debugging messages may be relatively expensive, one @@ -510,7 +510,7 @@ weston_debug_scope_destroy(struct weston_log_scope *scope) * \memberof weston_log_scope */ WL_EXPORT bool -weston_debug_scope_is_enabled(struct weston_log_scope *scope) +weston_log_scope_is_enabled(struct weston_log_scope *scope) { if (!scope) return false; @@ -626,7 +626,7 @@ weston_debug_stream_complete(struct weston_debug_stream *stream) weston_debug_stream_v1_send_complete(stream->resource); } -/** Write debug data for a scope +/** Write log data for a scope * * \param scope The debug scope to write for; may be NULL, in which case * nothing will be written. @@ -641,8 +641,8 @@ weston_debug_stream_complete(struct weston_debug_stream *stream) * \memberof weston_log_scope */ WL_EXPORT void -weston_debug_scope_write(struct weston_log_scope *scope, - const char *data, size_t len) +weston_log_scope_write(struct weston_log_scope *scope, + const char *data, size_t len) { struct weston_debug_stream *stream; @@ -655,7 +655,7 @@ weston_debug_scope_write(struct weston_log_scope *scope, /** Write a formatted string for a scope (varargs) * - * \param scope The debug scope to write for; may be NULL, in which case + * \param scope The log scope to write for; may be NULL, in which case * nothing will be written. * \param fmt Printf-style format string. * \param ap Formatting arguments. @@ -668,28 +668,28 @@ weston_debug_scope_write(struct weston_log_scope *scope, * \memberof weston_log_scope */ WL_EXPORT void -weston_debug_scope_vprintf(struct weston_log_scope *scope, - const char *fmt, va_list ap) +weston_log_scope_vprintf(struct weston_log_scope *scope, + const char *fmt, va_list ap) { static const char oom[] = "Out of memory"; char *str; int len; - if (!weston_debug_scope_is_enabled(scope)) + if (!weston_log_scope_is_enabled(scope)) return; len = vasprintf(&str, fmt, ap); if (len >= 0) { - weston_debug_scope_write(scope, str, len); + weston_log_scope_write(scope, str, len); free(str); } else { - weston_debug_scope_write(scope, oom, sizeof oom - 1); + weston_log_scope_write(scope, oom, sizeof oom - 1); } } /** Write a formatted string for a scope * - * \param scope The debug scope to write for; may be NULL, in which case + * \param scope The log scope to write for; may be NULL, in which case * nothing will be written. * \param fmt Printf-style format string and arguments. * @@ -701,13 +701,13 @@ weston_debug_scope_vprintf(struct weston_log_scope *scope, * \memberof weston_log_scope */ WL_EXPORT void -weston_debug_scope_printf(struct weston_log_scope *scope, +weston_log_scope_printf(struct weston_log_scope *scope, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - weston_debug_scope_vprintf(scope, fmt, ap); + weston_log_scope_vprintf(scope, fmt, ap); va_end(ap); } @@ -723,8 +723,8 @@ weston_debug_scope_printf(struct weston_log_scope *scope, * The string is NUL-terminated, even if truncated. */ WL_EXPORT char * -weston_debug_scope_timestamp(struct weston_log_scope *scope, - char *buf, size_t len) +weston_log_scope_timestamp(struct weston_log_scope *scope, + char *buf, size_t len) { struct timeval tv; struct tm *bdt; diff --git a/libweston/weston-debug.h b/libweston/weston-debug.h index da29bf4aa..2dbaa534c 100644 --- a/libweston/weston-debug.h +++ b/libweston/weston-debug.h @@ -51,7 +51,7 @@ struct weston_debug_stream; * * \param stream The debug stream. * \param user_data The \c user_data argument given to - * weston_compositor_add_debug_scope() + * weston_compositor_add_log_scope() * * \memberof weston_log_scope * \sa weston_debug_stream @@ -60,28 +60,28 @@ typedef void (*weston_log_scope_cb)(struct weston_debug_stream *stream, void *user_data); struct weston_log_scope * -weston_compositor_add_debug_scope(struct weston_log_context *compositor, +weston_compositor_add_log_scope(struct weston_log_context *compositor, const char *name, const char *description, weston_log_scope_cb begin_cb, void *user_data); void -weston_debug_scope_destroy(struct weston_log_scope *scope); +weston_compositor_log_scope_destroy(struct weston_log_scope *scope); bool -weston_debug_scope_is_enabled(struct weston_log_scope *scope); +weston_log_scope_is_enabled(struct weston_log_scope *scope); void -weston_debug_scope_write(struct weston_log_scope *scope, +weston_log_scope_write(struct weston_log_scope *scope, const char *data, size_t len); void -weston_debug_scope_vprintf(struct weston_log_scope *scope, +weston_log_scope_vprintf(struct weston_log_scope *scope, const char *fmt, va_list ap); void -weston_debug_scope_printf(struct weston_log_scope *scope, +weston_log_scope_printf(struct weston_log_scope *scope, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); @@ -102,7 +102,7 @@ void weston_debug_stream_complete(struct weston_debug_stream *stream); char * -weston_debug_scope_timestamp(struct weston_log_scope *scope, +weston_log_scope_timestamp(struct weston_log_scope *scope, char *buf, size_t len); #ifdef __cplusplus diff --git a/xwayland/launcher.c b/xwayland/launcher.c index 339b6464c..283833847 100644 --- a/xwayland/launcher.c +++ b/xwayland/launcher.c @@ -230,7 +230,7 @@ weston_xserver_destroy(struct wl_listener *l, void *data) if (wxs->loop) weston_xserver_shutdown(wxs); - weston_debug_scope_destroy(wxs->wm_debug); + weston_compositor_log_scope_destroy(wxs->wm_debug); free(wxs); } @@ -395,10 +395,10 @@ weston_module_init(struct weston_compositor *compositor) wl_signal_add(&compositor->destroy_signal, &wxs->destroy_listener); wxs->wm_debug = - weston_compositor_add_debug_scope(wxs->compositor->weston_log_ctx, - "xwm-wm-x11", - "XWM's window management X11 events\n", - NULL, NULL); + weston_compositor_add_log_scope(wxs->compositor->weston_log_ctx, + "xwm-wm-x11", + "XWM's window management X11 events\n", + NULL, NULL); return 0; } diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index f5ca5ed9e..61460af88 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -196,7 +196,7 @@ xserver_map_shell_surface(struct weston_wm_window *window, static bool wm_debug_is_enabled(struct weston_wm *wm) { - return weston_debug_scope_is_enabled(wm->server->wm_debug); + return weston_log_scope_is_enabled(wm->server->wm_debug); } static void __attribute__ ((format (printf, 2, 3))) @@ -206,12 +206,12 @@ wm_printf(struct weston_wm *wm, const char *fmt, ...) char timestr[128]; if (wm_debug_is_enabled(wm)) - weston_debug_scope_printf(wm->server->wm_debug, "%s ", - weston_debug_scope_timestamp(wm->server->wm_debug, + weston_log_scope_printf(wm->server->wm_debug, "%s ", + weston_log_scope_timestamp(wm->server->wm_debug, timestr, sizeof timestr)); va_start(ap, fmt); - weston_debug_scope_vprintf(wm->server->wm_debug, fmt, ap); + weston_log_scope_vprintf(wm->server->wm_debug, fmt, ap); va_end(ap); } static void @@ -1379,7 +1379,7 @@ weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *even if (fp) { fprintf(fp, "%s XCB_PROPERTY_NOTIFY: window %d, ", - weston_debug_scope_timestamp(wm->server->wm_debug, + weston_log_scope_timestamp(wm->server->wm_debug, timestr, sizeof timestr), property_notify->window); if (property_notify->state == XCB_PROPERTY_DELETE) @@ -1390,7 +1390,7 @@ weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *even property_notify->atom); if (fclose(fp) == 0) - weston_debug_scope_write(wm->server->wm_debug, + weston_log_scope_write(wm->server->wm_debug, logstr, logsize); free(logstr); } else { From 6f098663c0380d63eaf19e61b47b3e979f6ef7bc Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Wed, 17 Apr 2019 13:50:28 +0300 Subject: [PATCH 0870/1642] weston-debug: Remove weston_compositor from weston_log_context This is no longer needed. Also assert if the context passed is NULL and compositor log context is already set. Signed-off-by: Marius Vlad --- libweston/weston-debug.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libweston/weston-debug.c b/libweston/weston-debug.c index b18d596ff..79b35a45a 100644 --- a/libweston/weston-debug.c +++ b/libweston/weston-debug.c @@ -46,7 +46,6 @@ * \internal */ struct weston_log_context { - struct weston_compositor *compositor; struct wl_listener compositor_destroy_listener; struct wl_global *global; struct wl_list scope_list; /**< weston_log_scope::compositor_link */ @@ -266,12 +265,10 @@ int weston_log_ctx_compositor_setup(struct weston_compositor *compositor, struct weston_log_context *log_ctx) { - if (compositor->weston_log_ctx) - return -1; + assert(!compositor->weston_log_ctx); + assert(log_ctx); - log_ctx->compositor = compositor; compositor->weston_log_ctx = log_ctx; - return 0; } From f4f4c2bcf10cbeb541871dfd117f2910c7fe3c60 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Mon, 29 Apr 2019 13:27:47 +0300 Subject: [PATCH 0871/1642] libweston: Add weston-debug header to libweston Signed-off-by: Marius Vlad --- compositor/main.c | 2 +- compositor/weston-screenshooter.c | 2 +- {libweston => include/libweston}/weston-debug.h | 0 libweston/compositor-drm.c | 2 +- libweston/compositor.c | 2 +- libweston/weston-debug.c | 2 +- xwayland/xwayland.h | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename {libweston => include/libweston}/weston-debug.h (100%) diff --git a/compositor/main.c b/compositor/main.c index 576809eed..df90b3c9a 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -62,7 +62,7 @@ #include #include #include -#include "weston-debug.h" +#include #include "../remoting/remoting-plugin.h" #define WINDOW_TITLE "Weston Compositor" diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c index 55fbf8dfc..b2aaf22fc 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -32,7 +32,7 @@ #include "weston.h" #include "weston-screenshooter-server-protocol.h" #include "shared/helpers.h" -#include "weston-debug.h" +#include struct screenshooter { struct weston_compositor *ec; diff --git a/libweston/weston-debug.h b/include/libweston/weston-debug.h similarity index 100% rename from libweston/weston-debug.h rename to include/libweston/weston-debug.h diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 6d40e72b8..627f58e54 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -52,7 +52,7 @@ #include #include -#include "weston-debug.h" +#include #include "shared/helpers.h" #include "shared/timespec-util.h" #include "renderer-gl/gl-renderer.h" diff --git a/libweston/compositor.c b/libweston/compositor.c index da34ee75a..fa2645fea 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -56,7 +56,7 @@ #include "timeline.h" #include -#include "weston-debug.h" +#include #include "linux-dmabuf.h" #include "viewporter-server-protocol.h" #include "presentation-time-server-protocol.h" diff --git a/libweston/weston-debug.c b/libweston/weston-debug.c index 79b35a45a..5f5053460 100644 --- a/libweston/weston-debug.c +++ b/libweston/weston-debug.c @@ -26,7 +26,7 @@ #include "config.h" -#include "weston-debug.h" +#include #include "helpers.h" #include diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index 1c3235028..d42d33c4b 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -33,7 +33,7 @@ #include #include "compositor/weston.h" #include -#include "weston-debug.h" +#include #define SEND_EVENT_MASK (0x80) #define EVENT_TYPE(event) ((event)->response_type & ~SEND_EVENT_MASK) From c0d205ba44bf068ea3b421ea5f72bf9ccca13ef0 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Mon, 29 Apr 2019 13:33:54 +0300 Subject: [PATCH 0872/1642] include: Install weston-debug header Signed-off-by: Marius Vlad --- include/libweston/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/include/libweston/meson.build b/include/libweston/meson.build index 8e8f81604..6ce8c9ce8 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -5,6 +5,7 @@ install_headers( 'plugin-registry.h', 'timeline-object.h', 'windowed-output-api.h', + 'weston-debug.h', 'zalloc.h', subdir: dir_include_libweston_install ) From 4e538141807c6c328f4f038c8bb92f9fa93d66ee Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Tue, 30 Apr 2019 20:26:32 +0300 Subject: [PATCH 0873/1642] build: libweston doesn't need -export-dynamic According to https://gitlab.freedesktop.org/wayland/weston/merge_requests/159#note_148104 it doesn't make sense to use export-dynamic on libraries. Signed-off-by: Marius Vlad Reported-by: Pekka Paalanen --- libweston/meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/libweston/meson.build b/libweston/meson.build index 3c9640887..a75182ad6 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -68,7 +68,6 @@ lib_weston = shared_library( 'weston-@0@'.format(libweston_major), srcs_libweston, include_directories: [ include_directories('..', '../shared'), public_inc ], - link_args: [ '-Wl,-export-dynamic' ], install: true, version: '0.0.@0@'.format(libweston_revision), link_whole: lib_libshared, From b40e051858728e4b44e93ef736fd204d544a80b6 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 5 Apr 2019 12:04:58 +0300 Subject: [PATCH 0874/1642] build: make libinput-backend a helper lib Rather than having fbdev and drm backends include the libinput files ad hoc, wrap them in a static library. Using the dependency object for that helper library will then automatically pull in any necerray include dirs for the users. This helps with moving the backends into subdirectories. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/libweston/meson.build b/libweston/meson.build index a75182ad6..a7a8105e2 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -166,14 +166,32 @@ lib_session_helper = static_library( dep_session_helper = declare_dependency(link_with: lib_session_helper) +lib_libinput_backend = static_library( + 'libinput-backend', + [ + 'libinput-device.c', + 'libinput-seat.c' + ], + dependencies: [ + dep_libweston, + dep_libinput, + dependency('libudev', version: '>= 136') + ], + include_directories: include_directories('..'), + install: false +) +dep_libinput_backend = declare_dependency( + link_with: lib_libinput_backend, + include_directories: include_directories('.') +) + + if get_option('backend-drm') config_h.set('BUILD_DRM_COMPOSITOR', '1') srcs_drm = [ 'compositor-drm.c', 'libbacklight.c', - 'libinput-device.c', - 'libinput-seat.c', linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_server_protocol_h, presentation_time_server_protocol_h, @@ -183,7 +201,7 @@ if get_option('backend-drm') dep_libweston, dep_session_helper, dep_libdrm, - dep_libinput, + dep_libinput_backend, dependency('libudev', version: '>= 136'), ] @@ -406,15 +424,13 @@ if get_option('backend-fbdev') srcs_fbdev = [ 'compositor-fbdev.c', - 'libinput-device.c', - 'libinput-seat.c', presentation_time_server_protocol_h, ] deps_fbdev = [ dep_libweston, dep_session_helper, - dep_libinput, + dep_libinput_backend, dependency('libudev', version: '>= 136'), ] From 6bc50b12f8ffabe7fbe652693c9909dd1dcad1f1 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 5 Apr 2019 12:28:14 +0300 Subject: [PATCH 0875/1642] build: make backlight a helper lib Right now only used by the DRM-backend, but there is a test program that should use this as well. This helps with building the test program and moving DRM-backend into a subdirectory. Signed-off-by: Pekka Paalanen --- libweston/meson.build | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/libweston/meson.build b/libweston/meson.build index a7a8105e2..e3957147c 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -185,13 +185,27 @@ dep_libinput_backend = declare_dependency( include_directories: include_directories('.') ) - if get_option('backend-drm') config_h.set('BUILD_DRM_COMPOSITOR', '1') + lib_backlight = static_library( + 'backlight', + 'libbacklight.c', + dependencies: [ + dep_libdrm_headers, + dependency('libudev') + ], + include_directories: include_directories('..'), + install: false, + build_by_default: false + ) + dep_backlight = declare_dependency( + link_with: lib_backlight, + include_directories: include_directories('.') + ) + srcs_drm = [ 'compositor-drm.c', - 'libbacklight.c', linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_server_protocol_h, presentation_time_server_protocol_h, @@ -203,6 +217,7 @@ if get_option('backend-drm') dep_libdrm, dep_libinput_backend, dependency('libudev', version: '>= 136'), + dep_backlight ] # XXX: Actually let DRM-backend build without GBM, it really should From 644eb64a5d818e6123a7a08d814289345ddbb88c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 5 Apr 2019 12:45:46 +0300 Subject: [PATCH 0876/1642] tests: build setbacklight Was missed in the Meson migration. This is built only if DRM-backend is built, because it exercises a sub-feature of the DRM-backend. Signed-off-by: Pekka Paalanen --- tests/meson.build | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/meson.build b/tests/meson.build index 704ab54df..b1f6807c1 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -382,3 +382,17 @@ foreach t : tests_weston_plugin test(t.get(0), exe_weston, env: env_test_weston, args: args_t) endif endforeach + +if get_option('backend-drm') + executable( + 'setbacklight', + 'setbacklight.c', + dependencies: [ + dep_backlight, + dep_libdrm, + dependency('libudev') + ], + include_directories: include_directories('..'), + install: false + ) +endif From f0f37bcaa1ce390cb2992233860680894a4c4a03 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 5 Apr 2019 12:58:27 +0300 Subject: [PATCH 0877/1642] backend-drm: move into new subdir Move the DRM-backend into a new sub-directory to make it stand out from libweston core. This facilitates splitting drm.c into more files later. vaapi-recorder is used only by DRM-backend, move that too. libbacklight is used only by DRM-backend and a manual test program, and is moved as well. Signed-off-by: Pekka Paalanen --- .../{compositor-drm.c => backend-drm/drm.c} | 0 libweston/{ => backend-drm}/libbacklight.c | 0 libweston/{ => backend-drm}/libbacklight.h | 0 libweston/backend-drm/meson.build | 90 ++++++++++++++++++ libweston/{ => backend-drm}/vaapi-recorder.c | 0 libweston/{ => backend-drm}/vaapi-recorder.h | 0 libweston/meson.build | 92 +------------------ 7 files changed, 91 insertions(+), 91 deletions(-) rename libweston/{compositor-drm.c => backend-drm/drm.c} (100%) rename libweston/{ => backend-drm}/libbacklight.c (100%) rename libweston/{ => backend-drm}/libbacklight.h (100%) create mode 100644 libweston/backend-drm/meson.build rename libweston/{ => backend-drm}/vaapi-recorder.c (100%) rename libweston/{ => backend-drm}/vaapi-recorder.h (100%) diff --git a/libweston/compositor-drm.c b/libweston/backend-drm/drm.c similarity index 100% rename from libweston/compositor-drm.c rename to libweston/backend-drm/drm.c diff --git a/libweston/libbacklight.c b/libweston/backend-drm/libbacklight.c similarity index 100% rename from libweston/libbacklight.c rename to libweston/backend-drm/libbacklight.c diff --git a/libweston/libbacklight.h b/libweston/backend-drm/libbacklight.h similarity index 100% rename from libweston/libbacklight.h rename to libweston/backend-drm/libbacklight.h diff --git a/libweston/backend-drm/meson.build b/libweston/backend-drm/meson.build new file mode 100644 index 000000000..d24fc9357 --- /dev/null +++ b/libweston/backend-drm/meson.build @@ -0,0 +1,90 @@ +if not get_option('backend-drm') + subdir_done() +endif + +lib_backlight = static_library( + 'backlight', + 'libbacklight.c', + dependencies: [ + dep_libdrm_headers, + dependency('libudev') + ], + include_directories: include_directories('../..'), + install: false +) +dep_backlight = declare_dependency( + link_with: lib_backlight, + include_directories: include_directories('.') +) + +config_h.set('BUILD_DRM_COMPOSITOR', '1') + +srcs_drm = [ + 'drm.c', + linux_dmabuf_unstable_v1_protocol_c, + linux_dmabuf_unstable_v1_server_protocol_h, + presentation_time_server_protocol_h, +] + +deps_drm = [ + dep_libweston, + dep_session_helper, + dep_libdrm, + dep_libinput_backend, + dependency('libudev', version: '>= 136'), + dep_backlight +] + +# XXX: Actually let DRM-backend build without GBM, it really should +if true # get_option('renderer-gl') + dep_gbm = dependency('gbm', required: false) + if not dep_gbm.found() + error('drm-backend requires gbm which was not found. Or, you can use \'-Dbackend-drm=false\'.') + endif + if dep_gbm.version().version_compare('>= 17.1') + config_h.set('HAVE_GBM_MODIFIERS', '1') + endif + if dep_gbm.version().version_compare('>= 17.2') + config_h.set('HAVE_GBM_FD_IMPORT', '1') + endif + deps_drm += dep_gbm +endif + +if get_option('backend-drm-screencast-vaapi') + foreach name : [ 'libva', 'libva-drm' ] + d = dependency(name, version: '>= 0.34.0', required: false) + if not d.found() + error('VA-API recorder requires @0@ >= 0.34.0 which was not found. Or, you can use \'-Dbackend-drm-screencast-vaapi=false\'.'.format(name)) + endif + deps_drm += d + endforeach + + srcs_drm += 'vaapi-recorder.c' + deps_drm += dependency('threads') + config_h.set('BUILD_VAAPI_RECORDER', '1') +endif + +if dep_libdrm.version().version_compare('>= 2.4.71') + config_h.set('HAVE_DRM_ADDFB2_MODIFIERS', '1') +endif + +if dep_libdrm.version().version_compare('>= 2.4.78') + config_h.set('HAVE_DRM_ATOMIC', '1') +endif + +if dep_libdrm.version().version_compare('>= 2.4.83') + config_h.set('HAVE_DRM_FORMATS_BLOB', '1') +endif + +plugin_drm = shared_library( + 'drm-backend', + srcs_drm, + include_directories: include_directories('../..', '../../shared'), + dependencies: deps_drm, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'drm-backend.so=@0@;'.format(plugin_drm.full_path()) + +install_headers(backend_drm_h, subdir: dir_include_libweston_install) diff --git a/libweston/vaapi-recorder.c b/libweston/backend-drm/vaapi-recorder.c similarity index 100% rename from libweston/vaapi-recorder.c rename to libweston/backend-drm/vaapi-recorder.c diff --git a/libweston/vaapi-recorder.h b/libweston/backend-drm/vaapi-recorder.h similarity index 100% rename from libweston/vaapi-recorder.h rename to libweston/backend-drm/vaapi-recorder.h diff --git a/libweston/meson.build b/libweston/meson.build index e3957147c..3474f273a 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -185,97 +185,6 @@ dep_libinput_backend = declare_dependency( include_directories: include_directories('.') ) -if get_option('backend-drm') - config_h.set('BUILD_DRM_COMPOSITOR', '1') - - lib_backlight = static_library( - 'backlight', - 'libbacklight.c', - dependencies: [ - dep_libdrm_headers, - dependency('libudev') - ], - include_directories: include_directories('..'), - install: false, - build_by_default: false - ) - dep_backlight = declare_dependency( - link_with: lib_backlight, - include_directories: include_directories('.') - ) - - srcs_drm = [ - 'compositor-drm.c', - linux_dmabuf_unstable_v1_protocol_c, - linux_dmabuf_unstable_v1_server_protocol_h, - presentation_time_server_protocol_h, - ] - - deps_drm = [ - dep_libweston, - dep_session_helper, - dep_libdrm, - dep_libinput_backend, - dependency('libudev', version: '>= 136'), - dep_backlight - ] - - # XXX: Actually let DRM-backend build without GBM, it really should - if true # get_option('renderer-gl') - dep_gbm = dependency('gbm', required: false) - if not dep_gbm.found() - error('drm-backend requires gbm which was not found. Or, you can use \'-Dbackend-drm=false\'.') - endif - if dep_gbm.version().version_compare('>= 17.1') - config_h.set('HAVE_GBM_MODIFIERS', '1') - endif - if dep_gbm.version().version_compare('>= 17.2') - config_h.set('HAVE_GBM_FD_IMPORT', '1') - endif - deps_drm += dep_gbm - endif - - if get_option('backend-drm-screencast-vaapi') - foreach name : [ 'libva', 'libva-drm' ] - d = dependency(name, version: '>= 0.34.0', required: false) - if not d.found() - error('VA-API recorder requires @0@ >= 0.34.0 which was not found. Or, you can use \'-Dbackend-drm-screencast-vaapi=false\'.'.format(name)) - endif - deps_drm += d - endforeach - - srcs_drm += 'vaapi-recorder.c' - deps_drm += dependency('threads') - config_h.set('BUILD_VAAPI_RECORDER', '1') - endif - - if dep_libdrm.version().version_compare('>= 2.4.71') - config_h.set('HAVE_DRM_ADDFB2_MODIFIERS', '1') - endif - - if dep_libdrm.version().version_compare('>= 2.4.78') - config_h.set('HAVE_DRM_ATOMIC', '1') - endif - - if dep_libdrm.version().version_compare('>= 2.4.83') - config_h.set('HAVE_DRM_FORMATS_BLOB', '1') - endif - - plugin_drm = shared_library( - 'drm-backend', - srcs_drm, - include_directories: include_directories('..', '../shared'), - dependencies: deps_drm, - name_prefix: '', - install: true, - install_dir: dir_module_libweston - ) - env_modmap += 'drm-backend.so=@0@;'.format(plugin_drm.full_path()) - - install_headers(backend_drm_h, subdir: dir_include_libweston_install) -endif - - if get_option('backend-headless') config_h.set('BUILD_HEADLESS_COMPOSITOR', '1') @@ -487,3 +396,4 @@ if get_option('weston-launch') endif subdir('renderer-gl') +subdir('backend-drm') From 8059d317a53d8e7313528eda0d331efbf1bd52aa Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 5 Apr 2019 13:11:01 +0300 Subject: [PATCH 0878/1642] backend-headless: move into new subdir For consistency with other backends. Signed-off-by: Pekka Paalanen --- .../headless.c} | 0 libweston/backend-headless/meson.build | 21 ++++++++++++++++++ libweston/meson.build | 22 +------------------ 3 files changed, 22 insertions(+), 21 deletions(-) rename libweston/{compositor-headless.c => backend-headless/headless.c} (100%) create mode 100644 libweston/backend-headless/meson.build diff --git a/libweston/compositor-headless.c b/libweston/backend-headless/headless.c similarity index 100% rename from libweston/compositor-headless.c rename to libweston/backend-headless/headless.c diff --git a/libweston/backend-headless/meson.build b/libweston/backend-headless/meson.build new file mode 100644 index 000000000..19b57605c --- /dev/null +++ b/libweston/backend-headless/meson.build @@ -0,0 +1,21 @@ +if not get_option('backend-headless') + subdir_done() +endif + +config_h.set('BUILD_HEADLESS_COMPOSITOR', '1') + +srcs_headless = [ + 'headless.c', + presentation_time_server_protocol_h, +] +plugin_headless = shared_library( + 'headless-backend', + srcs_headless, + include_directories: include_directories('../..', '../../shared'), + dependencies: dep_libweston, + name_prefix: '', + install: true, + install_dir: dir_module_libweston, +) +env_modmap += 'headless-backend.so=@0@;'.format(plugin_headless.full_path()) +install_headers(backend_headless_h, subdir: dir_include_libweston_install) diff --git a/libweston/meson.build b/libweston/meson.build index 3474f273a..6d658ac70 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -185,27 +185,6 @@ dep_libinput_backend = declare_dependency( include_directories: include_directories('.') ) -if get_option('backend-headless') - config_h.set('BUILD_HEADLESS_COMPOSITOR', '1') - - srcs_headless = [ - 'compositor-headless.c', - presentation_time_server_protocol_h, - ] - plugin_headless = shared_library( - 'headless-backend', - srcs_headless, - include_directories: include_directories('..', '../shared'), - dependencies: dep_libweston, - name_prefix: '', - install: true, - install_dir: dir_module_libweston, - ) - env_modmap += 'headless-backend.so=@0@;'.format(plugin_headless.full_path()) - install_headers(backend_headless_h, subdir: dir_include_libweston_install) -endif - - if get_option('backend-rdp') config_h.set('BUILD_RDP_COMPOSITOR', '1') @@ -397,3 +376,4 @@ endif subdir('renderer-gl') subdir('backend-drm') +subdir('backend-headless') From b70ee941b58bb19374f01ac7f89385f0d07e90d1 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 5 Apr 2019 13:14:15 +0300 Subject: [PATCH 0879/1642] backend-rdp: move into new subdir For consistency with other backends. Signed-off-by: Pekka Paalanen --- libweston/backend-rdp/meson.build | 38 ++++++++++++++++++ .../{compositor-rdp.c => backend-rdp/rdp.c} | 0 libweston/meson.build | 39 +------------------ 3 files changed, 39 insertions(+), 38 deletions(-) create mode 100644 libweston/backend-rdp/meson.build rename libweston/{compositor-rdp.c => backend-rdp/rdp.c} (100%) diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build new file mode 100644 index 000000000..5c0a293a6 --- /dev/null +++ b/libweston/backend-rdp/meson.build @@ -0,0 +1,38 @@ +if not get_option('backend-rdp') + subdir_done() +endif + +config_h.set('BUILD_RDP_COMPOSITOR', '1') + +dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) +if not dep_frdp.found() + error('RDP-backend requires freerdp2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') +endif + +if cc.has_header('freerdp/version.h', dependencies: dep_frdp) + config_h.set('HAVE_FREERDP_VERSION_H', '1') +endif + +if cc.has_member( + 'SURFACE_BITS_COMMAND', 'bmp', + dependencies : dep_frdp, + prefix : '#include ' +) + config_h.set('HAVE_SURFACE_BITS_BMP', '1') +endif + +deps_rdp = [ + dep_libweston, + dep_frdp, +] +plugin_rdp = shared_library( + 'rdp-backend', + 'rdp.c', + include_directories: include_directories('../..', '../../shared'), + dependencies: deps_rdp, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'rdp-backend.so=@0@;'.format(plugin_rdp.full_path()) +install_headers(backend_rdp_h, subdir: dir_include_libweston_install) diff --git a/libweston/compositor-rdp.c b/libweston/backend-rdp/rdp.c similarity index 100% rename from libweston/compositor-rdp.c rename to libweston/backend-rdp/rdp.c diff --git a/libweston/meson.build b/libweston/meson.build index 6d658ac70..7e9c87a28 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -185,44 +185,6 @@ dep_libinput_backend = declare_dependency( include_directories: include_directories('.') ) -if get_option('backend-rdp') - config_h.set('BUILD_RDP_COMPOSITOR', '1') - - dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) - if not dep_frdp.found() - error('RDP-backend requires freerdp2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') - endif - - if cc.has_header('freerdp/version.h', dependencies: dep_frdp) - config_h.set('HAVE_FREERDP_VERSION_H', '1') - endif - - if cc.has_member( - 'SURFACE_BITS_COMMAND', 'bmp', - dependencies : dep_frdp, - prefix : '#include ' - ) - config_h.set('HAVE_SURFACE_BITS_BMP', '1') - endif - - deps_rdp = [ - dep_libweston, - dep_frdp, - ] - plugin_rdp = shared_library( - 'rdp-backend', - 'compositor-rdp.c', - include_directories: include_directories('..', '../shared'), - dependencies: deps_rdp, - name_prefix: '', - install: true, - install_dir: dir_module_libweston - ) - env_modmap += 'rdp-backend.so=@0@;'.format(plugin_rdp.full_path()) - install_headers(backend_rdp_h, subdir: dir_include_libweston_install) -endif - - if get_option('backend-wayland') config_h.set('BUILD_WAYLAND_COMPOSITOR', '1') @@ -377,3 +339,4 @@ endif subdir('renderer-gl') subdir('backend-drm') subdir('backend-headless') +subdir('backend-rdp') From 4f1573e48eaf6417cc34210fe1187011bb13a740 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 5 Apr 2019 13:17:22 +0300 Subject: [PATCH 0880/1642] backend-wayland: move into new subdir For consistency with other backends. Signed-off-by: Pekka Paalanen --- libweston/backend-wayland/meson.build | 43 ++++++++++++++++++ .../wayland.c} | 0 libweston/meson.build | 44 +------------------ 3 files changed, 44 insertions(+), 43 deletions(-) create mode 100644 libweston/backend-wayland/meson.build rename libweston/{compositor-wayland.c => backend-wayland/wayland.c} (100%) diff --git a/libweston/backend-wayland/meson.build b/libweston/backend-wayland/meson.build new file mode 100644 index 000000000..bcb0e2a6f --- /dev/null +++ b/libweston/backend-wayland/meson.build @@ -0,0 +1,43 @@ +if not get_option('backend-wayland') + subdir_done() +endif + +config_h.set('BUILD_WAYLAND_COMPOSITOR', '1') + +srcs_wlwl = [ + 'wayland.c', + fullscreen_shell_unstable_v1_client_protocol_h, + fullscreen_shell_unstable_v1_protocol_c, + presentation_time_protocol_c, + presentation_time_server_protocol_h, + xdg_shell_server_protocol_h, + xdg_shell_protocol_c, +] + +deps_wlwl = [ + dependency('wayland-client'), + dependency('wayland-cursor'), + dep_pixman, + dep_libweston, + dep_lib_cairo_shared, +] + +if get_option('renderer-gl') + d = dependency('wayland-egl', required: false) + if not d.found() + error('wayland-backend + gl-renderer requires wayland-egl which was not found. Or, you can use \'-Dbackend-wayland=false\' or \'-Drenderer-gl=false\'.') + endif + deps_wlwl += d +endif + +plugin_wlwl = shared_library( + 'wayland-backend', + srcs_wlwl, + include_directories: include_directories('../..', '../../shared'), + dependencies: deps_wlwl, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'wayland-backend.so=@0@;'.format(plugin_wlwl.full_path()) +install_headers(backend_wayland_h, subdir: dir_include_libweston_install) diff --git a/libweston/compositor-wayland.c b/libweston/backend-wayland/wayland.c similarity index 100% rename from libweston/compositor-wayland.c rename to libweston/backend-wayland/wayland.c diff --git a/libweston/meson.build b/libweston/meson.build index 7e9c87a28..db3a911c4 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -185,49 +185,6 @@ dep_libinput_backend = declare_dependency( include_directories: include_directories('.') ) -if get_option('backend-wayland') - config_h.set('BUILD_WAYLAND_COMPOSITOR', '1') - - srcs_wlwl = [ - 'compositor-wayland.c', - fullscreen_shell_unstable_v1_client_protocol_h, - fullscreen_shell_unstable_v1_protocol_c, - presentation_time_protocol_c, - presentation_time_server_protocol_h, - xdg_shell_server_protocol_h, - xdg_shell_protocol_c, - ] - - deps_wlwl = [ - dependency('wayland-client'), - dependency('wayland-cursor'), - dep_pixman, - dep_libweston, - dep_lib_cairo_shared, - ] - - if get_option('renderer-gl') - d = dependency('wayland-egl', required: false) - if not d.found() - error('wayland-backend + gl-renderer requires wayland-egl which was not found. Or, you can use \'-Dbackend-wayland=false\' or \'-Drenderer-gl=false\'.') - endif - deps_wlwl += d - endif - - plugin_wlwl = shared_library( - 'wayland-backend', - srcs_wlwl, - include_directories: include_directories('..', '../shared'), - dependencies: deps_wlwl, - name_prefix: '', - install: true, - install_dir: dir_module_libweston - ) - env_modmap += 'wayland-backend.so=@0@;'.format(plugin_wlwl.full_path()) - install_headers(backend_wayland_h, subdir: dir_include_libweston_install) -endif - - if get_option('backend-x11') config_h.set('BUILD_X11_COMPOSITOR', '1') @@ -340,3 +297,4 @@ subdir('renderer-gl') subdir('backend-drm') subdir('backend-headless') subdir('backend-rdp') +subdir('backend-wayland') From 526b85401eac105f5c1a37dc75308a119aa45495 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 5 Apr 2019 13:20:53 +0300 Subject: [PATCH 0881/1642] backend-x11: move into new subdir For consistency with other backends. Signed-off-by: Pekka Paalanen --- libweston/backend-x11/meson.build | 57 +++++++++++++++++++ .../{compositor-x11.c => backend-x11/x11.c} | 0 libweston/meson.build | 57 +------------------ 3 files changed, 58 insertions(+), 56 deletions(-) create mode 100644 libweston/backend-x11/meson.build rename libweston/{compositor-x11.c => backend-x11/x11.c} (100%) diff --git a/libweston/backend-x11/meson.build b/libweston/backend-x11/meson.build new file mode 100644 index 000000000..6acee8ae5 --- /dev/null +++ b/libweston/backend-x11/meson.build @@ -0,0 +1,57 @@ + +if not get_option('backend-x11') + subdir_done() +endif + +config_h.set('BUILD_X11_COMPOSITOR', '1') + +srcs_x11 = [ + 'x11.c', + presentation_time_server_protocol_h, +] + +dep_x11_xcb = dependency('xcb', version: '>= 1.8', required: false) +if not dep_x11_xcb.found() + error('x11-backend requires xcb >= 1.8 which was not found. Or, you can use \'-Dbackend-x11=false\'.') +endif + +deps_x11 = [ + dep_libweston, + dep_x11_xcb, + dep_lib_cairo_shared, + dep_pixman, +] + +foreach name : [ 'xcb-shm', 'x11', 'x11-xcb' ] + d = dependency(name, required: false) + if not d.found() + error('x11-backend requires @0@ which was not found. Or, you can use \'-Dbackend-x11=false\'.'.format(name)) + endif + deps_x11 += d +endforeach + +dep_xcb_xkb = dependency('xcb-xkb', version: '>= 1.9', required: false) +if dep_xcb_xkb.found() + deps_x11 += dep_xcb_xkb + config_h.set('HAVE_XCB_XKB', '1') +endif + +if get_option('renderer-gl') + if not dep_egl.found() + error('x11-backend + gl-renderer requires egl which was not found. Or, you can use \'-Dbackend-x11=false\' or \'-Drenderer-gl=false\'.') + endif + deps_x11 += dep_egl +endif + +plugin_x11 = shared_library( + 'x11-backend', + srcs_x11, + include_directories: include_directories('../..', '../../shared'), + dependencies: deps_x11, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'x11-backend.so=@0@;'.format(plugin_x11.full_path()) + +install_headers(backend_x11_h, subdir: dir_include_libweston_install) diff --git a/libweston/compositor-x11.c b/libweston/backend-x11/x11.c similarity index 100% rename from libweston/compositor-x11.c rename to libweston/backend-x11/x11.c diff --git a/libweston/meson.build b/libweston/meson.build index db3a911c4..357a910b5 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -185,62 +185,6 @@ dep_libinput_backend = declare_dependency( include_directories: include_directories('.') ) -if get_option('backend-x11') - config_h.set('BUILD_X11_COMPOSITOR', '1') - - srcs_x11 = [ - 'compositor-x11.c', - presentation_time_server_protocol_h, - ] - - dep_x11_xcb = dependency('xcb', version: '>= 1.8', required: false) - if not dep_x11_xcb.found() - error('x11-backend requires xcb >= 1.8 which was not found. Or, you can use \'-Dbackend-x11=false\'.') - endif - - deps_x11 = [ - dep_libweston, - dep_x11_xcb, - dep_lib_cairo_shared, - dep_pixman, - ] - - foreach name : [ 'xcb-shm', 'x11', 'x11-xcb' ] - d = dependency(name, required: false) - if not d.found() - error('x11-backend requires @0@ which was not found. Or, you can use \'-Dbackend-x11=false\'.'.format(name)) - endif - deps_x11 += d - endforeach - - dep_xcb_xkb = dependency('xcb-xkb', version: '>= 1.9', required: false) - if dep_xcb_xkb.found() - deps_x11 += dep_xcb_xkb - config_h.set('HAVE_XCB_XKB', '1') - endif - - if get_option('renderer-gl') - if not dep_egl.found() - error('x11-backend + gl-renderer requires egl which was not found. Or, you can use \'-Dbackend-x11=false\' or \'-Drenderer-gl=false\'.') - endif - deps_x11 += dep_egl - endif - - plugin_x11 = shared_library( - 'x11-backend', - srcs_x11, - include_directories: include_directories('..', '../shared'), - dependencies: deps_x11, - name_prefix: '', - install: true, - install_dir: dir_module_libweston - ) - env_modmap += 'x11-backend.so=@0@;'.format(plugin_x11.full_path()) - - install_headers(backend_x11_h, subdir: dir_include_libweston_install) -endif - - if get_option('backend-fbdev') config_h.set('BUILD_FBDEV_COMPOSITOR', '1') @@ -298,3 +242,4 @@ subdir('backend-drm') subdir('backend-headless') subdir('backend-rdp') subdir('backend-wayland') +subdir('backend-x11') From c8acc5f23d7c9e611792be14ceeb9f9125de58cb Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 5 Apr 2019 13:23:53 +0300 Subject: [PATCH 0882/1642] backend-fbdev: more into new subdir For consistency with other backends. Signed-off-by: Pekka Paalanen --- .../fbdev.c} | 0 libweston/backend-fbdev/meson.build | 30 +++++++++++++++++++ libweston/meson.build | 30 +------------------ 3 files changed, 31 insertions(+), 29 deletions(-) rename libweston/{compositor-fbdev.c => backend-fbdev/fbdev.c} (100%) create mode 100644 libweston/backend-fbdev/meson.build diff --git a/libweston/compositor-fbdev.c b/libweston/backend-fbdev/fbdev.c similarity index 100% rename from libweston/compositor-fbdev.c rename to libweston/backend-fbdev/fbdev.c diff --git a/libweston/backend-fbdev/meson.build b/libweston/backend-fbdev/meson.build new file mode 100644 index 000000000..a84686e20 --- /dev/null +++ b/libweston/backend-fbdev/meson.build @@ -0,0 +1,30 @@ +if not get_option('backend-fbdev') + subdir_done() +endif + +config_h.set('BUILD_FBDEV_COMPOSITOR', '1') + +srcs_fbdev = [ + 'fbdev.c', + presentation_time_server_protocol_h, +] + +deps_fbdev = [ + dep_libweston, + dep_session_helper, + dep_libinput_backend, + dependency('libudev', version: '>= 136'), +] + +plugin_fbdev = shared_library( + 'fbdev-backend', + srcs_fbdev, + include_directories: include_directories('../..', '../../shared'), + dependencies: deps_fbdev, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'fbdev-backend.so=@0@;'.format(plugin_fbdev.full_path()) + +install_headers(backend_fbdev_h, subdir: dir_include_libweston_install) diff --git a/libweston/meson.build b/libweston/meson.build index 357a910b5..33c6cf85f 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -185,35 +185,6 @@ dep_libinput_backend = declare_dependency( include_directories: include_directories('.') ) -if get_option('backend-fbdev') - config_h.set('BUILD_FBDEV_COMPOSITOR', '1') - - srcs_fbdev = [ - 'compositor-fbdev.c', - presentation_time_server_protocol_h, - ] - - deps_fbdev = [ - dep_libweston, - dep_session_helper, - dep_libinput_backend, - dependency('libudev', version: '>= 136'), - ] - - plugin_fbdev = shared_library( - 'fbdev-backend', - srcs_fbdev, - include_directories: include_directories('..', '../shared'), - dependencies: deps_fbdev, - name_prefix: '', - install: true, - install_dir: dir_module_libweston - ) - env_modmap += 'fbdev-backend.so=@0@;'.format(plugin_fbdev.full_path()) - - install_headers(backend_fbdev_h, subdir: dir_include_libweston_install) -endif - dep_vertex_clipping = declare_dependency( sources: 'vertex-clipping.c', include_directories: include_directories('.') @@ -239,6 +210,7 @@ endif subdir('renderer-gl') subdir('backend-drm') +subdir('backend-fbdev') subdir('backend-headless') subdir('backend-rdp') subdir('backend-wayland') From cda278dd1d0d99d9845a8e8cb59588fd9e0c2e48 Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Sat, 23 Mar 2019 19:03:59 -0400 Subject: [PATCH 0883/1642] man: Update "See also" references for weston.1 Signed-off-by: Manuel Stoeckl --- man/weston.man | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/man/weston.man b/man/weston.man index a60023dfc..322fa0de2 100644 --- a/man/weston.man +++ b/man/weston.man @@ -1,4 +1,4 @@ -.TH WESTON 1 "2012-11-27" "Weston @version@" +.TH WESTON 1 "2019-03-23" "Weston @version@" .SH NAME weston \- the reference Wayland server .SH SYNOPSIS @@ -358,7 +358,7 @@ weston . .\" *************************************************************** .SH "SEE ALSO" -.BR weston-drm (7) -.BR weston-rdp (7) -.\".BR weston-launch (1), -.\".BR weston.ini (5) +.BR weston-debug (1), +.BR weston-drm (7), +.BR weston-rdp (7), +.BR weston.ini (5) From 6666dee52b1e980f91df6af0ca7cc73524efde11 Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Tue, 26 Mar 2019 21:00:55 -0400 Subject: [PATCH 0884/1642] man: Add weston-bindings(7) describing desktop shell shortcuts The desktop-shell-specific bindings were identified from shell_add_bindings in desktop-shell/shell.c. Various general shortcuts (like Ctrl+Alt+F) are provided by files in libweston/ and compositor/ . Also introduce references to the new manual in weston(1) and weston.ini(5). Signed-off-by: Manuel Stoeckl --- man/meson.build | 8 +++ man/weston-bindings.man | 137 ++++++++++++++++++++++++++++++++++++++++ man/weston.ini.man | 9 +-- man/weston.man | 1 + 4 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 man/weston-bindings.man diff --git a/man/meson.build b/man/meson.build index 80e231203..ab69aa54e 100644 --- a/man/meson.build +++ b/man/meson.build @@ -15,6 +15,14 @@ configure_file( configuration: man_conf ) +configure_file( + input: 'weston-bindings.man', + output: 'weston-bindings.7', + install: true, + install_dir: join_paths(dir_man, 'man7'), + configuration: man_conf +) + configure_file( input: 'weston-debug.man', output: 'weston-debug.1', diff --git a/man/weston-bindings.man b/man/weston-bindings.man new file mode 100644 index 000000000..ee55e406a --- /dev/null +++ b/man/weston-bindings.man @@ -0,0 +1,137 @@ +.\" shorthand for double quote that works everywhere. +.ds q \N'34' +.TH weston-bindings 7 "2019-03-27" "Weston @version@" +.SH NAME +weston-bindings \- a list of keyboard bindings for +.B Weston +\- the reference Wayland +compositor +.SH INTRODUCTION +The Weston desktop shell has a number of keyboard shortcuts. They are listed here. +.SH DESCRIPTION +Almost all keyboard shortcuts for +.B Weston +include a specified modifier +.I mod +which is determined in the file +.BR weston.ini (5). +.\" Begin list +.P +.RE +.B mod + Shift + F +.RS 4 +Make active window fullscreen +.P +.RE +.B mod + K +.RS 4 +Kill active window +.P +.RE +.B mod + Shift + M +.RS 4 +Maximize active window +.P +.RE +.B mod + PageUp, mod + PageDown +.RS 4 +Zoom desktop in (or out) +.P +.RE +.B mod + Tab +.RS 4 +Switch active window +.P +.RE +.B mod + Up, mod + Down +.RS 4 +Increment/decrement active workspace number, if there are multiple +.P +.RE +.B mod + Shift + Up, mod + Shift + Down +.RS 4 +Move active window to the succeeding/preceding workspace, if possible +.P +.RE +.B mod + F1/F2/F3/F4/F5/F6 +.RS 4 +Jump to the numbered workspace, if it exists +.P +.RE +.B Ctrl + Alt + Backspace +.RS 4 +If supported, terminate Weston. (Note this combination often is used to hard restart Xorg.) +.P +.RE +.B Ctrl + Alt + F +.RS 4 +Toggle if Weston is fullscreen; only works when nested under a Wayland compositor +.P +.RE +.B Ctrl + Alt + S +.RS 4 +Share output screen, if possible +.P +.RE +.B Ctrl + Alt + F1/F2/F3/F4/F5/F6/F7/F8 +.RS 4 +Switch virtual terminal, if possible +.P +.RE +.B Super + S +.RS 4 +Make a screenshot of the desktop +.P +.RE +.B Super + R +.RS 4 +Start or stop recording video of the desktop + +.SS "TOUCH / MOUSE BINDINGS" + +There are also a number of bindings involving a mouse: +.P +.RE +.B \fI\fI, \fI\fB, \fI\fB +.RS 4 +Activate clicked window +.P +.RE +.B Super + Alt + \fI\fB +.RS 4 +Change the opacity of a window +.P +.RE +.B mod + \fI\fB +.RS 4 +Zoom/magnify the visible desktop +.P +.RE +.B mod + \fI\fB +.RS 4 +Click and drag to move a window +.P +.RE +.B mod + Shift + \fI\fB, mod + \fI\fB, mod + \fI\fB +.RS 4 +Click and drag to resize a window +.P +.RE +.B mod + \fI\fB +.RS 4 +Rotate the window (if supported) + +.SS DEBUG BINDINGS +The combination \fBmod + Shift + Space\fR begins a debug binding. Debug +bindings are completed by pressing an additional key. For example, pressing +T next may toggle timeline recording, and F may toggle texture mesh +wireframes with the GL renderer. (In fact, most debug effects can be +disabled again by repeating the command.) Debug bindings are often tied to +specific backends. + +.SH "SEE ALSO" +.BR weston (1), +.BR weston-launch (1), +.BR weston-drm (7), +.BR weston.ini (5), +.BR xkeyboard-config (7) diff --git a/man/weston.ini.man b/man/weston.ini.man index 5c0f1097e..4b0887675 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -1,6 +1,6 @@ .\" shorthand for double quote that works everywhere. .ds q \N'34' -.TH weston.ini 5 "2013-01-17" "Weston @version@" +.TH weston.ini 5 "2019-03-26" "Weston @version@" .SH NAME weston.ini \- configuration file for .B Weston @@ -423,8 +423,9 @@ pressed .BI "binding-modifier=" ctrl sets the modifier key used for common bindings (string), such as moving surfaces, resizing, rotating, switching, closing and setting the transparency -for windows, controlling the backlight and zooming the desktop. Possible values: -none, ctrl, alt, super (default) +for windows, controlling the backlight and zooming the desktop. See +.BR weston-bindings (7). +Possible values: none, ctrl, alt, super (default) .TP 7 .BI "num-workspaces=" 6 defines the number of workspaces (unsigned integer). The user can switch @@ -642,6 +643,6 @@ sets the command to start a fullscreen-shell server for screen sharing (string). .RE .SH "SEE ALSO" .BR weston (1), -.BR weston-launch (1), +.BR weston-bindings (7), .BR weston-drm (7), .BR xkeyboard-config (7) diff --git a/man/weston.man b/man/weston.man index 322fa0de2..2353779c9 100644 --- a/man/weston.man +++ b/man/weston.man @@ -358,6 +358,7 @@ weston . .\" *************************************************************** .SH "SEE ALSO" +.BR weston-bindings (7), .BR weston-debug (1), .BR weston-drm (7), .BR weston-rdp (7), From e51478c13432cbd0483908e6e2279a2ec2d081a4 Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Fri, 24 May 2019 12:53:39 +0200 Subject: [PATCH 0885/1642] Fix build-time warning with meson 0.50.1 Commit 6666dee52b1e ("man: Add weston-bindings(7) describing desktop shell shortcuts") adds in file "man/meson.build" the line install: true, This line triggers a warning with meson 0.50.1: WARNING: Project targetting '>= 0.47' but tried to use feature introduced in '0.50.0': install arg in configure_file Accordingly with https://github.com/mesonbuild/meson/issues/5048 the line was silently ignored by meson before 0.50.0 One possible fix for this warning would require updating the minumum version of meson required by weston, but then forcing every builder to update meson. Instead, since all the other instances in "man/meson.build" of configure_file don't use the feature "install:", it seams safe to simply remove the feature for the instal of "weston-bindings.man". Signed-off-by: Antonio Borneo --- man/meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/man/meson.build b/man/meson.build index ab69aa54e..a2b8edc25 100644 --- a/man/meson.build +++ b/man/meson.build @@ -18,7 +18,6 @@ configure_file( configure_file( input: 'weston-bindings.man', output: 'weston-bindings.7', - install: true, install_dir: join_paths(dir_man, 'man7'), configuration: man_conf ) From 2edbcbd9cdd30e00556e9618570234ea68d89b11 Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Wed, 29 May 2019 18:27:30 +1200 Subject: [PATCH 0886/1642] compositor: Fix incorrect use of bool options WESTON_OPTION_BOOLEAN takes a pointer to an int as an argument, but there were several cases of being passed a pointer to a bool instead. This changes it to use a local int instead, and then write that value to the bool. Signed-off-by: Scott Anderson --- compositor/main.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index df90b3c9a..8e53ae586 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2329,6 +2329,7 @@ load_drm_backend(struct weston_compositor *c, int use_shadow; int ret = 0; int use_pixman_config_ = 0; + int drm_use_current_mode = 0; int32_t use_pixman_ = 0; wet->drm_use_current_mode = false; @@ -2342,11 +2343,12 @@ load_drm_backend(struct weston_compositor *c, { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, { WESTON_OPTION_STRING, "drm-device", 0, &config.specific_device }, - { WESTON_OPTION_BOOLEAN, "current-mode", 0, &wet->drm_use_current_mode }, + { WESTON_OPTION_BOOLEAN, "current-mode", 0, &drm_use_current_mode }, { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &use_pixman_ }, }; parse_options(options, ARRAY_LENGTH(options), argc, argv); + wet->drm_use_current_mode = drm_use_current_mode; config.use_pixman = use_pixman_; section = weston_config_get_section(wc, "core", NULL, NULL); @@ -2618,6 +2620,8 @@ load_x11_backend(struct weston_compositor *c, char const *section_name; int i; int32_t use_pixman_config_ = 0; + int fullscreen = 0; + int no_input = 0; int use_pixman_ = 0; struct wet_output_config *parsed_options = wet_init_parsed_options(c); @@ -2633,13 +2637,15 @@ load_x11_backend(struct weston_compositor *c, { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, - { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &config.fullscreen }, + { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &fullscreen }, { WESTON_OPTION_INTEGER, "output-count", 0, &option_count }, - { WESTON_OPTION_BOOLEAN, "no-input", 0, &config.no_input }, + { WESTON_OPTION_BOOLEAN, "no-input", 0, &no_input }, { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &use_pixman_ }, }; parse_options(options, ARRAY_LENGTH(options), argc, argv); + config.fullscreen = fullscreen; + config.no_input = no_input; config.use_pixman = use_pixman_; config.base.struct_version = WESTON_X11_BACKEND_CONFIG_VERSION; From 21a1f40760f2da174fa52399fa0d9a8c1f772e68 Mon Sep 17 00:00:00 2001 From: Silva Alejandro Ismael Date: Fri, 31 May 2019 16:39:43 -0300 Subject: [PATCH 0887/1642] compositor: fix segfaults if wl_display_create fails Added check to log the error if wl_display_create return NULL. Fixes: #101 Signed-off-by: Silva Alejandro Ismael --- compositor/main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index 8e53ae586..945f99aee 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2990,6 +2990,10 @@ int main(int argc, char *argv[]) verify_xdg_runtime_dir(); display = wl_display_create(); + if (display == NULL) { + weston_log("fatal: failed to create display\n"); + goto out_display; + } loop = wl_display_get_event_loop(display); signals[0] = wl_event_loop_add_signal(loop, SIGTERM, on_term_signal, @@ -3186,6 +3190,7 @@ int main(int argc, char *argv[]) wl_display_destroy(display); +out_display: weston_log_file_close(); if (config) From 137b811ecd566086f7eb42c1e49e7c0cf27fb85e Mon Sep 17 00:00:00 2001 From: Fabrice Fontaine Date: Mon, 10 Jun 2019 12:02:03 +0200 Subject: [PATCH 0888/1642] Fix build with kernel < 4.4 weston includes input-event-codes.h since version 5.0.91 and https://github.com/wayland-project/weston/commit/6e229ca26381bc8191fd9af1e439c311da709aff input-event-codes.h is available only since kernel 4.4 and https://github.com/torvalds/linux/commit/f902dd893427eade90f7eaf858e5ff8b150a5a12 To fix this build failure, replace include on linux/input-event-codes.h by linux/input.h Fixes: - http://autobuild.buildroot.org/results/210c2759900f15ea0030d088f6f45cd8bb199b29 Signed-off-by: Fabrice Fontaine --- compositor/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compositor/main.c b/compositor/main.c index 945f99aee..bf4062afd 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -42,7 +42,7 @@ #include #include #include -#include +#include #include #include From 2dff4dd6950f371dcb94553c5cc724f535ef4f12 Mon Sep 17 00:00:00 2001 From: Harish Krupo Date: Sat, 20 Apr 2019 18:10:56 +0530 Subject: [PATCH 0889/1642] desktop-shell: Don't re-position views when output_list is empty Signed-off-by: Harish Krupo --- desktop-shell/shell.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 33fda6d0f..187be799c 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -4732,6 +4732,9 @@ shell_reposition_view_on_output_destroy(struct weston_view *view) float x, y; int visible; + if (wl_list_empty(&ec->output_list)) + return; + x = view->geometry.x; y = view->geometry.y; From dc3edc04aa301d63e97e80fe8e2cc5227cb6baca Mon Sep 17 00:00:00 2001 From: Harish Krupo Date: Sat, 20 Apr 2019 17:50:18 +0530 Subject: [PATCH 0890/1642] desktop-shell: Re-position views when outputs change When the last output is destroyed or when a new output is created after the last output is destroyed, we need to re-position the views to ensure that all the views are displayed on the output. Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/210 Signed-off-by: Harish Krupo --- desktop-shell/shell.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 187be799c..07a6856b2 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -4724,7 +4724,7 @@ workspace_move_surface_down_binding(struct weston_keyboard *keyboard, } static void -shell_reposition_view_on_output_destroy(struct weston_view *view) +shell_reposition_view_on_output_change(struct weston_view *view) { struct weston_output *output, *first_output; struct weston_compositor *ec = view->surface->compositor; @@ -4789,14 +4789,15 @@ shell_for_each_layer(struct desktop_shell *shell, } static void -shell_output_destroy_move_layer(struct desktop_shell *shell, +shell_output_changed_move_layer(struct desktop_shell *shell, struct weston_layer *layer, void *data) { struct weston_view *view; wl_list_for_each(view, &layer->view_list.link, layer_link.link) - shell_reposition_view_on_output_destroy(view); + shell_reposition_view_on_output_change(view); + } static void @@ -4806,7 +4807,7 @@ handle_output_destroy(struct wl_listener *listener, void *data) container_of(listener, struct shell_output, destroy_listener); struct desktop_shell *shell = output_listener->shell; - shell_for_each_layer(shell, shell_output_destroy_move_layer, NULL); + shell_for_each_layer(shell, shell_output_changed_move_layer, NULL); if (output_listener->panel_surface) wl_list_remove(&output_listener->panel_surface_listener.link); @@ -4860,6 +4861,10 @@ create_shell_output(struct desktop_shell *shell, wl_signal_add(&output->destroy_signal, &shell_output->destroy_listener); wl_list_insert(shell->output_list.prev, &shell_output->link); + + if (wl_list_length(&shell->output_list) == 1) + shell_for_each_layer(shell, + shell_output_changed_move_layer, NULL); } static void From 82c8ca162889414c5e9b6b61932d90eff5cd7873 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 22 Apr 2019 18:05:56 +0100 Subject: [PATCH 0891/1642] dbus: Don't return value from void function Just discard the value, rather than trying to return a value from a void function. Signed-off-by: Daniel Stone --- libweston/dbus.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libweston/dbus.c b/libweston/dbus.c index d2530aca3..91f2be7e8 100644 --- a/libweston/dbus.c +++ b/libweston/dbus.c @@ -398,11 +398,11 @@ void weston_dbus_remove_match_signal(DBusConnection *c, const char *sender, const char *iface, const char *member, const char *path) { - return weston_dbus_remove_match(c, - "type='signal'," - "sender='%s'," - "interface='%s'," - "member='%s'," - "path='%s'", - sender, iface, member, path); + weston_dbus_remove_match(c, + "type='signal'," + "sender='%s'," + "interface='%s'," + "member='%s'," + "path='%s'", + sender, iface, member, path); } From a9278d28fc4e948214689db6777ba4065f31ee40 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 22 Apr 2019 18:09:27 +0100 Subject: [PATCH 0892/1642] timespec: Don't return value from void function timespec_add_msec() doesn't return any values, so don't try to return any. Signed-off-by: Daniel Stone --- shared/timespec-util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/timespec-util.h b/shared/timespec-util.h index ca0156afc..f79969bb7 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -81,7 +81,7 @@ timespec_add_nsec(struct timespec *r, const struct timespec *a, int64_t b) static inline void timespec_add_msec(struct timespec *r, const struct timespec *a, int64_t b) { - return timespec_add_nsec(r, a, b * 1000000); + timespec_add_nsec(r, a, b * 1000000); } /* Convert timespec to nanoseconds From 814335821eb0fb5e74ec031c0cf68399e1dd370a Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 11 Jun 2019 11:32:14 +0100 Subject: [PATCH 0893/1642] weston: Properly test for output-creation failure We were testing the wrong variable to see if output creation had failed: instead of testing the return of the function we'd just called, we were testing something we'd already checked earlier. Signed-off-by: Daniel Stone --- compositor/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compositor/main.c b/compositor/main.c index bf4062afd..1289327bc 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1818,7 +1818,7 @@ wet_layoutput_create_output(struct wet_layoutput *lo, const char *name) output->output = weston_compositor_create_output(lo->compositor->compositor, name); - if (!output) { + if (!output->output) { free(output); return NULL; } From 5898cd32c50752c45bfdfeeacb5a259545bf4ac3 Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Mon, 1 Apr 2019 17:50:14 +0900 Subject: [PATCH 0894/1642] desktop-shell: unmap a view which was faded out When Fading out a destroyed surface view finishes, the view is rendered with very little alpha. After that, since the output isn't updated unless a event on the output doesn't occurs, the view is still on the output. By unmapping the view, the output repaint scheduled without the surface. Signed-off-by: Tomohito Esaki --- desktop-shell/shell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 07a6856b2..f94a4ca2e 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2348,7 +2348,7 @@ fade_out_done(struct weston_view_animation *animation, void *data) loop = wl_display_get_event_loop(shsurf->shell->compositor->wl_display); if (weston_view_is_mapped(shsurf->view)) { - shsurf->view->is_mapped = false; + weston_view_unmap(shsurf->view); wl_event_loop_add_idle(loop, fade_out_done_idle_cb, shsurf); } } From 34473d703f04b73f02d4c895ebb467a96fc27cae Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 11 Jun 2019 11:40:13 +0100 Subject: [PATCH 0895/1642] build: Suppress 'pedantic' GCC warnings GCC's 'pedantic' warnings warn about a bunch of things which are true of ISO C but not the toolchains we care about (GCC, Clang). Suppress those warnings to allow us to build with Meson's warning_level=3. Signed-off-by: Daniel Stone --- meson.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meson.build b/meson.build index 551900282..ce332db56 100644 --- a/meson.build +++ b/meson.build @@ -60,6 +60,8 @@ global_args_maybe = [ '-Wno-unused-parameter', '-Wno-shift-negative-value', # required due to Pixman '-Wno-missing-field-initializers', + '-Wno-pedantic', + '-Wno-error=pedantic', '-fvisibility=hidden', ] foreach a : global_args_maybe From e2f9c1b76c043dcc86f17a883b575307c6b9be45 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 11 Jun 2019 11:41:41 +0100 Subject: [PATCH 0896/1642] Default build to warning_level=3 After suppressing the pedantic errors, we can now enable a higher warning_level by default, so developers can catch warnings earlier. Signed-off-by: Daniel Stone --- meson.build | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/meson.build b/meson.build index ce332db56..cd75c6527 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project('weston', 'c', version: '6.0.90', default_options: [ - 'warning_level=2', + 'warning_level=3', 'c_std=gnu99', 'b_lundef=false', ], @@ -61,7 +61,6 @@ global_args_maybe = [ '-Wno-shift-negative-value', # required due to Pixman '-Wno-missing-field-initializers', '-Wno-pedantic', - '-Wno-error=pedantic', '-fvisibility=hidden', ] foreach a : global_args_maybe From 68d49d772cfba6c53033cb009b0f490fd38f24ad Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Wed, 27 Mar 2019 15:45:33 +0900 Subject: [PATCH 0897/1642] compositor-drm: run finish_frame when dpms is turned off in update_complete A output repaint loop isn't scheduled beacuse the output repaint_status is AWAITING_COMPLETION when dmps is turned off in update_complete(). Therefore, the display attached to the output is remain inactive even if weston wakes up. By going through finish_frame, the output repaint_status is fixed to correct status. Signed-off-by: Tomohito Esaki --- libweston/backend-drm/drm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 627f58e54..ed9756b5d 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -1933,7 +1933,6 @@ drm_output_update_complete(struct drm_output *output, uint32_t flags, output->dpms_off_pending = 0; drm_output_get_disable_state(pending, output); drm_pending_state_apply_sync(pending); - return; } else if (output->state_cur->dpms == WESTON_DPMS_OFF && output->base.repaint_status != REPAINT_AWAITING_COMPLETION) { /* DPMS can happen to us either in the middle of a repaint From 8d23ab78bdbca420c63125648a483a65851970b1 Mon Sep 17 00:00:00 2001 From: Robert Beckett Date: Thu, 13 Jun 2019 16:55:44 +0100 Subject: [PATCH 0898/1642] backend-drm: handle multiple drm nodes with logind When using logind launcher, we receive a PauseDevice "gone" message from logind session management for each device we close while looking for KMS devices. Make logind notify the backend of the device add/remove so that the backend can decide what to do, instead of assuming that if it is a DRM_MAJOR device the session should be (de)activated. The backend can then react to its specific device. Fixes #251 Signed-off-by: Robert Beckett --- include/libweston/libweston.h | 13 +++++++++++++ libweston/backend-drm/drm.c | 28 ++++++++++++++++++++++++++++ libweston/launcher-logind.c | 17 ++++++++++------- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index ccd662ce7..253baeb4f 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -1034,6 +1034,19 @@ struct weston_backend { struct weston_output * (*create_output)(struct weston_compositor *compositor, const char *name); + + /** Notify of device addition/removal + * + * @param compositor The compositor. + * @param device The device that has changed. + * @param added Where it was added (or removed) + * + * Called when a device has been added/removed from the session. + * The backend can decide what to do based on whether it is a + * device that it is controlling or not. + */ + void (*device_changed)(struct weston_compositor *compositor, + dev_t device, bool added); }; /** Callback for saving calibration diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index ed9756b5d..cde195076 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -310,6 +310,7 @@ struct drm_backend { int id; int fd; char *filename; + dev_t devnum; } drm; struct gbm_device *gbm; struct wl_listener session_listener; @@ -6840,6 +6841,30 @@ session_notify(struct wl_listener *listener, void *data) } } + +/** + * Handle KMS GPU being added/removed + * + * If the device being added/removed is the KMS device, we activate/deactivate + * the compositor session. + * + * @param compositor The compositor instance. + * @param device The device being added/removed. + * @param added Whether the device is being added (or removed) + */ +static void +drm_device_changed(struct weston_compositor *compositor, + dev_t device, bool added) +{ + struct drm_backend *b = to_drm_backend(compositor); + + if (b->drm.fd < 0 || b->drm.devnum != device) + return; + + compositor->session_active = added; + wl_signal_emit(&compositor->session_signal, compositor); +} + /** * Determines whether or not a device is capable of modesetting. If successful, * sets b->drm.fd and b->drm.filename to the opened device. @@ -6849,6 +6874,7 @@ drm_device_is_kms(struct drm_backend *b, struct udev_device *device) { const char *filename = udev_device_get_devnode(device); const char *sysnum = udev_device_get_sysnum(device); + dev_t devnum = udev_device_get_devnum(device); drmModeRes *res; int id = -1, fd; @@ -6883,6 +6909,7 @@ drm_device_is_kms(struct drm_backend *b, struct udev_device *device) b->drm.fd = fd; b->drm.id = id; b->drm.filename = strdup(filename); + b->drm.devnum = devnum; drmModeFreeResources(res); @@ -7558,6 +7585,7 @@ drm_backend_create(struct weston_compositor *compositor, b->base.repaint_flush = drm_repaint_flush; b->base.repaint_cancel = drm_repaint_cancel; b->base.create_output = drm_output_create; + b->base.device_changed = drm_device_changed; weston_setup_vt_switch_bindings(compositor); diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c index 9893cca14..3b0a10d84 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -488,20 +488,21 @@ device_paused(struct launcher_logind *wl, DBusMessage *m) if (!strcmp(type, "pause")) launcher_logind_pause_device_complete(wl, major, minor); - if (wl->sync_drm && major == DRM_MAJOR) - launcher_logind_set_active(wl, false); + if (wl->sync_drm && wl->compositor->backend->device_changed) + wl->compositor->backend->device_changed(wl->compositor, + makedev(major,minor), + false); } static void device_resumed(struct launcher_logind *wl, DBusMessage *m) { bool r; - uint32_t major; + uint32_t major, minor; r = dbus_message_get_args(m, NULL, DBUS_TYPE_UINT32, &major, - /*DBUS_TYPE_UINT32, &minor, - DBUS_TYPE_UNIX_FD, &fd,*/ + DBUS_TYPE_UINT32, &minor, DBUS_TYPE_INVALID); if (!r) { weston_log("logind: cannot parse ResumeDevice dbus signal\n"); @@ -514,8 +515,10 @@ device_resumed(struct launcher_logind *wl, DBusMessage *m) * there is no need for us to handle this event for evdev. For DRM, we * notify the compositor to wake up. */ - if (wl->sync_drm && major == DRM_MAJOR) - launcher_logind_set_active(wl, true); + if (wl->sync_drm && wl->compositor->backend->device_changed) + wl->compositor->backend->device_changed(wl->compositor, + makedev(major,minor), + true); } static DBusHandlerResult From 9eb974aaaf64d7bd374cdda612d85ea26a264326 Mon Sep 17 00:00:00 2001 From: Silva Alejandro Ismael Date: Mon, 17 Jun 2019 11:35:54 -0300 Subject: [PATCH 0899/1642] build: Fix hint to disable remoting If a dependency is missing, the error message should tell the build option to disable it. Show the correct build option in the error message of the remoting plugin. Signed-off-by: Silva Alejandro Ismael --- remoting/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remoting/meson.build b/remoting/meson.build index 703ecdbb0..0054e0501 100644 --- a/remoting/meson.build +++ b/remoting/meson.build @@ -1,5 +1,5 @@ if get_option('remoting') - user_hint = 'If you rather not build this, set "remoting=false".' + user_hint = 'If you rather not build this, set \'-Dremoting=false\'.' if not get_option('backend-drm') error('Attempting to build the remoting plugin without the required DRM backend. ' + user_hint) From d93c0f7059498b90bbeb5d878ddc46cacc81f7ec Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 17 Jun 2019 12:33:35 +0200 Subject: [PATCH 0900/1642] backend-rdp: fix memory leak Free command data after all rects have been updated. This fixes a rather huge memory leak when using the RDP backend. Signed-off-by: Stefan Agner --- libweston/backend-rdp/rdp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 1f39d03ca..b55f45acc 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -360,6 +360,8 @@ rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_p } } + free(SURFACE_BITMAP_DATA(cmd)); + marker.frameAction = SURFACECMD_FRAMEACTION_END; update->SurfaceFrameMarker(peer->context, &marker); } From b0e16d4c5394f86424be0ff2889b37a9eb723a91 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 20 Jun 2019 16:27:27 +0200 Subject: [PATCH 0901/1642] backend-rdp: allow to force compression off By default the client communicates its preference with regards to compression to the server. However, some clients always use compression, which is not ideal for certain environments (e.g. low performance embedded devices in a local network with plenty of bandwidth). Allow to disable compression server-side which will override the clients request for compression. Signed-off-by: Stefan Agner --- compositor/main.c | 4 +++- include/libweston/backend-rdp.h | 1 + libweston/backend-rdp/rdp.c | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/compositor/main.c b/compositor/main.c index 1289327bc..fa46292f4 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2508,6 +2508,7 @@ weston_rdp_backend_config_init(struct weston_rdp_backend_config *config) config->server_key = NULL; config->env_socket = 0; config->no_clients_resize = 0; + config->force_no_compression = 0; } static int @@ -2532,7 +2533,8 @@ load_rdp_backend(struct weston_compositor *c, { WESTON_OPTION_BOOLEAN, "no-clients-resize", 0, &config.no_clients_resize }, { 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_STRING, "rdp-tls-key", 0, &config.server_key }, + { WESTON_OPTION_BOOLEAN, "force-no-compression", 0, &config.force_no_compression }, }; parse_options(rdp_options, ARRAY_LENGTH(rdp_options), argc, argv); diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index 7a63643f5..b3542507a 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -65,6 +65,7 @@ struct weston_rdp_backend_config { char *server_key; int env_socket; int no_clients_resize; + int force_no_compression; }; #ifdef __cplusplus diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index b55f45acc..31af10965 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -131,6 +131,7 @@ struct rdp_backend { char *rdp_key; int tls_enabled; int no_clients_resize; + int force_no_compression; }; enum peer_item_flags { @@ -971,6 +972,11 @@ xf_peer_activate(freerdp_peer* client) return FALSE; } + if (b->force_no_compression && settings->CompressionEnabled) { + weston_log("Forcing compression off\n"); + settings->CompressionEnabled = FALSE; + } + if (output->base.width != (int)settings->DesktopWidth || output->base.height != (int)settings->DesktopHeight) { @@ -1364,6 +1370,7 @@ rdp_backend_create(struct weston_compositor *compositor, b->base.create_output = rdp_output_create; b->rdp_key = config->rdp_key ? strdup(config->rdp_key) : NULL; b->no_clients_resize = config->no_clients_resize; + b->force_no_compression = config->force_no_compression; compositor->backend = &b->base; @@ -1447,6 +1454,7 @@ config_init_to_defaults(struct weston_rdp_backend_config *config) config->server_key = NULL; config->env_socket = 0; config->no_clients_resize = 0; + config->force_no_compression = 0; } WL_EXPORT int From 87fab1ca4efe014055c87a63dad299411bab304f Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 17 Jun 2019 11:13:20 +0100 Subject: [PATCH 0902/1642] compositor-drm: Only assign planes with atomic Without atomic modesetting, we have no way to know whether or not our desired configuration is usable. It might fail for a number of reasons: scaling limits, bandwidth limits, global resource (e.g. decompression) unit contention, or really just anything. Not only this, but there is no good way to ensure that our configuration actually lands together in the same refresh cycle - hence the 'atomic' in atomic modesetting. Some drivers implement a synchronously blocking drmModeSetPlane, whereas others return immediately. Using overlay planes can thus decimate your framerate. The pre-atomic API is not extensible either, so we need numerous out clauses: fail if we're cropping or scaling (sometimes), or changing formats, or fencing, or ... Now we've had atomic support stable for a couple of releases, just remove support for doing anything more fancy than displaying our composited output and a cursor with drivers which don't support atomic modesetting. Support for using overlay planes was already disabled by default when using the legacy API, and required a debug key combination to toggle it on by flipping the sprites_are_broken variable. We can ensure that we never try to use it on legacy by simply ignoring the hotkey when in legacy mode. Signed-off-by: Daniel Stone --- libweston/backend-drm/drm.c | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index cde195076..b879d161b 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -2025,6 +2025,7 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, pixman_box32_t *extents; assert(!b->sprites_are_broken); + assert(b->atomic_modeset); assert(mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); /* Check the view spans exactly the output size, calculated in the @@ -2039,8 +2040,7 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, /* If the surface buffer has an in-fence fd, but the plane doesn't * support fences, we can't place the buffer on this plane. */ if (ev->surface->acquire_fence_fd >= 0 && - (!b->atomic_modeset || - scanout_plane->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0)) + scanout_plane->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0) return NULL; fb = drm_fb_get_from_view(output_state, ev); @@ -2050,12 +2050,6 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, return NULL; } - /* Can't change formats with just a pageflip */ - if (!b->atomic_modeset && fb->format->format != output->gbm_format) { - drm_fb_unref(fb); - return NULL; - } - state = drm_output_state_get_plane(output_state, scanout_plane); /* The only way we can already have a buffer in the scanout plane is @@ -2075,13 +2069,6 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state, state->dest_h != (unsigned) output->base.current_mode->height) goto err; - /* The legacy API does not let us perform cropping or scaling. */ - if (!b->atomic_modeset && - (state->src_x != 0 || state->src_y != 0 || - state->src_w != state->dest_w << 16 || - state->src_h != state->dest_h << 16)) - goto err; - state->in_fence_fd = ev->surface->acquire_fence_fd; /* In plane-only mode, we don't need to test the state now, as we @@ -3297,6 +3284,7 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, } availability = NO_PLANES; assert(!b->sprites_are_broken); + assert(b->atomic_modeset); fb = drm_fb_get_from_view(output_state, ev); if (!fb) { @@ -3356,22 +3344,12 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state, state = NULL; continue; } - if (!b->atomic_modeset && - (state->src_w != state->dest_w << 16 || - state->src_h != state->dest_h << 16)) { - drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " - "no scaling without atomic\n", ev); - drm_plane_state_put_back(state); - state = NULL; - continue; - } /* If the surface buffer has an in-fence fd, but the plane * doesn't support fences, we can't place the buffer on this * plane. */ if (ev->surface->acquire_fence_fd >= 0 && - (!b->atomic_modeset || - p->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0)) { + p->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0) { drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " "no in-fence support\n", ev); drm_plane_state_put_back(state); @@ -7044,7 +7022,9 @@ planes_binding(struct weston_keyboard *keyboard, const struct timespec *time, b->cursors_are_broken ^= 1; break; case KEY_V: - b->sprites_are_broken ^= 1; + /* We don't support overlay-plane usage with legacy KMS. */ + if (b->atomic_modeset) + b->sprites_are_broken ^= 1; break; case KEY_O: b->sprites_hidden ^= 1; From 31838bf17e3a5a5e39e25700496c7f8315ecab42 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 17 Jun 2019 11:23:25 +0100 Subject: [PATCH 0903/1642] compositor-drm: Remove legacy plane and vblank usage As of the previous commit, we never create state which uses overlay planes on non-atomic drivers. We can thus remove the calls to drmModeSetPlane. The only time we ever waited for vblank events was when we had called drmModeSetPlane and needed to make sure we waited until it was active. We can thus also remove all the vblank event machinery. Signed-off-by: Daniel Stone --- libweston/backend-drm/drm.c | 106 ++---------------------------------- 1 file changed, 4 insertions(+), 102 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index b879d161b..1ad23be7c 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -538,7 +538,6 @@ struct drm_output { /* Holds the properties for the CRTC */ struct drm_property_info props_crtc[WDRM_CRTC__COUNT]; - int vblank_pending; int page_flip_pending; int atomic_complete_pending; int destroy_pending; @@ -2005,9 +2004,8 @@ drm_output_assign_state(struct drm_output_state *state, if (b->atomic_modeset) continue; - if (plane->type == WDRM_PLANE_TYPE_OVERLAY) - output->vblank_pending++; - else if (plane->type == WDRM_PLANE_TYPE_PRIMARY) + assert(plane->type != WDRM_PLANE_TYPE_OVERLAY); + if (plane->type == WDRM_PLANE_TYPE_PRIMARY) output->page_flip_pending = 1; } } @@ -2254,7 +2252,6 @@ drm_output_apply_state_legacy(struct drm_output_state *state) struct drm_plane *scanout_plane = output->scanout_plane; struct drm_property_info *dpms_prop; struct drm_plane_state *scanout_state; - struct drm_plane_state *ps; struct drm_mode *mode; struct drm_head *head; const struct pixel_format_info *pinfo = NULL; @@ -2281,21 +2278,6 @@ drm_output_apply_state_legacy(struct drm_output_state *state) } if (state->dpms != WESTON_DPMS_ON) { - wl_list_for_each(ps, &state->plane_list, link) { - struct drm_plane *p = ps->plane; - assert(ps->fb == NULL); - assert(ps->output == NULL); - - if (p->type != WDRM_PLANE_TYPE_OVERLAY) - continue; - - ret = drmModeSetPlane(backend->drm.fd, p->plane_id, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - if (ret) - weston_log("drmModeSetPlane failed disable: %s\n", - strerror(errno)); - } - if (output->cursor_plane) { ret = drmModeSetCursor(backend->drm.fd, output->crtc_id, 0, 0, 0); @@ -2374,56 +2356,6 @@ drm_output_apply_state_legacy(struct drm_output_state *state) drm_output_set_cursor(state); - /* - * Now, update all the sprite surfaces - */ - wl_list_for_each(ps, &state->plane_list, link) { - uint32_t flags = 0, fb_id = 0; - drmVBlank vbl = { - .request.type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT, - .request.sequence = 1, - }; - struct drm_plane *p = ps->plane; - - if (p->type != WDRM_PLANE_TYPE_OVERLAY) - continue; - - assert(p->state_cur->complete); - assert(!!p->state_cur->output == !!p->state_cur->fb); - assert(!p->state_cur->output || p->state_cur->output == output); - assert(!ps->complete); - assert(!ps->output || ps->output == output); - assert(!!ps->output == !!ps->fb); - /* The legacy SetPlane API doesn't support fences */ - assert(ps->in_fence_fd == -1); - - if (ps->fb && !backend->sprites_hidden) - fb_id = ps->fb->fb_id; - - ret = drmModeSetPlane(backend->drm.fd, p->plane_id, - output->crtc_id, fb_id, flags, - ps->dest_x, ps->dest_y, - ps->dest_w, ps->dest_h, - ps->src_x, ps->src_y, - ps->src_w, ps->src_h); - if (ret) - weston_log("setplane failed: %d: %s\n", - ret, strerror(errno)); - - vbl.request.type |= drm_waitvblank_pipe(output); - - /* - * Queue a vblank signal so we know when the surface - * becomes active on the display or has been replaced. - */ - vbl.request.signal = (unsigned long) ps; - ret = drmWaitVBlank(backend->drm.fd, &vbl); - if (ret) { - weston_log("vblank event request failed: %d: %s\n", - ret, strerror(errno)); - } - } - if (state->dpms != output->state_cur->dpms) { wl_list_for_each(head, &output->base.head_list, base.output_link) { dpms_prop = &head->props_conn[WDRM_CONNECTOR_DPMS]; @@ -3123,30 +3055,6 @@ drm_output_update_msc(struct drm_output *output, unsigned int seq) output->base.msc = (msc_hi << 32) + seq; } -static void -vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, - void *data) -{ - struct drm_plane_state *ps = (struct drm_plane_state *) data; - struct drm_output_state *os = ps->output_state; - struct drm_output *output = os->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); - uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | - WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; - - assert(!b->atomic_modeset); - - drm_output_update_msc(output, frame); - output->vblank_pending--; - assert(output->vblank_pending >= 0); - - assert(ps->fb); - - if (output->page_flip_pending || output->vblank_pending) - return; - - drm_output_update_complete(output, flags, sec, usec); -} static void page_flip_handler(int fd, unsigned int frame, @@ -3164,9 +3072,6 @@ page_flip_handler(int fd, unsigned int frame, assert(output->page_flip_pending); output->page_flip_pending = 0; - if (output->vblank_pending) - return; - drm_output_update_complete(output, flags, sec, usec); } @@ -4152,7 +4057,6 @@ on_drm_input(int fd, uint32_t mask, void *data) else #endif evctx.page_flip_handler = page_flip_handler; - evctx.vblank_handler = vblank_handler; drmHandleEvent(fd, &evctx); return 1; @@ -6305,8 +6209,7 @@ drm_output_destroy(struct weston_output *base) assert(!output->virtual); - if (output->page_flip_pending || output->vblank_pending || - output->atomic_complete_pending) { + if (output->page_flip_pending || output->atomic_complete_pending) { output->destroy_pending = 1; weston_log("destroy output while page flip pending\n"); return; @@ -6335,8 +6238,7 @@ drm_output_disable(struct weston_output *base) assert(!output->virtual); - if (output->page_flip_pending || output->vblank_pending || - output->atomic_complete_pending) { + if (output->page_flip_pending || output->atomic_complete_pending) { output->disable_pending = 1; return -1; } From 71309894f38b83613ee90f40f02672e3e82903d7 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 14 Jun 2019 10:43:40 +0300 Subject: [PATCH 0904/1642] doc: Move helper scripts to doc/scripts No functional change, just re-arrange bits in doc/. Signed-off-by: Marius Vlad --- doc/{ => scripts}/calibration-helper.bash | 0 doc/{ => scripts}/remoting-client-receive.bash | 0 remoting/README | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename doc/{ => scripts}/calibration-helper.bash (100%) rename doc/{ => scripts}/remoting-client-receive.bash (100%) diff --git a/doc/calibration-helper.bash b/doc/scripts/calibration-helper.bash similarity index 100% rename from doc/calibration-helper.bash rename to doc/scripts/calibration-helper.bash diff --git a/doc/remoting-client-receive.bash b/doc/scripts/remoting-client-receive.bash similarity index 100% rename from doc/remoting-client-receive.bash rename to doc/scripts/remoting-client-receive.bash diff --git a/remoting/README b/remoting/README index 2166d30e0..a5e8ea5c6 100644 --- a/remoting/README +++ b/remoting/README @@ -11,7 +11,7 @@ This plugin sends motion jpeg images to a client via RTP using gstreamer, and so requires gstreamer-1.0. This plugin starts sending images immediately when weston is run, and keeps sending them until weston shuts down. The image stream can be received by any appropriately configured RTP client, but a sample -gstreamer RTP client script can be found at doc/remoting-client-receive.bash. +gstreamer RTP client script can be found at doc/scripts/remoting-client-receive.bash. Script usage: remoting-client-receive.bash From bbf6ea0b4f5bfd7990e71d5f6f55152d90646a1a Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 14 Jun 2019 12:41:02 +0300 Subject: [PATCH 0905/1642] build: Add sphinx/breathe support for generating documentation This is adds basic configuration files for doxygen and for breathe, which is a doxygen-to-sphinx bridge that can document C symbols. Breathe is configured with default project 'weston' and implicitly adds :members: and :undoc-members: to breathe configuration options. This allows a shorter way to call breathe directives without the need specify the project and also to display implicitly all the members, documented or not. A 'docs' run_target to force the docs to be re-built has been added. Initially (the first time the build system is ran) the documentation will automatically be built, but later re-builds will require the use of the 'docs' target. This avoid further delays in building weston but in the same time allows the possiblity to update/improve the documentation bits to those who want that. Signed-off-by: Marius Vlad --- doc/sphinx/conf.py.in | 201 ++ doc/sphinx/doxygen.ini.in | 2494 +++++++++++++++++++++++ doc/sphinx/index.rst | 32 + doc/sphinx/meson.build | 96 + doc/sphinx/run_doxygen_sphinx.sh.in | 2 + doc/sphinx/toc/libweston.rst | 46 + doc/sphinx/toc/libweston/compositor.rst | 7 + doc/sphinx/toc/libweston/head.rst | 18 + doc/sphinx/toc/libweston/meson.build | 6 + doc/sphinx/toc/libweston/output.rst | 10 + doc/sphinx/toc/meson.build | 8 + meson.build | 7 +- meson_options.txt | 6 + 13 files changed, 2931 insertions(+), 2 deletions(-) create mode 100644 doc/sphinx/conf.py.in create mode 100644 doc/sphinx/doxygen.ini.in create mode 100644 doc/sphinx/index.rst create mode 100644 doc/sphinx/meson.build create mode 100755 doc/sphinx/run_doxygen_sphinx.sh.in create mode 100644 doc/sphinx/toc/libweston.rst create mode 100644 doc/sphinx/toc/libweston/compositor.rst create mode 100644 doc/sphinx/toc/libweston/head.rst create mode 100644 doc/sphinx/toc/libweston/meson.build create mode 100644 doc/sphinx/toc/libweston/output.rst create mode 100644 doc/sphinx/toc/meson.build diff --git a/doc/sphinx/conf.py.in b/doc/sphinx/conf.py.in new file mode 100644 index 000000000..6ec48e0f7 --- /dev/null +++ b/doc/sphinx/conf.py.in @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +import sphinx + +sys.path.append(os.path.abspath('sphinxext')) + +# -- Project information ----------------------------------------------------- + +project = u'weston' +copyright = u'2019, Weston community' +author = u'Weston community ' + + +# The short X.Y version +version = u'' +# The full version, including alpha/beta/rc tags +release = u'@VERSION@' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +needs_sphinx = '2.1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.autosectionlabel', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'breathe', +] + +breathe_projects = { "weston": "@BUILD_ROOT@/xml/" } +breathe_default_members = ('members', 'undoc-members') +breathe_default_project = "weston" + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +source_suffix = ['.rst' ] + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + +# default domain +primary_domain = 'cpp' + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'weston' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'weston.tex', u'Weston Documentation', + u'Weston community', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'weston', u'Weston Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'weston', u'Wweston Documentation', + author, 'Weston community', 'Weston Documentation' + 'Miscellaneous'), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/3': None} + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True diff --git a/doc/sphinx/doxygen.ini.in b/doc/sphinx/doxygen.ini.in new file mode 100644 index 000000000..fdfdfc6c1 --- /dev/null +++ b/doc/sphinx/doxygen.ini.in @@ -0,0 +1,2494 @@ +# Doxyfile 1.8.13 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "libweston" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @OUTPUT_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @SRC_ROOT@/libweston @SRC_ROOT@/include/libweston + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse-libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = NO + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /E{TXW$$ohJ-O8AO0NrnXK@BTO9Ky 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 w [label = "weston_head_is_connected()"]; + c << w [label = "bool"]; + c => w [label = "weston_head_is_enabled()"]; + c << w [label = "bool"]; + c => w [label = "weston_head_is_device_changed()"]; + c << w [label = "bool"]; + c abox c [label = "If the head needs enabling, create an output."]; + c abox c [label = "If the head needs disabling, disable the output or destroy the output."]; + c >> w; +} diff --git a/doc/sphinx/toc/libweston/images/react-to-heads-changed.png b/doc/sphinx/toc/libweston/images/react-to-heads-changed.png new file mode 100644 index 0000000000000000000000000000000000000000..ba1b8132b82bc609283cbe8a8c35bdbfc090fe03 GIT binary patch literal 27241 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 Date: Wed, 18 Dec 2019 14:52:23 -0300 Subject: [PATCH 1264/1642] weston-log: rename a confusing parameter name in weston_compositor_add_log_scope() In the function weston_compositor_add_log_scope() we have a struct weston_log_context parameter that in the .c file is named log_ctx. In the .h, the same parameter is named compositor. This is confusing, since its type is not struct weston_compositor, but struct weston_log_context. Rename the parameter in the .h to log_ctx. Signed-off-by: Leandro Ribeiro --- include/libweston/weston-log.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libweston/weston-log.h b/include/libweston/weston-log.h index 88c0d1a8a..707cb69f8 100644 --- a/include/libweston/weston-log.h +++ b/include/libweston/weston-log.h @@ -63,7 +63,7 @@ typedef void (*weston_log_scope_cb)(struct weston_log_subscription *sub, void *user_data); struct weston_log_scope * -weston_compositor_add_log_scope(struct weston_log_context *compositor, +weston_compositor_add_log_scope(struct weston_log_context *log_ctx, const char *name, const char *description, weston_log_scope_cb new_subscription, From ee73105f50969620dc25373ead2a405956c1c4bc Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 26 Dec 2019 12:44:06 -0300 Subject: [PATCH 1265/1642] weston-log: rename a confusing parameter name in weston_log_subscription_printf() In the function weston_log_subscription_printf() we have a struct weston_log_subscription parameter that in the .c file is named sub. In the .h, the same parameter is named scope. This is confusing, since its type is not struct weston_log_scope, but struct weston_log_subscription. Rename the parameter in the .h to sub. Signed-off-by: Leandro Ribeiro --- include/libweston/weston-log.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libweston/weston-log.h b/include/libweston/weston-log.h index 707cb69f8..8ef2a467f 100644 --- a/include/libweston/weston-log.h +++ b/include/libweston/weston-log.h @@ -89,7 +89,7 @@ weston_log_scope_printf(struct weston_log_scope *scope, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); void -weston_log_subscription_printf(struct weston_log_subscription *scope, +weston_log_subscription_printf(struct weston_log_subscription *sub, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); void From 5976dbbbb5bbdbae44880b5467b055a0a3aa83f1 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Wed, 18 Dec 2019 15:21:03 -0300 Subject: [PATCH 1266/1642] weston-log: rename the confusing function name weston_compositor_add_log_scope() There's a function named weston_compositor_add_log_scope() but it doesn't take a struct weston_compositor argument. Rename it to weston_log_ctx_add_log_scope(), as the log_scope is being added to a log_context. Also, bump libweston_major to 9. Signed-off-by: Leandro Ribeiro --- compositor/main.c | 9 ++++----- doc/sphinx/toc/libweston/log.rst | 2 +- include/libweston/weston-log.h | 14 +++++++------- libweston/backend-drm/drm.c | 8 ++++---- libweston/compositor.c | 18 +++++++++--------- libweston/content-protection.c | 8 ++++---- libweston/weston-log.c | 20 ++++++++++---------- meson.build | 2 +- pipewire/pipewire-plugin.c | 10 +++++----- xwayland/launcher.c | 8 ++++---- 10 files changed, 49 insertions(+), 50 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 8eb8a470e..309904c9c 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -3162,7 +3162,7 @@ wet_main(int argc, char *argv[]) return EXIT_FAILURE; } - log_scope = weston_compositor_add_log_scope(log_ctx, "log", + log_scope = weston_log_ctx_add_log_scope(log_ctx, "log", "Weston and Wayland log\n", NULL, NULL, NULL); weston_log_file_open(log); @@ -3248,10 +3248,9 @@ wet_main(int argc, char *argv[]) segv_compositor = wet.compositor; protocol_scope = - weston_compositor_add_log_scope(log_ctx, - "proto", - "Wayland protocol dump for all clients.\n", - NULL, NULL, NULL); + weston_log_ctx_add_log_scope(log_ctx, "proto", + "Wayland protocol dump for all clients.\n", + NULL, NULL, NULL); protologger = wl_display_add_protocol_logger(display, protocol_log_fn, diff --git a/doc/sphinx/toc/libweston/log.rst b/doc/sphinx/toc/libweston/log.rst index fd68385f4..e1b033379 100644 --- a/doc/sphinx/toc/libweston/log.rst +++ b/doc/sphinx/toc/libweston/log.rst @@ -28,7 +28,7 @@ Log scopes A scope represents a source for a data stream (i.e., a producer). You'll require one as a way to generate data. Creating a log scope is done using -:func:`weston_compositor_add_log_scope()`. You can customize the scope +:func:`weston_log_ctx_add_log_scope()`. You can customize the scope behaviour and you'll require at least a name and a description for the scope. diff --git a/include/libweston/weston-log.h b/include/libweston/weston-log.h index 8ef2a467f..40b88571d 100644 --- a/include/libweston/weston-log.h +++ b/include/libweston/weston-log.h @@ -55,7 +55,7 @@ struct weston_debug_stream; * * @param sub The subscription. * @param user_data The \c user_data argument given to - * weston_compositor_add_log_scope() + * weston_log_ctx_add_log_scope() * * @memberof weston_log_scope */ @@ -63,12 +63,12 @@ typedef void (*weston_log_scope_cb)(struct weston_log_subscription *sub, void *user_data); struct weston_log_scope * -weston_compositor_add_log_scope(struct weston_log_context *log_ctx, - const char *name, - const char *description, - weston_log_scope_cb new_subscription, - weston_log_scope_cb destroy_subscription, - void *user_data); +weston_log_ctx_add_log_scope(struct weston_log_context *log_ctx, + const char *name, + const char *description, + weston_log_scope_cb new_subscription, + weston_log_scope_cb destroy_subscription, + void *user_data); void weston_compositor_log_scope_destroy(struct weston_log_scope *scope); diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index e0b1cbd7d..8780bebec 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -2825,10 +2825,10 @@ drm_backend_create(struct weston_compositor *compositor, b->pageflip_timeout = config->pageflip_timeout; b->use_pixman_shadow = config->use_pixman_shadow; - b->debug = weston_compositor_add_log_scope(compositor->weston_log_ctx, - "drm-backend", - "Debug messages from DRM/KMS backend\n", - NULL, NULL, NULL); + b->debug = weston_log_ctx_add_log_scope(compositor->weston_log_ctx, + "drm-backend", + "Debug messages from DRM/KMS backend\n", + NULL, NULL, NULL); compositor->backend = &b->base; diff --git a/libweston/compositor.c b/libweston/compositor.c index dbc649279..0a70c273f 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -7321,17 +7321,17 @@ weston_compositor_create(struct wl_display *display, WESTON_LAYER_POSITION_CURSOR); ec->debug_scene = - weston_compositor_add_log_scope(ec->weston_log_ctx, "scene-graph", - "Scene graph details\n", - debug_scene_graph_cb, NULL, - ec); + weston_log_ctx_add_log_scope(ec->weston_log_ctx, "scene-graph", + "Scene graph details\n", + debug_scene_graph_cb, NULL, + ec); ec->timeline = - weston_compositor_add_log_scope(ec->weston_log_ctx, "timeline", - "Timeline event points\n", - weston_timeline_create_subscription, - weston_timeline_destroy_subscription, - ec); + weston_log_ctx_add_log_scope(ec->weston_log_ctx, "timeline", + "Timeline event points\n", + weston_timeline_create_subscription, + weston_timeline_destroy_subscription, + ec); return ec; fail: diff --git a/libweston/content-protection.c b/libweston/content-protection.c index 236614531..0d9c47880 100644 --- a/libweston/content-protection.c +++ b/libweston/content-protection.c @@ -339,9 +339,9 @@ weston_compositor_enable_content_protection(struct weston_compositor *compositor cp->destroy_listener.notify = cp_destroy_listener; wl_signal_add(&compositor->destroy_signal, &cp->destroy_listener); - cp->debug = weston_compositor_add_log_scope(compositor->weston_log_ctx, - "content-protection-debug", - "debug-logs for content-protection", - NULL, NULL, NULL); + cp->debug = weston_log_ctx_add_log_scope(compositor->weston_log_ctx, + "content-protection-debug", + "debug-logs for content-protection", + NULL, NULL, NULL); return 0; } diff --git a/libweston/weston-log.c b/libweston/weston-log.c index b2c4597c7..57ad07be9 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -355,7 +355,7 @@ weston_log_subscription_remove(struct weston_log_subscription *sub) * matching against the \c name. * * @param log_ctx - * @param name the scope name, see weston_compositor_add_log_scope() + * @param name the scope name, see weston_log_ctx_add_log_scope() * @returns NULL if none found, or a pointer to a weston_log_scope * * @ingroup internal-log @@ -580,12 +580,12 @@ weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) * @sa weston_log_scope_cb, weston_log_subscribe */ WL_EXPORT struct weston_log_scope * -weston_compositor_add_log_scope(struct weston_log_context *log_ctx, - const char *name, - const char *description, - weston_log_scope_cb new_subscription, - weston_log_scope_cb destroy_subscription, - void *user_data) +weston_log_ctx_add_log_scope(struct weston_log_context *log_ctx, + const char *name, + const char *description, + weston_log_scope_cb new_subscription, + weston_log_scope_cb destroy_subscription, + void *user_data) { struct weston_log_scope *scope; struct weston_log_subscription *pending_sub = NULL; @@ -726,7 +726,7 @@ weston_log_subscription_complete(struct weston_log_subscription *sub) * stream. Particularly useful for the weston-debug protocol. * * @memberof weston_log_scope - * @sa weston_compositor_add_log_scope, weston_compositor_log_scope_destroy + * @sa weston_log_ctx_add_log_scope, weston_compositor_log_scope_destroy */ WL_EXPORT void weston_log_scope_complete(struct weston_log_scope *scope) @@ -895,9 +895,9 @@ weston_log_scope_timestamp(struct weston_log_scope *scope, * to the scope \c scope_name. * * If \c scope_name has already been created (using - * weston_compositor_add_log_scope) the subscription will take place + * weston_log_ctx_add_log_scope) the subscription will take place * immediately, otherwise we store the subscription into a pending list. See - * also weston_compositor_add_log_scope(). + * also weston_log_ctx_add_log_scope(). * * @param log_ctx the log context, used for accessing pending list * @param subscriber the subscriber, which has to be created before diff --git a/meson.build b/meson.build index 43e1795f4..662867cbd 100644 --- a/meson.build +++ b/meson.build @@ -10,7 +10,7 @@ project('weston', license: 'MIT/Expat', ) -libweston_major = 8 +libweston_major = 9 # libweston_revision is manufactured to follow the autotools build's # library file naming, thanks to libtool diff --git a/pipewire/pipewire-plugin.c b/pipewire/pipewire-plugin.c index 552f5745d..d1e0a29ec 100644 --- a/pipewire/pipewire-plugin.c +++ b/pipewire/pipewire-plugin.c @@ -822,11 +822,11 @@ weston_module_init(struct weston_compositor *compositor) goto failed; } - pipewire->debug = weston_compositor_add_log_scope( - compositor->weston_log_ctx, - "pipewire", - "Debug messages from pipewire plugin\n", - NULL, NULL, NULL); + pipewire->debug = + weston_log_ctx_add_log_scope(compositor->weston_log_ctx, + "pipewire", + "Debug messages from pipewire plugin\n", + NULL, NULL, NULL); return 0; diff --git a/xwayland/launcher.c b/xwayland/launcher.c index 2f2cebbef..e15dd4f66 100644 --- a/xwayland/launcher.c +++ b/xwayland/launcher.c @@ -396,10 +396,10 @@ weston_module_init(struct weston_compositor *compositor) } wxs->wm_debug = - weston_compositor_add_log_scope(wxs->compositor->weston_log_ctx, - "xwm-wm-x11", - "XWM's window management X11 events\n", - NULL, NULL, NULL); + weston_log_ctx_add_log_scope(wxs->compositor->weston_log_ctx, + "xwm-wm-x11", + "XWM's window management X11 events\n", + NULL, NULL, NULL); return 0; From f014964f6f5a521e42721814033bf868a191d947 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Wed, 18 Dec 2019 15:52:18 -0300 Subject: [PATCH 1267/1642] weston-log: rename the confusing function name weston_compositor_log_scope_destroy() There's a function named weston_compositor_log_scope_destroy() but it doesn't take a struct weston_compositor argument. Rename it to weston_log_scope_destroy(), as the argument is a struct weston_log_scope. Signed-off-by: Leandro Ribeiro --- compositor/main.c | 4 ++-- doc/sphinx/toc/libweston/log.rst | 2 +- include/libweston/weston-log.h | 2 +- libweston/backend-drm/drm.c | 2 +- libweston/compositor.c | 4 ++-- libweston/content-protection.c | 2 +- libweston/weston-log.c | 6 +++--- pipewire/pipewire-plugin.c | 2 +- xwayland/launcher.c | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 309904c9c..e802badf2 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -3375,11 +3375,11 @@ wet_main(int argc, char *argv[]) if (protologger) wl_protocol_logger_destroy(protologger); - weston_compositor_log_scope_destroy(protocol_scope); + weston_log_scope_destroy(protocol_scope); protocol_scope = NULL; weston_compositor_tear_down(wet.compositor); - weston_compositor_log_scope_destroy(log_scope); + weston_log_scope_destroy(log_scope); log_scope = NULL; weston_log_ctx_compositor_destroy(wet.compositor); weston_compositor_destroy(wet.compositor); diff --git a/doc/sphinx/toc/libweston/log.rst b/doc/sphinx/toc/libweston/log.rst index e1b033379..2d48e489b 100644 --- a/doc/sphinx/toc/libweston/log.rst +++ b/doc/sphinx/toc/libweston/log.rst @@ -39,7 +39,7 @@ scope. important for the subscription part, detailed bit later. Log scopes are managed **explicitly**, and destroying the scope is done using -:func:`weston_compositor_log_scope_destroy`. +:func:`weston_log_scope_destroy`. Available scopes in weston ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/include/libweston/weston-log.h b/include/libweston/weston-log.h index 40b88571d..5b6ed1b1d 100644 --- a/include/libweston/weston-log.h +++ b/include/libweston/weston-log.h @@ -71,7 +71,7 @@ weston_log_ctx_add_log_scope(struct weston_log_context *log_ctx, void *user_data); void -weston_compositor_log_scope_destroy(struct weston_log_scope *scope); +weston_log_scope_destroy(struct weston_log_scope *scope); bool weston_log_scope_is_enabled(struct weston_log_scope *scope); diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 8780bebec..8395a546b 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -2387,7 +2387,7 @@ drm_destroy(struct weston_compositor *ec) destroy_sprites(b); - weston_compositor_log_scope_destroy(b->debug); + weston_log_scope_destroy(b->debug); b->debug = NULL; weston_compositor_shutdown(ec); diff --git a/libweston/compositor.c b/libweston/compositor.c index 0a70c273f..0166b73af 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -7699,10 +7699,10 @@ weston_compositor_tear_down(struct weston_compositor *compositor) if (compositor->heads_changed_source) wl_event_source_remove(compositor->heads_changed_source); - weston_compositor_log_scope_destroy(compositor->debug_scene); + weston_log_scope_destroy(compositor->debug_scene); compositor->debug_scene = NULL; - weston_compositor_log_scope_destroy(compositor->timeline); + weston_log_scope_destroy(compositor->timeline); compositor->timeline = NULL; } diff --git a/libweston/content-protection.c b/libweston/content-protection.c index 0d9c47880..7b7d5419e 100644 --- a/libweston/content-protection.c +++ b/libweston/content-protection.c @@ -186,7 +186,7 @@ cp_destroy_listener(struct wl_listener *listener, void *data) destroy_listener); wl_list_remove(&cp->destroy_listener.link); wl_list_remove(&cp->protected_list); - weston_compositor_log_scope_destroy(cp->debug); + weston_log_scope_destroy(cp->debug); cp->debug = NULL; cp->surface_protection_update = NULL; free(cp); diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 57ad07be9..20b9deae9 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -573,7 +573,7 @@ weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) * against the scope being created and if found will be added to the scope's * subscription list. * - * The log scope must be destroyed using weston_compositor_log_scope_destroy() + * The log scope must be destroyed using weston_log_scope_destroy() * before destroying the weston_compositor. * * @memberof weston_log_scope @@ -654,7 +654,7 @@ weston_log_ctx_add_log_scope(struct weston_log_context *log_ctx, * @memberof weston_log_scope */ WL_EXPORT void -weston_compositor_log_scope_destroy(struct weston_log_scope *scope) +weston_log_scope_destroy(struct weston_log_scope *scope) { struct weston_log_subscription *sub, *sub_tmp; @@ -726,7 +726,7 @@ weston_log_subscription_complete(struct weston_log_subscription *sub) * stream. Particularly useful for the weston-debug protocol. * * @memberof weston_log_scope - * @sa weston_log_ctx_add_log_scope, weston_compositor_log_scope_destroy + * @sa weston_log_ctx_add_log_scope, weston_log_scope_destroy */ WL_EXPORT void weston_log_scope_complete(struct weston_log_scope *scope) diff --git a/pipewire/pipewire-plugin.c b/pipewire/pipewire-plugin.c index d1e0a29ec..26a120187 100644 --- a/pipewire/pipewire-plugin.c +++ b/pipewire/pipewire-plugin.c @@ -648,7 +648,7 @@ weston_pipewire_destroy(struct wl_listener *l, void *data) struct weston_pipewire *pipewire = wl_container_of(l, pipewire, destroy_listener); - weston_compositor_log_scope_destroy(pipewire->debug); + weston_log_scope_destroy(pipewire->debug); pipewire->debug = NULL; wl_event_source_remove(pipewire->loop_source); diff --git a/xwayland/launcher.c b/xwayland/launcher.c index e15dd4f66..bbea817cb 100644 --- a/xwayland/launcher.c +++ b/xwayland/launcher.c @@ -230,7 +230,7 @@ weston_xserver_destroy(struct wl_listener *l, void *data) if (wxs->loop) weston_xserver_shutdown(wxs); - weston_compositor_log_scope_destroy(wxs->wm_debug); + weston_log_scope_destroy(wxs->wm_debug); free(wxs); } From 60b6572b356dfd0510a4de6d0891d4541b2f15c7 Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Tue, 28 Jan 2020 17:18:33 +1300 Subject: [PATCH 1268/1642] Fix Wmaybe-uninitialized warnings Just a couple of places which shouldn't be possible, so initialized and added assertions to make sure. Signed-off-by: Scott Anderson --- libweston/backend-drm/modes.c | 3 ++- libweston/backend-headless/headless.c | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/libweston/backend-drm/modes.c b/libweston/backend-drm/modes.c index 7c45e50a0..e12ed625d 100644 --- a/libweston/backend-drm/modes.c +++ b/libweston/backend-drm/modes.c @@ -685,7 +685,7 @@ static int drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info) { struct weston_mode *base; - struct drm_mode *mode; + struct drm_mode *mode = NULL; struct drm_backend *backend; const drmModeModeInfo *chosen = NULL; @@ -699,6 +699,7 @@ drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info) } if (chosen == info) { + assert(mode); backend = to_drm_backend(output->base.compositor); drm_output_destroy_mode(backend, mode); chosen = NULL; diff --git a/libweston/backend-headless/headless.c b/libweston/backend-headless/headless.c index c98bdc243..e9d0ad8f0 100644 --- a/libweston/backend-headless/headless.c +++ b/libweston/backend-headless/headless.c @@ -450,6 +450,9 @@ headless_backend_create(struct weston_compositor *compositor, case HEADLESS_NOOP: ret = noop_renderer_init(compositor); break; + default: + assert(0 && "invalid renderer type"); + ret = -1; } if (ret < 0) From b4bd12b738e9b2795af9c61e8e1df6054e422ce9 Mon Sep 17 00:00:00 2001 From: Guillaume Champagne Date: Mon, 27 Jan 2020 20:08:15 -0500 Subject: [PATCH 1269/1642] launcher: move weston_environment_get_fd weston_environment_get_fd was declared in weston-launch and implemented in compositor.c. Since the function is not used elsewhere in the code, it is replaced by a static function in launcher-weston-launch.c Signed-off-by: Guillaume Champagne --- libweston/compositor.c | 20 -------------------- libweston/launcher-weston-launch.c | 26 ++++++++++++++++++++++++-- libweston/weston-launch.h | 3 --- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 0166b73af..81bd23bf7 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6929,26 +6929,6 @@ compositor_bind(struct wl_client *client, compositor, NULL); } -WL_EXPORT int -weston_environment_get_fd(const char *env) -{ - char *e; - int fd, flags; - - e = getenv(env); - if (!e || !safe_strtoint(e, &fd)) - return -1; - - flags = fcntl(fd, F_GETFD); - if (flags == -1) - return -1; - - fcntl(fd, F_SETFD, flags | FD_CLOEXEC); - unsetenv(env); - - return fd; -} - static const char * output_repaint_status_text(struct weston_output *output) { diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c index 7bac0a307..d9397052a 100644 --- a/libweston/launcher-weston-launch.c +++ b/libweston/launcher-weston-launch.c @@ -47,6 +47,7 @@ #include #include "weston-launch.h" #include "launcher-impl.h" +#include "shared/string-helpers.h" #define DRM_MAJOR 226 @@ -231,6 +232,27 @@ launcher_weston_launch_activate_vt(struct weston_launcher *launcher_base, int vt return ioctl(launcher->tty, VT_ACTIVATE, vt); } +static int +launcher_weston_environment_get_fd(const char *env) +{ + char *e; + int fd, flags; + + e = getenv(env); + if (!e || !safe_strtoint(e, &fd)) + return -1; + + flags = fcntl(fd, F_GETFD); + if (flags == -1) + return -1; + + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); + unsetenv(env); + + return fd; +} + + static int launcher_weston_launch_connect(struct weston_launcher **out, struct weston_compositor *compositor, int tty, const char *seat_id, bool sync_drm) @@ -246,9 +268,9 @@ launcher_weston_launch_connect(struct weston_launcher **out, struct weston_compo * (struct launcher_weston_launch **) out = launcher; launcher->compositor = compositor; launcher->drm_fd = -1; - launcher->fd = weston_environment_get_fd("WESTON_LAUNCHER_SOCK"); + launcher->fd = launcher_weston_environment_get_fd("WESTON_LAUNCHER_SOCK"); if (launcher->fd != -1) { - launcher->tty = weston_environment_get_fd("WESTON_TTY_FD"); + launcher->tty = launcher_weston_environment_get_fd("WESTON_TTY_FD"); /* We don't get a chance to read out the original kb * mode for the tty, so just hard code K_UNICODE here * in case we have to clean if weston-launch dies. */ diff --git a/libweston/weston-launch.h b/libweston/weston-launch.h index 819321c85..bd974de8f 100644 --- a/libweston/weston-launch.h +++ b/libweston/weston-launch.h @@ -46,7 +46,4 @@ struct weston_launcher_open { char path[0]; }; -int -weston_environment_get_fd(const char *env); - #endif From 1cb09480e29b5e9bec50dc4ce1bb56b7327e499b Mon Sep 17 00:00:00 2001 From: Guillaume Champagne Date: Mon, 27 Jan 2020 20:12:16 -0500 Subject: [PATCH 1270/1642] window: fix missing prototypes warning Declare touch_handle_shape and touch_handle_orientation as static functions as they are local to window.c. Signed-off-by: Guillaume Champagne --- clients/window.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clients/window.c b/clients/window.c index 2bd303eec..1bb9f3dc3 100644 --- a/clients/window.c +++ b/clients/window.c @@ -3436,13 +3436,15 @@ touch_handle_cancel(void *data, struct wl_touch *wl_touch) } } -void touch_handle_shape(void *data, struct wl_touch *wl_touch, int32_t id, - wl_fixed_t major, wl_fixed_t minor) +static void +touch_handle_shape(void *data, struct wl_touch *wl_touch, int32_t id, + wl_fixed_t major, wl_fixed_t minor) { } -void touch_handle_orientation(void *data, struct wl_touch *wl_touch, int32_t id, - wl_fixed_t orientation) +static void +touch_handle_orientation(void *data, struct wl_touch *wl_touch, int32_t id, + wl_fixed_t orientation) { } From f1e8fc9dbfbe5025be1ad04ef3dab0700a519dba Mon Sep 17 00:00:00 2001 From: Guillaume Champagne Date: Mon, 27 Jan 2020 20:14:29 -0500 Subject: [PATCH 1271/1642] libweston: add missing include Fixes missing prototypes compilation warnings emitted when a function is defined before its prototype is declared. These warnings were introduced over time since the switch to meson because the -Wmissing-protoypes was not included in the compilation arguments. Signed-off-by: Guillaume Champagne --- libweston/animation.c | 1 + libweston/bindings.c | 1 + libweston/clipboard.c | 1 + libweston/content-protection.c | 1 + libweston/data-device.c | 1 + libweston/log.c | 1 + libweston/noop-renderer.c | 1 + libweston/touch-calibration.c | 1 + xwayland/selection.c | 1 + 9 files changed, 9 insertions(+) diff --git a/libweston/animation.c b/libweston/animation.c index a81a8c194..9ca777456 100644 --- a/libweston/animation.c +++ b/libweston/animation.c @@ -36,6 +36,7 @@ #include #include +#include "libweston-internal.h" #include "shared/helpers.h" #include "shared/timespec-util.h" diff --git a/libweston/bindings.c b/libweston/bindings.c index 68c07a22f..2ca999a3f 100644 --- a/libweston/bindings.c +++ b/libweston/bindings.c @@ -30,6 +30,7 @@ #include #include +#include "libweston-internal.h" #include "shared/helpers.h" #include "shared/timespec-util.h" diff --git a/libweston/clipboard.c b/libweston/clipboard.c index c8296b01d..7d60351a7 100644 --- a/libweston/clipboard.c +++ b/libweston/clipboard.c @@ -34,6 +34,7 @@ #include #include +#include "libweston-internal.h" #include "shared/helpers.h" struct clipboard_source { diff --git a/libweston/content-protection.c b/libweston/content-protection.c index 7b7d5419e..5659a32b4 100644 --- a/libweston/content-protection.c +++ b/libweston/content-protection.c @@ -32,6 +32,7 @@ #include #include #include +#include "libweston-internal.h" #include "weston-content-protection-server-protocol.h" #include "shared/helpers.h" #include "shared/timespec-util.h" diff --git a/libweston/data-device.c b/libweston/data-device.c index 865d74979..8b0a282fa 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -33,6 +33,7 @@ #include #include +#include "libweston-internal.h" #include "shared/helpers.h" #include "shared/timespec-util.h" diff --git a/libweston/log.c b/libweston/log.c index 6ce9454b6..da57e0343 100644 --- a/libweston/log.c +++ b/libweston/log.c @@ -35,6 +35,7 @@ #include #include +#include "weston-log-internal.h" /** * \defgroup wlog weston-logging diff --git a/libweston/noop-renderer.c b/libweston/noop-renderer.c index d4bd2efe1..d86e7f0b1 100644 --- a/libweston/noop-renderer.c +++ b/libweston/noop-renderer.c @@ -29,6 +29,7 @@ #include #include +#include "libweston-internal.h" static int noop_renderer_read_pixels(struct weston_output *output, diff --git a/libweston/touch-calibration.c b/libweston/touch-calibration.c index 187c89878..3c76036d6 100644 --- a/libweston/touch-calibration.c +++ b/libweston/touch-calibration.c @@ -35,6 +35,7 @@ #include "shared/timespec-util.h" #include #include "libweston-internal.h" +#include "backend.h" #include "weston-touch-calibration-server-protocol.h" diff --git a/xwayland/selection.c b/xwayland/selection.c index e4d797aec..c4845f20a 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -32,6 +32,7 @@ #include #include +#include #include "xwayland.h" #include "shared/helpers.h" From 556afd14826733e240a55798760dab13b7c0a220 Mon Sep 17 00:00:00 2001 From: Guillaume Champagne Date: Sun, 26 Jan 2020 18:24:44 -0500 Subject: [PATCH 1272/1642] meson: add -Wmissing-prototypes to the build Meson's warning level maps to -Wall, -Wextra and -Wpedantic. -Wmissing-prototypes is added by neither of those flag. Consequently, it is manually added to the build command line arguments. Signed-off-by: Guillaume Champagne --- meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/meson.build b/meson.build index 662867cbd..7e735607a 100644 --- a/meson.build +++ b/meson.build @@ -58,6 +58,7 @@ cc = meson.get_compiler('c') global_args = [] global_args_maybe = [ + '-Wmissing-prototypes', '-Wno-unused-parameter', '-Wno-shift-negative-value', # required due to Pixman '-Wno-missing-field-initializers', From ac691a89cd8b671c54525b0ab41b071d2fd40e07 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Wed, 18 Dec 2019 15:21:03 -0300 Subject: [PATCH 1273/1642] weston-log: rename the confusing function name weston_compositor_add_log_scope() There's a function named weston_compositor_add_log_scope() but it doesn't take a struct weston_compositor argument. Rename it to weston_log_ctx_add_log_scope(), as the log_scope is being added to a log_context. Signed-off-by: Leandro Ribeiro --- libweston/weston-log.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 20b9deae9..544e777f7 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -726,7 +726,8 @@ weston_log_subscription_complete(struct weston_log_subscription *sub) * stream. Particularly useful for the weston-debug protocol. * * @memberof weston_log_scope - * @sa weston_log_ctx_add_log_scope, weston_log_scope_destroy + * @sa weston_log_ctx_add_log_scope, weston_compositor_add_log_scope, + * weston_log_scope_destroy */ WL_EXPORT void weston_log_scope_complete(struct weston_log_scope *scope) From f66685d9dbae7e1d463efdbd2bf1ef2c5249831a Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 26 Dec 2019 16:03:04 -0300 Subject: [PATCH 1274/1642] weston-log: add function to avoid direct access to compositor members in non-core code If we use the function weston_log_context_add_log_scope() in non-core code, it's necessary to access weston_compositor::weston_log_ctx. This is not ideal, since the goal is to make core structs (weston_compositor, weston_surface, weston_output, etc) opaque. Add function weston_compositor_add_log_scope(), so non-core users are able to pass weston_compositor as argument instead of weston_compositor::weston_log_ctx. Signed-off-by: Leandro Ribeiro --- doc/sphinx/toc/libweston/log.rst | 10 +++---- include/libweston/weston-log.h | 10 ++++++- libweston/weston-log.c | 46 +++++++++++++++++++++++++++++--- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/doc/sphinx/toc/libweston/log.rst b/doc/sphinx/toc/libweston/log.rst index 2d48e489b..c8bba7fb1 100644 --- a/doc/sphinx/toc/libweston/log.rst +++ b/doc/sphinx/toc/libweston/log.rst @@ -26,11 +26,11 @@ Instantiation of the :type:`weston_log_context` object takes place using Log scopes ---------- -A scope represents a source for a data stream (i.e., a producer). You'll require -one as a way to generate data. Creating a log scope is done using -:func:`weston_log_ctx_add_log_scope()`. You can customize the scope -behaviour and you'll require at least a name and a description for the -scope. +A scope represents a source for a data stream (i.e., a producer). You'll +require one as a way to generate data. Creating a log scope is done using +:func:`weston_log_ctx_add_log_scope()` or +:func:`weston_compositor_add_log_scope()`. You can customize the scope +behaviour and you'll require at least a name and a description for the scope. .. note:: diff --git a/include/libweston/weston-log.h b/include/libweston/weston-log.h index 5b6ed1b1d..70f6d3477 100644 --- a/include/libweston/weston-log.h +++ b/include/libweston/weston-log.h @@ -55,7 +55,7 @@ struct weston_debug_stream; * * @param sub The subscription. * @param user_data The \c user_data argument given to - * weston_log_ctx_add_log_scope() + * weston_log_ctx_add_log_scope() or weston_compositor_add_log_scope(). * * @memberof weston_log_scope */ @@ -70,6 +70,14 @@ weston_log_ctx_add_log_scope(struct weston_log_context *log_ctx, weston_log_scope_cb destroy_subscription, void *user_data); +struct weston_log_scope * +weston_compositor_add_log_scope(struct weston_compositor *compositor, + const char *name, + const char *description, + weston_log_scope_cb new_subscription, + weston_log_scope_cb destroy_subscription, + void *user_data); + void weston_log_scope_destroy(struct weston_log_scope *scope); diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 544e777f7..80b6eed85 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -355,7 +355,8 @@ weston_log_subscription_remove(struct weston_log_subscription *sub) * matching against the \c name. * * @param log_ctx - * @param name the scope name, see weston_log_ctx_add_log_scope() + * @param name the scope name, see weston_log_ctx_add_log_scope() and + * weston_compositor_add_log_scope() * @returns NULL if none found, or a pointer to a weston_log_scope * * @ingroup internal-log @@ -644,6 +645,42 @@ weston_log_ctx_add_log_scope(struct weston_log_context *log_ctx, return scope; } +/** Register a new stream name, creating a log scope. + * + * @param compositor The compositor that contains the log context where the log + * scope will be linked. + * @param name The debug stream/scope name; must not be NULL. + * @param description The log scope description for humans; must not be NULL. + * @param new_subscription Optional callback when a client subscribes to this + * scope. + * @param destroy_subscription Optional callback when a client destroys the + * subscription. + * @param user_data Optional user data pointer for the callback. + * @returns A valid pointer on success, NULL on failure. + * + * This function works like weston_log_ctx_add_log_scope(), but the log scope + * created is linked to the log context of \c compositor. + * + * @memberof weston_compositor + * @sa weston_log_ctx_add_log_scope + */ +WL_EXPORT struct weston_log_scope * +weston_compositor_add_log_scope(struct weston_compositor *compositor, + const char *name, + const char *description, + weston_log_scope_cb new_subscription, + weston_log_scope_cb destroy_subscription, + void *user_data) +{ + struct weston_log_scope *scope; + scope = weston_log_ctx_add_log_scope(compositor->weston_log_ctx, + name, description, + new_subscription, + destroy_subscription, + user_data); + return scope; +} + /** Destroy a log scope * * @param scope The log scope to destroy; may be NULL. @@ -896,9 +933,10 @@ weston_log_scope_timestamp(struct weston_log_scope *scope, * to the scope \c scope_name. * * If \c scope_name has already been created (using - * weston_log_ctx_add_log_scope) the subscription will take place - * immediately, otherwise we store the subscription into a pending list. See - * also weston_log_ctx_add_log_scope(). + * weston_log_ctx_add_log_scope or weston_compositor_add_log_scope) the + * subscription will take place immediately, otherwise we store the + * subscription into a pending list. See also weston_log_ctx_add_log_scope() + * and weston_compositor_add_log_scope() * * @param log_ctx the log context, used for accessing pending list * @param subscriber the subscriber, which has to be created before From 172afc2178fd064b694c244f6554baaaa97c2c4f Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 26 Dec 2019 16:23:43 -0300 Subject: [PATCH 1275/1642] backend-drm: stop direct accessing core struct member Commit "weston-log: add function to avoid direct access to compositor members in non-core code" added the function weston_compositor_add_log_scope mainly to allow libweston users to avoid direct accessing core structs, as weston_compositor. Replace weston_log_context_add_log_scope usage by weston_compositor_add_log_scope. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/drm.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 8395a546b..db128286a 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -2825,10 +2825,9 @@ drm_backend_create(struct weston_compositor *compositor, b->pageflip_timeout = config->pageflip_timeout; b->use_pixman_shadow = config->use_pixman_shadow; - b->debug = weston_log_ctx_add_log_scope(compositor->weston_log_ctx, - "drm-backend", - "Debug messages from DRM/KMS backend\n", - NULL, NULL, NULL); + b->debug = weston_compositor_add_log_scope(compositor, "drm-backend", + "Debug messages from DRM/KMS backend\n", + NULL, NULL, NULL); compositor->backend = &b->base; From ce1001966ea87026fc9dd58c69455db3e4582b3b Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 26 Dec 2019 16:35:49 -0300 Subject: [PATCH 1276/1642] compositor: stop direct accessing core struct member Commit "weston-log: add function to avoid direct access to compositor members in non-core code" added the function weston_compositor_add_log_scope mainly to allow libweston users to avoid direct accessing core structs, as weston_compositor. Replace weston_log_context_add_log_scope usage by weston_compositor_add_log_scope. Signed-off-by: Leandro Ribeiro --- libweston/compositor.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 81bd23bf7..de6931ae9 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -7301,17 +7301,17 @@ weston_compositor_create(struct wl_display *display, WESTON_LAYER_POSITION_CURSOR); ec->debug_scene = - weston_log_ctx_add_log_scope(ec->weston_log_ctx, "scene-graph", - "Scene graph details\n", - debug_scene_graph_cb, NULL, - ec); + weston_compositor_add_log_scope(ec, "scene-graph", + "Scene graph details\n", + debug_scene_graph_cb, NULL, + ec); ec->timeline = - weston_log_ctx_add_log_scope(ec->weston_log_ctx, "timeline", - "Timeline event points\n", - weston_timeline_create_subscription, - weston_timeline_destroy_subscription, - ec); + weston_compositor_add_log_scope(ec, "timeline", + "Timeline event points\n", + weston_timeline_create_subscription, + weston_timeline_destroy_subscription, + ec); return ec; fail: From fa505c588f041750b1429cf166ee0096e3ae19ca Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 26 Dec 2019 16:41:09 -0300 Subject: [PATCH 1277/1642] content-protection: stop direct accessing core struct member Commit "weston-log: add function to avoid direct access to compositor members in non-core code" added the function weston_compositor_add_log_scope mainly to allow libweston users to avoid direct accessing core structs, as weston_compositor. Replace weston_log_context_add_log_scope usage by weston_compositor_add_log_scope. Signed-off-by: Leandro Ribeiro --- libweston/content-protection.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libweston/content-protection.c b/libweston/content-protection.c index 5659a32b4..625d35675 100644 --- a/libweston/content-protection.c +++ b/libweston/content-protection.c @@ -340,9 +340,8 @@ weston_compositor_enable_content_protection(struct weston_compositor *compositor cp->destroy_listener.notify = cp_destroy_listener; wl_signal_add(&compositor->destroy_signal, &cp->destroy_listener); - cp->debug = weston_log_ctx_add_log_scope(compositor->weston_log_ctx, - "content-protection-debug", - "debug-logs for content-protection", - NULL, NULL, NULL); + cp->debug = weston_compositor_add_log_scope(compositor, "content-protection-debug", + "debug-logs for content-protection", + NULL, NULL, NULL); return 0; } From 23289358af00bb67dd97d67b04d3bc65808b2f1e Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 26 Dec 2019 16:44:56 -0300 Subject: [PATCH 1278/1642] pipewire: stop direct accessing core struct member Commit "weston-log: add function to avoid direct access to compositor members in non-core code" added the function weston_compositor_add_log_scope mainly to allow libweston users to avoid direct accessing core structs, as weston_compositor. Replace weston_log_context_add_log_scope usage by weston_compositor_add_log_scope. Signed-off-by: Leandro Ribeiro --- pipewire/pipewire-plugin.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pipewire/pipewire-plugin.c b/pipewire/pipewire-plugin.c index 26a120187..9c75a7833 100644 --- a/pipewire/pipewire-plugin.c +++ b/pipewire/pipewire-plugin.c @@ -823,10 +823,9 @@ weston_module_init(struct weston_compositor *compositor) } pipewire->debug = - weston_log_ctx_add_log_scope(compositor->weston_log_ctx, - "pipewire", - "Debug messages from pipewire plugin\n", - NULL, NULL, NULL); + weston_compositor_add_log_scope(compositor, "pipewire", + "Debug messages from pipewire plugin\n", + NULL, NULL, NULL); return 0; From bdd45d62627b5b03fd3e315fbce542b226cce682 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 26 Dec 2019 16:46:16 -0300 Subject: [PATCH 1279/1642] xwayland: stop direct accessing core struct member Commit "weston-log: add function to avoid direct access to compositor members in non-core code" added the function weston_compositor_add_log_scope mainly to allow libweston users to avoid direct accessing core structs, as weston_compositor. Replace weston_log_context_add_log_scope usage by weston_compositor_add_log_scope. Signed-off-by: Leandro Ribeiro --- xwayland/launcher.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/xwayland/launcher.c b/xwayland/launcher.c index bbea817cb..60854c8a0 100644 --- a/xwayland/launcher.c +++ b/xwayland/launcher.c @@ -396,10 +396,9 @@ weston_module_init(struct weston_compositor *compositor) } wxs->wm_debug = - weston_log_ctx_add_log_scope(wxs->compositor->weston_log_ctx, - "xwm-wm-x11", - "XWM's window management X11 events\n", - NULL, NULL, NULL); + weston_compositor_add_log_scope(wxs->compositor, "xwm-wm-x11", + "XWM's window management X11 events\n", + NULL, NULL, NULL); return 0; From 134e14969fab690dd057155f367dff0b4901a2b8 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 4 Dec 2019 12:47:12 +0200 Subject: [PATCH 1280/1642] xwm: fix initially-fullscreen windows It looks like commit ad0da4596d6315b18e888af75eee0a9bad1ff44d introduced a bug for X11 windows that are initially fullscreen by adding code to the end of xserver_map_shell_surface() while ignoring the 'return' that this patch removes. That may have caused some annoying window state issues, but the problem became more pronounced with 7ace831ca6205ea288e49fdbd6b63f53e73fae59 when used with an Xwayland version that honours _XWAYLAND_ALLOW_COMMITS. In the latter case, there is a possiblity the window will never show up, as XWM forgets to set allow_commits=true. However, the window may sometimes actually show up due to an oversight in Xwayland: the Present code may be flipping the window buffers and not checking _XWAYLAND_ALLOW_COMMITS if it is supposed commit at all. Since then, f568968f8a30eab6bfd8a15518014deb8f6c81d5 added more places where allow_commits is set to true, masking the window-does-not-show-up issue. Window pending state likely still remained an issue. This patch properly fixes the "window never appears" issue by making sure allow_commit=true is set. At the same time, it ensures the pending state functions are called at the end of xserver_map_shell_surface(), which may fix some window state issues like misplaced decorations and/or position of initially-fullscreen windows. Unfortunately, it certainly does not fix all such state problems. Signed-off-by: Pekka Paalanen --- xwayland/window-manager.c | 1 - 1 file changed, 1 deletion(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 9be25eef9..c23f7107a 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2925,7 +2925,6 @@ xserver_map_shell_surface(struct weston_wm_window *window, window->saved_height = window->height; xwayland_interface->set_fullscreen(window->shsurf, window->legacy_fullscreen_output.output); - return; } else if (window->override_redirect) { xwayland_interface->set_xwayland(window->shsurf, window->x, window->y); From a24989e47bd79cb00978ad3268107b903a578bcb Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 5 Dec 2019 16:36:17 +0200 Subject: [PATCH 1281/1642] xwm: remove configure_source on dispatch This function is called also directly from weston_wm_window_set_toplevel(). If configure_source is set at that point, simply resetting the pointer will "leak" the source until it fires and calls this function again. Let's keep the variable up-to-date by removing the source when called, dispatched or not. This removes the second call. I only hope it doesn't cause issues. This is also necessary if we intend to remove the source on window destruction too. Found by inspection. Signed-off-by: Pekka Paalanen --- xwayland/window-manager.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index c23f7107a..0fe33dcfa 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2687,6 +2687,11 @@ weston_wm_window_configure(void *data) uint32_t values[4]; int x, y, width, height; + if (window->configure_source) { + wl_event_source_remove(window->configure_source); + window->configure_source = NULL; + } + weston_wm_window_set_allow_commits(window, false); weston_wm_window_get_child_position(window, &x, &y); @@ -2709,8 +2714,6 @@ weston_wm_window_configure(void *data) XCB_CONFIG_WINDOW_HEIGHT, values); - window->configure_source = NULL; - weston_wm_window_schedule_repaint(window); } From dcd2b420a6bca2073ebf56284e56aeb9cb47622e Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 5 Dec 2019 16:42:48 +0200 Subject: [PATCH 1282/1642] xwm: remove configure_source on destroy It would lead to use-after-free if there was a pending idle callback to weston_wm_window_configure() when the weston_wm_window gets destroyed. Make sure the callback will not fire. Found by inspection. Signed-off-by: Pekka Paalanen --- xwayland/window-manager.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 0fe33dcfa..eaa96901f 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -1514,6 +1514,8 @@ weston_wm_window_destroy(struct weston_wm_window *window) weston_output_weak_ref_clear(&window->legacy_fullscreen_output); + if (window->configure_source) + wl_event_source_remove(window->configure_source); if (window->repaint_source) wl_event_source_remove(window->repaint_source); if (window->cairo_surface) From 2add217690e12e69478d5222fa81857b3337667b Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 5 Dec 2019 16:47:16 +0200 Subject: [PATCH 1283/1642] xwm: do not configure frame window None Spotted this in debug log: [xwm-wm-x11] XWM: configure window 4194324: x=32 y=32 width=1920 height=1080 border_width=0 stack_mode=0 [xwm-wm-x11] XWM: configure window 0: width=1984 height=1144 Trying to configure window 0 makes no sense. So do not try. To avoid patching two different places with the same thing, refactor the code into a common helper. Signed-off-by: Pekka Paalanen --- xwayland/window-manager.c | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index eaa96901f..666e8e80a 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -754,6 +754,23 @@ weston_wm_configure_window(struct weston_wm *wm, xcb_window_t window_id, free(buf); } +static void +weston_wm_window_configure_frame(struct weston_wm_window *window) +{ + uint16_t mask; + uint32_t values[2]; + int width, height; + + if (!window->frame_id) + return; + + weston_wm_window_get_frame_size(window, &width, &height); + values[0] = width; + values[1] = height; + mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + weston_wm_configure_window(window->wm, window->frame_id, mask, values); +} + static void weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *event) { @@ -762,7 +779,8 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev struct weston_wm_window *window; uint32_t values[16]; uint16_t mask; - int x, y, width, height, i = 0; + int x, y; + int i = 0; wm_printf(wm, "XCB_CONFIGURE_REQUEST (window %d) %d,%d @ %dx%d\n", configure_request->window, @@ -806,13 +824,7 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev } weston_wm_configure_window(wm, window->id, mask, values); - - weston_wm_window_get_frame_size(window, &width, &height); - values[0] = width; - values[1] = height; - mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; - weston_wm_configure_window(wm, window->frame_id, mask, values); - + weston_wm_window_configure_frame(window); weston_wm_window_schedule_repaint(window); } @@ -2687,7 +2699,7 @@ weston_wm_window_configure(void *data) struct weston_wm_window *window = data; struct weston_wm *wm = window->wm; uint32_t values[4]; - int x, y, width, height; + int x, y; if (window->configure_source) { wl_event_source_remove(window->configure_source); @@ -2708,14 +2720,7 @@ weston_wm_window_configure(void *data) XCB_CONFIG_WINDOW_HEIGHT, values); - weston_wm_window_get_frame_size(window, &width, &height); - values[0] = width; - values[1] = height; - weston_wm_configure_window(wm, window->frame_id, - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT, - values); - + weston_wm_window_configure_frame(window); weston_wm_window_schedule_repaint(window); } From 83fb745ccf454fdd312515bc9cf969f1315a765a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 9 Dec 2019 13:03:02 +0200 Subject: [PATCH 1284/1642] xwm: always configure on send_configure() There is more state than just the application window width and height that affects whether calling weston_wm_window_configure() is necessary: everything that affects the frame window, fullscreen state in particular. Therefore do not skip the call by just width and height. If send_configure() happens to be called "unnecessarily", this will now forward some of those calls to the X11 clients. However, since it uses an idle task, it should not result in a flood at least. And if send_configure() is spammed, maybe that should then be fixed in its callers. This patch should fix the misplacement of a fullscreen X11 window due to the frame window being incorrectly sized and positioned, and the app window incorrectly positioned inside the frame window. The fullscreen window problems were observed in a case where the window does not hit legacy_fullscreen() but first maps and then sets _NET_WM_STATE_FULLSCREEN. Additionally the initial window size must match the output size where it gets fullscreened. In that case the frame window was left as if not fullscreened. This practically reverts 3f53d9179bcdb11d053527336ac4a49f274bc8d1. I'm not sure what problem that patch was fixing, but I couldn't make any resizing freeze. Signed-off-by: Pekka Paalanen --- xwayland/window-manager.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 666e8e80a..747bf363d 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2756,14 +2756,15 @@ send_configure(struct weston_surface *surface, int32_t width, int32_t height) else new_height = 1; - if (window->width == new_width && window->height == new_height) - return; - - window->width = new_width; - window->height = new_height; + if (window->width != new_width || window->height != new_height) { + window->width = new_width; + window->height = new_height; - if (window->frame) - frame_resize_inside(window->frame, window->width, window->height); + if (window->frame) { + frame_resize_inside(window->frame, + window->width, window->height); + } + } if (window->configure_source) return; From 4960955e0fe5c6b4d0139f600eae5aeba362aa69 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Tue, 22 Oct 2019 13:13:28 +0300 Subject: [PATCH 1285/1642] client/dmabuf-v4l: Use zwp_linux_dmabuf version 3 We're missing format checks and still using first version of zwp_linux_dmabuf protocol. Use the latest release and check that the advertised formats/modifier accepts the user-supplied requested DRM format. Accept the format only if the modifier is LINEAR (@emersion). Signed-off-by: Marius Vlad --- clients/simple-dmabuf-v4l.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c index ecdc6c3cc..a48374149 100644 --- a/clients/simple-dmabuf-v4l.c +++ b/clients/simple-dmabuf-v4l.c @@ -51,6 +51,8 @@ #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "shared/helpers.h" + #define CLEAR(x) memset(&(x), 0, sizeof(x)) static void @@ -696,17 +698,27 @@ static const struct wl_callback_listener frame_listener = { }; static void -dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, - uint32_t format) +dmabuf_modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, + uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { struct display *d = data; + uint64_t modifier = ((uint64_t) modifier_hi << 32 ) | modifier_lo; - if (format == d->drm_format) + if (format == d->drm_format && modifier == DRM_FORMAT_MOD_LINEAR) d->requested_format_found = true; } + +static void +dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, + uint32_t format) +{ + /* deprecated */ +} + static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { - dmabuf_format + dmabuf_format, + dmabuf_modifier }; static void @@ -813,8 +825,7 @@ registry_handle_global(void *data, struct wl_registry *registry, 1); } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { d->dmabuf = wl_registry_bind(registry, - id, &zwp_linux_dmabuf_v1_interface, - 1); + id, &zwp_linux_dmabuf_v1_interface, 3); zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); } @@ -857,11 +868,9 @@ create_display(uint32_t requested_format) wl_display_roundtrip(display->display); - /* XXX: fake, because the compositor does not yet advertise anything */ - display->requested_format_found = true; - if (!display->requested_format_found) { - fprintf(stderr, "DRM_FORMAT_YUYV not available\n"); + fprintf(stderr, "0x%lx requested DRM format not available\n", + (unsigned long) requested_format); exit(1); } From baa1ef22e835b18a9a645f172dc97e0c0a3c55e9 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Tue, 22 Oct 2019 13:36:37 +0300 Subject: [PATCH 1286/1642] clients/dmabuf-v4l: Pass FLAGS_Y_INVERT to linux-dmabuf if v4l2 reports so Zero-initialize the display as to correctly pass the options if it was supplied (@emersion). Signed-off-by: Marius Vlad --- clients/simple-dmabuf-v4l.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c index a48374149..b7d441dbe 100644 --- a/clients/simple-dmabuf-v4l.c +++ b/clients/simple-dmabuf-v4l.c @@ -54,6 +54,7 @@ #include "shared/helpers.h" #define CLEAR(x) memset(&(x), 0, sizeof(x)) +#define OPT_FLAG_INVERT (1 << 0) static void redraw(void *data, struct wl_callback *callback, uint32_t time); @@ -106,6 +107,7 @@ struct display { struct zwp_fullscreen_shell_v1 *fshell; struct zwp_linux_dmabuf_v1 *dmabuf; bool requested_format_found; + uint32_t opts; int v4l_fd; struct buffer_format format; @@ -247,6 +249,8 @@ v4l_connect(struct display *display, const char *dev_name) { struct v4l2_capability cap; struct v4l2_requestbuffers req; + struct v4l2_input input; + int index_input = -1; unsigned int num_planes; display->v4l_fd = open(dev_name, O_RDWR); @@ -264,6 +268,16 @@ v4l_connect(struct display *display, const char *dev_name) return 0; } + if (xioctl(display->v4l_fd, VIDIOC_G_INPUT, &index_input) == 0) { + input.index = index_input; + if (xioctl(display->v4l_fd, VIDIOC_ENUMINPUT, &input) == 0) { + if (input.status & V4L2_IN_ST_VFLIP) { + fprintf(stdout, "Found camera sensor y-flipped\n"); + display->opts |= OPT_FLAG_INVERT; + } + } + } + if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) display->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; else if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) @@ -367,10 +381,8 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer) modifier = 0; flags = 0; - /* XXX: apparently some webcams may actually provide y-inverted images, - * in which case we should set - * flags = ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT - */ + if (display->opts & OPT_FLAG_INVERT) + flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); for (i = 0; i < display->format.num_planes; ++i) @@ -847,7 +859,7 @@ create_display(uint32_t requested_format) { struct display *display; - display = malloc(sizeof *display); + display = zalloc(sizeof *display); if (display == NULL) { fprintf(stderr, "out of memory\n"); exit(1); From 3345452c1b4a4383ad0686549820623004d1cec8 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Wed, 20 Nov 2019 23:10:22 +0200 Subject: [PATCH 1287/1642] clients/simple-dmabuf-v4l: Convert to use getopt_long Makes adding further flags/options/args much easier. Signed-off-by: Marius Vlad Suggested-by: Pekka Paalanen --- clients/simple-dmabuf-v4l.c | 54 +++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c index b7d441dbe..9e75df287 100644 --- a/clients/simple-dmabuf-v4l.c +++ b/clients/simple-dmabuf-v4l.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -913,7 +914,7 @@ destroy_display(struct display *display) static void usage(const char *argv0) { - printf("Usage: %s [V4L2 device] [V4L2 format] [DRM format]\n" + printf("Usage: %s [-v v4l2_device] [-f v4l2_format] [-d drm_format]\n" "\n" "The default V4L2 device is /dev/video0\n" "\n" @@ -934,7 +935,7 @@ usage(const char *argv0) "- set the pixel format:\n" " $ v4l2-ctl -d /dev/video0 --set-fmt-video=width=640,pixelformat=XR24\n" "- launch the demo:\n" - " $ %s /dev/video0 XR24 XR24\n" + " $ %s -v /dev/video0 -f XR24 -d XR24\n" "You should see a test pattern with color bars, and some text.\n" "\n" "More about vivid: https://www.kernel.org/doc/Documentation/video4linux/vivid.txt\n" @@ -955,27 +956,46 @@ main(int argc, char **argv) struct sigaction sigint; struct display *display; struct window *window; - const char *v4l_device; - uint32_t v4l_format, drm_format; - int ret = 0; + const char *v4l_device = NULL; + uint32_t v4l_format = 0x0; + uint32_t drm_format = 0x0; + int c, opt_index, ret = 0; + + static struct option long_options[] = { + { "v4l2-device", required_argument, NULL, 'v' }, + { "v4l2-format", required_argument, NULL, 'f' }, + { "drm-format", required_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { 0, 0, NULL, 0 } + }; + + while ((c = getopt_long(argc, argv, "hv:d:f:", long_options, + &opt_index)) != -1) { + switch (c) { + case 'v': + v4l_device = optarg; + break; + case 'f': + v4l_format = parse_format(optarg); + break; + case 'd': + drm_format = parse_format(optarg); + break; + default: + case 'h': + usage(argv[0]); + break; + } + } - if (argc < 2) { + if (!v4l_device) v4l_device = "/dev/video0"; - } else if (!strcmp(argv[1], "--help")) { - usage(argv[0]); - } else { - v4l_device = argv[1]; - } - if (argc < 3) + if (v4l_format == 0x0) v4l_format = parse_format("YUYV"); - else - v4l_format = parse_format(argv[2]); - if (argc < 4) + if (drm_format == 0x0) drm_format = v4l_format; - else - drm_format = parse_format(argv[3]); display = create_display(drm_format); display->format.format = v4l_format; From b6d1509d6327a648adc7b249aaf4429ba2bdf0f7 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Wed, 20 Nov 2019 23:44:20 +0200 Subject: [PATCH 1288/1642] clients/simple-dmabuf-v4l: Add Y_INVERT option flag Allow clients to pass Y_INVERT, not only when v4l reports it so. Document it briefly and add a note about this Y_INVERT flag is passed if the camera sensors is detected as being y-flipped. Signed-off-by: Marius Vlad --- clients/simple-dmabuf-v4l.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c index 9e75df287..a5d6e0211 100644 --- a/clients/simple-dmabuf-v4l.c +++ b/clients/simple-dmabuf-v4l.c @@ -856,7 +856,7 @@ static const struct wl_registry_listener registry_listener = { }; static struct display * -create_display(uint32_t requested_format) +create_display(uint32_t requested_format, uint32_t opt_flags) { struct display *display; @@ -887,6 +887,8 @@ create_display(uint32_t requested_format) exit(1); } + if (opt_flags) + display->opts = opt_flags; return display; } @@ -914,7 +916,7 @@ destroy_display(struct display *display) static void usage(const char *argv0) { - printf("Usage: %s [-v v4l2_device] [-f v4l2_format] [-d drm_format]\n" + printf("Usage: %s [-v v4l2_device] [-f v4l2_format] [-d drm_format] [-i|--y-invert]\n" "\n" "The default V4L2 device is /dev/video0\n" "\n" @@ -923,7 +925,11 @@ usage(const char *argv0) "DRM formats are defined in \n" "The default for both formats is YUYV.\n" "If the V4L2 and DRM formats differ, the data is simply " - "reinterpreted rather than converted.\n", argv0); + "reinterpreted rather than converted.\n\n" + "Flags:\n" + "- y-invert force the image to be y-flipped;\n note will be " + "automatically added if we detect if the camera sensor is " + "y-flipped\n", argv0); printf("\n" "How to set up Vivid the virtual video driver for testing:\n" @@ -959,17 +965,19 @@ main(int argc, char **argv) const char *v4l_device = NULL; uint32_t v4l_format = 0x0; uint32_t drm_format = 0x0; + uint32_t opts_flags = 0x0; int c, opt_index, ret = 0; static struct option long_options[] = { { "v4l2-device", required_argument, NULL, 'v' }, { "v4l2-format", required_argument, NULL, 'f' }, { "drm-format", required_argument, NULL, 'd' }, + { "y-invert", no_argument, NULL, 'i' }, { "help", no_argument, NULL, 'h' }, { 0, 0, NULL, 0 } }; - while ((c = getopt_long(argc, argv, "hv:d:f:", long_options, + while ((c = getopt_long(argc, argv, "hiv:d:f:", long_options, &opt_index)) != -1) { switch (c) { case 'v': @@ -981,6 +989,9 @@ main(int argc, char **argv) case 'd': drm_format = parse_format(optarg); break; + case 'i': + opts_flags |= OPT_FLAG_INVERT; + break; default: case 'h': usage(argv[0]); @@ -997,7 +1008,7 @@ main(int argc, char **argv) if (drm_format == 0x0) drm_format = v4l_format; - display = create_display(drm_format); + display = create_display(drm_format, opts_flags); display->format.format = v4l_format; window = create_window(display); From ab2c72b05c15eb442a1663304e2bc609f80f8b04 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Thu, 21 Nov 2019 00:28:31 +0200 Subject: [PATCH 1289/1642] clients/simple-dmabuf-v4l: Add 'weston-direct-display' protocol Makes use of weston-direct-display protocol to pass the dmabuf straight to the display-controller if such a path is possible. Removes the Y_INVERT flag in case that was passed, and notifies the user about it, as the weston implementation would force going through the renderer when passing the Y_INVERT flag, but in the same time direct-display avoids any GPU import so having them both in the same time would result into weston refusing the create a buffer. Signed-off-by: Marius Vlad --- clients/meson.build | 2 ++ clients/simple-dmabuf-v4l.c | 30 +++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/clients/meson.build b/clients/meson.build index bdc5d44ad..2c016b845 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -81,6 +81,8 @@ simple_clients = [ linux_dmabuf_unstable_v1_protocol_c, xdg_shell_client_protocol_h, xdg_shell_protocol_c, + weston_direct_display_client_protocol_h, + weston_direct_display_protocol_c, fullscreen_shell_unstable_v1_client_protocol_h, fullscreen_shell_unstable_v1_protocol_c, ], diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c index a5d6e0211..c7bd51155 100644 --- a/clients/simple-dmabuf-v4l.c +++ b/clients/simple-dmabuf-v4l.c @@ -51,11 +51,13 @@ #include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "weston-direct-display-client-protocol.h" #include "shared/helpers.h" #define CLEAR(x) memset(&(x), 0, sizeof(x)) #define OPT_FLAG_INVERT (1 << 0) +#define OPT_FLAG_DIRECT_DISPLAY (1 << 1) static void redraw(void *data, struct wl_callback *callback, uint32_t time); @@ -107,6 +109,7 @@ struct display { struct xdg_wm_base *wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct zwp_linux_dmabuf_v1 *dmabuf; + struct weston_direct_display_v1 *direct_display; bool requested_format_found; uint32_t opts; @@ -386,6 +389,17 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer) flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); + + if ((display->opts & OPT_FLAG_DIRECT_DISPLAY) && display->direct_display) { + weston_direct_display_v1_enable(display->direct_display, params); + + if (display->opts & OPT_FLAG_INVERT) { + flags &= ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; + fprintf(stdout, "dmabuf y-inverted attribute flag was removed" + ", as display-direct flag was set\n"); + } + } + for (i = 0; i < display->format.num_planes; ++i) zwp_linux_buffer_params_v1_add(params, buffer->dmabuf_fds[i], @@ -841,6 +855,9 @@ registry_handle_global(void *data, struct wl_registry *registry, id, &zwp_linux_dmabuf_v1_interface, 3); zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); + } else if (strcmp(interface, "weston_direct_display_v1") == 0) { + d->direct_display = wl_registry_bind(registry, + id, &weston_direct_display_v1_interface, 1); } } @@ -916,7 +933,7 @@ destroy_display(struct display *display) static void usage(const char *argv0) { - printf("Usage: %s [-v v4l2_device] [-f v4l2_format] [-d drm_format] [-i|--y-invert]\n" + printf("Usage: %s [-v v4l2_device] [-f v4l2_format] [-d drm_format] [-i|--y-invert] [-g|--d-display]\n" "\n" "The default V4L2 device is /dev/video0\n" "\n" @@ -929,7 +946,10 @@ usage(const char *argv0) "Flags:\n" "- y-invert force the image to be y-flipped;\n note will be " "automatically added if we detect if the camera sensor is " - "y-flipped\n", argv0); + "y-flipped\n" + "- d-display skip importing dmabuf-based buffer into the GPU\n " + "and attempt pass the buffer straight to the display controller\n", + argv0); printf("\n" "How to set up Vivid the virtual video driver for testing:\n" @@ -973,11 +993,12 @@ main(int argc, char **argv) { "v4l2-format", required_argument, NULL, 'f' }, { "drm-format", required_argument, NULL, 'd' }, { "y-invert", no_argument, NULL, 'i' }, + { "d-display", no_argument, NULL, 'g' }, { "help", no_argument, NULL, 'h' }, { 0, 0, NULL, 0 } }; - while ((c = getopt_long(argc, argv, "hiv:d:f:", long_options, + while ((c = getopt_long(argc, argv, "hiv:d:f:g", long_options, &opt_index)) != -1) { switch (c) { case 'v': @@ -992,6 +1013,9 @@ main(int argc, char **argv) case 'i': opts_flags |= OPT_FLAG_INVERT; break; + case 'g': + opts_flags |= OPT_FLAG_DIRECT_DISPLAY; + break; default: case 'h': usage(argv[0]); From dd718b07882dbc81de0829933ce67434de31a3ff Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Thu, 5 Dec 2019 14:09:58 +0200 Subject: [PATCH 1290/1642] clients/simple-dmabuf-v4l: Dmabuf-contiguous for vivid module For certain cases when using vivid module, some display-controllers require to allocate the dmabuf in a contiguous fashion so explain that to the user when adding details about vivid module. Signed-off-by: Marius Vlad --- clients/simple-dmabuf-v4l.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c index c7bd51155..331f049f8 100644 --- a/clients/simple-dmabuf-v4l.c +++ b/clients/simple-dmabuf-v4l.c @@ -960,6 +960,9 @@ usage(const char *argv0) " here we assume /dev/video0\n" "- set the pixel format:\n" " $ v4l2-ctl -d /dev/video0 --set-fmt-video=width=640,pixelformat=XR24\n" + "- optionally could add 'allocators=0x1' to options as to create" + " the buffer in a dmabuf-contiguous way\n" + " (as some display-controllers require it)\n" "- launch the demo:\n" " $ %s -v /dev/video0 -f XR24 -d XR24\n" "You should see a test pattern with color bars, and some text.\n" From bd9c0a6ff5060a3c95e7874e5cf6bff18b311588 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 17 Jan 2020 10:47:49 -0300 Subject: [PATCH 1291/1642] weston-log: fold weston_log_ctx_compositor_setup() into weston_compositor_create() The function weston_log_ctx_compositor_setup() is being called only inside weston_compositor_create() and it is so tiny that the code gets easier to follow if it gets folded in weston_compositor_create(). Signed-off-by: Leandro Ribeiro --- libweston/compositor.c | 7 ++++--- libweston/weston-log-internal.h | 3 --- libweston/weston-log.c | 22 ---------------------- 3 files changed, 4 insertions(+), 28 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index de6931ae9..2f8af3c29 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -7203,10 +7203,14 @@ weston_compositor_create(struct wl_display *display, struct weston_compositor *ec; struct wl_event_loop *loop; + if (!log_ctx) + return NULL; + ec = zalloc(sizeof *ec); if (!ec) return NULL; + ec->weston_log_ctx = log_ctx; ec->wl_display = display; ec->user_data = user_data; wl_signal_init(&ec->destroy_signal); @@ -7258,9 +7262,6 @@ weston_compositor_create(struct wl_display *display, ec, bind_presentation)) goto fail; - if (weston_log_ctx_compositor_setup(ec, log_ctx) < 0) - goto fail; - if (weston_input_init(ec) != 0) goto fail; diff --git a/libweston/weston-log-internal.h b/libweston/weston-log-internal.h index ba5b51ce1..9c94dbb5c 100644 --- a/libweston/weston-log-internal.h +++ b/libweston/weston-log-internal.h @@ -93,9 +93,6 @@ weston_log_run_cb_new_subscription(struct weston_log_subscription *sub); void weston_debug_protocol_advertise_scopes(struct weston_log_context *log_ctx, struct wl_resource *res); -int -weston_log_ctx_compositor_setup(struct weston_compositor *compositor, - struct weston_log_context *log_ctx); int weston_vlog(const char *fmt, va_list ap); diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 80b6eed85..2c5da4ac4 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -398,28 +398,6 @@ weston_debug_protocol_advertise_scopes(struct weston_log_context *log_ctx, weston_debug_v1_send_available(res, scope->name, scope->desc); } -/** - * Connect weston_compositor structure to weston_log_context structure. - * - * \param compositor - * \param log_ctx - * \return 0 on success, -1 on failure - * - * Sets weston_compositor::weston_log_ctx. - * - * @ingroup log - */ -int -weston_log_ctx_compositor_setup(struct weston_compositor *compositor, - struct weston_log_context *log_ctx) -{ - assert(!compositor->weston_log_ctx); - assert(log_ctx); - - compositor->weston_log_ctx = log_ctx; - return 0; -} - /** Creates weston_log_context structure * * \return NULL in case of failure, or a weston_log_context object in case of From 4f13595aa70f71b3b6afa981604ba70d7b726d44 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 17 Jan 2020 11:19:59 -0300 Subject: [PATCH 1292/1642] weston-log: rename weston_log_ctx_compositor_create() to weston_log_ctx_create() Since weston_log_ctx_compositor_create() does not have any relation with weston_compositor, rename it to weston_log_ctx_create(). Signed-off-by: Leandro Ribeiro --- compositor/main.c | 2 +- doc/sphinx/toc/libweston/log.rst | 2 +- include/libweston/libweston.h | 2 +- libweston/weston-log.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index e802badf2..a0e54eed8 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -3156,7 +3156,7 @@ wet_main(int argc, char *argv[]) return EXIT_SUCCESS; } - log_ctx = weston_log_ctx_compositor_create(); + log_ctx = weston_log_ctx_create(); if (!log_ctx) { fprintf(stderr, "Failed to initialize weston debug framework.\n"); return EXIT_FAILURE; diff --git a/doc/sphinx/toc/libweston/log.rst b/doc/sphinx/toc/libweston/log.rst index c8bba7fb1..edc8ef27c 100644 --- a/doc/sphinx/toc/libweston/log.rst +++ b/doc/sphinx/toc/libweston/log.rst @@ -20,7 +20,7 @@ instance can be brought up much more later, but in the same time logging can take place much earlier without the need of a compositor instance. Instantiation of the :type:`weston_log_context` object takes place using -:func:`weston_log_ctx_compositor_create()` and clean-up/destroy with +:func:`weston_log_ctx_create()` and clean-up/destroy with :func:`weston_log_ctx_compositor_destroy()`. Log scopes diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index ab2be42d8..84fd1e068 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -2042,7 +2042,7 @@ weston_compositor_enable_touch_calibrator(struct weston_compositor *compositor, weston_touch_calibration_save_func save); struct weston_log_context * -weston_log_ctx_compositor_create(void); +weston_log_ctx_create(void); void weston_log_ctx_compositor_destroy(struct weston_compositor *compositor); diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 2c5da4ac4..4cba0e833 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -408,7 +408,7 @@ weston_debug_protocol_advertise_scopes(struct weston_log_context *log_ctx, * */ WL_EXPORT struct weston_log_context * -weston_log_ctx_compositor_create(void) +weston_log_ctx_create(void) { struct weston_log_context *log_ctx; From 4ec38d18b335de42e7560d59b98ba021e9a029d4 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 24 Jan 2020 01:12:59 -0300 Subject: [PATCH 1293/1642] weston-log: replace weston_log_ctx_compositor_destroy() by weston_log_ctx_destroy() The function weston_log_ctx_compositor_destroy(), which destroys struct weston_log_context, takes weston_compositor as argument. We may have a weston_log_context unlinked from a weston_compositor and currently there is no way to destroy it. Add function weston_log_ctx_destroy(), what makes the destruction of weston_log_context independent of weston_compositor. With this change, one could destroy a weston_compositor and keep the related weston_log_context (since now weston_log_context can be destroyed without the need of a weston_compositor). But if weston_compositor gets destroyed it's also necessary to destroy weston_log_context::global, as the debug protocol depends on the compositor. So a listener has been added to the destroy signal of weston_compositor. Signed-off-by: Leandro Ribeiro --- compositor/main.c | 2 +- doc/sphinx/toc/libweston/log.rst | 2 +- include/libweston/libweston.h | 2 +- libweston/weston-log.c | 48 +++++++++++++++++++++++++++----- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index a0e54eed8..94f8e7d26 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -3381,8 +3381,8 @@ wet_main(int argc, char *argv[]) weston_log_scope_destroy(log_scope); log_scope = NULL; - weston_log_ctx_compositor_destroy(wet.compositor); weston_compositor_destroy(wet.compositor); + weston_log_ctx_destroy(log_ctx); weston_log_subscriber_destroy_log(logger); weston_log_subscriber_destroy_flight_rec(flight_rec); diff --git a/doc/sphinx/toc/libweston/log.rst b/doc/sphinx/toc/libweston/log.rst index edc8ef27c..d768de0d4 100644 --- a/doc/sphinx/toc/libweston/log.rst +++ b/doc/sphinx/toc/libweston/log.rst @@ -21,7 +21,7 @@ take place much earlier without the need of a compositor instance. Instantiation of the :type:`weston_log_context` object takes place using :func:`weston_log_ctx_create()` and clean-up/destroy with -:func:`weston_log_ctx_compositor_destroy()`. +:func:`weston_log_ctx_destroy()` or :func:`weston_log_ctx_compositor_destroy()`. Log scopes ---------- diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 84fd1e068..57fa183dd 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -2045,7 +2045,7 @@ struct weston_log_context * weston_log_ctx_create(void); void -weston_log_ctx_compositor_destroy(struct weston_compositor *compositor); +weston_log_ctx_destroy(struct weston_log_context *log_ctx); int weston_compositor_enable_content_protection(struct weston_compositor *compositor); diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 4cba0e833..43ca1708e 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -66,6 +66,7 @@ */ struct weston_log_context { struct wl_global *global; + struct wl_listener compositor_destroy_listener; struct wl_list scope_list; /**< weston_log_scope::compositor_link */ struct wl_list pending_subscription_list; /**< weston_log_subscription::source_link */ }; @@ -398,6 +399,22 @@ weston_debug_protocol_advertise_scopes(struct weston_log_context *log_ctx, weston_debug_v1_send_available(res, scope->name, scope->desc); } +/** Disable debug-protocol + * + * @param log_ctx The log context where the debug-protocol is linked + * + * @ingroup internal-log + */ +static void +weston_log_ctx_disable_debug_protocol(struct weston_log_context *log_ctx) +{ + if (!log_ctx->global) + return; + + wl_global_destroy(log_ctx->global); + log_ctx->global = NULL; +} + /** Creates weston_log_context structure * * \return NULL in case of failure, or a weston_log_context object in case of @@ -418,27 +435,29 @@ weston_log_ctx_create(void) wl_list_init(&log_ctx->scope_list); wl_list_init(&log_ctx->pending_subscription_list); + wl_list_init(&log_ctx->compositor_destroy_listener.link); return log_ctx; } /** Destroy weston_log_context structure * - * \param compositor The libweston compositor whose weston-debug to tear down. + * \param log_ctx The log context to destroy. * - * Clears weston_compositor::weston_log_ctx. * @ingroup log * */ WL_EXPORT void -weston_log_ctx_compositor_destroy(struct weston_compositor *compositor) +weston_log_ctx_destroy(struct weston_log_context *log_ctx) { - struct weston_log_context *log_ctx = compositor->weston_log_ctx; struct weston_log_scope *scope; struct weston_log_subscription *pending_sub, *pending_sub_tmp; - if (log_ctx->global) - wl_global_destroy(log_ctx->global); + /* We can't destroy the log context if there's still a compositor + * that depends on it. This is an user error */ + assert(wl_list_empty(&log_ctx->compositor_destroy_listener.link)); + + weston_log_ctx_disable_debug_protocol(log_ctx); wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) fprintf(stderr, "Internal warning: debug scope '%s' has not been destroyed.\n", @@ -456,8 +475,20 @@ weston_log_ctx_compositor_destroy(struct weston_compositor *compositor) /* pending_subscription_list should be empty at this point */ free(log_ctx); +} - compositor->weston_log_ctx = NULL; +static void +compositor_destroy_listener(struct wl_listener *listener, void *data) +{ + struct weston_log_context *log_ctx = + wl_container_of(listener, log_ctx, compositor_destroy_listener); + + /* We have to keep this list initalized as weston_log_ctx_destroy() has + * to check if there's any compositor destroy listener registered */ + wl_list_remove(&log_ctx->compositor_destroy_listener.link); + wl_list_init(&log_ctx->compositor_destroy_listener.link); + + weston_log_ctx_disable_debug_protocol(log_ctx); } /** Enable weston-debug protocol extension @@ -493,6 +524,9 @@ weston_compositor_enable_debug_protocol(struct weston_compositor *compositor) if (!log_ctx->global) return; + log_ctx->compositor_destroy_listener.notify = compositor_destroy_listener; + wl_signal_add(&compositor->destroy_signal, &log_ctx->compositor_destroy_listener); + fprintf(stderr, "WARNING: debug protocol has been enabled. " "This is a potential denial-of-service attack vector and " "information leak.\n"); From ca640d51203a7b7f1237af79e6ba4e5608333356 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Mon, 27 Jan 2020 19:12:01 -0300 Subject: [PATCH 1294/1642] libweston: fold weston_compositor_tear_down() into weston_compositor_destroy() The only reason why we have both weston_compositor_tear_down() and weston_compositor_destroy() is that the only we had to destroy the log context was keeping weston_compositor alive and calling weston_log_ctx_compositor_destroy(). After commit "weston-log: replace weston_log_ctx_compositor_destroy() by weston_log_ctx_destroy()", it's not necessary to keep a zombie weston_compositor just to be able to call weston_log_ctx_compositor_destroy(). Fold weston_compositor_tear_down() into weston_compositor_destroy(), as this split is useless now. Signed-off-by: Leandro Ribeiro --- compositor/main.c | 4 +--- doc/sphinx/toc/libweston/compositor.rst | 4 ++-- include/libweston/libweston.h | 2 -- libweston/compositor.c | 28 ++++++------------------- 4 files changed, 9 insertions(+), 29 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 94f8e7d26..97c3c45ce 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -3375,13 +3375,11 @@ wet_main(int argc, char *argv[]) if (protologger) wl_protocol_logger_destroy(protologger); + weston_compositor_destroy(wet.compositor); weston_log_scope_destroy(protocol_scope); protocol_scope = NULL; - weston_compositor_tear_down(wet.compositor); - weston_log_scope_destroy(log_scope); log_scope = NULL; - weston_compositor_destroy(wet.compositor); weston_log_ctx_destroy(log_ctx); weston_log_subscriber_destroy_log(logger); weston_log_subscriber_destroy_flight_rec(flight_rec); diff --git a/doc/sphinx/toc/libweston/compositor.rst b/doc/sphinx/toc/libweston/compositor.rst index 4fc1f57d8..755d781fa 100644 --- a/doc/sphinx/toc/libweston/compositor.rst +++ b/doc/sphinx/toc/libweston/compositor.rst @@ -4,8 +4,8 @@ Compositor :type:`weston_compositor` represents the core object of the library, which aggregates all the other objects and maintains their state. You can create it using :func:`weston_compositor_create`, while for releasing all the resources -associated with it, you should use :func:`weston_compositor_tear_down`, -followed by :func:`weston_compositor_destroy` to destroy it. +associated with it and then destroy it, you should use +:func:`weston_compositor_destroy`. Compositor API -------------- diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 57fa183dd..1439775ef 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -1765,8 +1765,6 @@ weston_buffer_from_resource(struct wl_resource *resource); void weston_compositor_get_time(struct timespec *time); -void -weston_compositor_tear_down(struct weston_compositor *ec); void weston_compositor_destroy(struct weston_compositor *ec); diff --git a/libweston/compositor.c b/libweston/compositor.c index 2f8af3c29..f7e2afdd7 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6529,7 +6529,7 @@ weston_compositor_create_output_with_head(struct weston_compositor *compositor, * The heads attached to the given output are detached and become unused again. * * It is not necessary to explicitly destroy all outputs at compositor exit. - * weston_compositor_tear_down() will automatically destroy any remaining + * weston_compositor_destroy() will automatically destroy any remaining * outputs. * * \ingroup ouput @@ -7633,7 +7633,7 @@ weston_load_module(const char *name, const char *entrypoint) * the plugin destruction order is not guaranteed: plugins that depend on other * plugins must be able to be torn down in arbitrary order. * - * \sa weston_compositor_tear_down, weston_compositor_destroy + * \sa weston_compositor_destroy */ WL_EXPORT bool weston_compositor_add_destroy_listener_once(struct weston_compositor *compositor, @@ -7648,19 +7648,16 @@ weston_compositor_add_destroy_listener_once(struct weston_compositor *compositor return true; } -/** Tear down the compositor. +/** Destroys the compositor. * - * This function cleans up the compositor state. While the compositor state has - * been cleaned do note that **only** weston_compositor_destroy() can be called - * afterwards, in order to destroy the compositor instance. + * This function cleans up the compositor state and then destroys it. * - * @param compositor The compositor to be tear-down/cleaned. + * @param compositor The compositor to be destroyed. * * @ingroup compositor - * @sa weston_compositor_destroy */ WL_EXPORT void -weston_compositor_tear_down(struct weston_compositor *compositor) +weston_compositor_destroy(struct weston_compositor *compositor) { /* prevent further rendering while shutting down */ compositor->state = WESTON_COMPOSITOR_OFFSCREEN; @@ -7685,20 +7682,7 @@ weston_compositor_tear_down(struct weston_compositor *compositor) weston_log_scope_destroy(compositor->timeline); compositor->timeline = NULL; -} -/** Destroys the compositor. - * - * This function destroys the compositor. **Do not** call this before - * calling weston_compositor_tear_down() - * - * @param compositor The compositor to be destroyed. - * @ingroup compositor - * @sa weston_compositor_tear_down() - */ -WL_EXPORT void -weston_compositor_destroy(struct weston_compositor *compositor) -{ free(compositor); } From 61a6b03d6fafb7d33a05c7af5fb7500da196ec64 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 25 Oct 2019 13:57:10 +0300 Subject: [PATCH 1295/1642] compositor: add test suite data Allow to set and get one opaque pointer. The test suite will then use this to pass data from the test runner to the test plugin, so that the plugin can then run what it needs to. This is useful when the test runner calls wet_main() directly instead of forking a compositor. Signed-off-by: Pekka Paalanen --- compositor/meson.build | 1 + compositor/testsuite-util.c | 62 +++++++++++++++++++++++++++++++++++++ compositor/weston.h | 12 +++++++ 3 files changed, 75 insertions(+) create mode 100644 compositor/testsuite-util.c diff --git a/compositor/meson.build b/compositor/meson.build index e1334d6a0..f17415bee 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -1,6 +1,7 @@ srcs_weston = [ git_version_h, 'main.c', + 'testsuite-util.c', 'text-backend.c', 'weston-screenshooter.c', text_input_unstable_v1_server_protocol_h, diff --git a/compositor/testsuite-util.c b/compositor/testsuite-util.c new file mode 100644 index 000000000..34882d1fc --- /dev/null +++ b/compositor/testsuite-util.c @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Collabora, Ltd. + * + * 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 "weston.h" + +static struct wet_testsuite_data *wet_testsuite_data_global; + +/** Set global test suite data + * + * \param data Custom test suite data. + * + * The type struct wet_testsuite_data is free to be defined by any test suite + * in any way they want. This function stores a single pointer to that data + * in a global variable. + * + * The data is expected to be fetched from a test suite specific plugin that + * knows how to interpret it. + * + * \sa wet_testsuite_data_get + */ +WL_EXPORT void +wet_testsuite_data_set(struct wet_testsuite_data *data) +{ + wet_testsuite_data_global = data; +} + +/** Get global test suite data + * + * \return Custom test suite data. + * + * Returns the value last set with wet_testsuite_data_set(). + */ +WL_EXPORT struct wet_testsuite_data * +wet_testsuite_data_get(void) +{ + return wet_testsuite_data_global; +} diff --git a/compositor/weston.h b/compositor/weston.h index 24a67370b..e09397f9b 100644 --- a/compositor/weston.h +++ b/compositor/weston.h @@ -98,6 +98,18 @@ text_backend_destroy(struct text_backend *text_backend); int wet_main(int argc, char *argv[]); + +/* test suite utilities */ + +/** Opaque type for a test suite to define. */ +struct wet_testsuite_data; + +void +wet_testsuite_data_set(struct wet_testsuite_data *data); + +struct wet_testsuite_data * +wet_testsuite_data_get(void); + #ifdef __cplusplus } #endif From 2c8203dcb0d235c4c5821b94ac2853d887dd2a44 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 31 Oct 2019 17:20:17 +0200 Subject: [PATCH 1296/1642] tests: move exit() from run_test() I will be able to re-use this function if it does not call exit() itself. Signed-off-by: Pekka Paalanen --- tests/weston-test-runner.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/weston-test-runner.c b/tests/weston-test-runner.c index 1fa1276e6..ee17ba4d5 100644 --- a/tests/weston-test-runner.c +++ b/tests/weston-test-runner.c @@ -85,7 +85,6 @@ run_test(const struct weston_test_entry *t, void *data, int iteration) } t->run(data); - exit(EXIT_SUCCESS); } static void @@ -111,8 +110,10 @@ exec_and_report_test(const struct weston_test_entry *t, pid_t pid = fork(); assert(pid >= 0); - if (pid == 0) - run_test(t, test_data, iteration); /* never returns */ + if (pid == 0) { + run_test(t, test_data, iteration); + exit(EXIT_SUCCESS); + } if (waitid(P_ALL, 0, &info, WEXITED)) { fprintf(stderr, "waitid failed: %s\n", strerror(errno)); From babb3b3bc2a94811ccd721abf0e9199d737f4a1d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 1 Nov 2019 14:02:15 +0200 Subject: [PATCH 1297/1642] tests: thread-based client harness This replaces the old test harness with a new one. The old harness relied on fork()'ing each test which makes tests independent, but makes debugging them harder. The new harness runs client code in a thread instead of a new process. A side-effect of not fork()'ing anymore is that any failure will stop running a test series short. Fortunately we do not have any tests that are expected to crash or fail. The old harness executed 'weston' from Meson, with lots of setup as both command line options and environment variables. The new harness executes wet_main() instead: the test program itself calls the compositor main function to execute the compositor in-process. Command line arguments are configured in the test program itself, not in meson.build. Environment variables aside, you are able to run a test by simply executing the test program, even if it is a plugin test. The new harness adds a new type of iteration: fixtures. For now, fixtures are used to set up the compositor for tests that need a compositor. If necessary, a fixture setup may include a data array of arbitrary type for executing the test series for each element in the array. This will be most useful for running screenshooting tests with both Pixman- and GL-renderers. The new harness outputs TAP formatted results into stdout. Meson is not switched to consume TAP yet though, because it would require a Meson version requirement bump and would not have any benefits at this time. OTOH outputting TAP is trivial and sets up a clear precedent of random test chatter belonging to stderr. This commit migrates only few tests to actually make use of the new features: roles is a basic client test, subsurface-shot is a client test that demonstrates the fixture array, and plugin-registry is a plugin test. The rest of the tests will be migrated later. Once all tests are migrated, we can remove the test-specific setup from meson.build, leaving only the actual build instructions in there. The not migrated tests and stand-alone tests suffer only a minor change: they no longer fork() for each TEST(), otherwise they keep running as before. Signed-off-by: Pekka Paalanen --- tests/meson.build | 55 ++- tests/plugin-registry-test.c | 37 +- tests/roles-test.c | 13 + tests/subsurface-shot-test.c | 27 +- tests/weston-test-fixture-compositor.c | 277 ++++++++++++ tests/weston-test-fixture-compositor.h | 127 ++++++ tests/weston-test-runner.c | 591 ++++++++++++++++++++----- tests/weston-test-runner.h | 178 +++++++- tests/weston-test.c | 204 ++++++++- tests/weston-testsuite-data.h | 88 ++++ 10 files changed, 1443 insertions(+), 154 deletions(-) create mode 100644 tests/weston-test-fixture-compositor.c create mode 100644 tests/weston-test-fixture-compositor.h create mode 100644 tests/weston-testsuite-data.h diff --git a/tests/meson.build b/tests/meson.build index a0735ad46..49135b435 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -11,7 +11,10 @@ env_modmap += 'weston-test-desktop-shell.so=@0@;'.format(plugin_test_shell_deskt lib_test_runner = static_library( 'test-runner', 'weston-test-runner.c', - dependencies: dep_wayland_client, + dependencies: [ + dep_libweston_private_h_deps, + dep_wayland_client, + ], include_directories: common_inc, install: false, ) @@ -22,13 +25,17 @@ dep_test_runner = declare_dependency( lib_test_client = static_library( 'test-client', - 'weston-test-client-helper.c', - weston_test_client_protocol_h, - weston_test_protocol_c, + [ + 'weston-test-client-helper.c', + 'weston-test-fixture-compositor.c', + weston_test_client_protocol_h, + weston_test_protocol_c, + ], include_directories: common_inc, dependencies: [ dep_libshared, dep_wayland_client, + dep_libexec_weston, dep_pixman, dependency('cairo'), ], @@ -49,10 +56,15 @@ exe_plugin_test = shared_library( weston_test_server_protocol_h, weston_test_protocol_c, include_directories: common_inc, - dependencies: [ dep_libexec_weston, dep_libweston_private ], + dependencies: [ + dep_libexec_weston, + dep_libweston_private, + dep_threads + ], name_prefix: '', install: false, ) +config_h.set_quoted('TESTSUITE_PLUGIN_PATH', exe_plugin_test.full_path()) deps_zuc = [ dep_libshared ] if get_option('test-junit-xml') @@ -99,6 +111,12 @@ dep_zucmain = declare_dependency( dependencies: dep_zuc ) +tests = [ + { 'name': 'plugin-registry', }, + { 'name': 'roles', }, + { 'name': 'subsurface-shot', }, +] + tests_standalone = [ ['config-parser', [], [ dep_zucmain ]], ['matrix', [], [ dep_libm, dep_matrix_c ]], @@ -149,9 +167,7 @@ tests_weston = [ input_timestamps_unstable_v1_protocol_c, ] ], - ['roles'], ['subsurface'], - ['subsurface-shot'], [ 'text', [ @@ -185,7 +201,6 @@ if get_option('xwayland') endif tests_weston_plugin = [ - ['plugin-registry'], ['surface'], ['surface-global'], ['surface-screenshot', 'surface-screenshot-test.c', dep_libshared], @@ -235,6 +250,30 @@ env_test_weston = [ 'WESTON_DATA_DIR=' + join_paths(meson.current_source_dir(), '..', 'data'), ] +foreach t : tests + t_name = 'test-' + t.get('name') + t_sources = t.get('sources', [t.get('name') + '-test.c']) + t_sources += weston_test_client_protocol_h + + t_deps = [ dep_test_client, dep_libweston_private_h ] + t_deps += t.get('dep_objs', []) + + t_exe = executable( + t_name, + t_sources, + c_args: [ + '-DUNIT_TEST', + '-DTHIS_TEST_NAME="' + t_name + '"', + ], + build_by_default: true, + include_directories: common_inc, + dependencies: t_deps, + install: false, + ) + + test(t.get('name'), t_exe, depends: t.get('test_deps', []), env: env_test_weston) +endforeach + # FIXME: the multiple loops is lame. rethink this. foreach t : tests_standalone if t[0] != 'zuc' diff --git a/tests/plugin-registry-test.c b/tests/plugin-registry-test.c index 4ffc69e55..85de5f8a2 100644 --- a/tests/plugin-registry-test.c +++ b/tests/plugin-registry-test.c @@ -31,6 +31,20 @@ #include "compositor/weston.h" #include +#include "weston-test-runner.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_plugin(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + static void dummy_func(void) { @@ -67,13 +81,14 @@ init_tests(struct weston_compositor *compositor) sizeof(my_test_api)) == 0); } -static void -runtime_tests(void *data) +PLUGIN_TEST(plugin_registry_test) { - struct weston_compositor *compositor = data; + /* struct weston_compositor *compositor; */ const struct my_api *api; size_t sz = sizeof(struct my_api); + init_tests(compositor); + assert(weston_plugin_api_get(compositor, MY_API_NAME, sz) == &my_test_api); @@ -84,20 +99,4 @@ runtime_tests(void *data) api = weston_plugin_api_get(compositor, MY_API_NAME, sz); assert(api && api->func2 == dummy_func); - - weston_compositor_exit(compositor); -} - -WL_EXPORT int -wet_module_init(struct weston_compositor *compositor, - int *argc, char *argv[]) -{ - struct wl_event_loop *loop; - - init_tests(compositor); - - loop = wl_display_get_event_loop(compositor->wl_display); - wl_event_loop_add_idle(loop, runtime_tests, compositor); - - return 0; } diff --git a/tests/roles-test.c b/tests/roles-test.c index e0c4377de..fbce0539e 100644 --- a/tests/roles-test.c +++ b/tests/roles-test.c @@ -30,6 +30,19 @@ #include #include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.logging_scopes = "log,proto,test-harness-plugin"; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); static struct wl_subcompositor * get_subcompositor(struct client *client) diff --git a/tests/subsurface-shot-test.c b/tests/subsurface-shot-test.c index d3dc05bad..dcbce0c3f 100644 --- a/tests/subsurface-shot-test.c +++ b/tests/subsurface-shot-test.c @@ -31,9 +31,32 @@ #include #include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" -char *server_parameters = "--use-pixman --width=320 --height=240" - " --shell=weston-test-desktop-shell.so"; +static const enum renderer_type renderers[] = { + RENDERER_PIXMAN, + RENDERER_GL, +}; + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness, const enum renderer_type *arg) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.renderer = *arg; + setup.width = 320; + setup.height = 240; + setup.shell = SHELL_TEST_DESKTOP; + setup.logging_scopes = "log,test-harness-plugin"; + + /* This test fails due to color rounding on GL */ + if (setup.renderer == RENDERER_GL) + return RESULT_SKIP; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, renderers); static struct wl_subcompositor * get_subcompositor(struct client *client) diff --git a/tests/weston-test-fixture-compositor.c b/tests/weston-test-fixture-compositor.c new file mode 100644 index 000000000..04c06409b --- /dev/null +++ b/tests/weston-test-fixture-compositor.c @@ -0,0 +1,277 @@ +/* + * Copyright 2019 Collabora, Ltd. + * + * 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 "shared/helpers.h" +#include "weston-test-fixture-compositor.h" +#include "weston.h" + +struct prog_args { + int argc; + char **argv; + char **saved; + int alloc; +}; + +static void +prog_args_init(struct prog_args *p) +{ + memset(p, 0, sizeof(*p)); +} + +static void +prog_args_take(struct prog_args *p, char *arg) +{ + assert(arg); + + if (p->argc == p->alloc) { + p->alloc += 10; + p->argv = realloc(p->argv, sizeof(char *) * p->alloc); + assert(p->argv); + } + + p->argv[p->argc++] = arg; +} + +/* + * The program to be executed will trample on argv, hence we need a copy to + * be able to free all our args. + */ +static void +prog_args_save(struct prog_args *p) +{ + assert(p->saved == NULL); + + p->saved = calloc(p->argc, sizeof(char *)); + assert(p->saved); + + memcpy(p->saved, p->argv, sizeof(char *) * p->argc); +} + +static void +prog_args_fini(struct prog_args *p) +{ + int i; + + assert(p->saved); + + for (i = 0; i < p->argc; i++) + free(p->saved[i]); + free(p->saved); + free(p->argv); + prog_args_init(p); +} + +/** Initialize part of compositor setup + * + * \param setup The variable to initialize. + * \param testset_name Value for testset_name member. + * + * \ingroup testharness_private + */ +void +compositor_setup_defaults_(struct compositor_setup *setup, + const char *testset_name) +{ + *setup = (struct compositor_setup) { + .backend = WESTON_BACKEND_HEADLESS, + .renderer = RENDERER_NOOP, + .shell = SHELL_DESKTOP, + .xwayland = false, + .width = 320, + .height = 240, + .config_file = NULL, + .extra_module = NULL, + .logging_scopes = NULL, + .testset_name = testset_name, + }; +} + +static const char * +backend_to_str(enum weston_compositor_backend b) +{ + static const char * const names[] = { + [WESTON_BACKEND_DRM] = "drm-backend.so", + [WESTON_BACKEND_FBDEV] = "fbdev-backend.so", + [WESTON_BACKEND_HEADLESS] = "headless-backend.so", + [WESTON_BACKEND_RDP] = "rdp-backend.so", + [WESTON_BACKEND_WAYLAND] = "wayland-backend.so", + [WESTON_BACKEND_X11] = "X11-backend.so", + }; + assert(b >= 0 && b < ARRAY_LENGTH(names)); + return names[b]; +} + +static const char * +renderer_to_arg(enum weston_compositor_backend b, enum renderer_type r) +{ + static const char * const headless_names[] = { + [RENDERER_NOOP] = NULL, + [RENDERER_PIXMAN] = "--use-pixman", + [RENDERER_GL] = "--use-gl", + }; + + assert(r >= 0 && r < ARRAY_LENGTH(headless_names)); + + switch (b) { + case WESTON_BACKEND_HEADLESS: + return headless_names[r]; + default: + assert(0 && "renderer_to_str() does not know the backend"); + } + + return NULL; +} + +static const char * +shell_to_str(enum shell_type t) +{ + static const char * const names[] = { + [SHELL_TEST_DESKTOP] = "weston-test-desktop-shell.so", + [SHELL_DESKTOP] = "desktop-shell.so", + [SHELL_FULLSCREEN] = "fullscreen-shell.so", + [SHELL_IVI] = "ivi-shell.so", + }; + assert(t >= 0 && t < ARRAY_LENGTH(names)); + return names[t]; +} + +/** Execute compositor + * + * Manufactures the compositor command line and calls wet_main(). + * + * Returns RESULT_SKIP if the given setup contains features that were disabled + * in the build, e.g. GL-renderer or DRM-backend. + * + * \ingroup testharness_private + */ +int +execute_compositor(const struct compositor_setup *setup, + struct wet_testsuite_data *data) +{ + struct prog_args args; + char *tmp; + const char *ctmp; + int ret; + +#ifndef BUILD_DRM_COMPOSITOR + if (setup->backend == WESTON_BACKEND_DRM) { + fprintf(stderr, "DRM-backend required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + +#ifndef BUILD_FBDEV_COMPOSITOR + if (setup->backend == WESTON_BACKEND_FBDEV) { + fprintf(stderr, "fbdev-backend required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + +#ifndef BUILD_RDP_COMPOSITOR + if (setup->backend == WESTON_BACKEND_RDP) { + fprintf(stderr, "RDP-backend required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + +#ifndef BUILD_WAYLAND_COMPOSITOR + if (setup->backend == WESTON_BACKEND_WAYLAND) { + fprintf(stderr, "wayland-backend required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + +#ifndef BUILD_X11_COMPOSITOR + if (setup->backend == WESTON_BACKEND_X11) { + fprintf(stderr, "X11-backend required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + +#ifndef ENABLE_EGL + if (setup->renderer == RENDERER_GL) { + fprintf(stderr, "GL-renderer required but not built, skipping.\n"); + return RESULT_SKIP; + } +#endif + + prog_args_init(&args); + + /* argv[0] */ + asprintf(&tmp, "weston-%s", setup->testset_name); + prog_args_take(&args, tmp); + + asprintf(&tmp, "--backend=%s", backend_to_str(setup->backend)); + prog_args_take(&args, tmp); + + asprintf(&tmp, "--socket=%s", setup->testset_name); + prog_args_take(&args, tmp); + + asprintf(&tmp, "--modules=%s%s%s", TESTSUITE_PLUGIN_PATH, + setup->extra_module ? "," : "", + setup->extra_module ? setup->extra_module : ""); + prog_args_take(&args, tmp); + + asprintf(&tmp, "--width=%d", setup->width); + prog_args_take(&args, tmp); + + asprintf(&tmp, "--height=%d", setup->height); + prog_args_take(&args, tmp); + + if (setup->config_file) { + asprintf(&tmp, "--config=%s", setup->config_file); + prog_args_take(&args, tmp); + } else { + prog_args_take(&args, strdup("--no-config")); + } + + ctmp = renderer_to_arg(setup->backend, setup->renderer); + if (ctmp) + prog_args_take(&args, strdup(ctmp)); + + asprintf(&tmp, "--shell=%s", shell_to_str(setup->shell)); + prog_args_take(&args, tmp); + + if (setup->logging_scopes) { + asprintf(&tmp, "--logger-scopes=%s", setup->logging_scopes); + prog_args_take(&args, tmp); + } + + if (setup->xwayland) + prog_args_take(&args, strdup("--xwayland")); + + wet_testsuite_data_set(data); + prog_args_save(&args); + ret = wet_main(args.argc, args.argv); + + prog_args_fini(&args); + + return ret; +} diff --git a/tests/weston-test-fixture-compositor.h b/tests/weston-test-fixture-compositor.h new file mode 100644 index 000000000..6e8d680e2 --- /dev/null +++ b/tests/weston-test-fixture-compositor.h @@ -0,0 +1,127 @@ +/* + * Copyright 2019 Collabora, Ltd. + * + * 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. + */ + +#ifndef WESTON_TEST_FIXTURE_COMPOSITOR_H +#define WESTON_TEST_FIXTURE_COMPOSITOR_H + +#include + +#include "weston-testsuite-data.h" + +/** Weston renderer type + * + * \sa compositor_setup + * \ingroup testharness + */ +enum renderer_type { + /** Dummy renderer that does nothing. */ + RENDERER_NOOP = 0, + /** Pixman-renderer */ + RENDERER_PIXMAN, + /** GL-renderer */ + RENDERER_GL +}; + +/** Weston shell plugin + * + * \sa compositor_setup + * \ingroup testharness + */ +enum shell_type { + /** Desktop test-shell with predictable window placement and + * no helper clients */ + SHELL_TEST_DESKTOP = 0, + /** The full desktop shell. */ + SHELL_DESKTOP, + /** The ivi-shell. */ + SHELL_IVI, + /** The fullscreen-shell. */ + SHELL_FULLSCREEN +}; + +/** Weston compositor configuration + * + * This structure determines the Weston compositor command line arguments. + * You should always use compositor_setup_defaults() to initialize this, then + * override any members you need with assignments. + * + * \ingroup testharness + */ +struct compositor_setup { + /** The backend to use. */ + enum weston_compositor_backend backend; + /** The renderer to use. */ + enum renderer_type renderer; + /** The shell plugin to use. */ + enum shell_type shell; + /** Whether to enable xwayland support. */ + bool xwayland; + /** Default output width. */ + unsigned width; + /** Default output height. */ + unsigned height; + /** The absolute path to \c weston.ini to use, + * or NULL for \c --no-config . */ + const char *config_file; + /** Full path to an extra plugin to load, or NULL for none. */ + const char *extra_module; + /** Debug scopes for the compositor log, + * or NULL for compositor defaults. */ + const char *logging_scopes; + /** The name of this test program, used as a unique identifier. */ + const char *testset_name; +}; + +void +compositor_setup_defaults_(struct compositor_setup *setup, + const char *testset_name); + +/** Initialize compositor setup to defaults + * + * \param s The variable to initialize. + * + * The defaults are: + * - backend: headless + * - renderer: noop + * - shell: desktop shell + * - xwayland: no + * - width: 320 + * - height: 240 + * - config_file: none + * - extra_module: none + * - logging_scopes: compositor defaults + * - testset_name: the test name from meson.build + * + * \ingroup testharness + */ +#define compositor_setup_defaults(s) do {\ + compositor_setup_defaults_(s, THIS_TEST_NAME); \ +} while (0) + +int +execute_compositor(const struct compositor_setup *setup, + struct wet_testsuite_data *data); + +#endif /* WESTON_TEST_FIXTURE_COMPOSITOR_H */ diff --git a/tests/weston-test-runner.c b/tests/weston-test-runner.c index ee17ba4d5..8f945f94d 100644 --- a/tests/weston-test-runner.c +++ b/tests/weston-test-runner.c @@ -33,23 +33,47 @@ #include #include #include +#include #include "weston-test-runner.h" +#include "weston-testsuite-data.h" +#include "shared/string-helpers.h" -#define SKIP 77 - -char __attribute__((weak)) *server_parameters=""; +/** + * \defgroup testharness Test harness + * \defgroup testharness_private Test harness private + */ extern const struct weston_test_entry __start_test_section, __stop_test_section; static const char *test_name_; +/** Get the test name string with counter + * + * \return The test name with fixture number \c f%%d- prefixed. For an array + * driven test, e.g. defined with TEST_P(), the name has a \c -e%%d suffix to + * indicate the array element number. + * + * This is only usable from code paths inside TEST(), TEST_P(), PLUGIN_TEST() + * etc. defined functions. + * + * \ingroup testharness + */ const char * get_test_name(void) { return test_name_; } +/** Print into test log + * + * This is exactly like printf() except the output goes to the test log, + * which is at stderr. + * + * \param fmt printf format string + * + * \ingroup testharness + */ void testlog(const char *fmt, ...) { @@ -72,157 +96,502 @@ find_test(const char *name) return NULL; } -static void -run_test(const struct weston_test_entry *t, void *data, int iteration) +static enum test_result_code +run_test(int fixture_nr, const struct weston_test_entry *t, void *data, + int iteration) { char str[512]; if (data) { - snprintf(str, sizeof(str), "%s[%d]", t->name, iteration); - test_name_ = str; + snprintf(str, sizeof(str), "f%d-%s-e%d", + fixture_nr, t->name, iteration); } else { - test_name_ = t->name; + snprintf(str, sizeof(str), "f%d-%s", fixture_nr, t->name); } + test_name_ = str; t->run(data); + test_name_ = NULL; + + /* + * XXX: We should return t->run(data); but that requires changing + * the function signature and stop using assert() in tests. + * https://gitlab.freedesktop.org/wayland/weston/issues/311 + */ + return RESULT_OK; } static void list_tests(void) { + const struct fixture_setup_array *fsa; const struct weston_test_entry *t; - fprintf(stderr, "Available test names:\n"); - for (t = &__start_test_section; t < &__stop_test_section; t++) - fprintf(stderr, " %s\n", t->name); + fsa = fixture_setup_array_get_(); + + printf("Fixture setups: %d\n", fsa->n_elements); + + for (t = &__start_test_section; t < &__stop_test_section; t++) { + printf(" %s\n", t->name); + if (t->n_elements > 1) + printf(" with array of %d cases\n", t->n_elements); + } } -/* iteration is valid only if test_data is not NULL */ -static int -exec_and_report_test(const struct weston_test_entry *t, - void *test_data, int iteration) -{ - int success = 0; - int skip = 0; - int hardfail = 0; - siginfo_t info; +struct weston_test_harness { + int32_t fixt_ind; + char *chosen_testname; + int32_t case_ind; - pid_t pid = fork(); - assert(pid >= 0); + struct wet_testsuite_data data; +}; - if (pid == 0) { - run_test(t, test_data, iteration); - exit(EXIT_SUCCESS); +typedef void (*weston_test_cb)(struct wet_testsuite_data *suite_data, + const struct weston_test_entry *t, + const void *test_data, + int iteration); + +static void +for_each_test_case(struct wet_testsuite_data *data, weston_test_cb cb) +{ + unsigned i; + + for (i = 0; i < data->tests_count; i++) { + const struct weston_test_entry *t = &data->tests[i]; + const void *current_test_data = t->table_data; + int elem; + int elem_end; + + if (data->case_index == -1) { + elem = 0; + elem_end = t->n_elements; + } else { + elem = data->case_index; + elem_end = elem + 1; + } + + for (; elem < elem_end; elem++) { + current_test_data = (char *)t->table_data + + elem * t->element_size; + cb(data, t, current_test_data, elem); + } } +} - if (waitid(P_ALL, 0, &info, WEXITED)) { - fprintf(stderr, "waitid failed: %s\n", strerror(errno)); - abort(); +static const char * +result_to_str(enum test_result_code ret) +{ + static const char *names[] = { + [RESULT_FAIL] = "fail", + [RESULT_HARD_ERROR] = "hard error", + [RESULT_OK] = "ok", + [RESULT_SKIP] = "skip", + }; + + assert(ret >= 0 && ret < ARRAY_LENGTH(names)); + return names[ret]; +} + +static void +run_case(struct wet_testsuite_data *suite_data, + const struct weston_test_entry *t, + const void *test_data, + int iteration) +{ + enum test_result_code ret; + const char *fail = ""; + const char *skip = ""; + int fixture_nr = suite_data->fixture_iteration + 1; + int iteration_nr = iteration + 1; + + testlog("*** Run fixture %d, %s/%d\n", + fixture_nr, t->name, iteration_nr); + + if (suite_data->type == TEST_TYPE_PLUGIN) { + ret = run_test(fixture_nr, t, suite_data->compositor, + iteration); + } else { + ret = run_test(fixture_nr, t, (void *)test_data, iteration); } - if (test_data) - fprintf(stderr, "test \"%s/%i\":\t", t->name, iteration); - else - fprintf(stderr, "test \"%s\":\t", t->name); - - switch (info.si_code) { - case CLD_EXITED: - fprintf(stderr, "exit status %d", info.si_status); - if (info.si_status == EXIT_SUCCESS) - success = 1; - else if (info.si_status == SKIP) - skip = 1; + switch (ret) { + case RESULT_OK: + suite_data->passed++; + break; + case RESULT_FAIL: + case RESULT_HARD_ERROR: + suite_data->failed++; + fail = "not "; break; - case CLD_KILLED: - case CLD_DUMPED: - fprintf(stderr, "signal %d", info.si_status); - if (info.si_status != SIGABRT) - hardfail = 1; + case RESULT_SKIP: + suite_data->skipped++; + skip = " # SKIP"; break; } - if (success && !hardfail) { - fprintf(stderr, ", pass.\n"); - return 1; - } else if (skip) { - fprintf(stderr, ", skip.\n"); - return SKIP; - } else { - fprintf(stderr, ", fail.\n"); - return 0; - } + testlog("*** Result fixture %d, %s/%d: %s\n", + fixture_nr, t->name, iteration_nr, result_to_str(ret)); + + suite_data->counter++; + printf("%sok %d fixture %d %s/%d%s\n", fail, suite_data->counter, + fixture_nr, t->name, iteration_nr, skip); } -/* Returns number of tests and number of pass / fail in param args. - * Even non-iterated tests go through here, they simply have n_elements = 1 and - * table_data = NULL. - */ -static int -iterate_test(const struct weston_test_entry *t, int *passed, int *skipped) -{ - int ret, i; - void *current_test_data = (void *) t->table_data; - for (i = 0; i < t->n_elements; ++i, current_test_data += t->element_size) - { - ret = exec_and_report_test(t, current_test_data, i); - if (ret == SKIP) - ++(*skipped); - else if (ret) - ++(*passed); - } +/* This function might run in a new thread */ +static void +testsuite_run(struct wet_testsuite_data *data) +{ + for_each_test_case(data, run_case); +} - return t->n_elements; +static void +count_case(struct wet_testsuite_data *suite_data, + const struct weston_test_entry *t, + const void *test_data, + int iteration) +{ + suite_data->total++; } -int main(int argc, char *argv[]) +static void +tap_plan(struct wet_testsuite_data *data, int count_fixtures) { - const struct weston_test_entry *t; - int total = 0; - int pass = 0; - int skip = 0; - - if (argc == 2) { - const char *testname = argv[1]; - if (strcmp(testname, "--help") == 0 || - strcmp(testname, "-h") == 0) { - fprintf(stderr, "Usage: %s [test-name]\n", program_invocation_short_name); + data->total = 0; + for_each_test_case(data, count_case); + + printf("1..%d\n", data->total * count_fixtures); +} + +static void +skip_case(struct wet_testsuite_data *suite_data, + const struct weston_test_entry *t, + const void *test_data, + int iteration) +{ + int fixture_nr = suite_data->fixture_iteration + 1; + int iteration_nr = iteration + 1; + + suite_data->counter++; + printf("ok %d fixture %d %s/%d # SKIP fixture\n", suite_data->counter, + fixture_nr, t->name, iteration_nr); +} + +static void +tap_skip_fixture(struct wet_testsuite_data *data) +{ + for_each_test_case(data, skip_case); +} + +static void +help(const char *exe) +{ + printf( + "Usage: %s [options] [testname [index]]\n" + "\n" + "This is a Weston test suite executable that runs some tests.\n" + "Options:\n" + " -f, --fixture N Run only fixture index N. Indices start from 1.\n" + " -h, --help Print this help and exit with success.\n" + " -l, --list List all tests in this executable and exit with success.\n" + "testname: Optional; name of the test to execute instead of all tests.\n" + "index: Optional; for a multi-case test, run the given case only.\n", + exe); +} + +static void +parse_command_line(struct weston_test_harness *harness, int argc, char **argv) +{ + int c; + static const struct option opts[] = { + { "fixture", required_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "list", no_argument, NULL, 'l' }, + { 0, 0, NULL, 0 } + }; + + while ((c = getopt_long(argc, argv, "f:hl", opts, NULL)) != -1) { + switch (c) { + case 'f': + if (!safe_strtoint(optarg, &harness->fixt_ind)) { + fprintf(stderr, + "Error: '%s' does not look like a number (command line).\n", + optarg); + exit(RESULT_HARD_ERROR); + } + harness->fixt_ind--; /* convert base-1 to base 0 */ + break; + case 'h': + help(argv[0]); + exit(RESULT_OK); + case 'l': list_tests(); - exit(EXIT_SUCCESS); + exit(RESULT_OK); + case 0: + break; + default: + exit(RESULT_HARD_ERROR); } + } + + if (optind < argc) + harness->chosen_testname = argv[optind++]; - if (strcmp(testname, "--params") == 0 || - strcmp(testname, "-p") == 0) { - printf("%s", server_parameters); - exit(EXIT_SUCCESS); + if (optind < argc) { + if (!safe_strtoint(argv[optind], &harness->case_ind)) { + fprintf(stderr, + "Error: '%s' does not look like a number (command line).\n", + argv[optind]); + exit(RESULT_HARD_ERROR); } + harness->case_ind--; /* convert base-1 to base 0 */ + optind++; + } - t = find_test(argv[1]); - if (t == NULL) { - fprintf(stderr, "unknown test: \"%s\"\n", argv[1]); - list_tests(); - exit(EXIT_FAILURE); + if (optind < argc) { + fprintf(stderr, "Unexpected extra arguments given (command line).\n\n"); + help(argv[0]); + exit(RESULT_HARD_ERROR); + } +} + +static struct weston_test_harness * +weston_test_harness_create(int argc, char **argv) +{ + const struct fixture_setup_array *fsa; + struct weston_test_harness *harness; + + harness = zalloc(sizeof(*harness)); + assert(harness); + + harness->fixt_ind = -1; + harness->case_ind = -1; + parse_command_line(harness, argc, argv); + + fsa = fixture_setup_array_get_(); + if (harness->fixt_ind < -1 || harness->fixt_ind >= fsa->n_elements) { + fprintf(stderr, + "Error: fixture index %d (command line) is invalid for this program.\n", + harness->fixt_ind + 1); + exit(RESULT_HARD_ERROR); + } + + if (harness->chosen_testname) { + const struct weston_test_entry *t; + + t = find_test(harness->chosen_testname); + if (!t) { + fprintf(stderr, + "Error: test '%s' not found (command line).\n", + harness->chosen_testname); + exit(RESULT_HARD_ERROR); } - int number_passed_in_test = 0, number_skipped_in_test = 0; - total += iterate_test(t, &number_passed_in_test, &number_skipped_in_test); - pass += number_passed_in_test; - skip += number_skipped_in_test; - } else { - for (t = &__start_test_section; t < &__stop_test_section; t++) { - int number_passed_in_test = 0, number_skipped_in_test = 0; - total += iterate_test(t, &number_passed_in_test, &number_skipped_in_test); - pass += number_passed_in_test; - skip += number_skipped_in_test; + if (harness->case_ind < -1 || + harness->case_ind >= t->n_elements) { + fprintf(stderr, + "Error: case index %d (command line) is invalid for this test.\n", + harness->case_ind + 1); + exit(RESULT_HARD_ERROR); } + + harness->data.tests = t; + harness->data.tests_count = 1; + harness->data.case_index = harness->case_ind; + } else { + harness->data.tests = &__start_test_section; + harness->data.tests_count = + &__stop_test_section - &__start_test_section; + harness->data.case_index = -1; } - fprintf(stderr, "%d tests, %d pass, %d skip, %d fail\n", - total, pass, skip, total - pass - skip); + harness->data.run = testsuite_run; + + return harness; +} + +static void +weston_test_harness_destroy(struct weston_test_harness *harness) +{ + free(harness); +} + +static enum test_result_code +counts_to_result(const struct wet_testsuite_data *data) +{ + /* RESULT_SKIP is reserved for fixture setup itself skipping everything */ + if (data->total == data->passed + data->skipped) + return RESULT_OK; + return RESULT_FAIL; +} + +/** Execute all tests as client tests + * + * \param harness The test harness context. + * \param setup The compositor configuration. + * + * Initializes the compositor with the given setup and executes the compositor. + * The compositor creates a new thread where all tests in the test program are + * serially executed. Once the thread finishes, the compositor returns from its + * event loop and cleans up. + * + * Returns RESULT_SKIP if the requested compositor features, e.g. GL-renderer, + * are not built. + * + * \sa DECLARE_FIXTURE_SETUP(), DECLARE_FIXTURE_SETUP_WITH_ARG() + * \ingroup testharness + */ +enum test_result_code +weston_test_harness_execute_as_client(struct weston_test_harness *harness, + const struct compositor_setup *setup) +{ + struct wet_testsuite_data *data = &harness->data; + + data->type = TEST_TYPE_CLIENT; + return execute_compositor(setup, data); +} + +/** Execute all tests as plugin tests + * + * \param harness The test harness context. + * \param setup The compositor configuration. + * + * Initializes the compositor with the given setup and executes the compositor. + * The compositor executes all tests in the test program serially from an idle + * handler, then returns from its event loop and cleans up. + * + * Returns RESULT_SKIP if the requested compositor features, e.g. GL-renderer, + * are not built. + * + * \sa DECLARE_FIXTURE_SETUP(), DECLARE_FIXTURE_SETUP_WITH_ARG() + * \ingroup testharness + */ +enum test_result_code +weston_test_harness_execute_as_plugin(struct weston_test_harness *harness, + const struct compositor_setup *setup) +{ + struct wet_testsuite_data *data = &harness->data; + + data->type = TEST_TYPE_PLUGIN; + return execute_compositor(setup, data); +} + +/** Execute all tests as standalone tests + * + * \param harness The test harness context. + * + * Executes all tests in the test program serially without any further setup, + * particularly without any compositor instance created. + * + * \sa DECLARE_FIXTURE_SETUP(), DECLARE_FIXTURE_SETUP_WITH_ARG() + * \ingroup testharness + */ +enum test_result_code +weston_test_harness_execute_standalone(struct weston_test_harness *harness) +{ + struct wet_testsuite_data *data = &harness->data; + + data->type = TEST_TYPE_STANDALONE; + data->run(data); + + return RESULT_OK; +} + +/** Fixture data array getter method + * + * DECLARE_FIXTURE_SETUP_WITH_ARG() overrides this in test programs. + * The default implementation has no data and makes the tests run once. + * + * \ingroup testharness + */ +__attribute__((weak)) const struct fixture_setup_array * +fixture_setup_array_get_(void) +{ + /* A dummy fixture without a data array. */ + static const struct fixture_setup_array default_fsa = { + .array = NULL, + .element_size = 0, + .n_elements = 1, + }; + + return &default_fsa; +} + +/** Fixture setup function + * + * DECLARE_FIXTURE_SETUP() and DECLARE_FIXTURE_SETUP_WITH_ARG() override + * this in test programs. + * The default implementation just calls + * weston_test_harness_execute_standalone(). + * + * \ingroup testharness + */ +__attribute__((weak)) enum test_result_code +fixture_setup_run_(struct weston_test_harness *harness, const void *arg_) +{ + return weston_test_harness_execute_standalone(harness); +} + +static void +fixture_report(const struct wet_testsuite_data *d, enum test_result_code ret) +{ + int fixture_nr = d->fixture_iteration + 1; + + testlog("--- Fixture %d %s: passed %d, skipped %d, failed %d, total %d\n", + fixture_nr, result_to_str(ret), + d->passed, d->skipped, d->failed, d->total); +} + +int +main(int argc, char *argv[]) +{ + struct weston_test_harness *harness; + enum test_result_code ret; + enum test_result_code result = RESULT_OK; + const struct fixture_setup_array *fsa; + const char *array_data; + int fi; + int fi_end; + + harness = weston_test_harness_create(argc, argv); + + fsa = fixture_setup_array_get_(); + array_data = fsa->array; + + if (harness->fixt_ind == -1) { + fi = 0; + fi_end = fsa->n_elements; + } else { + fi = harness->fixt_ind; + fi_end = fi + 1; + } + + tap_plan(&harness->data, fi_end - fi); + testlog("Iterating through %d fixtures.\n", fi_end - fi); + + for (; fi < fi_end; fi++) { + const void *arg = array_data + fi * fsa->element_size; + + testlog("--- Fixture %d...\n", fi + 1); + harness->data.fixture_iteration = fi; + harness->data.passed = 0; + harness->data.skipped = 0; + harness->data.failed = 0; + + ret = fixture_setup_run_(harness, arg); + fixture_report(&harness->data, ret); + + if (ret == RESULT_SKIP) { + tap_skip_fixture(&harness->data); + continue; + } + + if (ret != RESULT_OK && result != RESULT_HARD_ERROR) + result = ret; + else if (counts_to_result(&harness->data) != RESULT_OK) + result = RESULT_FAIL; + } - if (skip == total) - return SKIP; - else if (pass + skip == total) - return EXIT_SUCCESS; + weston_test_harness_destroy(harness); - return EXIT_FAILURE; + return result; } diff --git a/tests/weston-test-runner.h b/tests/weston-test-runner.h index df9a0b806..c47deba90 100644 --- a/tests/weston-test-runner.h +++ b/tests/weston-test-runner.h @@ -33,11 +33,22 @@ #include #include "shared/helpers.h" +#include "weston-test-fixture-compositor.h" +#include "weston-testsuite-data.h" #ifdef NDEBUG #error "Tests must not be built with NDEBUG defined, they rely on assert()." #endif +/** Test program entry + * + * Each invocation of TEST(), TEST_P(), or PLUGIN_TEST() will create one + * more weston_test_entry in a custom named section in the final binary. + * Iterating through the section then allows to iterate through all + * the defined tests. + * + * \ingroup testharness_private + */ struct weston_test_entry { const char *name; void (*run)(void *); @@ -75,22 +86,171 @@ struct weston_test_entry { ARRAY_LENGTH(test_data)) \ TEST_BEGIN(name, void *data) \ +/** Add a test with no parameters + * + * This defines one test as a new function. Use this macro in place of the + * function signature and put the function body after this. + * + * \param name Name for the test, must be a valid function name. + * + * \ingroup testharness + */ #define TEST(name) NO_ARG_TEST(name) -#define TEST_P(name, data) ARG_TEST(name, data) -void -testlog(const char *fmt, ...) WL_PRINTF(1, 2); +/** Add an array driven test with a parameter + * + * This defines an array of tests as a new function. Use this macro in place + * of the function signature and put the function body after this. The function + * will be executed once for each element in \c data_array, passing the + * element as the argument void *data to the function. + * + * This macro is not usable if fixture setup is using + * weston_test_harness_execute_as_plugin(). + * + * \param name Name for the test, must be a valid function name. + * \param data_array A static const array of any type. The length will be + * recorded automatically. + * + * \ingroup testharness + */ +#define TEST_P(name, data_array) ARG_TEST(name, data_array) -/** - * Get the test name string with counter +/** Add a test with weston_compositor argument + * + * This defines one test as a new function. Use this macro in place of the + * function signature and put the function body after this. The function + * will have one argument struct weston_compositor *compositor. * - * \return The test name. For an iterated test, e.g. defined with TEST_P(), - * the name has a '[%d]' suffix to indicate the iteration. + * This macro is only usable if fixture setup is using + * weston_test_harness_execute_as_plugin(). * - * This is only usable from code paths inside TEST(), TEST_P(), etc. - * defined functions. + * \param name Name for the test, must be a valid function name. + * + * \ingroup testharness */ +#define PLUGIN_TEST(name) \ + TEST_COMMON(wrap##name, name, NULL, 0, 1) \ + static void name(struct weston_compositor *); \ + static void wrap##name(void *compositor) \ + { \ + name(compositor); \ + } \ + TEST_BEGIN(name, struct weston_compositor *compositor) + +void +testlog(const char *fmt, ...) WL_PRINTF(1, 2); + const char * get_test_name(void); +/** Fixture setup array record + * + * Helper to store the attributes of the data array passed in to + * DECLARE_FIXTURE_SETUP_WITH_ARG(). + * + * \ingroup testharness_private + */ +struct fixture_setup_array { + const void *array; + size_t element_size; + int n_elements; +}; + +const struct fixture_setup_array * +fixture_setup_array_get_(void); + +/** Test harness context + * + * \ingroup testharness + */ +struct weston_test_harness; + +enum test_result_code +fixture_setup_run_(struct weston_test_harness *harness, const void *arg_); + +/** Register a fixture setup function + * + * This registers the given (preferably static) function to be used for setting + * up any fixtures you might need. The function must have the signature: + * + * \code + * enum test_result_code func_(struct weston_test_harness *harness) + * \endcode + * + * The function must call one of weston_test_harness_execute_standalone(), + * weston_test_harness_execute_as_plugin() or + * weston_test_harness_execute_as_client() passing in the \c harness argument, + * and return the return value from that call. The function can also return a + * test_result_code on its own if it does not want to run the tests, + * e.g. RESULT_SKIP or RESULT_HARD_ERROR. + * + * The function will be called once to run all tests. + * + * \param func_ The function to be used as fixture setup. + * + * \ingroup testharness + */ +#define DECLARE_FIXTURE_SETUP(func_) \ + enum test_result_code \ + fixture_setup_run_(struct weston_test_harness *harness, \ + const void *arg_) \ + { \ + return func_(harness); \ + } + +/** Register a fixture setup function with a data array + * + * This registers the given (preferably static) function to be used for setting + * up any fixtures you might need. The function must have the signature: + * + * \code + * enum test_result_code func_(struct weston_test_harness *harness, typeof(array_[0]) *arg) + * \endcode + * + * The function must call one of weston_test_harness_execute_standalone(), + * weston_test_harness_execute_as_plugin() or + * weston_test_harness_execute_as_client() passing in the \c harness argument, + * and return the return value from that call. The function can also return a + * test_result_code on its own if it does not want to run the tests, + * e.g. RESULT_SKIP or RESULT_HARD_ERROR. + * + * The function will be called once with each element of the array pointed to + * by \c arg, so that all tests would be repeated for each element in turn. + * + * \param func_ The function to be used as fixture setup. + * \param array_ A static const array of arbitrary type. + * + * \ingroup testharness + */ +#define DECLARE_FIXTURE_SETUP_WITH_ARG(func_, array_) \ + const struct fixture_setup_array * \ + fixture_setup_array_get_(void) \ + { \ + static const struct fixture_setup_array arr = { \ + .array = array_, \ + .element_size = sizeof(array_[0]), \ + .n_elements = ARRAY_LENGTH(array_) \ + }; \ + return &arr; \ + } \ + \ + enum test_result_code \ + fixture_setup_run_(struct weston_test_harness *harness, \ + const void *arg_) \ + { \ + typeof(array_[0]) *arg = arg_; \ + return func_(harness, arg); \ + } + +enum test_result_code +weston_test_harness_execute_as_client(struct weston_test_harness *harness, + const struct compositor_setup *setup); + +enum test_result_code +weston_test_harness_execute_as_plugin(struct weston_test_harness *harness, + const struct compositor_setup *setup); + +enum test_result_code +weston_test_harness_execute_standalone(struct weston_test_harness *harness); + #endif diff --git a/tests/weston-test.c b/tests/weston-test.c index c404f2813..f019b0a40 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -32,12 +32,17 @@ #include #include #include +#include +#include #include +#include #include "backend.h" #include "libweston-internal.h" #include "compositor/weston.h" #include "weston-test-server-protocol.h" +#include "weston.h" +#include "weston-testsuite-data.h" #include "shared/helpers.h" #include "shared/timespec-util.h" @@ -46,15 +51,19 @@ struct weston_test { struct weston_compositor *compositor; - /* XXX: missing compositor destroy listener - * https://gitlab.freedesktop.org/wayland/weston/issues/300 - */ + struct wl_listener destroy_listener; + + struct weston_log_scope *log; + struct weston_layer layer; struct weston_process process; struct weston_seat seat; struct weston_touch_device *touch_device[MAX_TOUCH_DEVICES]; int nr_touch_devices; bool is_seat_initialized; + + pthread_t client_thread; + struct wl_event_source *client_source; }; struct weston_test_surface { @@ -667,6 +676,174 @@ idle_launch_client(void *data) weston_watch_process(&test->process); } +static void +client_thread_cleanup(void *data_) +{ + struct wet_testsuite_data *data = data_; + + close(data->thread_event_pipe); + data->thread_event_pipe = -1; +} + +static void * +client_thread_routine(void *data_) +{ + struct wet_testsuite_data *data = data_; + + pthread_setname_np(pthread_self(), "client"); + pthread_cleanup_push(client_thread_cleanup, data); + data->run(data); + pthread_cleanup_pop(true); + + return NULL; +} + +static void +client_thread_join(struct weston_test *test) +{ + assert(test->client_source); + + pthread_join(test->client_thread, NULL); + wl_event_source_remove(test->client_source); + test->client_source = NULL; + + weston_log_scope_printf(test->log, "Test thread reaped.\n"); +} + +static int +handle_client_thread_event(int fd, uint32_t mask, void *data_) +{ + struct weston_test *test = data_; + + weston_log_scope_printf(test->log, + "Received thread event mask 0x%x\n", mask); + + if (mask != WL_EVENT_HANGUP) + weston_log("%s: unexpected event %u\n", __func__, mask); + + client_thread_join(test); + weston_compositor_exit(test->compositor); + + return 0; +} + +static int +create_client_thread(struct weston_test *test, struct wet_testsuite_data *data) +{ + struct wl_event_loop *loop; + int pipefd[2] = { -1, -1 }; + sigset_t saved; + sigset_t blocked; + int ret; + + weston_log_scope_printf(test->log, "Creating a thread for running tests...\n"); + + if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) < 0) { + weston_log("Creating pipe for a client thread failed: %s\n", + strerror(errno)); + return -1; + } + + loop = wl_display_get_event_loop(test->compositor->wl_display); + test->client_source = wl_event_loop_add_fd(loop, pipefd[0], + WL_EVENT_READABLE, + handle_client_thread_event, + test); + close(pipefd[0]); + + if (!test->client_source) { + weston_log("Adding client thread fd to event loop failed.\n"); + goto out_pipe; + } + + data->thread_event_pipe = pipefd[1]; + + /* Ensure we don't accidentally get signals to the thread. */ + sigfillset(&blocked); + sigdelset(&blocked, SIGSEGV); + sigdelset(&blocked, SIGFPE); + sigdelset(&blocked, SIGILL); + sigdelset(&blocked, SIGCONT); + sigdelset(&blocked, SIGSYS); + if (pthread_sigmask(SIG_BLOCK, &blocked, &saved) != 0) + goto out_source; + + ret = pthread_create(&test->client_thread, NULL, + client_thread_routine, data); + + pthread_sigmask(SIG_SETMASK, &saved, NULL); + + if (ret != 0) { + weston_log("Creating client thread failed: %s (%d)\n", + strerror(ret), ret); + goto out_source; + } + + return 0; + +out_source: + data->thread_event_pipe = -1; + wl_event_source_remove(test->client_source); + test->client_source = NULL; + +out_pipe: + close(pipefd[1]); + + return -1; +} + +static void +idle_launch_testsuite(void *test_) +{ + struct weston_test *test = test_; + struct wet_testsuite_data *data = wet_testsuite_data_get(); + + if (!data) + return; + + switch (data->type) { + case TEST_TYPE_CLIENT: + if (create_client_thread(test, data) < 0) { + weston_log("Error: creating client thread for test suite failed.\n"); + weston_compositor_exit_with_code(test->compositor, + RESULT_HARD_ERROR); + } + break; + + case TEST_TYPE_PLUGIN: + data->compositor = test->compositor; + weston_log_scope_printf(test->log, + "Running tests from idle handler...\n"); + data->run(data); + weston_compositor_exit(test->compositor); + break; + + case TEST_TYPE_STANDALONE: + weston_log("Error: unknown test internal type %d.\n", + data->type); + weston_compositor_exit_with_code(test->compositor, + RESULT_HARD_ERROR); + } +} + +static void +handle_compositor_destroy(struct wl_listener *listener, + void *weston_compositor) +{ + struct weston_test *test; + + test = wl_container_of(listener, test, destroy_listener); + + if (test->client_source) { + weston_log_scope_printf(test->log, "Cancelling client thread...\n"); + pthread_cancel(test->client_thread); + client_thread_join(test); + } + + weston_log_scope_destroy(test->log); + test->log = NULL; +} + WL_EXPORT int wet_module_init(struct weston_compositor *ec, int *argc, char *argv[]) @@ -678,19 +855,36 @@ wet_module_init(struct weston_compositor *ec, if (test == NULL) return -1; + if (!weston_compositor_add_destroy_listener_once(ec, + &test->destroy_listener, + handle_compositor_destroy)) { + free(test); + return 0; + } + test->compositor = ec; weston_layer_init(&test->layer, ec); weston_layer_set_position(&test->layer, WESTON_LAYER_POSITION_CURSOR - 1); + test->log = weston_compositor_add_log_scope(ec, "test-harness-plugin", + "weston-test plugin's own actions", + NULL, NULL, NULL); + if (wl_global_create(ec->wl_display, &weston_test_interface, 1, test, bind_test) == NULL) - return -1; + goto out_free; if (test_seat_init(test) == -1) - return -1; + goto out_free; loop = wl_display_get_event_loop(ec->wl_display); wl_event_loop_add_idle(loop, idle_launch_client, test); + wl_event_loop_add_idle(loop, idle_launch_testsuite, test); return 0; + +out_free: + wl_list_remove(&test->destroy_listener.link); + free(test); + return -1; } diff --git a/tests/weston-testsuite-data.h b/tests/weston-testsuite-data.h new file mode 100644 index 000000000..06c35bd7b --- /dev/null +++ b/tests/weston-testsuite-data.h @@ -0,0 +1,88 @@ +/* + * Copyright 2019 Collabora, Ltd. + * + * 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. + */ + +#ifndef WESTON_TESTSUITE_DATA_H +#define WESTON_TESTSUITE_DATA_H + +/** Standard return codes + * + * Both Autotools and Meson use these codes as test program exit codes + * to denote the test result for the whole process. + * + * \ingroup testharness + */ +enum test_result_code { + RESULT_OK = 0, + RESULT_SKIP = 77, + RESULT_FAIL = 1, + RESULT_HARD_ERROR = 99, +}; + +struct weston_test; +struct weston_compositor; + +/** Weston test types + * + * \sa weston_test_harness_execute_standalone + * weston_test_harness_execute_as_plugin + * weston_test_harness_execute_as_client + * + * \ingroup testharness_private + */ +enum test_type { + TEST_TYPE_STANDALONE, + TEST_TYPE_PLUGIN, + TEST_TYPE_CLIENT, +}; + +/** Test harness specific data for running tests + * + * \ingroup testharness_private + */ +struct wet_testsuite_data { + void (*run)(struct wet_testsuite_data *); + + /* test definitions */ + const struct weston_test_entry *tests; + unsigned tests_count; + int case_index; + enum test_type type; + struct weston_compositor *compositor; + + /* client thread control */ + int thread_event_pipe; + + /* informational run state */ + int fixture_iteration; + + /* test counts */ + unsigned counter; + unsigned passed; + unsigned skipped; + unsigned failed; + unsigned total; +}; + +#endif /* WESTON_TESTSUITE_DATA_H */ From 82dd6ce8302787e2f96ec53a408fe04b4523aeb7 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 1 Nov 2019 15:39:30 +0200 Subject: [PATCH 1298/1642] tests: stop relying on environment in the new harness Instead of relying on Meson setting up environment so that Weston and tests find all their files, build those values into the tests. This way one can execute a test program successfully wihtout Meson, simply by running it. The old environment variables are still honoured if set. This might change in the future. Baking the source or build directory paths into the tests should not regress reproducible builds, because the binaries where test-config.h values are used will not be installed. Signed-off-by: Pekka Paalanen --- tests/meson.build | 10 ++++++++-- tests/weston-test-client-helper.c | 3 ++- tests/weston-test-fixture-compositor.c | 7 +++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index 49135b435..a9cde8960 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -64,7 +64,6 @@ exe_plugin_test = shared_library( name_prefix: '', install: false, ) -config_h.set_quoted('TESTSUITE_PLUGIN_PATH', exe_plugin_test.full_path()) deps_zuc = [ dep_libshared ] if get_option('test-junit-xml') @@ -250,6 +249,13 @@ env_test_weston = [ 'WESTON_DATA_DIR=' + join_paths(meson.current_source_dir(), '..', 'data'), ] +test_config_h = configuration_data() +test_config_h.set_quoted('WESTON_TEST_REFERENCE_PATH', meson.current_source_dir() + '/reference') +test_config_h.set_quoted('WESTON_MODULE_MAP', env_modmap) +test_config_h.set_quoted('WESTON_DATA_DIR', join_paths(meson.current_source_dir(), '..', 'data')) +test_config_h.set_quoted('TESTSUITE_PLUGIN_PATH', exe_plugin_test.full_path()) +configure_file(output: 'test-config.h', configuration: test_config_h) + foreach t : tests t_name = 'test-' + t.get('name') t_sources = t.get('sources', [t.get('name') + '-test.c']) @@ -271,7 +277,7 @@ foreach t : tests install: false, ) - test(t.get('name'), t_exe, depends: t.get('test_deps', []), env: env_test_weston) + test(t.get('name'), t_exe, depends: t.get('test_deps', [])) endforeach # FIXME: the multiple loops is lame. rethink this. diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 503ed0983..3fb6b72f8 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -34,6 +34,7 @@ #include #include +#include "test-config.h" #include "shared/os-compatibility.h" #include "shared/xalloc.h" #include @@ -1025,7 +1026,7 @@ reference_path(void) char *path = getenv("WESTON_TEST_REFERENCE_PATH"); if (!path) - return "./tests/reference"; + return WESTON_TEST_REFERENCE_PATH; return path; } diff --git a/tests/weston-test-fixture-compositor.c b/tests/weston-test-fixture-compositor.c index 04c06409b..ea206be01 100644 --- a/tests/weston-test-fixture-compositor.c +++ b/tests/weston-test-fixture-compositor.c @@ -31,6 +31,7 @@ #include "shared/helpers.h" #include "weston-test-fixture-compositor.h" #include "weston.h" +#include "test-config.h" struct prog_args { int argc; @@ -180,6 +181,12 @@ execute_compositor(const struct compositor_setup *setup, const char *ctmp; int ret; + if (setenv("WESTON_MODULE_MAP", WESTON_MODULE_MAP, 0) < 0 || + setenv("WESTON_DATA_DIR", WESTON_DATA_DIR, 0) < 0) { + fprintf(stderr, "Error: environment setup failed.\n"); + return RESULT_HARD_ERROR; + } + #ifndef BUILD_DRM_COMPOSITOR if (setup->backend == WESTON_BACKEND_DRM) { fprintf(stderr, "DRM-backend required but not built, skipping.\n"); From c22f357464ba64c5a50c07cd04194ebb18d28ab7 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 18 Nov 2019 16:13:19 +0200 Subject: [PATCH 1299/1642] doc: overview of the test suite This should lower the barrier to entry for writing more tests. Signed-off-by: Pekka Paalanen --- doc/sphinx/doxygen.ini.in | 6 +- doc/sphinx/index.rst | 1 + doc/sphinx/toc/meson.build | 6 +- doc/sphinx/toc/test-suite-api.rst | 15 ++ doc/sphinx/toc/test-suite.rst | 241 ++++++++++++++++++++++++++++++ 5 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 doc/sphinx/toc/test-suite-api.rst create mode 100644 doc/sphinx/toc/test-suite.rst diff --git a/doc/sphinx/doxygen.ini.in b/doc/sphinx/doxygen.ini.in index 1819cf0d8..cfeba0905 100644 --- a/doc/sphinx/doxygen.ini.in +++ b/doc/sphinx/doxygen.ini.in @@ -793,7 +793,9 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = @SRC_ROOT@/libweston @SRC_ROOT@/include/libweston +INPUT = @SRC_ROOT@/libweston \ + @SRC_ROOT@/include/libweston \ + @SRC_ROOT@/tests # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -2083,7 +2085,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = WL_EXPORT= WL_PRINTF(x,y)= +PREDEFINED = WL_EXPORT= WL_PRINTF(x,y)= __attribute__(x)= # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/doc/sphinx/index.rst b/doc/sphinx/index.rst index cd6ca0189..d29dc0f89 100644 --- a/doc/sphinx/index.rst +++ b/doc/sphinx/index.rst @@ -6,6 +6,7 @@ Welcome to Weston documentation! :caption: Contents: toc/libweston.rst + toc/test-suite.rst Weston ------ diff --git a/doc/sphinx/toc/meson.build b/doc/sphinx/toc/meson.build index 3cc968bd8..6ab3cda50 100644 --- a/doc/sphinx/toc/meson.build +++ b/doc/sphinx/toc/meson.build @@ -1,5 +1,9 @@ # you need to add here any files you add to the toc directory as well -files = [ 'libweston.rst' ] +files = [ + 'libweston.rst', + 'test-suite.rst', + 'test-suite-api.rst', +] foreach file : files configure_file(input: file, output: file, copy: true) diff --git a/doc/sphinx/toc/test-suite-api.rst b/doc/sphinx/toc/test-suite-api.rst new file mode 100644 index 000000000..4f97139ff --- /dev/null +++ b/doc/sphinx/toc/test-suite-api.rst @@ -0,0 +1,15 @@ +Reference manual +================ + + +Test harness API +---------------- + +.. doxygengroup:: testharness + :content-only: + +Test harness Internals +---------------------- + +.. doxygengroup:: testharness_private + :content-only: diff --git a/doc/sphinx/toc/test-suite.rst b/doc/sphinx/toc/test-suite.rst new file mode 100644 index 000000000..7fafe3e9d --- /dev/null +++ b/doc/sphinx/toc/test-suite.rst @@ -0,0 +1,241 @@ +Weston test suite +================= + +Weston test suite aims to test features of the Weston compositor and libweston. +The automatic tests are executed as part of ``meson test`` and in the Gitlab CI. +In addition to automatic tests, there are few manual tests that have not been +automated, but being manual means they are also not routinely (or ever) +executed. + + +Test execution +-------------- + +The test execution hierarchy is: + +* ``meson test`` + + * a test program + + * a fixture setup + + * a test + + * a sub-test from a data array + +When ``meson test`` is executed, it will run all defined *test programs* +potentially in parallel and collect their exit status. Therefore it is +important to design each test program to be executable in parallel with every +other test program. + +A **test program** is essentially one ``.c`` source code file that is built into +one executable file (not a library, module, or plugin). Each test program is +possible to run manually without Meson straight from the build directory +without any environment or command line setup, e.g. with GDB or Valgrind. + +A test program may define one **fixture setup** function. The function may be +defined alone or with a data array of an arbitrary data type. If an array is +defined, the fixture setup will be called and all the tests in the program +executed for each element in the array serially. Fixture setups are used for +setting up the Weston compositor for the tests that need it. The array is +useful for running the compositor with different settings for the same tests, +e.g. with Pixman-renderer and GL-renderer. + +**A test** in a test program is defined with one of the macros :c:func:`TEST`, +:c:func:`TEST_P`, or :c:func:`PLUGIN_TEST`. :c:func:`TEST` defines a single +test with no sub-tests. :c:func:`TEST_P` defines a data-driven array of tests: +a set of sub-tests. :c:func:`PLUGIN_TEST` is used specifically by *plugin +tests* that require access to :type:`weston_compositor`. + +All tests and sub-tests are executed serially in a test program. The test +harness does not ``fork()`` which means that any test that crashes or hits an +assert failure will quit the whole test program on the spot, leaving following +tests in that program not executed. + +The test suite has no tests that are expected to fail in general. All tests +that test for a failure must check the exact error condition expected and +succeed if it is met or fail for any other or no error. + + +Types of tests +-------------- + +Aside from manual vs. automatic, there are three types of tests: + +Standalone tests + Standalone tests do not launch the full compositor. + +Plugin tests + Plugin tests launch the Weston compositor and execute the list of tests + from an idle callback handler in the compositor context, blocking the + compositor while they run. + +Client tests + Client tests launch the Weston compositor and execute the list of tests + in a new thread that is created from an idle callback handler. This means + the compositor runs independently from the tests and one can write a test + like as a normal Wayland client. + +The type of all the tests in a test program is defined by the fixture setup +function. A fixture setup function is any defined function with a specific +signature and registered with either :c:func:`DECLARE_FIXTURE_SETUP` or +:c:func:`DECLARE_FIXTURE_SETUP_WITH_ARG`. + + +.. _test-suite-standalone: + +Standalone tests +^^^^^^^^^^^^^^^^ + +Standalone tests do not have a fixture setup function defined in the test +program or the fixture setup function calls +:func:`weston_test_harness_execute_standalone` explicitly. All test cases must +be defined with :c:func:`TEST` or :c:func:`TEST_P`. + +This is the simplest possible test example: + +.. code-block:: c + + TEST(always_success) + { + /* true */ + } + + +.. _test-suite-plugin: + +Plugin tests +^^^^^^^^^^^^ + +Plugin tests must have a fixture setup function that calls +:func:`weston_test_harness_execute_as_plugin`. All test cases must be defined +with :c:func:`PLUGIN_TEST` which declares an implicit function argument +:type:`weston_compositor` ``*compositor``. + +The compositor fixture manufactures the necessary environment variables and the +command line argument array to launch Weston, and calls :func:`wet_main` +directly. An idle task handler is registered, which gets invoked when +initialization is done. All tests are executed from that idle handler, and then +the compositor exits. + +This is an example of a plugin test that just logs a line: + +.. code-block:: c + + static enum test_result_code + fixture_setup(struct weston_test_harness *harness) + { + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_plugin(harness, &setup); + } + DECLARE_FIXTURE_SETUP(fixture_setup); + + PLUGIN_TEST(plugin_registry_test) + { + /* struct weston_compositor *compositor; */ + testlog("Got compositor %p\n", compositor); + } + + +.. _test-suite-client: + +Client tests +^^^^^^^^^^^^ + +Plugin tests must have a fixture setup function that calls +:func:`weston_test_harness_execute_as_client`. All test cases must be +defined with :c:func:`TEST` or :c:func:`TEST_P`. + +The compositor fixture manufactures the necessary environment variables and the +command line argument array to launch Weston, and calls :func:`wet_main` +directly. An idle task handler is registered, which gets invoked when +initialization is done. The idle handler creates a new thread and returns. The +new thread will execute all tests and then signal the compositor to exit. + +This is an incomplete example of an array of sub-tests and another test as +clients: + +.. code-block:: c + + static enum test_result_code + fixture_setup(struct weston_test_harness *harness) + { + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); + } + DECLARE_FIXTURE_SETUP(fixture_setup); + + struct bad_source_rect_args { + int x, y, w, h; + }; + + static const struct bad_source_rect_args bad_source_rect_args[] = { + { -5, 0, 20, 10 }, + { 0, -5, 20, 10 }, + { 5, 6, 0, 10 }, + { 5, 6, 20, 0 }, + { 5, 6, -20, 10 }, + { 5, 6, 20, -10 }, + { -1, -1, 20, 10 }, + { 5, 6, -1, -1 }, + }; + + TEST_P(test_viewporter_bad_source_rect, bad_source_rect_args) + { + const struct bad_source_rect_args *args = data; + struct client *client; + struct wp_viewport *vp; + + client = create_client_and_test_surface(100, 50, 123, 77); + + vp = create_viewport(client); + + testlog("wp_viewport.set_source x=%d, y=%d, w=%d, h=%d\n", + args->x, args->y, args->w, args->h); + set_source(vp, args->x, args->y, args->w, args->h); + + expect_protocol_error(client, &wp_viewport_interface, + WP_VIEWPORT_ERROR_BAD_VALUE); + } + + TEST(test_roundtrip) + { + struct client *client; + + client = create_client_and_test_surface(100, 50, 123, 77); + client_roundtrip(client); + } + + +Writing tests +------------- + +Test programs do not have a ``main()`` of their own. They all share the +``main()`` from the test harness and only define test cases and a fixture +setup. + +It is recommended to have one test program (one ``.c`` file) contain only one +type of tests to keep the fixture setup simple. See +:ref:`test-suite-standalone`, :ref:`test-suite-plugin` and +:ref:`test-suite-client` how to set up each type in a test program. + +.. note:: + + **TODO:** Currently it is not possible to gracefully skip or fail a test. + You can skip with ``exit(RESULT_SKIP)`` but that will quit the whole test + program and all defined tests that were not ran yet will be counted as + failed. You can fail a test by any means, e.g. ``exit(RESULT_FAIL)``, but + the same caveat applies. Succeeded tests must simply return and not call any + exit function. + + +.. toctree:: + :hidden: + + test-suite-api.rst From 57a4508ee4896251b286d771bfd8322e320d61ed Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 7 Nov 2019 12:59:30 +0200 Subject: [PATCH 1300/1642] tests: migrate string and vertex-clip These are the only remaining standalone non-ZUC tests. They do not need any changes to be built with the new harness - in fact they have already been running through the new harness. Signed-off-by: Pekka Paalanen --- tests/meson.build | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index a9cde8960..fdfe13cb1 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -113,14 +113,17 @@ dep_zucmain = declare_dependency( tests = [ { 'name': 'plugin-registry', }, { 'name': 'roles', }, + { 'name': 'string', }, { 'name': 'subsurface-shot', }, + { + 'name': 'vertex-clip', + 'dep_objs': dep_vertex_clipping, + }, ] tests_standalone = [ ['config-parser', [], [ dep_zucmain ]], ['matrix', [], [ dep_libm, dep_matrix_c ]], - ['string'], - [ 'vertex-clip', [], [ dep_test_client, dep_vertex_clipping ]], ['timespec', [], [ dep_zucmain ]], ['zuc', [ From 7f840b721acfa3b46eefe2ab4f7351262c0a0f03 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 11 Nov 2019 15:30:07 +0200 Subject: [PATCH 1301/1642] tests: migrate ivi-layout-test The ivi-layout-test comprises of two halves: the client and the plugin. This migrates the test to the new test harness. In the old harness, the plugin was built as the test in meson.build and it fork & exec'd the client part. In the new harness client tests start from the client program which sets up the compositor in-process, so now the client is built as the test in meson.build and the plugin is just an additional file. Therefore there is not need for the plugin for fork & exec anything anymore, so all that code is removed. Signed-off-by: Pekka Paalanen --- tests/ivi-layout-test-client.c | 17 ++++++-- tests/ivi-layout-test-plugin.c | 76 ++++------------------------------ tests/meson.build | 41 +++++++++--------- 3 files changed, 43 insertions(+), 91 deletions(-) diff --git a/tests/ivi-layout-test-client.c b/tests/ivi-layout-test-client.c index def9482e4..b7edf8e8a 100644 --- a/tests/ivi-layout-test-client.c +++ b/tests/ivi-layout-test-client.c @@ -34,6 +34,20 @@ #include "weston-test-client-helper.h" #include "ivi-application-client-protocol.h" #include "ivi-test.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.shell = SHELL_IVI; + setup.extra_module = "test-ivi-layout.so"; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); struct runner { struct client *client; @@ -171,9 +185,6 @@ ivi_window_destroy(struct ivi_window *wnd) /******************************** tests ********************************/ /* - * This is a test program, launched by ivi-layout-test-plugin.c. Each TEST() - * is forked and exec'd as usual with the weston-test-runner framework. - * * These tests make use of weston_test_runner global interface exposed by * ivi-layout-test-plugin.c. This allows these tests to trigger compositor-side * checks. diff --git a/tests/ivi-layout-test-plugin.c b/tests/ivi-layout-test-plugin.c index 9e7bb3d98..cad2fa52b 100644 --- a/tests/ivi-layout-test-plugin.c +++ b/tests/ivi-layout-test-plugin.c @@ -91,8 +91,6 @@ struct test_context { struct test_launcher { struct weston_compositor *compositor; struct test_context context; - char exe[2048]; - struct weston_process process; const struct ivi_layout_interface *layout_interface; }; @@ -181,56 +179,10 @@ bind_runner(struct wl_client *client, void *data, } } -static void -test_client_sigchld(struct weston_process *process, int status) -{ - struct test_launcher *launcher = - container_of(process, struct test_launcher, process); - struct weston_compositor *c = launcher->compositor; - - /* Chain up from weston-test-runner's exit code so that ninja - * knows the exit status and can report e.g. skipped tests. */ - if (WIFEXITED(status)) - weston_compositor_exit_with_code(c, WEXITSTATUS(status)); - else - weston_compositor_exit_with_code(c, EXIT_FAILURE); -} - -static void -idle_launch_client(void *data) -{ - struct test_launcher *launcher = data; - pid_t pid; - sigset_t allsigs; - - pid = fork(); - if (pid == -1) { - weston_log("fatal: failed to fork '%s': %s\n", launcher->exe, - strerror(errno)); - weston_compositor_exit_with_code(launcher->compositor, - EXIT_FAILURE); - return; - } - - if (pid == 0) { - sigfillset(&allsigs); - sigprocmask(SIG_UNBLOCK, &allsigs, NULL); - execl(launcher->exe, launcher->exe, NULL); - weston_log("compositor: executing '%s' failed: %s\n", - launcher->exe, strerror(errno)); - _exit(EXIT_FAILURE); - } - - launcher->process.pid = pid; - launcher->process.cleanup = test_client_sigchld; - weston_watch_process(&launcher->process); -} - WL_EXPORT int wet_module_init(struct weston_compositor *compositor, int *argc, char *argv[]) { - struct wl_event_loop *loop; struct test_launcher *launcher; const struct ivi_layout_interface *iface; @@ -245,13 +197,6 @@ wet_module_init(struct weston_compositor *compositor, if (!launcher) return -1; - if (weston_module_path_from_env("ivi-layout-test-client.ivi", - launcher->exe, - sizeof launcher->exe) == 0) { - weston_log("test setup failure: WESTON_MODULE_MAP not set\n"); - return -1; - } - launcher->compositor = compositor; launcher->layout_interface = iface; @@ -260,9 +205,6 @@ wet_module_init(struct weston_compositor *compositor, launcher, bind_runner) == NULL) return -1; - loop = wl_display_get_event_loop(compositor->wl_display); - wl_event_loop_add_idle(loop, idle_launch_client, launcher); - return 0; } @@ -301,18 +243,15 @@ runner_assert_fail(const char *cond, const char *file, int line, /*************************** tests **********************************/ /* - * This is a controller module: a plugin to ivi-shell.so, i.e. a sub-plugin. + * This is a IVI controller module requiring ivi-shell.so. * This module is specially written to execute tests that target the * ivi_layout API. * - * This module is listed in meson.build which handles - * this module specially by loading it in ivi-shell. - * - * Once Weston init completes, this module launches one test program: - * ivi-layout-test-client.ivi (ivi-layout-test-client.c). - * That program uses the weston-test-runner - * framework to fork and exec each TEST() in ivi-layout-test-client.c with a fresh - * connection to the single compositor instance. + * The test program containing the fixture setup and initiating the tests is + * test-ivi-layout-client (ivi-layout-test-client.c). + * That program uses the weston-test-runner framework to execute each TEST() + * in ivi-layout-test-client.c with a fresh connection to the single + * compositor instance. * * Each TEST() in ivi-layout-test-client.c will bind to weston_test_runner global * interface. A TEST() will set up the client state, and issue @@ -324,8 +263,7 @@ runner_assert_fail(const char *cond, const char *file, int line, * * A RUNNER_TEST() function simply returns when it succeeds. If it fails, * a fatal protocol error is sent to the client from runner_assert() or - * runner_assert_or_return(). This module catches the test program exit - * code and passes it out of Weston to the test harness. + * runner_assert_or_return(). * * A single TEST() in ivi-layout-test-client.c may use multiple RUNNER_TEST()s to * achieve multiple test points over a client action sequence. diff --git a/tests/meson.build b/tests/meson.build index fdfe13cb1..b3f8ef262 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -220,30 +220,33 @@ if get_option('shell-ivi') ] tests_weston_plugin += [ ['ivi-layout-internal'], - [ - 'ivi-layout', - [ - 'ivi-layout-test-plugin.c', - weston_test_server_protocol_h, - weston_test_protocol_c, - ], - [ dep_libexec_weston ] - ], ] - exe_ivi_layout_client = executable( - 'ivi-layout-test-client', - 'ivi-layout-test-client.c', - weston_test_client_protocol_h, - weston_test_protocol_c, - ivi_application_client_protocol_h, - ivi_application_protocol_c, + ivi_layout_test_plugin = shared_library( + 'test-ivi-layout', + [ + 'ivi-layout-test-plugin.c', + weston_test_server_protocol_h, + weston_test_protocol_c, + ], include_directories: common_inc, - dependencies: dep_test_client, - install: false + dependencies: [ dep_libweston_private, dep_libexec_weston ], + name_prefix: '', + install: false, ) + env_modmap += 'test-ivi-layout.so=' + ivi_layout_test_plugin.full_path() + ';' - env_modmap += 'ivi-layout-test-client.ivi=@0@;'.format(exe_ivi_layout_client.full_path()) + tests += [ + { + 'name': 'ivi-layout-client', + 'sources': [ + 'ivi-layout-test-client.c', + ivi_application_client_protocol_h, + ivi_application_protocol_c, + ], + 'test_deps': [ ivi_layout_test_plugin ], + }, + ] endif env_test_weston = [ From ad1a4102fec4702be7b5cc0c888ade5b6cbf84ec Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 11 Nov 2019 16:28:28 +0200 Subject: [PATCH 1302/1642] tests: migrate ivi-layout-internal Moving to the new harness. It would be possible to convert every case here into a separate PLUGIN_TEST, but I did not see the value in that at this time. Signed-off-by: Pekka Paalanen --- tests/ivi-layout-internal-test.c | 45 ++++++++++++++++---------------- tests/meson.build | 4 +-- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/tests/ivi-layout-internal-test.c b/tests/ivi-layout-internal-test.c index 9a552681d..8f2d6be87 100644 --- a/tests/ivi-layout-internal-test.c +++ b/tests/ivi-layout-internal-test.c @@ -38,6 +38,20 @@ #include "ivi-shell/ivi-layout-private.h" #include "ivi-test.h" #include "shared/helpers.h" +#include "weston-test-runner.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.shell = SHELL_IVI; + + return weston_test_harness_execute_as_plugin(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); struct test_context { struct weston_compositor *compositor; @@ -942,10 +956,8 @@ test_surface_bad_remove_notification(struct test_context *ctx) /************************ tests end ********************************/ static void -run_internal_tests(void *data) +run_internal_tests(struct test_context *ctx) { - struct test_context *ctx = data; - test_surface_bad_visibility(ctx); test_surface_bad_destination_rectangle(ctx); test_surface_bad_source_rectangle(ctx); @@ -987,35 +999,24 @@ run_internal_tests(void *data) test_surface_bad_create_notification(ctx); test_layer_bad_remove_notification(ctx); test_surface_bad_remove_notification(ctx); - - weston_compositor_exit_with_code(ctx->compositor, EXIT_SUCCESS); - free(ctx); } -WL_EXPORT int -wet_module_init(struct weston_compositor *compositor, - int *argc, char *argv[]) +PLUGIN_TEST(ivi_layout_internal) { - struct wl_event_loop *loop; - struct test_context *ctx; + /* struct weston_compositor *compositor; */ + struct test_context ctx = {}; const struct ivi_layout_interface *iface; iface = ivi_layout_get_api(compositor); if (!iface) { weston_log("fatal: cannot use ivi_layout_interface.\n"); - return -1; + weston_compositor_exit_with_code(compositor, RESULT_HARD_ERROR); + return; } - ctx = zalloc(sizeof(*ctx)); - if (!ctx) - return -1; - - ctx->compositor = compositor; - ctx->layout_interface = iface; - - loop = wl_display_get_event_loop(compositor->wl_display); - wl_event_loop_add_idle(loop, run_internal_tests, ctx); + ctx.compositor = compositor; + ctx.layout_interface = iface; - return 0; + run_internal_tests(&ctx); } diff --git a/tests/meson.build b/tests/meson.build index b3f8ef262..371286fc5 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -218,9 +218,6 @@ if get_option('shell-ivi') ] ] ] - tests_weston_plugin += [ - ['ivi-layout-internal'], - ] ivi_layout_test_plugin = shared_library( 'test-ivi-layout', @@ -246,6 +243,7 @@ if get_option('shell-ivi') ], 'test_deps': [ ivi_layout_test_plugin ], }, + { 'name': 'ivi-layout-internal', }, ] endif From 431ec067cbb68add53fb9fc6498e7f8228a5a3c1 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 11 Nov 2019 17:17:57 +0200 Subject: [PATCH 1303/1642] tests: migrate ivi-shell-app Moving to the new test harness. Carrying the test ini file still just to keep it the same even though I accidentally noticed the test succeeds also with --no-config. Signed-off-by: Pekka Paalanen --- tests/ivi-shell-app-test.c | 15 +++++++++++++++ tests/meson.build | 19 +++++++++---------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/tests/ivi-shell-app-test.c b/tests/ivi-shell-app-test.c index 02398b16b..b2550e945 100644 --- a/tests/ivi-shell-app-test.c +++ b/tests/ivi-shell-app-test.c @@ -30,6 +30,21 @@ #include "weston-test-client-helper.h" #include "ivi-application-client-protocol.h" +#include "weston-test-fixture-compositor.h" +#include "test-config.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.shell = SHELL_IVI; + setup.config_file = TESTSUITE_IVI_CONFIG_PATH; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); static struct ivi_application * get_ivi_application(struct client *client) diff --git a/tests/meson.build b/tests/meson.build index 371286fc5..145824784 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -209,16 +209,6 @@ tests_weston_plugin = [ ] if get_option('shell-ivi') - tests_weston += [ - [ - 'ivi-shell-app', - [ - ivi_application_client_protocol_h, - ivi_application_protocol_c, - ] - ] - ] - ivi_layout_test_plugin = shared_library( 'test-ivi-layout', [ @@ -244,6 +234,14 @@ if get_option('shell-ivi') 'test_deps': [ ivi_layout_test_plugin ], }, { 'name': 'ivi-layout-internal', }, + { + 'name': 'ivi-shell-app', + 'sources': [ + 'ivi-shell-app-test.c', + ivi_application_client_protocol_h, + ivi_application_protocol_c, + ], + }, ] endif @@ -258,6 +256,7 @@ test_config_h.set_quoted('WESTON_TEST_REFERENCE_PATH', meson.current_source_dir( test_config_h.set_quoted('WESTON_MODULE_MAP', env_modmap) test_config_h.set_quoted('WESTON_DATA_DIR', join_paths(meson.current_source_dir(), '..', 'data')) test_config_h.set_quoted('TESTSUITE_PLUGIN_PATH', exe_plugin_test.full_path()) +test_config_h.set_quoted('TESTSUITE_IVI_CONFIG_PATH', join_paths(meson.current_build_dir(), '../ivi-shell/weston-ivi-test.ini')) configure_file(output: 'test-config.h', configuration: test_config_h) foreach t : tests From 9615ad8b91b26bcf2b31233059c55cb82d5d1757 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 11 Nov 2019 17:29:41 +0200 Subject: [PATCH 1304/1642] tests: migrate internal-screenshot Signed-off-by: Pekka Paalanen --- tests/internal-screenshot-test.c | 18 +++++++++++++++++- tests/meson.build | 3 ++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/internal-screenshot-test.c b/tests/internal-screenshot-test.c index fb5ed5f8d..e1a7c43be 100644 --- a/tests/internal-screenshot-test.c +++ b/tests/internal-screenshot-test.c @@ -29,8 +29,24 @@ #include #include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" +#include "test-config.h" -char *server_parameters="--use-pixman --width=320 --height=240"; +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.renderer = RENDERER_PIXMAN; + setup.width = 320; + setup.height = 240; + setup.shell = SHELL_DESKTOP; + setup.config_file = TESTSUITE_INTERNAL_SCREENSHOT_CONFIG_PATH; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); static void draw_stuff(pixman_image_t *image) diff --git a/tests/meson.build b/tests/meson.build index 145824784..2daf9cffd 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -111,6 +111,7 @@ dep_zucmain = declare_dependency( ) tests = [ + { 'name': 'internal-screenshot', }, { 'name': 'plugin-registry', }, { 'name': 'roles', }, { 'name': 'string', }, @@ -153,7 +154,6 @@ tests_weston = [ linux_explicit_synchronization_unstable_v1_protocol_c, ] ], - ['internal-screenshot'], [ 'presentation', [ @@ -257,6 +257,7 @@ test_config_h.set_quoted('WESTON_MODULE_MAP', env_modmap) test_config_h.set_quoted('WESTON_DATA_DIR', join_paths(meson.current_source_dir(), '..', 'data')) test_config_h.set_quoted('TESTSUITE_PLUGIN_PATH', exe_plugin_test.full_path()) test_config_h.set_quoted('TESTSUITE_IVI_CONFIG_PATH', join_paths(meson.current_build_dir(), '../ivi-shell/weston-ivi-test.ini')) +test_config_h.set_quoted('TESTSUITE_INTERNAL_SCREENSHOT_CONFIG_PATH', join_paths(meson.current_source_dir(), 'internal-screenshot.ini')) configure_file(output: 'test-config.h', configuration: test_config_h) foreach t : tests From 99c536db91ed814e8e09a3b0c4624788f65033c5 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Nov 2019 14:20:45 +0200 Subject: [PATCH 1305/1642] tests: migrate surface, surface-global These are normal plugin tests, moved to the new harness. Signed-off-by: Pekka Paalanen --- tests/meson.build | 4 ++-- tests/surface-global-test.c | 33 +++++++++++++++------------------ tests/surface-test.c | 33 +++++++++++++++------------------ 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index 2daf9cffd..cb1a16b2e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -116,6 +116,8 @@ tests = [ { 'name': 'roles', }, { 'name': 'string', }, { 'name': 'subsurface-shot', }, + { 'name': 'surface', }, + { 'name': 'surface-global', }, { 'name': 'vertex-clip', 'dep_objs': dep_vertex_clipping, @@ -203,8 +205,6 @@ if get_option('xwayland') endif tests_weston_plugin = [ - ['surface'], - ['surface-global'], ['surface-screenshot', 'surface-screenshot-test.c', dep_libshared], ] diff --git a/tests/surface-global-test.c b/tests/surface-global-test.c index 2c9c80790..cd9b78481 100644 --- a/tests/surface-global-test.c +++ b/tests/surface-global-test.c @@ -31,11 +31,23 @@ #include #include "libweston-internal.h" #include "compositor/weston.h" +#include "weston-test-runner.h" +#include "weston-test-fixture-compositor.h" -static void -surface_to_from_global(void *data) +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) { - struct weston_compositor *compositor = data; + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_plugin(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +PLUGIN_TEST(surface_to_from_global) +{ + /* struct weston_compositor *compositor; */ struct weston_surface *surface; struct weston_view *view; float x, y; @@ -76,19 +88,4 @@ surface_to_from_global(void *data) weston_view_from_global(view, 5, 10, &ix, &iy); assert(ix == 0 && iy == 0); - - weston_compositor_exit(compositor); -} - -WL_EXPORT int -wet_module_init(struct weston_compositor *compositor, - int *argc, char *argv[]) -{ - struct wl_event_loop *loop; - - loop = wl_display_get_event_loop(compositor->wl_display); - - wl_event_loop_add_idle(loop, surface_to_from_global, compositor); - - return 0; } diff --git a/tests/surface-test.c b/tests/surface-test.c index 1dce42b87..fca74c9bb 100644 --- a/tests/surface-test.c +++ b/tests/surface-test.c @@ -30,11 +30,23 @@ #include #include "compositor/weston.h" +#include "weston-test-runner.h" +#include "weston-test-fixture-compositor.h" -static void -surface_transform(void *data) +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) { - struct weston_compositor *compositor = data; + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_plugin(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +PLUGIN_TEST(surface_transform) +{ + /* struct weston_compositor *compositor; */ struct weston_surface *surface; struct weston_view *view; float x, y; @@ -56,19 +68,4 @@ surface_transform(void *data) weston_view_update_transform(view); weston_view_to_global_float(view, 50, 40, &x, &y); assert(x == 200 && y == 340); - - weston_compositor_exit(compositor); -} - -WL_EXPORT int -wet_module_init(struct weston_compositor *compositor, - int *argc, char *argv[]) -{ - struct wl_event_loop *loop; - - loop = wl_display_get_event_loop(compositor->wl_display); - - wl_event_loop_add_idle(loop, surface_transform, compositor); - - return 0; } From af18eb0b5cc392b06c2728758bb69436bbb5593a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Nov 2019 14:39:22 +0200 Subject: [PATCH 1306/1642] meson: remove tests_weston_plugin All plugin tests have been converted to the new harness, so the old definition can be removed. The one remaining test surface-screenshot is a manual test, the plugin only installs a debug key binding. Hence it is open-coded as a normal plugin, not as a test. Signed-off-by: Pekka Paalanen --- tests/meson.build | 59 ++++++++--------------------------------------- 1 file changed, 9 insertions(+), 50 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index cb1a16b2e..985a4b2b5 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -204,9 +204,15 @@ if get_option('xwayland') tests_weston += [ [ 'xwayland', [], d ] ] endif -tests_weston_plugin = [ - ['surface-screenshot', 'surface-screenshot-test.c', dep_libshared], -] +# Manual test plugin, not used in the automatic suite +surface_screenshot_test = shared_library( + 'test-surface-screenshot', + 'surface-screenshot-test.c', + include_directories: common_inc, + dependencies: [ dep_libweston_private, dep_libshared ], + name_prefix: '', + install: false, +) if get_option('shell-ivi') ivi_layout_test_plugin = shared_library( @@ -387,53 +393,6 @@ foreach t : tests_weston test(t.get(0), exe_weston, env: env_t, args: args_t) endforeach -foreach t : tests_weston_plugin - srcs_t = [] - - if t.length() > 1 - srcs_t += t.get(1) - else - srcs_t += '@0@-test.c'.format(t.get(0)) - endif - - deps_t = [ - dep_libweston_private, - ] - if t.length() > 2 - deps_t += t.get(2) - endif - - exe_t = shared_library( - 'test-@0@'.format(t.get(0)), - srcs_t, - include_directories: common_inc, - dependencies: deps_t, - name_prefix: '', - install: false, - ) - - args_t = [ - '--backend=headless-backend.so', - '--socket=test-@0@'.format(t.get(0)), - ] - - # FIXME: Get this from the array ... ? - if t.get(0).startswith('ivi-') - args_t += [ '--no-config' ] - args_t += [ '--modules=@1@,@0@'.format(exe_plugin_test.full_path(),exe_t.full_path()) ] - args_t += [ '--shell=ivi-shell.so' ] - else - args_t += [ '--no-config' ] - args_t += [ '--shell=desktop-shell.so' ] - args_t += [ '--modules=@0@'.format(exe_t.full_path()) ] - endif - - # surface-screenshot is a manual test - if t[0] != 'surface-screenshot' - test(t.get(0), exe_weston, env: env_test_weston, args: args_t) - endif -endforeach - if get_option('backend-drm') executable( 'setbacklight', From 3fb67936a93a507e948c2ae467da60f070c7c625 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Nov 2019 15:35:22 +0200 Subject: [PATCH 1307/1642] tests: migrate devices The devices test was actually using the defaults instead of weston-test-desktop-shell in meson.build, so this patch keeps it that way. Signed-off-by: Pekka Paalanen --- tests/devices-test.c | 14 ++++++++++++-- tests/meson.build | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/devices-test.c b/tests/devices-test.c index 147a27913..6b154194b 100644 --- a/tests/devices-test.c +++ b/tests/devices-test.c @@ -27,6 +27,18 @@ #include #include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); /** * Test (un)plugging devices @@ -40,8 +52,6 @@ WL_SEAT_CAPABILITY_POINTER |\ WL_SEAT_CAPABILITY_TOUCH) -char *server_parameters = "--shell=weston-test-desktop-shell.so"; - /* simply test if weston sends the right capabilities when * some devices are removed */ TEST(seat_capabilities_test) diff --git a/tests/meson.build b/tests/meson.build index 985a4b2b5..54bc43947 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -111,6 +111,7 @@ dep_zucmain = declare_dependency( ) tests = [ + { 'name': 'devices', }, { 'name': 'internal-screenshot', }, { 'name': 'plugin-registry', }, { 'name': 'roles', }, @@ -139,7 +140,6 @@ tests_standalone = [ tests_weston = [ ['bad-buffer'], - ['devices'], ['event'], [ 'keyboard', From 701676d8c6e86c61681df0d4e2b0e92645d1f778 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Nov 2019 15:45:10 +0200 Subject: [PATCH 1308/1642] tests: migrate client tests This migrates all the client tests that have nothing special in them to the new test harness. Signed-off-by: Pekka Paalanen --- tests/bad-buffer-test.c | 12 +++++ tests/event-test.c | 12 +++++ tests/keyboard-test.c | 12 +++++ tests/meson.build | 102 ++++++++++++++++++++------------------ tests/pointer-test.c | 12 +++++ tests/presentation-test.c | 12 +++++ tests/subsurface-test.c | 12 +++++ tests/text-test.c | 12 +++++ tests/touch-test.c | 12 +++++ tests/viewporter-test.c | 12 +++++ 10 files changed, 162 insertions(+), 48 deletions(-) diff --git a/tests/bad-buffer-test.c b/tests/bad-buffer-test.c index b7abda239..360a81ce3 100644 --- a/tests/bad-buffer-test.c +++ b/tests/bad-buffer-test.c @@ -34,6 +34,18 @@ #include "shared/os-compatibility.h" #include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); /* These three functions are copied from shared/os-compatibility.c in order to * behave like older clients, and allow ftruncate() to shrink the file’s size, diff --git a/tests/event-test.c b/tests/event-test.c index c1ba3ac1e..75419a6ce 100644 --- a/tests/event-test.c +++ b/tests/event-test.c @@ -27,6 +27,18 @@ #include "config.h" #include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); static int output_contains_client(struct client *client) diff --git a/tests/keyboard-test.c b/tests/keyboard-test.c index 37ef83d07..f2a769e3a 100644 --- a/tests/keyboard-test.c +++ b/tests/keyboard-test.c @@ -30,6 +30,18 @@ #include "input-timestamps-helper.h" #include "shared/timespec-util.h" #include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; diff --git a/tests/meson.build b/tests/meson.build index 54bc43947..fc793ede2 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -111,18 +111,72 @@ dep_zucmain = declare_dependency( ) tests = [ + { 'name': 'bad-buffer', }, { 'name': 'devices', }, + { 'name': 'event', }, { 'name': 'internal-screenshot', }, + { + 'name': 'keyboard', + 'sources': [ + 'keyboard-test.c', + 'input-timestamps-helper.c', + input_timestamps_unstable_v1_client_protocol_h, + input_timestamps_unstable_v1_protocol_c, + ], + }, { 'name': 'plugin-registry', }, + { + 'name': 'pointer', + 'sources': [ + 'pointer-test.c', + 'input-timestamps-helper.c', + input_timestamps_unstable_v1_client_protocol_h, + input_timestamps_unstable_v1_protocol_c, + ], + }, + { + 'name': 'presentation', + 'sources': [ + 'presentation-test.c', + presentation_time_client_protocol_h, + presentation_time_protocol_c, + ], + }, { 'name': 'roles', }, { 'name': 'string', }, + { 'name': 'subsurface', }, { 'name': 'subsurface-shot', }, { 'name': 'surface', }, { 'name': 'surface-global', }, + { + 'name': 'text', + 'sources': [ + 'text-test.c', + text_input_unstable_v1_client_protocol_h, + text_input_unstable_v1_protocol_c, + ], + }, + { + 'name': 'touch', + 'sources': [ + 'touch-test.c', + 'input-timestamps-helper.c', + input_timestamps_unstable_v1_client_protocol_h, + input_timestamps_unstable_v1_protocol_c, + ], + }, { 'name': 'vertex-clip', 'dep_objs': dep_vertex_clipping, }, + { + 'name': 'viewporter', + 'sources': [ + 'viewporter-test.c', + viewporter_client_protocol_h, + viewporter_protocol_c, + ], + }, ] tests_standalone = [ @@ -139,16 +193,6 @@ tests_standalone = [ ] tests_weston = [ - ['bad-buffer'], - ['event'], - [ - 'keyboard', - [ - 'input-timestamps-helper.c', - input_timestamps_unstable_v1_client_protocol_h, - input_timestamps_unstable_v1_protocol_c, - ] - ], [ 'linux-explicit-synchronization', [ @@ -156,44 +200,6 @@ tests_weston = [ linux_explicit_synchronization_unstable_v1_protocol_c, ] ], - [ - 'presentation', - [ - presentation_time_client_protocol_h, - presentation_time_protocol_c, - ] - ], - [ - 'pointer', - [ - 'input-timestamps-helper.c', - input_timestamps_unstable_v1_client_protocol_h, - input_timestamps_unstable_v1_protocol_c, - ] - ], - ['subsurface'], - [ - 'text', - [ - text_input_unstable_v1_client_protocol_h, - text_input_unstable_v1_protocol_c, - ] - ], - [ - 'touch', - [ - 'input-timestamps-helper.c', - input_timestamps_unstable_v1_client_protocol_h, - input_timestamps_unstable_v1_protocol_c, - ] - ], - [ - 'viewporter', - [ - viewporter_client_protocol_h, - viewporter_protocol_c, - ] - ], ] if get_option('xwayland') diff --git a/tests/pointer-test.c b/tests/pointer-test.c index eef522288..5d618c2e4 100644 --- a/tests/pointer-test.c +++ b/tests/pointer-test.c @@ -31,6 +31,18 @@ #include "input-timestamps-helper.h" #include "shared/timespec-util.h" #include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); static const struct timespec t0 = { .tv_sec = 0, .tv_nsec = 100000000 }; static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; diff --git a/tests/presentation-test.c b/tests/presentation-test.c index 6790ab663..5e9d991f2 100644 --- a/tests/presentation-test.c +++ b/tests/presentation-test.c @@ -37,6 +37,18 @@ #include "shared/timespec-util.h" #include "weston-test-client-helper.h" #include "presentation-time-client-protocol.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); static struct wp_presentation * get_presentation(struct client *client) diff --git a/tests/subsurface-test.c b/tests/subsurface-test.c index cdf71a194..1f9c90c0c 100644 --- a/tests/subsurface-test.c +++ b/tests/subsurface-test.c @@ -29,6 +29,18 @@ #include #include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); #define NUM_SUBSURFACES 3 diff --git a/tests/text-test.c b/tests/text-test.c index 0436f9b64..84988f3d9 100644 --- a/tests/text-test.c +++ b/tests/text-test.c @@ -31,6 +31,18 @@ #include "weston-test-client-helper.h" #include "text-input-unstable-v1-client-protocol.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); struct text_input_state { int activated; diff --git a/tests/touch-test.c b/tests/touch-test.c index baf5bc58e..b2133bde8 100644 --- a/tests/touch-test.c +++ b/tests/touch-test.c @@ -31,6 +31,18 @@ #include "shared/timespec-util.h" #include "weston-test-client-helper.h" #include "wayland-server-protocol.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); static const struct timespec t1 = { .tv_sec = 1, .tv_nsec = 1000001 }; static const struct timespec t2 = { .tv_sec = 2, .tv_nsec = 2000001 }; diff --git a/tests/viewporter-test.c b/tests/viewporter-test.c index 2dd6be4c8..ad4f37790 100644 --- a/tests/viewporter-test.c +++ b/tests/viewporter-test.c @@ -35,6 +35,18 @@ #include "shared/xalloc.h" #include "weston-test-client-helper.h" #include "viewporter-client-protocol.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); static struct wp_viewporter * get_viewporter(struct client *client) From dd13498862470a63199867bc69a8abc55b72ba56 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Nov 2019 16:28:20 +0200 Subject: [PATCH 1309/1642] tests: migrate linux-explicit-synchronization Moved to the new test harness. Signed-off-by: Pekka Paalanen --- tests/linux-explicit-synchronization-test.c | 20 ++++++++++++++++---- tests/meson.build | 15 ++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/tests/linux-explicit-synchronization-test.c b/tests/linux-explicit-synchronization-test.c index 7ef122c6a..4d4c24bea 100644 --- a/tests/linux-explicit-synchronization-test.c +++ b/tests/linux-explicit-synchronization-test.c @@ -31,11 +31,23 @@ #include "linux-explicit-synchronization-unstable-v1-client-protocol.h" #include "weston-test-client-helper.h" #include "wayland-server-protocol.h" +#include "weston-test-fixture-compositor.h" -/* We need to use the pixman renderer, since a few of the tests depend - * on the renderer holding onto a surface buffer until the next one - * is committed, which the noop renderer doesn't do. */ -char *server_parameters = "--use-pixman"; +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + + /* We need to use the pixman renderer, since a few of the tests depend + * on the renderer holding onto a surface buffer until the next one + * is committed, which the noop renderer doesn't do. */ + setup.renderer = RENDERER_PIXMAN; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); static struct zwp_linux_explicit_synchronization_v1 * get_linux_explicit_synchronization(struct client *client) diff --git a/tests/meson.build b/tests/meson.build index fc793ede2..c84b885d4 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -124,6 +124,14 @@ tests = [ input_timestamps_unstable_v1_protocol_c, ], }, + { + 'name': 'linux-explicit-synchronization', + 'sources': [ + 'linux-explicit-synchronization-test.c', + linux_explicit_synchronization_unstable_v1_client_protocol_h, + linux_explicit_synchronization_unstable_v1_protocol_c, + ], + }, { 'name': 'plugin-registry', }, { 'name': 'pointer', @@ -193,13 +201,6 @@ tests_standalone = [ ] tests_weston = [ - [ - 'linux-explicit-synchronization', - [ - linux_explicit_synchronization_unstable_v1_client_protocol_h, - linux_explicit_synchronization_unstable_v1_protocol_c, - ] - ], ] if get_option('xwayland') From 56b94b58947fab405952bbe03a881e1439dbcc26 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Nov 2019 16:41:46 +0200 Subject: [PATCH 1310/1642] tests: migrate xwayland Move xwayland test to the new harness. This is the only test that can actually skip. It does it by exit(77) and that is fine, because there is only one test case in the file so far. To get rid of the exit() calls we need to return a value from the TEST() function but that is a big surgery for another time. Signed-off-by: Pekka Paalanen --- tests/meson.build | 5 ++++- tests/xwayland-test.c | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/meson.build b/tests/meson.build index c84b885d4..5ae110dee 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -208,7 +208,10 @@ if get_option('xwayland') if not d.found() error('Xwayland tests require libX11 which was not found. Or, you can use \'-Dxwayland=false\'.') endif - tests_weston += [ [ 'xwayland', [], d ] ] + tests += { + 'name': 'xwayland', + 'dep_objs': d, + } endif # Manual test plugin, not used in the automatic suite diff --git a/tests/xwayland-test.c b/tests/xwayland-test.c index af0b5bae7..665f6ea7b 100644 --- a/tests/xwayland-test.c +++ b/tests/xwayland-test.c @@ -42,6 +42,19 @@ #include #include "weston-test-runner.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.xwayland = true; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); TEST(xwayland_client_test) { From 7df5349763d060efa2fa1da833c544acc0b92c87 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 13 Nov 2019 16:47:40 +0200 Subject: [PATCH 1311/1642] tests: remove tests_weston and WESTON_TEST_CLIENT_PATH This test category is empty, so it and all the supporting code can go. Signed-off-by: Pekka Paalanen --- tests/meson.build | 75 --------------------------------------------- tests/weston-test.c | 47 ---------------------------- 2 files changed, 122 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index 5ae110dee..87dad82ef 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -200,9 +200,6 @@ tests_standalone = [ ], ] -tests_weston = [ -] - if get_option('xwayland') d = dependency('x11', required: false) if not d.found() @@ -261,12 +258,6 @@ if get_option('shell-ivi') ] endif -env_test_weston = [ - 'WESTON_TEST_REFERENCE_PATH=@0@/reference'.format(meson.current_source_dir()), - 'WESTON_MODULE_MAP=@0@'.format(env_modmap), - 'WESTON_DATA_DIR=' + join_paths(meson.current_source_dir(), '..', 'data'), -] - test_config_h = configuration_data() test_config_h.set_quoted('WESTON_TEST_REFERENCE_PATH', meson.current_source_dir() + '/reference') test_config_h.set_quoted('WESTON_MODULE_MAP', env_modmap) @@ -337,72 +328,6 @@ foreach t : tests_standalone endif endforeach -foreach t : tests_weston - srcs_t = [ - '@0@-test.c'.format(t.get(0)), - weston_test_client_protocol_h, - ] - if t.length() > 1 - srcs_t += t.get(1) - endif - - deps_t = [ - dep_test_client - ] - if t.length() > 2 - deps_t += t.get(2) - endif - - args_t = [ - '--backend=headless-backend.so', - '--socket=test-@0@'.format(t.get(0)), - '--modules=@0@'.format(exe_plugin_test.full_path()), - '--width=320', - '--height=240', - ] - - if t.get(0) == 'xwayland' - args_t += '--xwayland' - endif - - # FIXME: Get this from the array ... ? - if t.get(0) == 'internal-screenshot' - args_t += [ '--config=@0@/internal-screenshot.ini'.format(meson.current_source_dir()) ] - args_t += [ '--use-pixman' ] - args_t += [ '--shell=desktop-shell.so' ] - elif t[0] == 'subsurface-shot' - args_t += [ '--no-config' ] - args_t += [ '--use-pixman' ] - args_t += [ '--width=320' ] - args_t += [ '--height=240' ] - args_t += [ '--shell=weston-test-desktop-shell.so' ] - elif t.get(0) == 'linux-explicit-synchronization' - args_t += [ '--use-pixman' ] - elif t.get(0).startswith('ivi-') - args_t += [ '--config=@0@/../ivi-shell/weston-ivi-test.ini'.format(meson.current_build_dir()) ] - args_t += [ '--shell=ivi-shell.so' ] - else - args_t += [ '--no-config' ] - args_t += [ '--shell=desktop-shell.so' ] - endif - - exe_t = executable( - 'test-@0@'.format(t.get(0)), - srcs_t, - c_args: [ '-DUNIT_TEST' ], - include_directories: common_inc, - dependencies: deps_t, - install: false, - ) - - env_t = [ - 'WESTON_TEST_CLIENT_PATH=@0@'.format(exe_t.full_path()) - ] - env_t += env_test_weston - - test(t.get(0), exe_weston, env: env_t, args: args_t) -endforeach - if get_option('backend-drm') executable( 'setbacklight', diff --git a/tests/weston-test.c b/tests/weston-test.c index f019b0a40..e438e823d 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -56,7 +56,6 @@ struct weston_test { struct weston_log_scope *log; struct weston_layer layer; - struct weston_process process; struct weston_seat seat; struct weston_touch_device *touch_device[MAX_TOUCH_DEVICES]; int nr_touch_devices; @@ -73,23 +72,6 @@ struct weston_test_surface { struct weston_test *test; }; -static void -test_client_sigchld(struct weston_process *process, int status) -{ - struct weston_test *test = - container_of(process, struct weston_test, process); - - /* Chain up from weston-test-runner's exit code so that ninja - * knows the exit status and can report e.g. skipped tests. */ - if (WIFEXITED(status) && WEXITSTATUS(status) != 0) - exit(WEXITSTATUS(status)); - - /* In case the child aborted or segfaulted... */ - assert(status == 0); - - weston_compositor_exit(test->compositor); -} - static void touch_device_add(struct weston_test *test) { @@ -648,34 +630,6 @@ bind_test(struct wl_client *client, void *data, uint32_t version, uint32_t id) notify_pointer_position(test, resource); } -static void -idle_launch_client(void *data) -{ - struct weston_test *test = data; - pid_t pid; - sigset_t allsigs; - char *path; - - path = getenv("WESTON_TEST_CLIENT_PATH"); - if (path == NULL) - return; - pid = fork(); - if (pid == -1) - exit(EXIT_FAILURE); - if (pid == 0) { - sigfillset(&allsigs); - sigprocmask(SIG_UNBLOCK, &allsigs, NULL); - execl(path, path, NULL); - weston_log("compositor: executing '%s' failed: %s\n", path, - strerror(errno)); - exit(EXIT_FAILURE); - } - - test->process.pid = pid; - test->process.cleanup = test_client_sigchld; - weston_watch_process(&test->process); -} - static void client_thread_cleanup(void *data_) { @@ -878,7 +832,6 @@ wet_module_init(struct weston_compositor *ec, goto out_free; loop = wl_display_get_event_loop(ec->wl_display); - wl_event_loop_add_idle(loop, idle_launch_client, test); wl_event_loop_add_idle(loop, idle_launch_testsuite, test); return 0; From aaa5b82e613739723714e859930aeec02b50af6a Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 14 Nov 2019 22:07:27 +0100 Subject: [PATCH 1312/1642] weston-launch: do not close tty prematurely The tty file descriptor is used in signal handling (when switching VT via SIGUSR1/SIGUSR2 for the VT_RELDISP ioctrl) and in quit() when weston-launch exits for the KDSKBMUTE/KDSKBMODE/KDSETMODE/VT_SETMODE ioctrls. This fixes VT switching when using weston-launch from a non-VT shell (e.g. ssh or from within a container). Signed-off-by: Stefan Agner --- libweston/weston-launch.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index 8a711b4a9..8d1aa39ef 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -457,6 +457,9 @@ quit(struct weston_launch *wl, int status) if (ioctl(wl->tty, VT_SETMODE, &mode) < 0) fprintf(stderr, "could not reset vt handling\n"); + if (wl->tty != STDIN_FILENO) + close(wl->tty); + exit(status); } @@ -847,8 +850,6 @@ main(int argc, char *argv[]) launch_compositor(&wl, argc - optind, argv + optind); close(wl.sock[1]); - if (wl.tty != STDIN_FILENO) - close(wl.tty); while (1) { struct pollfd fds[2]; From 77e3b056d76bc386c1d2375198a8c6b67b682600 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 23 Jan 2020 00:37:16 +0100 Subject: [PATCH 1313/1642] weston-launch: get ttynr also when no user is given In case an user is given but no tty, the code opens tty0 to allocate a new tty. With that ttynr is known. In case a tty name is given the user must be given too. In this case we later recover the ttynr by using stat on the file tty file descriptor which allows as to find the ttynr by looking at the devices minor number. However, the third case, when no user and no tty name is given, we do not get the ttynr. This hasn't been a problem in practise since ttynr has not been used. However, it makes sense to get the ttynr always for consistency. Also upcomming fixes will start to make use of ttynr. Fixes: 636156d5f693 ("weston-launch: Don't start new session unless -u is given") Signed-off-by: Stefan Agner --- libweston/weston-launch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index 8d1aa39ef..d014fc11b 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -589,7 +589,7 @@ setup_tty(struct weston_launch *wl, const char *tty) return -1; } - if (tty) { + if (!wl->new_user || tty) { if (fstat(wl->tty, &buf) < 0) { fprintf(stderr, "weston: stat %s failed: %s\n", tty, strerror(errno)); From cb24a7d1ebe5cbfdb6ec209ae2d23e376a66d1ef Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 23 Jan 2020 00:53:31 +0100 Subject: [PATCH 1314/1642] weston-launch: fix newline in error message Add newline character at the end of the error message to make sure we get a new line after this error has been printed. Fixes: a1450a8a71ef ("make error() portable") Signed-off-by: Stefan Agner --- libweston/weston-launch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index d014fc11b..d5eae960e 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -607,7 +607,7 @@ setup_tty(struct weston_launch *wl, const char *tty) if (ioctl(wl->tty, KDGKBMODE, &wl->kb_mode)) { fprintf(stderr, - "weston: failed to get current keyboard mode: %s", + "weston: failed to get current keyboard mode: %s\n", strerror(errno)); return -1; } From c6f818a01611d65fd5d383bf2a6b1ee3f7389567 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 14 Nov 2019 23:29:09 +0100 Subject: [PATCH 1315/1642] weston-launch: make sure weston-launch activates the VT Currently weston-launch does not activate the VT when opening the terminal directly (e.g. using --tty=/dev/tty7). Weston takes full control over the terminal by switching it to graphical mode etc. However, the old VT stays active as can be seen when looking at sysfs: # cat /sys/class/tty/tty0/active tty1 Always switch to the new VT to make sure the correct VT is active. This aligns with how TTY setup is implemented in launcher-direct.c. Signed-off-by: Stefan Agner Reviewed-by: Emil Velikov --- libweston/weston-launch.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index d5eae960e..bfe70a7b0 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -605,6 +605,20 @@ setup_tty(struct weston_launch *wl, const char *tty) wl->ttynr = minor(buf.st_rdev); } + if (ioctl(wl->tty, VT_ACTIVATE, wl->ttynr) < 0) { + fprintf(stderr, + "weston: failed to activate VT: %s\n", + strerror(errno)); + return -1; + } + + if (ioctl(wl->tty, VT_WAITACTIVE, wl->ttynr) < 0) { + fprintf(stderr, + "weston: failed to wait for VT to be active: %s\n", + strerror(errno)); + return -1; + } + if (ioctl(wl->tty, KDGKBMODE, &wl->kb_mode)) { fprintf(stderr, "weston: failed to get current keyboard mode: %s\n", From 247392a322ebabea3a20f3fe4d0237e703137153 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 22 Jan 2020 22:23:07 +0100 Subject: [PATCH 1316/1642] weston-launch: check string truncation Since weston-launch is a setuid-root program we should be extra careful: Check for a potential string trunction. Move the check in a separate function and return with error in case trunction has happened. Signed-off-by: Stefan Agner --- libweston/weston-launch.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index bfe70a7b0..911748a55 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -294,6 +294,19 @@ setenv_fd(const char *env, int fd) setenv(env, buf, 1); } +static int +open_tty_by_number(int ttynr) +{ + int ret; + char filename[16]; + + ret = snprintf(filename, sizeof filename, "/dev/tty%d", ttynr); + if (ret < 0) + return -1; + + return open(filename, O_RDWR | O_NOCTTY); +} + static int send_reply(struct weston_launch *wl, int reply) { @@ -557,7 +570,6 @@ setup_tty(struct weston_launch *wl, const char *tty) wl->tty = open(tty, O_RDWR | O_NOCTTY); } else { int tty0 = open("/dev/tty0", O_WRONLY | O_CLOEXEC); - char filename[16]; if (tty0 < 0) { fprintf(stderr, "weston: could not open tty0: %s\n", @@ -572,8 +584,7 @@ setup_tty(struct weston_launch *wl, const char *tty) return -1; } - snprintf(filename, sizeof filename, "/dev/tty%d", wl->ttynr); - wl->tty = open(filename, O_RDWR | O_NOCTTY); + wl->tty = open_tty_by_number(wl->ttynr); close(tty0); } From bd1e39a78776f0fbd4270b7fcf9d19835fc06c70 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 14 Nov 2019 23:39:40 +0100 Subject: [PATCH 1317/1642] weston-launch: reset tty properly On weston-launch exit we see errors such as: failed to restore keyboard mode: Invalid argument failed to set KD_TEXT mode on tty: Invalid argument This has been resolved by making sure the tty file descriptor does not get closed. However, the ioctrl's KDSKBMODE/KDSETMODE and VT_SETMODE still fail with -EIO: failed to restore keyboard mode: Input/output error failed to set KD_TEXT mode on tty: Input/output error It turns out the reason for this lies in some very particular behavior of the kernel, the separation of weston-launch/weston and the fact that we restore the tty only after the weston process quits: When the controlling process for a TTY exits, all open file descriptors for that TTY are put in a hung-up state! For more details see this systemd-logind issue: https://github.com/systemd/systemd/issues/989 We can work around by reopening the particular TTY. This allows to properly restore the TTY settings such that a successive VT switch will show text terminals fine again. Signed-off-by: Stefan Agner Reviewed-by: Emil Velikov --- libweston/weston-launch.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index 911748a55..05525e341 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -440,6 +440,7 @@ quit(struct weston_launch *wl, int status) { struct vt_mode mode = { 0 }; int err; + int oldtty; close(wl->signalfd); close(wl->sock[0]); @@ -452,6 +453,19 @@ quit(struct weston_launch *wl, int status) pam_end(wl->ph, err); } + /* + * Get a fresh handle to the tty as the previous one is in + * hang-up state since weston (the controlling process for + * the tty) exit at this point. Reopen before closing the + * file descriptor to avoid a potential race condition. + * + * A similar fix exists in logind, see: + * https://github.com/systemd/systemd/pull/990 + */ + oldtty = wl->tty; + wl->tty = open_tty_by_number(wl->ttynr); + close(oldtty); + if (ioctl(wl->tty, KDSKBMUTE, 0) && ioctl(wl->tty, KDSKBMODE, wl->kb_mode)) fprintf(stderr, "failed to restore keyboard mode: %s\n", From 10356a247b96c5466ec160dc7010de89ae67b898 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 22 Jan 2020 22:40:19 +0100 Subject: [PATCH 1318/1642] launcher-weston-launch: move send loop into separate function Create a separate function handling the send loop. This allows to reuse the same code later on. Signed-off-by: Stefan Agner --- libweston/launcher-weston-launch.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c index d9397052a..aad8b16e2 100644 --- a/libweston/launcher-weston-launch.c +++ b/libweston/launcher-weston-launch.c @@ -95,6 +95,18 @@ struct launcher_weston_launch { int kb_mode, tty, drm_fd; }; +static ssize_t +launcher_weston_launch_send(int sockfd, void *buf, size_t buflen) +{ + ssize_t len; + + do { + len = send(sockfd, buf, buflen, 0); + } while (len < 0 && errno == EINTR); + + return len; +} + static int launcher_weston_launch_open(struct weston_launcher *launcher_base, const char *path, int flags) @@ -118,9 +130,7 @@ launcher_weston_launch_open(struct weston_launcher *launcher_base, message->flags = flags; strcpy(message->path, path); - do { - len = send(launcher->fd, message, n, 0); - } while (len < 0 && errno == EINTR); + launcher_weston_launch_send(launcher->fd, message, n); free(message); memset(&msg, 0, sizeof msg); From da0cd688d6e35ab509341b0ad6cc5ffcb35c04d8 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 14 Nov 2019 23:55:35 +0100 Subject: [PATCH 1319/1642] launcher-weston-launch: avoid race condition when switching VT merge When using weston-launch launcher deactivating the VT is sometimes racy and leads to Weston still being displayed. The launcher-direct.c backend makes sure that the session signal is emitted first, then DRM master is dropped and finally the VT switch is acknowledged via VT_RELDISP. However, in the weston-launch case the session signal is emitted via a socket message to the weston process, which might get handled a bit later. This leads to dropping DRM master and acknowledging the VT switch prematurely. Add a socket message which allows weston to notify weston-launch that the signal has been emitted and deactivating can be proceeded. Signed-off-by: Stefan Agner Reviewed-by: Emil Velikov --- libweston/launcher-weston-launch.c | 6 ++++- libweston/weston-launch.c | 40 ++++++++++++++++-------------- libweston/weston-launch.h | 3 ++- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c index aad8b16e2..e3b4e1ac1 100644 --- a/libweston/launcher-weston-launch.c +++ b/libweston/launcher-weston-launch.c @@ -201,7 +201,7 @@ static int launcher_weston_launch_data(int fd, uint32_t mask, void *data) { struct launcher_weston_launch *launcher = data; - int len, ret; + int len, ret, reply; if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { weston_log("launcher socket closed, exiting\n"); @@ -226,6 +226,10 @@ launcher_weston_launch_data(int fd, uint32_t mask, void *data) launcher->compositor->session_active = false; wl_signal_emit(&launcher->compositor->session_signal, launcher->compositor); + + reply = WESTON_LAUNCHER_DEACTIVATE_DONE; + launcher_weston_launch_send(launcher->fd, &reply, sizeof reply); + break; default: weston_log("unexpected event from weston-launch\n"); diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c index 05525e341..521cb2cb9 100644 --- a/libweston/weston-launch.c +++ b/libweston/weston-launch.c @@ -399,6 +399,22 @@ handle_open(struct weston_launch *wl, struct msghdr *msg, ssize_t len) return 0; } +static void +close_input_fds(struct weston_launch *wl) +{ + struct stat s; + int fd; + + for (fd = 3; fd <= wl->last_input_fd; fd++) { + if (fstat(fd, &s) == 0 && major(s.st_rdev) == INPUT_MAJOR) { + /* EVIOCREVOKE may fail if the kernel doesn't + * support it, but all we can do is ignore it. */ + ioctl(fd, EVIOCREVOKE, 0); + close(fd); + } + } +} + static int handle_socket_msg(struct weston_launch *wl) { @@ -430,6 +446,11 @@ handle_socket_msg(struct weston_launch *wl) case WESTON_LAUNCHER_OPEN: ret = handle_open(wl, &msg, len); break; + case WESTON_LAUNCHER_DEACTIVATE_DONE: + close_input_fds(wl); + drmDropMaster(wl->drm_fd); + ioctl(wl->tty, VT_RELDISP, 1); + break; } return ret; @@ -490,22 +511,6 @@ quit(struct weston_launch *wl, int status) exit(status); } -static void -close_input_fds(struct weston_launch *wl) -{ - struct stat s; - int fd; - - for (fd = 3; fd <= wl->last_input_fd; fd++) { - if (fstat(fd, &s) == 0 && major(s.st_rdev) == INPUT_MAJOR) { - /* EVIOCREVOKE may fail if the kernel doesn't - * support it, but all we can do is ignore it. */ - ioctl(fd, EVIOCREVOKE, 0); - close(fd); - } - } -} - static int handle_signal(struct weston_launch *wl) { @@ -551,9 +556,6 @@ handle_signal(struct weston_launch *wl) break; case SIGUSR1: send_reply(wl, WESTON_LAUNCHER_DEACTIVATE); - close_input_fds(wl); - drmDropMaster(wl->drm_fd); - ioctl(wl->tty, VT_RELDISP, 1); break; case SIGUSR2: ioctl(wl->tty, VT_RELDISP, VT_ACKACQ); diff --git a/libweston/weston-launch.h b/libweston/weston-launch.h index bd974de8f..575d2cf99 100644 --- a/libweston/weston-launch.h +++ b/libweston/weston-launch.h @@ -33,7 +33,8 @@ enum weston_launcher_opcode { enum weston_launcher_event { WESTON_LAUNCHER_SUCCESS, WESTON_LAUNCHER_ACTIVATE, - WESTON_LAUNCHER_DEACTIVATE + WESTON_LAUNCHER_DEACTIVATE, + WESTON_LAUNCHER_DEACTIVATE_DONE, }; struct weston_launcher_message { From 7b36f171d09354a2d3a48db0ae2d34d66aa4f1ae Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Sat, 1 Feb 2020 20:02:29 -0700 Subject: [PATCH 1320/1642] unconditionally include sys/mman.h in os-compatibility.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: ../shared/os-compatibility.c:273:25: error: ‘PROT_READ’ undeclared (first use in this function); did you mean ‘LOCK_READ’? map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, file->fd, 0); ^~~~~~~~~ LOCK_READ Signed-off-by: James Hilliard --- shared/os-compatibility.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/shared/os-compatibility.c b/shared/os-compatibility.c index 5e1ce4793..041c929f8 100644 --- a/shared/os-compatibility.c +++ b/shared/os-compatibility.c @@ -34,10 +34,7 @@ #include #include #include - -#ifdef HAVE_MEMFD_CREATE #include -#endif #include "os-compatibility.h" From 6d2e73b314db7e32982f9220ce32079477990f78 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 3 Feb 2020 20:00:54 +0000 Subject: [PATCH 1321/1642] gl-renderer: Fail earlier if shader compilation fails If we can't compile our shaders, there's no point trying to link them. Signed-off-by: Daniel Stone --- libweston/renderer-gl/gl-renderer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 6c4355076..a40db3607 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -2970,6 +2970,8 @@ shader_init(struct gl_shader *shader, struct gl_renderer *renderer, shader->vertex_shader = compile_shader(GL_VERTEX_SHADER, 1, &vertex_source); + if (shader->vertex_shader == GL_NONE) + return -1; if (renderer->fragment_shader_debug) { sources[0] = fragment_source; @@ -2984,6 +2986,8 @@ shader_init(struct gl_shader *shader, struct gl_renderer *renderer, shader->fragment_shader = compile_shader(GL_FRAGMENT_SHADER, count, sources); + if (shader->fragment_shader == GL_NONE) + return -1; shader->program = glCreateProgram(); glAttachShader(shader->program, shader->vertex_shader); From d76947b6668e0fabe0a4551ac6c2c978f93768cd Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 3 Feb 2020 20:01:21 +0000 Subject: [PATCH 1322/1642] gl-renderer: Avoid double-free on init failure If gl-renderer fails its initialisation, we return to compositor teardown, which will try to free the renderer if ec->renderer was set. This is unfortunate when we've already torn it down whilst failing gl-renderer init, so just clear the renderer member so we don't try to tear down twice. Signed-off-by: Daniel Stone Reported-by: Emil Velikov --- libweston/renderer-gl/gl-renderer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index a40db3607..79285c008 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -3596,6 +3596,7 @@ gl_renderer_display_create(struct weston_compositor *ec, eglTerminate(gr->egl_display); fail: free(gr); + ec->renderer = NULL; return -1; } From 9096dee405add553dda6a4254aa5faa7651e7365 Mon Sep 17 00:00:00 2001 From: Guillaume Champagne Date: Sun, 2 Feb 2020 19:45:36 -0500 Subject: [PATCH 1323/1642] headless: fix uninitialized variable `no_outputs` is declared on the stack and left uninitialized if no weston option changing its value is provided. Signed-off-by: Guillaume Champagne --- compositor/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compositor/main.c b/compositor/main.c index 97c3c45ce..2e1a93567 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2550,7 +2550,7 @@ load_headless_backend(struct weston_compositor *c, const struct weston_windowed_output_api *api; struct weston_headless_backend_config config = {{ 0, }}; struct weston_config_section *section; - bool no_outputs; + bool no_outputs = false; int ret = 0; char *transform = NULL; From 7bce28b5433bdbf66dc6c86fb9e129cec354ba89 Mon Sep 17 00:00:00 2001 From: Guillaume Champagne Date: Sun, 2 Feb 2020 19:59:52 -0500 Subject: [PATCH 1324/1642] tests: release resources on compositor destruction Releases touch devices and seat if they were allocated, clean up the layers and free the weston_test structure. Signed-off-by: Guillaume Champagne --- tests/weston-test.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/weston-test.c b/tests/weston-test.c index e438e823d..e3a0cb66d 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -794,8 +794,14 @@ handle_compositor_destroy(struct wl_listener *listener, client_thread_join(test); } + if (test->is_seat_initialized) + test_seat_release(test); + + wl_list_remove(&test->layer.view_list.link); + wl_list_remove(&test->layer.link); + weston_log_scope_destroy(test->log); - test->log = NULL; + free(test); } WL_EXPORT int From f0d3a6149158f682230ae9a1e69289431974f635 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 5 Feb 2020 10:27:23 +0100 Subject: [PATCH 1325/1642] shared: guard all the seal logic behind HAVE_MEMFD_CREATE The initial version of os_ro_anonymous_file missed two guards around the seal logic which leads to a compilation error on older systems. Also make the check for a read-only file symmetric in os_ro_anonymous_file_get_fd and os_ro_anonymous_file_put_fd. Signed-off-by: Sebastian Wick --- shared/os-compatibility.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/shared/os-compatibility.c b/shared/os-compatibility.c index 041c929f8..2e12b7cc3 100644 --- a/shared/os-compatibility.c +++ b/shared/os-compatibility.c @@ -340,6 +340,7 @@ os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file, void *src, *dst; int seals, fd; +#ifdef HAVE_MEMFD_CREATE seals = fcntl(file->fd, F_GET_SEALS); /* file was sealed for read-only and we don't have to support MAP_SHARED @@ -348,6 +349,7 @@ os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file, if (seals != -1 && mapmode == RO_ANONYMOUS_FILE_MAPMODE_PRIVATE && (seals & READONLY_SEALS) == READONLY_SEALS) return file->fd; +#endif /* for all other cases we create a new anonymous file that can be mapped * with MAP_SHARED and copy the contents to it and return that instead @@ -388,17 +390,18 @@ os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file, int os_ro_anonymous_file_put_fd(int fd) { +#ifdef HAVE_MEMFD_CREATE int seals = fcntl(fd, F_GET_SEALS); if (seals == -1 && errno != EINVAL) return -1; - /* If the fd cannot be sealed seals is -1 at this point - * or the file can be sealed but has not been sealed for writing. - * In both cases we created a new anonymous file that we have to - * close. + /* The only case in which we do NOT have to close the file is when the file + * was sealed for read-only */ - if (seals == -1 || !(seals & F_SEAL_WRITE)) - close(fd); + if (seals != -1 && (seals & READONLY_SEALS) == READONLY_SEALS) + return 0; +#endif + close(fd); return 0; } From 8fc4b59bfdd99b9916da523a6358232beb2ba829 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 13 Dec 2019 11:47:18 +0200 Subject: [PATCH 1326/1642] weston-log-flight-rec: allow re-running a compositor weston_primary_flight_recorder_ring_buffer needs to be cleared on destruction of the subscriber it was assigned from so that a compositor and be re-executed in-process (static variables do not get re-initialized automatically). This will be used by the test harness when it will execute wet_main() multiple times in the same process. Otherwise it would hit the assert in weston_log_subscriber_create_flight_rec(). Signed-off-by: Pekka Paalanen --- libweston/weston-log-flight-rec.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libweston/weston-log-flight-rec.c b/libweston/weston-log-flight-rec.c index 31ea30454..16d18a906 100644 --- a/libweston/weston-log-flight-rec.c +++ b/libweston/weston-log-flight-rec.c @@ -265,11 +265,18 @@ weston_log_subscriber_create_flight_rec(size_t size) * * @param sub the weston_log_subscriber object * + * This also resets weston_primary_flight_recorder_ring_buffer to NULL if it + * is the destroyed subscriber. */ WL_EXPORT void weston_log_subscriber_destroy_flight_rec(struct weston_log_subscriber *sub) { - struct weston_debug_log_flight_recorder *flight_rec = to_flight_recorder(sub); + struct weston_debug_log_flight_recorder *flight_rec; + + flight_rec = to_flight_recorder(sub); + if (weston_primary_flight_recorder_ring_buffer == &flight_rec->rb) + weston_primary_flight_recorder_ring_buffer = NULL; + free(flight_rec->rb.buf); free(flight_rec); } From 007ab1e5a47fdd31e8425b746f07dd4ac3c3e0cc Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 25 Nov 2019 14:54:06 +0200 Subject: [PATCH 1327/1642] tests: add range argument for fuzzy image matching The fuzzy range will be used with GL-renderer testing, as it may produce slightly different images than Pixman-renderer yet still correct results. Such allowed differences are due to different rounding. Signed-off-by: Pekka Paalanen --- tests/internal-screenshot-test.c | 6 +-- tests/subsurface-shot-test.c | 4 +- tests/weston-test-client-helper.c | 62 ++++++++++++++++++++++++++++--- tests/weston-test-client-helper.h | 11 +++++- 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/tests/internal-screenshot-test.c b/tests/internal-screenshot-test.c index e1a7c43be..6c1b774e3 100644 --- a/tests/internal-screenshot-test.c +++ b/tests/internal-screenshot-test.c @@ -141,7 +141,7 @@ TEST(internal_screenshot) /* Test check_images_match() without a clip. * We expect this to fail since we use a bad reference image */ - match = check_images_match(screenshot->image, reference_bad, NULL); + match = check_images_match(screenshot->image, reference_bad, NULL, NULL); testlog("Screenshot %s reference image\n", match? "equal to" : "different from"); assert(!match); pixman_image_unref(reference_bad); @@ -155,10 +155,10 @@ TEST(internal_screenshot) clip.width = 100; clip.height = 100; testlog("Clip: %d,%d %d x %d\n", clip.x, clip.y, clip.width, clip.height); - match = check_images_match(screenshot->image, reference_good, &clip); + match = check_images_match(screenshot->image, reference_good, &clip, NULL); testlog("Screenshot %s reference image in clipped area\n", match? "matches" : "doesn't match"); if (!match) { - diffimg = visualize_image_difference(screenshot->image, reference_good, &clip); + diffimg = visualize_image_difference(screenshot->image, reference_good, &clip, NULL); fname = screenshot_output_filename("internal-screenshot-error", 0); write_image_as_png(diffimg, fname); pixman_image_unref(diffimg); diff --git a/tests/subsurface-shot-test.c b/tests/subsurface-shot-test.c index dcbce0c3f..dc11bec0c 100644 --- a/tests/subsurface-shot-test.c +++ b/tests/subsurface-shot-test.c @@ -135,7 +135,7 @@ write_visual_diff(pixman_image_t *ref_image, assert(ret >= 0); fname = screenshot_output_filename(ext_test_name, seq_no); - diff = visualize_image_difference(shot->image, ref_image, clip); + diff = visualize_image_difference(shot->image, ref_image, clip, NULL); write_image_as_png(diff, fname); pixman_image_unref(diff); @@ -166,7 +166,7 @@ check_screen(struct client *client, shot = capture_screenshot_of_output(client); assert(shot); - match = check_images_match(shot->image, ref, clip); + match = check_images_match(shot->image, ref, clip, NULL); testlog("ref %s vs. shot %s: %s\n", ref_fname, shot_fname, match ? "PASS" : "FAIL"); diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 3fb6b72f8..927523cba 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -1077,6 +1077,24 @@ format_pixman2cairo(pixman_format_code_t fmt) assert(0 && "unknown Pixman pixel format"); } +/** + * Validate range + * + * \param r Range to validate or NULL. + * \return The given range, or {0, 0} for NULL. + * + * Will abort if range is invalid, that is a > b. + */ +static struct range +range_get(const struct range *r) +{ + if (!r) + return (struct range){ 0, 0 }; + + assert(r->a <= r->b); + return *r; +} + /** * Compute the ROI for image comparisons * @@ -1157,8 +1175,25 @@ image_iter_get_row(struct image_iterator *it, int y) return (uint32_t *)(it->data + y * it->stride); } +static bool +fuzzy_match_pixels(uint32_t pix_a, uint32_t pix_b, const struct range *fuzz) +{ + int shift; + + for (shift = 0; shift < 32; shift += 8) { + int val_a = (pix_a >> shift) & 0xffu; + int val_b = (pix_b >> shift) & 0xffu; + int d = val_b - val_a; + + if (d < fuzz->a || d > fuzz->b) + return false; + } + + return true; +} + /** - * Test if a given region within two images are pixel-identical. + * Test if a given region within two images are pixel-identical * * Returns true if the two images pixel-wise identical, and false otherwise. * @@ -1166,15 +1201,23 @@ image_iter_get_row(struct image_iterator *it, int y) * \param img_b Second image. * \param clip_rect The region of interest, or NULL for comparing the whole * images. + * \param prec Per-channel allowed difference, or NULL for identical match + * required. * * This function hard-fails if clip_rect is not inside both images. If clip_rect * is given, the images do not have to match in size, otherwise size mismatch * will be a hard failure. + * + * The per-pixel, per-channel difference is computed as img_b - img_a which is + * required to be in the range [prec->a, prec->b] inclusive. The difference is + * signed. All four channels are compared the same way, without any special + * meaning on alpha channel. */ bool check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, - const struct rectangle *clip_rect) + const struct rectangle *clip_rect, const struct range *prec) { + struct range fuzz = range_get(prec); struct image_iterator it_a; struct image_iterator it_b; pixman_box32_t box; @@ -1192,7 +1235,7 @@ check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, pix_b = image_iter_get_row(&it_b, y) + box.x1; for (x = box.x1; x < box.x2; x++) { - if (*pix_a != *pix_b) + if (!fuzzy_match_pixels(*pix_a, *pix_b, &fuzz)) return false; pix_a++; @@ -1232,6 +1275,8 @@ tint(uint32_t src, uint32_t add) * \param img_b Second image. * \param clip_rect The region of interest, or NULL for comparing the whole * images. + * \param prec Per-channel allowed difference, or NULL for identical match + * required. * \return A new image with the differences highlighted. * * Regions outside of the region of interest are shaded with black, matching @@ -1241,11 +1286,18 @@ tint(uint32_t src, uint32_t add) * This function hard-fails if clip_rect is not inside both images. If clip_rect * is given, the images do not have to match in size, otherwise size mismatch * will be a hard failure. + * + * The per-pixel, per-channel difference is computed as img_b - img_a which is + * required to be in the range [prec->a, prec->b] inclusive. The difference is + * signed. All four channels are compared the same way, without any special + * meaning on alpha channel. */ pixman_image_t * visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, - const struct rectangle *clip_rect) + const struct rectangle *clip_rect, + const struct range *prec) { + struct range fuzz = range_get(prec); pixman_image_t *diffimg; pixman_image_t *shade; struct image_iterator it_a; @@ -1288,7 +1340,7 @@ visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, pix_d = image_iter_get_row(&it_d, y) + box.x1; for (x = box.x1; x < box.x2; x++) { - if (*pix_a == *pix_b) + if (fuzzy_match_pixels(*pix_a, *pix_b, &fuzz)) *pix_d = tint(*pix_d, 0x00008000); /* green */ else *pix_d = tint(*pix_d, 0x00c00000); /* red */ diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 52e8a5608..45e6db932 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -176,6 +176,11 @@ struct rectangle { int height; }; +struct range { + int a; + int b; +}; + struct client * create_client(void); @@ -224,11 +229,13 @@ screenshot_reference_filename(const char *basename, uint32_t seq); bool check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, - const struct rectangle *clip); + const struct rectangle *clip, + const struct range *prec); pixman_image_t * visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, - const struct rectangle *clip_rect); + const struct rectangle *clip_rect, + const struct range *prec); bool write_image_as_png(pixman_image_t *image, const char *fname); From 1618697dc3db9e63ed601be7cbc2fb5e84c94b41 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 4 Feb 2020 16:59:59 +0200 Subject: [PATCH 1328/1642] build: add test-gl-renderer option This shall be used by CI due to https://gitlab.freedesktop.org/mesa/mesa/issues/2219 It defaults to true, meaning that people by default will be running the GL-renderer tests. It works fine on hardware drivers, just not llvmpipe. Signed-off-by: Pekka Paalanen --- meson.build | 2 ++ meson_options.txt | 6 ++++++ tests/weston-test-fixture-compositor.c | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/meson.build b/meson.build index 7e735607a..090fc7136 100644 --- a/meson.build +++ b/meson.build @@ -115,6 +115,8 @@ config_h.set_quoted('LIBEXECDIR', dir_libexec) config_h.set_quoted('MODULEDIR', dir_module_weston) config_h.set_quoted('LIBWESTON_MODULEDIR', dir_module_libweston) +config_h.set10('TEST_GL_RENDERER', get_option('test-gl-renderer')) + backend_default = get_option('backend-default') if backend_default == 'auto' foreach b : [ 'headless', 'fbdev', 'x11', 'wayland', 'drm' ] diff --git a/meson_options.txt b/meson_options.txt index c862ecc47..9f6b08c31 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -204,6 +204,12 @@ option( value: true, description: 'Tests: output JUnit XML results' ) +option( + 'test-gl-renderer', + type: 'boolean', + value: true, + description: 'Tests: allow running with GL-renderer' +) option( 'doc', type: 'boolean', diff --git a/tests/weston-test-fixture-compositor.c b/tests/weston-test-fixture-compositor.c index ea206be01..0c8004b4c 100644 --- a/tests/weston-test-fixture-compositor.c +++ b/tests/weston-test-fixture-compositor.c @@ -229,6 +229,13 @@ execute_compositor(const struct compositor_setup *setup, } #endif +#if !TEST_GL_RENDERER + if (setup->renderer == RENDERER_GL) { + fprintf(stderr, "GL-renderer disabled for tests, skipping.\n"); + return RESULT_SKIP; + } +#endif + prog_args_init(&args); /* argv[0] */ From 1eae54714fd2ae38756b24ddc58a7d55bec5d14d Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 4 Feb 2020 17:03:07 +0200 Subject: [PATCH 1329/1642] CI: do not attempt to test GL-renderer Because of https://gitlab.freedesktop.org/mesa/mesa/issues/2219 it would crash, so disable GL-renderer. https://gitlab.freedesktop.org/wayland/weston/issues/358 shall revert this. Signed-off-by: Pekka Paalanen --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dee26b629..ce8126e2c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -69,6 +69,7 @@ build-native-meson-default-options: MESON_OPTIONS: > -Dwerror=true -Ddoc=true + -Dtest-gl-renderer=false extends: .build-native-meson build-native-meson-no-gl-renderer: From 741fca40b539ec344742ec28e830b0ca20f6cb16 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 25 Nov 2019 15:17:56 +0200 Subject: [PATCH 1330/1642] tests: run subsurface-shot on GL too This adds the necessary fuzz to image matching to let GL-renderer pass. The difference is due to rounding. weston-test-desktop-shell.c uses weston_surface_set_color(dts->background_surface, 0.16, 0.32, 0.48, 1.); to set the background color. Pixman-renderer will truncate those to uint8, but GL-renderer seems to round instead, which causes the +1 in background color channel values. 0.16 * 255 = 40.8 0.32 * 255 = 81.6 0.48 * 255 = 122.4 Signed-off-by: Pekka Paalanen --- tests/subsurface-shot-test.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/subsurface-shot-test.c b/tests/subsurface-shot-test.c index dc11bec0c..017f54469 100644 --- a/tests/subsurface-shot-test.c +++ b/tests/subsurface-shot-test.c @@ -50,10 +50,6 @@ fixture_setup(struct weston_test_harness *harness, const enum renderer_type *arg setup.shell = SHELL_TEST_DESKTOP; setup.logging_scopes = "log,test-harness-plugin"; - /* This test fails due to color rounding on GL */ - if (setup.renderer == RENDERER_GL) - return RESULT_SKIP; - return weston_test_harness_execute_as_client(harness, &setup); } DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, renderers); @@ -124,7 +120,8 @@ write_visual_diff(pixman_image_t *ref_image, struct buffer *shot, const struct rectangle *clip, const char *test_name, - int seq_no) + int seq_no, + const struct range *fuzz) { char *fname; char *ext_test_name; @@ -135,7 +132,7 @@ write_visual_diff(pixman_image_t *ref_image, assert(ret >= 0); fname = screenshot_output_filename(ext_test_name, seq_no); - diff = visualize_image_difference(shot->image, ref_image, clip, NULL); + diff = visualize_image_difference(ref_image, shot->image, clip, fuzz); write_image_as_png(diff, fname); pixman_image_unref(diff); @@ -151,6 +148,7 @@ check_screen(struct client *client, int seq_no) { const char *test_name = get_test_name(); + const struct range gl_fuzz = { 0, 1 }; struct buffer *shot; pixman_image_t *ref; char *ref_fname; @@ -166,13 +164,13 @@ check_screen(struct client *client, shot = capture_screenshot_of_output(client); assert(shot); - match = check_images_match(shot->image, ref, clip, NULL); + match = check_images_match(ref, shot->image, clip, &gl_fuzz); testlog("ref %s vs. shot %s: %s\n", ref_fname, shot_fname, match ? "PASS" : "FAIL"); write_image_as_png(shot->image, shot_fname); if (!match) - write_visual_diff(ref, shot, clip, test_name, seq_no); + write_visual_diff(ref, shot, clip, test_name, seq_no, &gl_fuzz); buffer_destroy(shot); pixman_image_unref(ref); From 4b1de0112b765e0c7548a9a39ad82c45373c60e7 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Wed, 5 Feb 2020 00:40:01 +0100 Subject: [PATCH 1331/1642] build: add rpath to modules that use symbols from libexec_weston The cms-static, desktop-shell, hmi-controller, ivi-shell, and screen-share modules use symbols from libexec_weston, e.g.: $ ldd /usr/lib/x86_64-linux-gnu/weston/desktop-shell.so | grep "not found" libexec_weston.so.0 => not found Loading these modules from weston happens to work because the weston executable itself links against libexec_weston, and has an rpath set. Still, these modules depend on a library in a non-standard location. Adding an rpath could help static checkers to make sure all shared objects' dependencies are present. Signed-off-by: Philipp Zabel --- compositor/meson.build | 6 ++++-- desktop-shell/meson.build | 3 ++- ivi-shell/meson.build | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/compositor/meson.build b/compositor/meson.build index f17415bee..621c3e076 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -90,7 +90,8 @@ if get_option('screenshare') dependencies: deps_screenshare, name_prefix: '', install: true, - install_dir: dir_module_weston + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' ) env_modmap += 'screen-share.so=@0@;'.format(plugin_screenshare.full_path()) endif @@ -115,7 +116,8 @@ if get_option('color-management-lcms') dependencies: [ dep_libexec_weston, dep_libweston_public, dep_lcms2 ], name_prefix: '', install: true, - install_dir: dir_module_weston + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' ) env_modmap += 'cms-static.so=@0@;'.format(plugin_lcms.full_path()) endif diff --git a/desktop-shell/meson.build b/desktop-shell/meson.build index c4f6b0da7..c67bfd600 100644 --- a/desktop-shell/meson.build +++ b/desktop-shell/meson.build @@ -24,7 +24,8 @@ if get_option('shell-desktop') dependencies: deps_shell_desktop, name_prefix: '', install: true, - install_dir: dir_module_weston + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' ) env_modmap += 'desktop-shell.so=@0@;'.format(plugin_shell_desktop.full_path()) endif diff --git a/ivi-shell/meson.build b/ivi-shell/meson.build index 8364fa573..03fc71756 100644 --- a/ivi-shell/meson.build +++ b/ivi-shell/meson.build @@ -20,7 +20,8 @@ if get_option('shell-ivi') ], name_prefix: '', install: true, - install_dir: dir_module_weston + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' ) env_modmap += 'ivi-shell.so=@0@;'.format(plugin_shell_ivi.full_path()) @@ -42,7 +43,8 @@ if get_option('shell-ivi') ], name_prefix: '', install: true, - install_dir: dir_module_weston + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' ) env_modmap += 'hmi-controller.so=@0@;'.format(plugin_ivi_hmi.full_path()) From 8c02ea1069274c07424fa9a16256d97c726dca9e Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 6 Feb 2020 15:41:32 -0300 Subject: [PATCH 1332/1642] weston-log: rename weston_log_subscriber::destroy to destroy_subscription weston_log_subscriber has a member named destroy. There are other structs (weston_output, for instance) that have this member, and by convention it is a pointer to a function that destroys the struct. In weston_log_subscriber it is being used to destroy subscriptions of the debug protocol, and not the subscriber, so this name is misleading. Rename it to destroy_subscription. Signed-off-by: Leandro Ribeiro --- libweston/weston-log-file.c | 2 +- libweston/weston-log-flight-rec.c | 2 +- libweston/weston-log-internal.h | 2 +- libweston/weston-log-wayland.c | 2 +- libweston/weston-log.c | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libweston/weston-log-file.c b/libweston/weston-log-file.c index 2cdb247d3..219a15dd9 100644 --- a/libweston/weston-log-file.c +++ b/libweston/weston-log-file.c @@ -79,7 +79,7 @@ weston_log_subscriber_create_log(FILE *dump_to) file->base.write = weston_log_file_write; - file->base.destroy = NULL; + file->base.destroy_subscription = NULL; file->base.complete = NULL; wl_list_init(&file->base.subscription_list); diff --git a/libweston/weston-log-flight-rec.c b/libweston/weston-log-flight-rec.c index 16d18a906..7243498e6 100644 --- a/libweston/weston-log-flight-rec.c +++ b/libweston/weston-log-flight-rec.c @@ -241,7 +241,7 @@ weston_log_subscriber_create_flight_rec(size_t size) return NULL; flight_rec->base.write = weston_log_flight_recorder_write; - flight_rec->base.destroy = NULL; + flight_rec->base.destroy_subscription = NULL; flight_rec->base.complete = NULL; wl_list_init(&flight_rec->base.subscription_list); diff --git a/libweston/weston-log-internal.h b/libweston/weston-log-internal.h index 9c94dbb5c..95e6e6539 100644 --- a/libweston/weston-log-internal.h +++ b/libweston/weston-log-internal.h @@ -54,7 +54,7 @@ struct weston_log_subscriber { void (*write)(struct weston_log_subscriber *sub, const char *data, size_t len); /** For the type of streams that required additional destroy operation * for destroying the stream */ - void (*destroy)(struct weston_log_subscriber *sub); + void (*destroy_subscription)(struct weston_log_subscriber *sub); /** For the type of streams that can inform the 'consumer' part that * write operation has been terminated/finished and should close the * stream. diff --git a/libweston/weston-log-wayland.c b/libweston/weston-log-wayland.c index 43fc28852..892b16315 100644 --- a/libweston/weston-log-wayland.c +++ b/libweston/weston-log-wayland.c @@ -180,7 +180,7 @@ stream_create(struct weston_log_context *log_ctx, const char *name, stream->resource = stream_resource; stream->base.write = weston_log_debug_wayland_write; - stream->base.destroy = weston_log_debug_wayland_to_destroy; + stream->base.destroy_subscription = weston_log_debug_wayland_to_destroy; stream->base.complete = weston_log_debug_wayland_complete; wl_list_init(&stream->base.subscription_list); diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 43ca1708e..6afda2c6a 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -712,8 +712,8 @@ weston_log_scope_destroy(struct weston_log_scope *scope) wl_list_for_each_safe(sub, sub_tmp, &scope->subscription_list, source_link) { /* destroy each subscription */ - if (sub->owner->destroy) - sub->owner->destroy(sub->owner); + if (sub->owner->destroy_subscription) + sub->owner->destroy_subscription(sub->owner); weston_log_subscription_destroy(sub); } From 1ded661aac2c9d452af9eb31dda142b451e93cea Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 6 Feb 2020 16:43:51 -0300 Subject: [PATCH 1333/1642] weston-log: merge functions that destroy different types of subscribers Log subscriber API is not type-safe. File and flight recorder subscribers are created with functions that return weston_log_subscriber objects. But there's a problem: to destroy these objects you have to call the right function for each type of subscriber, and a user calling the wrong destroy function wouldn't get a warning. Merge functions that destroy different types of subscribers, making the log subscriber API type-safe. Signed-off-by: Leandro Ribeiro --- compositor/main.c | 4 ++-- include/libweston/weston-log.h | 9 +++----- libweston/weston-log-file.c | 24 ++++++++----------- libweston/weston-log-flight-rec.c | 38 +++++++++++++------------------ libweston/weston-log-internal.h | 3 ++- libweston/weston-log-wayland.c | 1 + libweston/weston-log.c | 15 ++++++++++++ 7 files changed, 49 insertions(+), 45 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 2e1a93567..0a8dbe84b 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -3381,8 +3381,8 @@ wet_main(int argc, char *argv[]) weston_log_scope_destroy(log_scope); log_scope = NULL; weston_log_ctx_destroy(log_ctx); - weston_log_subscriber_destroy_log(logger); - weston_log_subscriber_destroy_flight_rec(flight_rec); + weston_log_subscriber_destroy(logger); + weston_log_subscriber_destroy(flight_rec); out_signals: for (i = ARRAY_LENGTH(signals) - 1; i >= 0; i--) diff --git a/include/libweston/weston-log.h b/include/libweston/weston-log.h index 70f6d3477..aeb7768bf 100644 --- a/include/libweston/weston-log.h +++ b/include/libweston/weston-log.h @@ -110,6 +110,9 @@ char * weston_log_scope_timestamp(struct weston_log_scope *scope, char *buf, size_t len); +void +weston_log_subscriber_destroy(struct weston_log_subscriber *subscriber); + void weston_log_subscribe(struct weston_log_context *log_ctx, struct weston_log_subscriber *subscriber, @@ -118,15 +121,9 @@ weston_log_subscribe(struct weston_log_context *log_ctx, struct weston_log_subscriber * weston_log_subscriber_create_log(FILE *dump_to); -void -weston_log_subscriber_destroy_log(struct weston_log_subscriber *sub); - struct weston_log_subscriber * weston_log_subscriber_create_flight_rec(size_t size); -void -weston_log_subscriber_destroy_flight_rec(struct weston_log_subscriber *sub); - void weston_log_subscriber_display_flight_rec(struct weston_log_subscriber *sub); diff --git a/libweston/weston-log-file.c b/libweston/weston-log-file.c index 219a15dd9..0f8f4f20a 100644 --- a/libweston/weston-log-file.c +++ b/libweston/weston-log-file.c @@ -54,14 +54,21 @@ weston_log_file_write(struct weston_log_subscriber *sub, fwrite(data, len, 1, stream->file); } +static void +weston_log_subscriber_destroy_log(struct weston_log_subscriber *subscriber) +{ + struct weston_debug_log_file *file = to_weston_debug_log_file(subscriber); + free(file); +} + /** Creates a file type of subscriber * - * Should be destroyed using weston_log_subscriber_destroy_log() + * Should be destroyed using weston_log_subscriber_destroy() * * @param dump_to if specified, used for writing data to * @returns a weston_log_subscriber object or NULL in case of failure * - * @sa weston_log_subscriber_destroy_log + * @sa weston_log_subscriber_destroy * */ WL_EXPORT struct weston_log_subscriber * @@ -79,6 +86,7 @@ weston_log_subscriber_create_log(FILE *dump_to) file->base.write = weston_log_file_write; + file->base.destroy = weston_log_subscriber_destroy_log; file->base.destroy_subscription = NULL; file->base.complete = NULL; @@ -86,15 +94,3 @@ weston_log_subscriber_create_log(FILE *dump_to) return &file->base; } - -/** Destroy the subscriber created with weston_log_subscriber_create_log - * - * @param subscriber the weston_log_subscriber object to destroy - * - */ -WL_EXPORT void -weston_log_subscriber_destroy_log(struct weston_log_subscriber *subscriber) -{ - struct weston_debug_log_file *file = to_weston_debug_log_file(subscriber); - free(file); -} diff --git a/libweston/weston-log-flight-rec.c b/libweston/weston-log-flight-rec.c index 7243498e6..36323f733 100644 --- a/libweston/weston-log-flight-rec.c +++ b/libweston/weston-log-flight-rec.c @@ -218,10 +218,24 @@ weston_log_subscriber_display_flight_rec(struct weston_log_subscriber *sub) weston_log_subscriber_display_flight_rec_data(rb, rb->file); } +static void +weston_log_subscriber_destroy_flight_rec(struct weston_log_subscriber *sub) +{ + struct weston_debug_log_flight_recorder *flight_rec = to_flight_recorder(sub); + + /* Resets weston_primary_flight_recorder_ring_buffer to NULL if it + * is the destroyed subscriber */ + if (weston_primary_flight_recorder_ring_buffer == &flight_rec->rb) + weston_primary_flight_recorder_ring_buffer = NULL; + + free(flight_rec->rb.buf); + free(flight_rec); +} + /** Create a flight recorder type of subscriber * * Allocates both the flight recorder and the underlying ring buffer. Use - * weston_log_subscriber_destroy_flight_rec() to clean-up. + * weston_log_subscriber_destroy() to clean-up. * * @param size specify the maximum size (in bytes) of the backing storage * for the flight recorder @@ -241,6 +255,7 @@ weston_log_subscriber_create_flight_rec(size_t size) return NULL; flight_rec->base.write = weston_log_flight_recorder_write; + flight_rec->base.destroy = weston_log_subscriber_destroy_flight_rec; flight_rec->base.destroy_subscription = NULL; flight_rec->base.complete = NULL; wl_list_init(&flight_rec->base.subscription_list); @@ -260,27 +275,6 @@ weston_log_subscriber_create_flight_rec(size_t size) return &flight_rec->base; } -/** Destroys the weston_log_subscriber object created with - * weston_log_subscriber_create_flight_rec() - * - * @param sub the weston_log_subscriber object - * - * This also resets weston_primary_flight_recorder_ring_buffer to NULL if it - * is the destroyed subscriber. - */ -WL_EXPORT void -weston_log_subscriber_destroy_flight_rec(struct weston_log_subscriber *sub) -{ - struct weston_debug_log_flight_recorder *flight_rec; - - flight_rec = to_flight_recorder(sub); - if (weston_primary_flight_recorder_ring_buffer == &flight_rec->rb) - weston_primary_flight_recorder_ring_buffer = NULL; - - free(flight_rec->rb.buf); - free(flight_rec); -} - /** Retrieve flight recorder ring buffer contents, could be useful when * implementing an assert()-like wrapper. * diff --git a/libweston/weston-log-internal.h b/libweston/weston-log-internal.h index 95e6e6539..fbf88e56d 100644 --- a/libweston/weston-log-internal.h +++ b/libweston/weston-log-internal.h @@ -52,6 +52,8 @@ struct weston_log_subscription; struct weston_log_subscriber { /** write the data pointed by @param data */ void (*write)(struct weston_log_subscriber *sub, const char *data, size_t len); + /** For destroying the subscriber */ + void (*destroy)(struct weston_log_subscriber *sub); /** For the type of streams that required additional destroy operation * for destroying the stream */ void (*destroy_subscription)(struct weston_log_subscriber *sub); @@ -79,7 +81,6 @@ weston_log_subscription_add(struct weston_log_scope *scope, void weston_log_subscription_remove(struct weston_log_subscription *sub); - void weston_log_bind_weston_debug(struct wl_client *client, void *data, uint32_t version, uint32_t id); diff --git a/libweston/weston-log-wayland.c b/libweston/weston-log-wayland.c index 892b16315..732439f2c 100644 --- a/libweston/weston-log-wayland.c +++ b/libweston/weston-log-wayland.c @@ -180,6 +180,7 @@ stream_create(struct weston_log_context *log_ctx, const char *name, stream->resource = stream_resource; stream->base.write = weston_log_debug_wayland_write; + stream->base.destroy = NULL; stream->base.destroy_subscription = weston_log_debug_wayland_to_destroy; stream->base.complete = weston_log_debug_wayland_complete; wl_list_init(&stream->base.subscription_list); diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 6afda2c6a..09717c48b 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -939,6 +939,21 @@ weston_log_scope_timestamp(struct weston_log_scope *scope, return buf; } +/** Destroy a file type or a flight-rec type subscriber. + * + * They are created, respectively, with weston_log_subscriber_create_log() + * and weston_log_subscriber_create_flight_rec() + * + * @param subscriber the weston_log_subscriber object to destroy + * + * @ingroup log + */ +WL_EXPORT void +weston_log_subscriber_destroy(struct weston_log_subscriber *subscriber) +{ + subscriber->destroy(subscriber); +} + /** Subscribe to a scope * * Creates a subscription which is used to subscribe the \p subscriber From 23491cd931c5bd3a0fd71be2e220ea9f9257c1bb Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Mon, 3 Feb 2020 22:59:16 -0300 Subject: [PATCH 1334/1642] weston-log: destroy subscriptions with destruction of subscribers The subscription is directly related to both the log scope and the subscriber. It makes no sense to destroy one of them and keep the subscriptions living. We only had code to destroy subscription with the destruction of log scopes. Add code to destroy subscriptions with destruction of subscribers. Signed-off-by: Leandro Ribeiro --- libweston/weston-log-file.c | 2 ++ libweston/weston-log-flight-rec.c | 1 + libweston/weston-log-internal.h | 3 +++ libweston/weston-log.c | 15 +++++++++++++++ 4 files changed, 21 insertions(+) diff --git a/libweston/weston-log-file.c b/libweston/weston-log-file.c index 0f8f4f20a..25e36b46a 100644 --- a/libweston/weston-log-file.c +++ b/libweston/weston-log-file.c @@ -58,6 +58,8 @@ static void weston_log_subscriber_destroy_log(struct weston_log_subscriber *subscriber) { struct weston_debug_log_file *file = to_weston_debug_log_file(subscriber); + + weston_log_subscriber_release(subscriber); free(file); } diff --git a/libweston/weston-log-flight-rec.c b/libweston/weston-log-flight-rec.c index 36323f733..7364c81ac 100644 --- a/libweston/weston-log-flight-rec.c +++ b/libweston/weston-log-flight-rec.c @@ -228,6 +228,7 @@ weston_log_subscriber_destroy_flight_rec(struct weston_log_subscriber *sub) if (weston_primary_flight_recorder_ring_buffer == &flight_rec->rb) weston_primary_flight_recorder_ring_buffer = NULL; + weston_log_subscriber_release(sub); free(flight_rec->rb.buf); free(flight_rec); } diff --git a/libweston/weston-log-internal.h b/libweston/weston-log-internal.h index fbf88e56d..02a05f19d 100644 --- a/libweston/weston-log-internal.h +++ b/libweston/weston-log-internal.h @@ -81,6 +81,9 @@ weston_log_subscription_add(struct weston_log_scope *scope, void weston_log_subscription_remove(struct weston_log_subscription *sub); +void +weston_log_subscriber_release(struct weston_log_subscriber *subscriber); + void weston_log_bind_weston_debug(struct wl_client *client, void *data, uint32_t version, uint32_t id); diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 09717c48b..f5954d77d 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -939,6 +939,21 @@ weston_log_scope_timestamp(struct weston_log_scope *scope, return buf; } +void +weston_log_subscriber_release(struct weston_log_subscriber *subscriber) +{ + struct weston_log_subscription *sub, *sub_tmp; + + wl_list_for_each_safe(sub, sub_tmp, &subscriber->subscription_list, owner_link) { + /* destroy each subscription */ + if (sub->owner->destroy_subscription) + sub->owner->destroy_subscription(sub->owner); + + weston_log_subscription_destroy(sub); + } + +} + /** Destroy a file type or a flight-rec type subscriber. * * They are created, respectively, with weston_log_subscriber_create_log() From 97d2d69909e25b75ed91fe4b7fa627274233f3ae Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 7 Feb 2020 16:06:19 -0300 Subject: [PATCH 1335/1642] weston-log: share code between weston_log_scope_destroy() and weston_log_subscriber_release() Both weston_log_scope_destroy() and weston_log_subscriber_release() have calls for destroy_subscription(). We can move this call to weston_log_subscription_destroy() without losing anything and avoiding repetition. Signed-off-by: Leandro Ribeiro --- libweston/weston-log.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/libweston/weston-log.c b/libweston/weston-log.c index f5954d77d..7d85a6be9 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -279,6 +279,9 @@ weston_log_subscription_destroy(struct weston_log_subscription *sub) { assert(sub); + if (sub->owner->destroy_subscription) + sub->owner->destroy_subscription(sub->owner); + if (sub->source->destroy_subscription) sub->source->destroy_subscription(sub, sub->source->user_data); @@ -710,13 +713,8 @@ weston_log_scope_destroy(struct weston_log_scope *scope) if (!scope) return; - wl_list_for_each_safe(sub, sub_tmp, &scope->subscription_list, source_link) { - /* destroy each subscription */ - if (sub->owner->destroy_subscription) - sub->owner->destroy_subscription(sub->owner); - + wl_list_for_each_safe(sub, sub_tmp, &scope->subscription_list, source_link) weston_log_subscription_destroy(sub); - } wl_list_remove(&scope->compositor_link); free(scope->name); @@ -944,14 +942,8 @@ weston_log_subscriber_release(struct weston_log_subscriber *subscriber) { struct weston_log_subscription *sub, *sub_tmp; - wl_list_for_each_safe(sub, sub_tmp, &subscriber->subscription_list, owner_link) { - /* destroy each subscription */ - if (sub->owner->destroy_subscription) - sub->owner->destroy_subscription(sub->owner); - + wl_list_for_each_safe(sub, sub_tmp, &subscriber->subscription_list, owner_link) weston_log_subscription_destroy(sub); - } - } /** Destroy a file type or a flight-rec type subscriber. From 9aaaf96a6a0563a678c8bc3e65f126fae5b62f96 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Tue, 4 Feb 2020 00:05:47 -0300 Subject: [PATCH 1336/1642] compositor: destroy log context only after the destruction of subscribers Before commit "weston-log: destroy subscriptions with destruction of subscribers", we had to destroy subscribers before the log context. Currently there's no required order, both are valid. But since we've created log context before the subscribers, we can destroy it after them. This is a style change and also a prove that now this order is valid as well. Signed-off-by: Leandro Ribeiro --- compositor/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compositor/main.c b/compositor/main.c index 0a8dbe84b..02944f7b2 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -3380,9 +3380,9 @@ wet_main(int argc, char *argv[]) protocol_scope = NULL; weston_log_scope_destroy(log_scope); log_scope = NULL; - weston_log_ctx_destroy(log_ctx); weston_log_subscriber_destroy(logger); weston_log_subscriber_destroy(flight_rec); + weston_log_ctx_destroy(log_ctx); out_signals: for (i = ARRAY_LENGTH(signals) - 1; i >= 0; i--) From d65483ec75cc8c0dc29353d3393cb99bed42ee3c Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Mon, 3 Feb 2020 23:45:59 -0300 Subject: [PATCH 1337/1642] weston-log-wayland: make stream_destroy() use weston_log_subscriber_release() Make stream_destroy() use weston_log_subscriber_release(). This avoids code duplication and allow us to destroy weston_log_subscriber_get_only_subscription(), since it's being used only in this case and it's internal. Calls for weson_log_subscriber_release() leads to weston_log_debug_wayland_to_destroy(), which should not send an error event when the stream has already been closed. Also, stream_destroy() shouldn't lead to an event error, as it is a wl_resource destroy handler. So close the stream before calling weston_log_subscriber_release() in stream_destroy() Signed-off-by: Leandro Ribeiro --- libweston/weston-log-internal.h | 3 --- libweston/weston-log-wayland.c | 18 +++++------------- libweston/weston-log.c | 24 ------------------------ 3 files changed, 5 insertions(+), 40 deletions(-) diff --git a/libweston/weston-log-internal.h b/libweston/weston-log-internal.h index 02a05f19d..f1bfa6a55 100644 --- a/libweston/weston-log-internal.h +++ b/libweston/weston-log-internal.h @@ -72,9 +72,6 @@ weston_log_subscription_create(struct weston_log_subscriber *owner, void weston_log_subscription_destroy(struct weston_log_subscription *sub); -struct weston_log_subscription * -weston_log_subscriber_get_only_subscription(struct weston_log_subscriber *subscriber); - void weston_log_subscription_add(struct weston_log_scope *scope, struct weston_log_subscription *sub); diff --git a/libweston/weston-log-wayland.c b/libweston/weston-log-wayland.c index 732439f2c..0add77286 100644 --- a/libweston/weston-log-wayland.c +++ b/libweston/weston-log-wayland.c @@ -162,7 +162,9 @@ static void weston_log_debug_wayland_to_destroy(struct weston_log_subscriber *sub) { struct weston_log_debug_wayland *stream = to_weston_log_debug_wayland(sub); - stream_close_on_failure(stream, "debug name removed"); + + if (stream->fd != -1) + stream_close_on_failure(stream, "debug name removed"); } static struct weston_log_debug_wayland * @@ -201,20 +203,10 @@ static void stream_destroy(struct wl_resource *stream_resource) { struct weston_log_debug_wayland *stream; - struct weston_log_subscription *sub = NULL; - stream = wl_resource_get_user_data(stream_resource); - if (stream->fd != -1) - close(stream->fd); - - sub = weston_log_subscriber_get_only_subscription(&stream->base); - - /* we can have a zero subscription if clients tried to subscribe - * to a non-existent scope */ - if (sub) - weston_log_subscription_destroy(sub); - + stream_close_unlink(stream); + weston_log_subscriber_release(&stream->base); free(stream); } diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 7d85a6be9..b6f46c31a 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -293,30 +293,6 @@ weston_log_subscription_destroy(struct weston_log_subscription *sub) free(sub); } -/** Retrieve a subscription by using the subscriber - * - * This is useful when trying to find a subscription from the subscriber by - * having only access to the stream. - * - * @param subscriber the subscriber in question - * @returns a weston_log_subscription object - * - * @memberof weston_log_subscription - */ -struct weston_log_subscription * -weston_log_subscriber_get_only_subscription(struct weston_log_subscriber *subscriber) -{ - struct weston_log_subscription *sub; - /* unlikely, but can happen */ - if (wl_list_length(&subscriber->subscription_list) == 0) - return NULL; - - assert(wl_list_length(&subscriber->subscription_list) == 1); - - return wl_container_of(subscriber->subscription_list.prev, - sub, owner_link); -} - /** Adds the subscription \c sub to the subscription list of the * scope. * From 598d3a15b7beeb4d994d89b54ddb8e9ae24e9da9 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 10 Feb 2020 15:49:20 +0100 Subject: [PATCH 1338/1642] clients/window: fail earlier when frame_create fails This adds a new NULL check to fail earlier when frame_create fails. This can happen because PNG files couldn't be loaded from the data directory. Signed-off-by: Simon Ser --- clients/window.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clients/window.c b/clients/window.c index 1bb9f3dc3..970d54ce4 100644 --- a/clients/window.c +++ b/clients/window.c @@ -2577,6 +2577,10 @@ window_frame_create(struct window *window, void *data) frame = xzalloc(sizeof *frame); frame->frame = frame_create(window->display->theme, 0, 0, buttons, window->title, NULL); + if (!frame->frame) { + free(frame); + return NULL; + } frame->widget = window_add_widget(window, frame); frame->child = widget_add_widget(frame->widget, data); From dddb592cfb4664b43477e2217e7cb6196205a6b0 Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Fri, 20 Dec 2019 14:28:44 +1300 Subject: [PATCH 1339/1642] gl-renderer: Move EGL client extension handling earlier EGL client extensions are not tied to the EGLDisplay we create, and have an effect on how we create the EGLDisplay. Since we're using this to look for EGL_EXT_platform_base, it makes more sense for this to be near the start of the GL renderer initialization. Signed-off-by: Scott Anderson --- libweston/renderer-gl/egl-glue.c | 6 ++---- libweston/renderer-gl/gl-renderer-internal.h | 3 +++ libweston/renderer-gl/gl-renderer.c | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/libweston/renderer-gl/egl-glue.c b/libweston/renderer-gl/egl-glue.c index d96efeaeb..aea2b23c7 100644 --- a/libweston/renderer-gl/egl-glue.c +++ b/libweston/renderer-gl/egl-glue.c @@ -455,8 +455,8 @@ gl_renderer_get_egl_config(struct gl_renderer *gr, return egl_config; } -static void -renderer_setup_egl_client_extensions(struct gl_renderer *gr) +void +gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr) { const char *extensions; @@ -593,7 +593,5 @@ gl_renderer_setup_egl_extensions(struct weston_compositor *ec) "to missing EGL_KHR_wait_sync extension\n"); } - renderer_setup_egl_client_extensions(gr); - return 0; } diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index 00f617a90..f38bc3445 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -135,6 +135,9 @@ gl_renderer_get_egl_config(struct gl_renderer *gr, const uint32_t *drm_formats, unsigned drm_formats_count); +void +gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr); + int gl_renderer_setup_egl_extensions(struct weston_compositor *ec); diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 79285c008..d481134a5 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -3480,6 +3480,8 @@ gl_renderer_display_create(struct weston_compositor *ec, if (gr == NULL) return -1; + gl_renderer_setup_egl_client_extensions(gr); + gr->base.read_pixels = gl_renderer_read_pixels; gr->base.repaint_output = gl_renderer_repaint_output; gr->base.flush_damage = gl_renderer_flush_damage; From 77254154782fbe3557e2db712ea023526b99a8c6 Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Fri, 20 Dec 2019 14:39:38 +1300 Subject: [PATCH 1340/1642] gl-renderer: Move get_platform_display to EGL client setup This is to put more of the EGL client extension handling in the same place. This also adds a boolean to check if EGL_EXT_platform_base is supported, similar to other extensions we check. Signed-off-by: Scott Anderson --- libweston/renderer-gl/egl-glue.c | 8 +++++-- libweston/renderer-gl/gl-renderer-internal.h | 3 +++ libweston/renderer-gl/gl-renderer.c | 24 ++++---------------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/libweston/renderer-gl/egl-glue.c b/libweston/renderer-gl/egl-glue.c index aea2b23c7..1620ab4e2 100644 --- a/libweston/renderer-gl/egl-glue.c +++ b/libweston/renderer-gl/egl-glue.c @@ -466,11 +466,15 @@ gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr) return; } - if (weston_check_egl_extension(extensions, "EGL_EXT_platform_base")) + if (weston_check_egl_extension(extensions, "EGL_EXT_platform_base")) { + gr->get_platform_display = + (void *) eglGetProcAddress("eglGetPlatformDisplayEXT"); gr->create_platform_window = (void *) eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); - else + gr->has_platform_base = true; + } else { weston_log("warning: EGL_EXT_platform_base not supported.\n"); + } } int diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index f38bc3445..b8127f931 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -63,7 +63,10 @@ struct gl_renderer { PFNEGLCREATEIMAGEKHRPROC create_image; PFNEGLDESTROYIMAGEKHRPROC destroy_image; PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage; + + PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display; PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC create_platform_window; + bool has_platform_base; bool has_unpack_subimage; diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index d481134a5..e5285e281 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -209,8 +209,6 @@ struct timeline_render_point { struct wl_event_source *event_source; }; -static PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = NULL; - static inline const char * dump_format(uint32_t format, char out[4]) { @@ -3495,22 +3493,10 @@ gl_renderer_display_create(struct weston_compositor *ec, gr->egl_display = NULL; /* extension_suffix is supported */ - if (supports) { - if (!get_platform_display) { - get_platform_display = (void *) eglGetProcAddress( - "eglGetPlatformDisplayEXT"); - } - - /* also wrap this in the supports check because - * eglGetProcAddress can return non-NULL and still not - * support the feature at runtime, so ensure the - * appropriate extension checks have been done. */ - if (get_platform_display && platform) { - gr->egl_display = get_platform_display(platform, - native_display, - NULL); - } - } + if (gr->has_platform_base && supports) + gr->egl_display = gr->get_platform_display(platform, + native_display, + NULL); if (!gr->egl_display) { weston_log("warning: either no EGL_EXT_platform_base " @@ -3519,7 +3505,7 @@ gl_renderer_display_create(struct weston_compositor *ec, gr->egl_display = eglGetDisplay(native_display); } - if (gr->egl_display == EGL_NO_DISPLAY) { + if (!gr->egl_display) { weston_log("failed to create display\n"); goto fail; } From 4ed58b1d47d7bf206a3e1b749dd71160258b9bbc Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Fri, 20 Dec 2019 14:55:39 +1300 Subject: [PATCH 1341/1642] gl-renderer: Move platform extension checks to EGL client setup This removes the duplicate checks for EGL_EXT_platform_base. Signed-off-by: Scott Anderson --- libweston/renderer-gl/egl-glue.c | 60 ++++++++++- libweston/renderer-gl/gl-renderer-internal.h | 5 +- libweston/renderer-gl/gl-renderer.c | 107 ++----------------- 3 files changed, 71 insertions(+), 101 deletions(-) diff --git a/libweston/renderer-gl/egl-glue.c b/libweston/renderer-gl/egl-glue.c index 1620ab4e2..4c426aa6f 100644 --- a/libweston/renderer-gl/egl-glue.c +++ b/libweston/renderer-gl/egl-glue.c @@ -455,17 +455,49 @@ gl_renderer_get_egl_config(struct gl_renderer *gr, return egl_config; } -void +static const char * +platform_to_extension(EGLenum platform) +{ + switch (platform) { + case EGL_PLATFORM_GBM_KHR: + return "gbm"; + case EGL_PLATFORM_WAYLAND_KHR: + return "wayland"; + case EGL_PLATFORM_X11_KHR: + return "x11"; + case EGL_PLATFORM_SURFACELESS_MESA: + return "surfaceless"; + default: + assert(0 && "bad EGL platform enum"); + } +} + +/** Checks for EGL client extensions (i.e. independent of EGL display), + * loads the function pointers, and checks if the platform is supported. + * + * \param gr The OpenGL renderer + * \return 0 for success, -1 if platform is unsupported + * + * This function checks whether a specific platform_* extension is supported + * by EGL by checking in order "EGL_KHR_platform_foo", "EGL_EXT_platform_foo", + * and "EGL_MESA_platform_foo" in order, for some platform "foo". + */ +int gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr) { const char *extensions; + const char *extension_suffix = platform_to_extension(gr->platform); + char s[64]; extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (!extensions) { weston_log("Retrieving EGL client extension string failed.\n"); - return; + return 0; } + gl_renderer_log_extensions("EGL client extensions", + extensions); + if (weston_check_egl_extension(extensions, "EGL_EXT_platform_base")) { gr->get_platform_display = (void *) eglGetProcAddress("eglGetPlatformDisplayEXT"); @@ -474,7 +506,31 @@ gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr) gr->has_platform_base = true; } else { weston_log("warning: EGL_EXT_platform_base not supported.\n"); + + /* Surfaceless is unusable without platform_base extension */ + if (gr->platform == EGL_PLATFORM_SURFACELESS_MESA) + return -1; + + return 0; } + + snprintf(s, sizeof s, "EGL_KHR_platform_%s", extension_suffix); + if (weston_check_egl_extension(extensions, s)) + return 0; + + snprintf(s, sizeof s, "EGL_EXT_platform_%s", extension_suffix); + if (weston_check_egl_extension(extensions, s)) + return 0; + + snprintf(s, sizeof s, "EGL_MESA_platform_%s", extension_suffix); + if (weston_check_egl_extension(extensions, s)) + return 0; + + /* at this point we definitely have some platform extensions but + * haven't found the supplied platform, so chances are it's + * not supported. */ + + return -1; } int diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index b8127f931..9454fa039 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -129,6 +129,9 @@ get_renderer(struct weston_compositor *ec) void gl_renderer_print_egl_error_state(void); +void +gl_renderer_log_extensions(const char *name, const char *extensions); + void log_egl_config_info(EGLDisplay egldpy, EGLConfig eglconfig); @@ -138,7 +141,7 @@ gl_renderer_get_egl_config(struct gl_renderer *gr, const uint32_t *drm_formats, unsigned drm_formats_count); -void +int gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr); int diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index e5285e281..29b689628 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -3023,8 +3023,8 @@ shader_release(struct gl_shader *shader) shader->program = 0; } -static void -log_extensions(const char *name, const char *extensions) +void +gl_renderer_log_extensions(const char *name, const char *extensions) { const char *p, *end; int l; @@ -3061,7 +3061,7 @@ log_egl_info(EGLDisplay egldpy) weston_log("EGL client APIs: %s\n", str ? str : "(null)"); str = eglQueryString(egldpy, EGL_EXTENSIONS); - log_extensions("EGL extensions", str ? str : "(null)"); + gl_renderer_log_extensions("EGL extensions", str ? str : "(null)"); } static void @@ -3082,7 +3082,7 @@ log_gl_info(void) weston_log("GL renderer: %s\n", str ? str : "(null)"); str = (char *)glGetString(GL_EXTENSIONS); - log_extensions("GL extensions", str ? str : "(null)"); + gl_renderer_log_extensions("GL extensions", str ? str : "(null)"); } static void @@ -3330,85 +3330,6 @@ gl_renderer_destroy(struct weston_compositor *ec) free(gr); } -/** Checks whether a platform EGL client extension is supported - * - * \param ec The weston compositor - * \param extension_suffix The EGL client extension suffix - * \return 1 if supported, 0 if using fallbacks, -1 unsupported - * - * This function checks whether a specific platform_* extension is supported - * by EGL. - * - * The extension suffix should be the suffix of the platform extension (that - * specifies a platform argument as defined in EGL_EXT_platform_base). For - * example, passing "foo" will check whether either "EGL_KHR_platform_foo", - * "EGL_EXT_platform_foo", or "EGL_MESA_platform_foo" is supported. - * - * The return value is 1: - * - if the supplied EGL client extension is supported. - * The return value is 0: - * - if the platform_base client extension isn't supported so will - * fallback to eglGetDisplay and friends. - * The return value is -1: - * - if the supplied EGL client extension is not supported. - */ -static int -gl_renderer_supports(struct weston_compositor *ec, - const char *extension_suffix) -{ - static const char *extensions = NULL; - char s[64]; - - if (!extensions) { - extensions = (const char *) eglQueryString( - EGL_NO_DISPLAY, EGL_EXTENSIONS); - - if (!extensions) - return 0; - - log_extensions("EGL client extensions", - extensions); - } - - if (!weston_check_egl_extension(extensions, "EGL_EXT_platform_base")) - return 0; - - snprintf(s, sizeof s, "EGL_KHR_platform_%s", extension_suffix); - if (weston_check_egl_extension(extensions, s)) - return 1; - - snprintf(s, sizeof s, "EGL_EXT_platform_%s", extension_suffix); - if (weston_check_egl_extension(extensions, s)) - return 1; - - snprintf(s, sizeof s, "EGL_MESA_platform_%s", extension_suffix); - if (weston_check_egl_extension(extensions, s)) - return 1; - - /* at this point we definitely have some platform extensions but - * haven't found the supplied platform, so chances are it's - * not supported. */ - - return -1; -} - -static const char * -platform_to_extension(EGLenum platform) -{ - switch (platform) { - case EGL_PLATFORM_GBM_KHR: - return "gbm"; - case EGL_PLATFORM_WAYLAND_KHR: - return "wayland"; - case EGL_PLATFORM_X11_KHR: - return "x11"; - case EGL_PLATFORM_SURFACELESS_MESA: - return "surfaceless"; - default: - assert(0 && "bad EGL platform enum"); - } -} - static void output_handle_destroy(struct wl_listener *listener, void *data) { @@ -3461,24 +3382,15 @@ gl_renderer_display_create(struct weston_compositor *ec, { struct gl_renderer *gr; EGLint major, minor; - int supports = 0; - - if (platform) { - supports = gl_renderer_supports( - ec, platform_to_extension(platform)); - if (supports < 0) - return -1; - } - - /* Surfaceless is unusable without platform_base extension */ - if (supports == 0 && platform == EGL_PLATFORM_SURFACELESS_MESA) - return -1; gr = zalloc(sizeof *gr); if (gr == NULL) return -1; - gl_renderer_setup_egl_client_extensions(gr); + gr->platform = platform; + + if (gl_renderer_setup_egl_client_extensions(gr) < 0) + goto fail; gr->base.read_pixels = gl_renderer_read_pixels; gr->base.repaint_output = gl_renderer_repaint_output; @@ -3489,11 +3401,10 @@ gl_renderer_display_create(struct weston_compositor *ec, gr->base.surface_get_content_size = gl_renderer_surface_get_content_size; gr->base.surface_copy_content = gl_renderer_surface_copy_content; - gr->platform = platform; gr->egl_display = NULL; /* extension_suffix is supported */ - if (gr->has_platform_base && supports) + if (gr->has_platform_base) gr->egl_display = gr->get_platform_display(platform, native_display, NULL); From 4ed62d47cc0d14a6331d8e3190a9f67d0e5d7cac Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Fri, 20 Dec 2019 15:07:43 +1300 Subject: [PATCH 1342/1642] gl-renderer: Move EGL display creation to egl-glue.c It makes more sense for it to be there instead. Signed-off-by: Scott Anderson --- libweston/renderer-gl/egl-glue.c | 36 ++++++++++++++++++++ libweston/renderer-gl/gl-renderer-internal.h | 3 ++ libweston/renderer-gl/gl-renderer.c | 24 +------------ 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/libweston/renderer-gl/egl-glue.c b/libweston/renderer-gl/egl-glue.c index 4c426aa6f..28be4ffe4 100644 --- a/libweston/renderer-gl/egl-glue.c +++ b/libweston/renderer-gl/egl-glue.c @@ -455,6 +455,42 @@ gl_renderer_get_egl_config(struct gl_renderer *gr, return egl_config; } +int +gl_renderer_setup_egl_display(struct gl_renderer *gr, + void *native_display) +{ + gr->egl_display = NULL; + + /* extension_suffix is supported */ + if (gr->has_platform_base) + gr->egl_display = gr->get_platform_display(gr->platform, + native_display, + NULL); + + if (!gr->egl_display) { + weston_log("warning: either no EGL_EXT_platform_base " + "support or specific platform support; " + "falling back to eglGetDisplay.\n"); + gr->egl_display = eglGetDisplay(native_display); + } + + if (!gr->egl_display) { + weston_log("failed to create display\n"); + return -1; + } + + if (!eglInitialize(gr->egl_display, NULL, NULL)) { + weston_log("failed to initialize display\n"); + goto fail; + } + + return 0; + +fail: + gl_renderer_print_egl_error_state(); + return -1; +} + static const char * platform_to_extension(EGLenum platform) { diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index 9454fa039..6e1b095c2 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -141,6 +141,9 @@ gl_renderer_get_egl_config(struct gl_renderer *gr, const uint32_t *drm_formats, unsigned drm_formats_count); +int +gl_renderer_setup_egl_display(struct gl_renderer *gr, void *native_display); + int gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr); diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 29b689628..462f422c6 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -3381,7 +3381,6 @@ gl_renderer_display_create(struct weston_compositor *ec, unsigned drm_formats_count) { struct gl_renderer *gr; - EGLint major, minor; gr = zalloc(sizeof *gr); if (gr == NULL) @@ -3401,30 +3400,9 @@ gl_renderer_display_create(struct weston_compositor *ec, gr->base.surface_get_content_size = gl_renderer_surface_get_content_size; gr->base.surface_copy_content = gl_renderer_surface_copy_content; - gr->egl_display = NULL; - /* extension_suffix is supported */ - if (gr->has_platform_base) - gr->egl_display = gr->get_platform_display(platform, - native_display, - NULL); - - if (!gr->egl_display) { - weston_log("warning: either no EGL_EXT_platform_base " - "support or specific platform support; " - "falling back to eglGetDisplay.\n"); - gr->egl_display = eglGetDisplay(native_display); - } - - if (!gr->egl_display) { - weston_log("failed to create display\n"); + if (gl_renderer_setup_egl_display(gr, native_display) < 0) goto fail; - } - - if (!eglInitialize(gr->egl_display, &major, &minor)) { - weston_log("failed to initialize display\n"); - goto fail_with_error; - } log_egl_info(gr->egl_display); From 8bd46c75f9362774e83f53d934b24b170d021f04 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 17 Feb 2020 15:32:18 +0200 Subject: [PATCH 1343/1642] CI: build a Mesa shapshot for getImage/putImage on pbuffer swrast https://gitlab.freedesktop.org/mesa/mesa/commit/c7617d8908a970124321ce731b43d5996c3c5775 is necessary for running GL-renderer with llvmpipe in Gitlab CI. Signed-off-by: Pekka Paalanen --- .gitlab-ci.yml | 2 +- .gitlab-ci/debian-install.sh | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ce8126e2c..7ef8820e5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,7 +5,7 @@ variables: DEBIAN_VERSION: buster DEBIAN_EXEC: 'bash .gitlab-ci/debian-install.sh' - DEBIAN_TAG: '2019-12-13.0' + DEBIAN_TAG: '2020-02-18.1' DEBIAN_CONTAINER_IMAGE: $CI_REGISTRY_IMAGE/debian/$DEBIAN_VERSION:$DEBIAN_TAG diff --git a/.gitlab-ci/debian-install.sh b/.gitlab-ci/debian-install.sh index accbf1fd5..aaa9df724 100644 --- a/.gitlab-ci/debian-install.sh +++ b/.gitlab-ci/debian-install.sh @@ -2,6 +2,24 @@ set -o xtrace -o errexit +# These get temporary installed for building Mesa and then force-removed. +MESA_DEV_PKGS=" + bison + flex + gettext + libwayland-egl-backend-dev + libxrandr-dev + llvm-8-dev + python-mako + python3-mako + wayland-protocols +" + +# Needed for running the custom-built mesa +MESA_RUNTIME_PKGS=" + libllvm8 +" + echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list apt-get update apt-get -y --no-install-recommends install \ @@ -57,9 +75,11 @@ apt-get -y --no-install-recommends install \ python3-pip \ python3-setuptools \ xwayland \ + $MESA_RUNTIME_PKGS pip3 install --user git+https://github.com/mesonbuild/meson.git@0.49 +export PATH=$HOME/.local/bin:$PATH # for documentation pip3 install sphinx==2.1.0 --user pip3 install breathe==4.13.0.post0 --user @@ -75,5 +95,16 @@ cd build make install cd ../../ +apt-get -y --no-install-recommends install $MESA_DEV_PKGS +git clone --single-branch --branch master --shallow-since='2020-02-15' https://gitlab.freedesktop.org/mesa/mesa.git mesa +cd mesa +git checkout -b snapshot c7617d8908a970124321ce731b43d5996c3c5775 +meson build -Dauto_features=disabled \ + -Dgallium-drivers=swrast -Dvulkan-drivers= -Ddri-drivers= +ninja -C build install +cd .. +rm -rf mesa +apt-get -y --autoremove purge $MESA_DEV_PKGS + mkdir -p /tmp/.X11-unix chmod 777 /tmp/.X11-unix From ea3b7857f288a2ab3a75a0d5825fb0a4f3b98341 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 17 Feb 2020 17:30:41 +0200 Subject: [PATCH 1344/1642] Revert "CI: do not attempt to test GL-renderer" This reverts commit 1eae54714fd2ae38756b24ddc58a7d55bec5d14d. We install a fixed Mesa into the CI image, so these tests can pass now. Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/358 Signed-off-by: Pekka Paalanen --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7ef8820e5..c2054caa6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -69,7 +69,6 @@ build-native-meson-default-options: MESON_OPTIONS: > -Dwerror=true -Ddoc=true - -Dtest-gl-renderer=false extends: .build-native-meson build-native-meson-no-gl-renderer: From 401b8767e962f484031aaabae59ae6793b94ef20 Mon Sep 17 00:00:00 2001 From: ahe Date: Wed, 19 Feb 2020 19:43:52 +0100 Subject: [PATCH 1345/1642] fullscreen-shell/fullscreen-shell.c: prevent seat_created() from being called with l == NULL --- fullscreen-shell/fullscreen-shell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fullscreen-shell/fullscreen-shell.c b/fullscreen-shell/fullscreen-shell.c index e785246ab..17b25a3ce 100644 --- a/fullscreen-shell/fullscreen-shell.c +++ b/fullscreen-shell/fullscreen-shell.c @@ -931,7 +931,7 @@ wet_shell_init(struct weston_compositor *compositor, wl_signal_add(&compositor->seat_created_signal, &shell->seat_created_listener); wl_list_for_each(seat, &compositor->seat_list, link) - seat_created(NULL, seat); + seat_created(&shell->seat_created_listener, seat); wl_global_create(compositor->wl_display, &zwp_fullscreen_shell_v1_interface, 1, shell, From 9cb81e440a9e56800f44e5d6cdf72d981b76dd36 Mon Sep 17 00:00:00 2001 From: Veeresh Kadasani Date: Thu, 6 Feb 2020 11:00:21 +0900 Subject: [PATCH 1346/1642] ivi-application: fix grammar Signed-off-by: Veeresh Kadasani --- protocol/ivi-application.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/protocol/ivi-application.xml b/protocol/ivi-application.xml index 7e0ac66e1..f51b7f4b5 100644 --- a/protocol/ivi-application.xml +++ b/protocol/ivi-application.xml @@ -30,7 +30,7 @@ - This removes link from ivi_id to wl_surface and destroys ivi_surface. + This removes the link from ivi_id to wl_surface and destroys ivi_surface. The ID, ivi_id, is free and can be used for surface_create again. @@ -58,7 +58,7 @@ This interface is exposed as a global singleton. This interface is implemented by servers that provide IVI-style user interfaces. - It allows clients to associate a ivi_surface with wl_surface. + It allows clients to associate an ivi_surface with wl_surface. @@ -77,15 +77,15 @@ 3. destroy the ivi_surface 4. create ivi_surface for the wl_surface (with the same or another ivi_id as before) - surface_create will create a interface:ivi_surface with numeric ID; ivi_id in + surface_create will create an interface:ivi_surface with numeric ID; ivi_id in ivi compositor. These ivi_ids are defined as unique in the system to identify it inside of ivi compositor. The ivi compositor implements business logic how to - set properties of the surface with ivi_id according to status of the system. + set properties of the surface with ivi_id according to the status of the system. E.g. a unique ID for Car Navigation application is used for implementing special logic of the application about where it shall be located. - The server regards following cases as protocol errors and disconnects the client. + The server regards the following cases as protocol errors and disconnects the client. - wl_surface already has another role. - - ivi_id is already assigned to an another wl_surface. + - ivi_id is already assigned to another wl_surface. If client destroys ivi_surface or wl_surface which is assigne to the ivi_surface, ivi_id which is assigned to the ivi_surface is free for reuse. From 8555877c6c6f6098179b2f8cbfdfced5ce164e7a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 14 Feb 2020 14:20:05 +0200 Subject: [PATCH 1347/1642] clients: transformed does not recognize -d It has no such command line option. Signed-off-by: Pekka Paalanen --- clients/transformed.c | 1 - 1 file changed, 1 deletion(-) diff --git a/clients/transformed.c b/clients/transformed.c index dd88fcd4b..ab9459fa1 100644 --- a/clients/transformed.c +++ b/clients/transformed.c @@ -227,7 +227,6 @@ static void usage(int error_code) { fprintf(stderr, "Usage: transformed [OPTIONS]\n\n" - " -d\t\tUse \"driver\" fullscreen method\n" " -w \tSet window width to \n" " -h \tSet window height to \n" " --help\tShow this help text\n\n"); From 0df4477924e2878d1bc80113a4b1ba20bff3c954 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 14 Feb 2020 14:50:03 +0200 Subject: [PATCH 1348/1642] libweston: document weston_transformed_*() Clarifies which direction the transformation happens. All exported function need documentation. Signed-off-by: Pekka Paalanen --- libweston/compositor.c | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index f7e2afdd7..d085126aa 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -608,6 +608,24 @@ weston_view_to_global_float(struct weston_view *view, } } +/** Transform a point to buffer coordinates + * + * \param width Surface width. + * \param height Surface height. + * \param transform Buffer transform. + * \param scale Buffer scale. + * \param sx Surface x coordinate of a point. + * \param sy Surface y coordinate of a point. + * \param[out] bx Buffer x coordinate of the point. + * \param[out] by Buffer Y coordinate of the point. + * + * Converts the given surface-local coordinates to buffer coordinates + * according to the given buffer transform and scale. + * This ignores wp_viewport. + * + * The given width and height must be the result of inverse scaled and + * inverse transformed buffer size. + */ WL_EXPORT void weston_transformed_coord(int width, int height, enum wl_output_transform transform, @@ -654,6 +672,23 @@ weston_transformed_coord(int width, int height, *by *= scale; } +/** Transform a rectangle to buffer coordinates + * + * \param width Surface width. + * \param height Surface height. + * \param transform Buffer transform. + * \param scale Buffer scale. + * \param rect Rectangle in surface coordinates. + * \return Rectangle in buffer coordinates. + * + * Converts the given surface-local rectangle to buffer coordinates + * according to the given buffer transform and scale. The resulting + * rectangle is guaranteed to be well-formed. + * This ignores wp_viewport. + * + * The given width and height must be the result of inverse scaled and + * inverse transformed buffer size. + */ WL_EXPORT pixman_box32_t weston_transformed_rect(int width, int height, enum wl_output_transform transform, @@ -744,6 +779,24 @@ weston_matrix_transform_region(pixman_region32_t *dest, free(dest_rects); } +/** Transform a region to buffer coordinates + * + * \param width Surface width. + * \param height Surface height. + * \param transform Buffer transform. + * \param scale Buffer scale. + * \param[in] src Region in surface coordinates. + * \param[out] dest Resulting region in buffer coordinates. + * + * Converts the given surface-local region to buffer coordinates + * according to the given buffer transform and scale. + * This ignores wp_viewport. + * + * The given width and height must be the result of inverse scaled and + * inverse transformed buffer size. + * + * src and dest are allowed to point to the same memory for in-place conversion. + */ WL_EXPORT void weston_transformed_region(int width, int height, enum wl_output_transform transform, From 8060d826b7a03f4765eaa14be966c520b0aacca9 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 6 Feb 2020 15:27:54 +0200 Subject: [PATCH 1349/1642] Redefine output rotations It was discovered in issue #99 that the implementations of the 90 and 270 degree rotations were actually the inverse of what the Wayland specification spelled out. This patch fixes the libweston implementation to follow the specification. As a result, the behaviour of the the weston.ini transform key also changes. To force all users to re-think their configuration, the transform key values are also changed. Since Weston and libweston change their behaviour, the handling of clients' buffer transform changes too. All the functions had their 90/270 cases simply swapped, probably due to confusion of whether WL_OUTPUT_TRANSFORM_* refers to rotating the monitor or the content. Hint: a key to understanding weston_matrix_rotate_xy(m, c, s) is that the rotation matrix is formed as c -s s c that is, it's column-major. This fooled me at first. Fixing window.c fixes weston-terminal and weston-transformed. In simple-damage, window_get_transformed_ball() is fixed to follow the proper transform definitions, but the fix to the viewport path in redraw() is purely mechanical. The viewport path looks broken to me in the presence of any transform, but it is not this patch's job to fix it. Screen-share fix just repeats the general code fix pattern, I did not even try to understand that bit. https://gitlab.freedesktop.org/wayland/weston/issues/99 Signed-off-by: Pekka Paalanen --- clients/simple-damage.c | 32 ++++++++++---------- clients/transformed.c | 3 ++ clients/window.c | 24 +++++++-------- compositor/main.c | 16 +++++----- compositor/screen-share.c | 8 ++--- libweston/compositor.c | 64 +++++++++++++++++++-------------------- man/weston-drm.man | 5 +-- man/weston.ini.man | 20 ++++++------ 8 files changed, 88 insertions(+), 84 deletions(-) diff --git a/clients/simple-damage.c b/clients/simple-damage.c index 0458bd064..821b42b54 100644 --- a/clients/simple-damage.c +++ b/clients/simple-damage.c @@ -461,32 +461,32 @@ window_get_transformed_ball(struct window *window, float *bx, float *by) *by = wy; break; case WL_OUTPUT_TRANSFORM_90: - *bx = window->height - wy; - *by = wx; + *bx = wy; + *by = window->width - wx; break; case WL_OUTPUT_TRANSFORM_180: *bx = window->width - wx; *by = window->height - wy; break; case WL_OUTPUT_TRANSFORM_270: - *bx = wy; - *by = window->width - wx; + *bx = window->height - wy; + *by = wx; break; case WL_OUTPUT_TRANSFORM_FLIPPED: *bx = window->width - wx; *by = wy; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: - *bx = window->height - wy; - *by = window->width - wx; + *bx = wy; + *by = wx; break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: *bx = wx; *by = window->height - wy; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: - *bx = wy; - *by = wx; + *bx = window->height - wy; + *by = window->width - wx; break; } @@ -570,32 +570,32 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) off_x = tx; break; case WL_OUTPUT_TRANSFORM_90: - off_y = tx; - off_x = bwidth - ty; + off_y = bheight - tx; + off_x = ty; break; case WL_OUTPUT_TRANSFORM_180: off_y = bheight - ty; off_x = bwidth - tx; break; case WL_OUTPUT_TRANSFORM_270: - off_y = bheight - tx; - off_x = ty; + off_y = tx; + off_x = bwidth - ty; break; case WL_OUTPUT_TRANSFORM_FLIPPED: off_y = ty; off_x = bwidth - tx; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: - off_y = bheight - tx; - off_x = bwidth - ty; + off_y = tx; + off_x = ty; break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: off_y = bheight - ty; off_x = tx; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: - off_y = tx; - off_x = ty; + off_y = bheight - tx; + off_x = bwidth - ty; break; } wp_viewport_set_source(window->viewport, diff --git a/clients/transformed.c b/clients/transformed.c index ab9459fa1..59f44bcaa 100644 --- a/clients/transformed.c +++ b/clients/transformed.c @@ -231,6 +231,9 @@ usage(int error_code) " -h \tSet window height to \n" " --help\tShow this help text\n\n"); + fprintf(stderr, "This version has been fixed for " + "https://gitlab.freedesktop.org/wayland/weston/issues/99 .\n"); + exit(error_code); } diff --git a/clients/window.c b/clients/window.c index 970d54ce4..5a75bd407 100644 --- a/clients/window.c +++ b/clients/window.c @@ -1832,14 +1832,14 @@ widget_cairo_update_transform(struct widget *widget, cairo_t *cr) translate_y = 0; break; case WL_OUTPUT_TRANSFORM_90: - angle = M_PI_2; - translate_x = surface_height; - translate_y = 0; + angle = M_PI + M_PI_2; + translate_x = 0; + translate_y = surface_width; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: - angle = M_PI_2; - translate_x = surface_height; - translate_y = surface_width; + angle = M_PI + M_PI_2; + translate_x = 0; + translate_y = 0; break; case WL_OUTPUT_TRANSFORM_180: angle = M_PI; @@ -1852,14 +1852,14 @@ widget_cairo_update_transform(struct widget *widget, cairo_t *cr) translate_y = surface_height; break; case WL_OUTPUT_TRANSFORM_270: - angle = M_PI + M_PI_2; - translate_x = 0; - translate_y = surface_width; + angle = M_PI_2; + translate_x = surface_height; + translate_y = 0; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: - angle = M_PI + M_PI_2; - translate_x = 0; - translate_y = 0; + angle = M_PI_2; + translate_x = surface_height; + translate_y = surface_width; break; } diff --git a/compositor/main.c b/compositor/main.c index 02944f7b2..d5a7f04ef 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1107,14 +1107,14 @@ weston_choose_default_backend(void) } static const struct { const char *name; uint32_t token; } transforms[] = { - { "normal", WL_OUTPUT_TRANSFORM_NORMAL }, - { "90", WL_OUTPUT_TRANSFORM_90 }, - { "180", WL_OUTPUT_TRANSFORM_180 }, - { "270", WL_OUTPUT_TRANSFORM_270 }, - { "flipped", WL_OUTPUT_TRANSFORM_FLIPPED }, - { "flipped-90", WL_OUTPUT_TRANSFORM_FLIPPED_90 }, - { "flipped-180", WL_OUTPUT_TRANSFORM_FLIPPED_180 }, - { "flipped-270", WL_OUTPUT_TRANSFORM_FLIPPED_270 }, + { "normal", WL_OUTPUT_TRANSFORM_NORMAL }, + { "rotate-90", WL_OUTPUT_TRANSFORM_90 }, + { "rotate-180", WL_OUTPUT_TRANSFORM_180 }, + { "rotate-270", WL_OUTPUT_TRANSFORM_270 }, + { "flipped", WL_OUTPUT_TRANSFORM_FLIPPED }, + { "flipped-rotate-90", WL_OUTPUT_TRANSFORM_FLIPPED_90 }, + { "flipped-rotate-180", WL_OUTPUT_TRANSFORM_FLIPPED_180 }, + { "flipped-rotate-270", WL_OUTPUT_TRANSFORM_FLIPPED_270 }, }; WL_EXPORT int diff --git a/compositor/screen-share.c b/compositor/screen-share.c index c8256faca..62b871bf3 100644 --- a/compositor/screen-share.c +++ b/compositor/screen-share.c @@ -551,8 +551,8 @@ output_compute_transform(struct weston_output *output, break; case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_FLIPPED_90: - pixman_transform_rotate(transform, NULL, 0, pixman_fixed_1); - pixman_transform_translate(transform, NULL, fh, 0); + pixman_transform_rotate(transform, NULL, 0, -pixman_fixed_1); + pixman_transform_translate(transform, NULL, 0, fw); break; case WL_OUTPUT_TRANSFORM_180: case WL_OUTPUT_TRANSFORM_FLIPPED_180: @@ -561,8 +561,8 @@ output_compute_transform(struct weston_output *output, break; case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_270: - pixman_transform_rotate(transform, NULL, 0, -pixman_fixed_1); - pixman_transform_translate(transform, NULL, 0, fw); + pixman_transform_rotate(transform, NULL, 0, pixman_fixed_1); + pixman_transform_translate(transform, NULL, fh, 0); break; } diff --git a/libweston/compositor.c b/libweston/compositor.c index d085126aa..29c0ab18a 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -643,12 +643,12 @@ weston_transformed_coord(int width, int height, *by = sy; break; case WL_OUTPUT_TRANSFORM_90: - *bx = height - sy; - *by = sx; + *bx = sy; + *by = width - sx; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: - *bx = height - sy; - *by = width - sx; + *bx = sy; + *by = sx; break; case WL_OUTPUT_TRANSFORM_180: *bx = width - sx; @@ -659,12 +659,12 @@ weston_transformed_coord(int width, int height, *by = height - sy; break; case WL_OUTPUT_TRANSFORM_270: - *bx = sy; - *by = width - sx; + *bx = height - sy; + *by = sx; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: - *bx = sy; - *by = sx; + *bx = height - sy; + *by = width - sx; break; } @@ -830,10 +830,10 @@ weston_transformed_region(int width, int height, dest_rects[i].y2 = src_rects[i].y2; break; case WL_OUTPUT_TRANSFORM_90: - dest_rects[i].x1 = height - src_rects[i].y2; - dest_rects[i].y1 = src_rects[i].x1; - dest_rects[i].x2 = height - src_rects[i].y1; - dest_rects[i].y2 = src_rects[i].x2; + dest_rects[i].x1 = src_rects[i].y1; + dest_rects[i].y1 = width - src_rects[i].x2; + dest_rects[i].x2 = src_rects[i].y2; + dest_rects[i].y2 = width - src_rects[i].x1; break; case WL_OUTPUT_TRANSFORM_180: dest_rects[i].x1 = width - src_rects[i].x2; @@ -842,10 +842,10 @@ weston_transformed_region(int width, int height, dest_rects[i].y2 = height - src_rects[i].y1; break; case WL_OUTPUT_TRANSFORM_270: - dest_rects[i].x1 = src_rects[i].y1; - dest_rects[i].y1 = width - src_rects[i].x2; - dest_rects[i].x2 = src_rects[i].y2; - dest_rects[i].y2 = width - src_rects[i].x1; + dest_rects[i].x1 = height - src_rects[i].y2; + dest_rects[i].y1 = src_rects[i].x1; + dest_rects[i].x2 = height - src_rects[i].y1; + dest_rects[i].y2 = src_rects[i].x2; break; case WL_OUTPUT_TRANSFORM_FLIPPED: dest_rects[i].x1 = width - src_rects[i].x2; @@ -854,10 +854,10 @@ weston_transformed_region(int width, int height, dest_rects[i].y2 = src_rects[i].y2; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: - dest_rects[i].x1 = height - src_rects[i].y2; - dest_rects[i].y1 = width - src_rects[i].x2; - dest_rects[i].x2 = height - src_rects[i].y1; - dest_rects[i].y2 = width - src_rects[i].x1; + dest_rects[i].x1 = src_rects[i].y1; + dest_rects[i].y1 = src_rects[i].x1; + dest_rects[i].x2 = src_rects[i].y2; + dest_rects[i].y2 = src_rects[i].x2; break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: dest_rects[i].x1 = src_rects[i].x1; @@ -866,10 +866,10 @@ weston_transformed_region(int width, int height, dest_rects[i].y2 = height - src_rects[i].y1; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: - dest_rects[i].x1 = src_rects[i].y1; - dest_rects[i].y1 = src_rects[i].x1; - dest_rects[i].x2 = src_rects[i].y2; - dest_rects[i].y2 = src_rects[i].x2; + dest_rects[i].x1 = height - src_rects[i].y2; + dest_rects[i].y1 = width - src_rects[i].x2; + dest_rects[i].x2 = height - src_rects[i].y1; + dest_rects[i].y2 = width - src_rects[i].x1; break; } } @@ -3402,9 +3402,9 @@ weston_surface_build_buffer_matrix(const struct weston_surface *surface, break; case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_FLIPPED_90: - weston_matrix_rotate_xy(matrix, 0, 1); + weston_matrix_rotate_xy(matrix, 0, -1); weston_matrix_translate(matrix, - surface->height_from_buffer, 0, 0); + 0, surface->width_from_buffer, 0); break; case WL_OUTPUT_TRANSFORM_180: case WL_OUTPUT_TRANSFORM_FLIPPED_180: @@ -3415,9 +3415,9 @@ weston_surface_build_buffer_matrix(const struct weston_surface *surface, break; case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_270: - weston_matrix_rotate_xy(matrix, 0, -1); + weston_matrix_rotate_xy(matrix, 0, 1); weston_matrix_translate(matrix, - 0, surface->width_from_buffer, 0); + surface->height_from_buffer, 0, 0); break; } @@ -5786,8 +5786,8 @@ weston_output_update_matrix(struct weston_output *output) break; case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_FLIPPED_90: - weston_matrix_translate(&output->matrix, 0, -output->height, 0); - weston_matrix_rotate_xy(&output->matrix, 0, 1); + weston_matrix_translate(&output->matrix, -output->width, 0, 0); + weston_matrix_rotate_xy(&output->matrix, 0, -1); break; case WL_OUTPUT_TRANSFORM_180: case WL_OUTPUT_TRANSFORM_FLIPPED_180: @@ -5797,8 +5797,8 @@ weston_output_update_matrix(struct weston_output *output) break; case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_270: - weston_matrix_translate(&output->matrix, -output->width, 0, 0); - weston_matrix_rotate_xy(&output->matrix, 0, -1); + weston_matrix_translate(&output->matrix, 0, -output->height, 0); + weston_matrix_rotate_xy(&output->matrix, 0, 1); break; } diff --git a/man/weston-drm.man b/man/weston-drm.man index 488eef6ef..ebf370ebf 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -119,8 +119,9 @@ can generate detailed mode lines. \fBtransform\fR=\fItransform\fR Transform for the output, which can be rotated in 90-degree steps and possibly flipped. Possible values are -.BR normal ", " 90 ", " 180 ", " 270 ", " -.BR flipped ", " flipped-90 ", " flipped-180 ", and " flipped-270 . +.BR normal ", " rotate-90 ", " rotate-180 ", " rotate-270 ", " +.BR flipped ", " flipped-rotate-90 ", " flipped-rotate-180 ", and " +.BR flipped-rotate-270 . .TP \fBpixman-shadow\fR=\fIboolean\fR If using the Pixman-renderer, use shadow framebuffers. Defaults to diff --git a/man/weston.ini.man b/man/weston.ini.man index 5e39e4a58..bce5628b6 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -504,19 +504,19 @@ for examples of modes-formats supported by DRM backend. .RE .TP 7 .BI "transform=" normal -The transformation applied to screen output (string). The transform key can -be one of the following 8 strings: +How you have rotated your monitor from its normal orientation (string). +The transform key can be one of the following 8 strings: .PP .RS 10 .nf -.BR "normal " "Normal output." -.BR "90 " "90 degrees clockwise." -.BR "180 " "Upside down." -.BR "270 " "90 degrees counter clockwise." -.BR "flipped " "Horizontally flipped" -.BR "flipped-90 " "Flipped and 90 degrees clockwise" -.BR "flipped-180 " "Flipped upside down" -.BR "flipped-270 " "Flipped and 90 degrees counter clockwise" +.BR "normal " "Normal output." +.BR "rotate-90 " "90 degrees clockwise." +.BR "rotate-180 " "Upside down." +.BR "rotate-270 " "90 degrees counter clockwise." +.BR "flipped " "Horizontally flipped" +.BR "flipped-rotate-90 " "Flipped and 90 degrees clockwise" +.BR "flipped-rotate-180 " "Flipped and upside down" +.BR "flipped-rotate-270 " "Flipped and 90 degrees counter clockwise" .fi .RE .TP 7 From 9b682302b852b29f5302f9ebe28030ddc27fdb79 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 22 Jan 2020 16:34:01 +0200 Subject: [PATCH 1350/1642] tests: move check_screen() into client helpers This will be useful in more tests. No changes to the code, aside from dropping one 'static'. Copyright 2017 is taken from git-blame of the moved code. Signed-off-by: Pekka Paalanen --- tests/subsurface-shot-test.c | 65 ------------------------------ tests/weston-test-client-helper.c | 66 +++++++++++++++++++++++++++++++ tests/weston-test-client-helper.h | 7 ++++ 3 files changed, 73 insertions(+), 65 deletions(-) diff --git a/tests/subsurface-shot-test.c b/tests/subsurface-shot-test.c index 017f54469..9a9a7fd6c 100644 --- a/tests/subsurface-shot-test.c +++ b/tests/subsurface-shot-test.c @@ -115,71 +115,6 @@ color(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b) return tmp; } -static void -write_visual_diff(pixman_image_t *ref_image, - struct buffer *shot, - const struct rectangle *clip, - const char *test_name, - int seq_no, - const struct range *fuzz) -{ - char *fname; - char *ext_test_name; - pixman_image_t *diff; - int ret; - - ret = asprintf(&ext_test_name, "%s-diff", test_name); - assert(ret >= 0); - - fname = screenshot_output_filename(ext_test_name, seq_no); - diff = visualize_image_difference(ref_image, shot->image, clip, fuzz); - write_image_as_png(diff, fname); - - pixman_image_unref(diff); - free(fname); - free(ext_test_name); -} - -static int -check_screen(struct client *client, - const char *ref_image, - int ref_seq_no, - const struct rectangle *clip, - int seq_no) -{ - const char *test_name = get_test_name(); - const struct range gl_fuzz = { 0, 1 }; - struct buffer *shot; - pixman_image_t *ref; - char *ref_fname; - char *shot_fname; - bool match; - - ref_fname = screenshot_reference_filename(ref_image, ref_seq_no); - shot_fname = screenshot_output_filename(test_name, seq_no); - - ref = load_image_from_png(ref_fname); - assert(ref); - - shot = capture_screenshot_of_output(client); - assert(shot); - - match = check_images_match(ref, shot->image, clip, &gl_fuzz); - testlog("ref %s vs. shot %s: %s\n", ref_fname, shot_fname, - match ? "PASS" : "FAIL"); - - write_image_as_png(shot->image, shot_fname); - if (!match) - write_visual_diff(ref, shot, clip, test_name, seq_no, &gl_fuzz); - - buffer_destroy(shot); - pixman_image_unref(ref); - free(ref_fname); - free(shot_fname); - - return match ? 0 : -1; -} - static struct buffer * surface_commit_color(struct client *client, struct wl_surface *surface, pixman_color_t *color, int width, int height) diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 927523cba..06cf5e076 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -1,5 +1,6 @@ /* * Copyright © 2012 Intel Corporation + * Copyright 2017 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -1516,3 +1517,68 @@ capture_screenshot_of_output(struct client *client) return buffer; } + +static void +write_visual_diff(pixman_image_t *ref_image, + struct buffer *shot, + const struct rectangle *clip, + const char *test_name, + int seq_no, + const struct range *fuzz) +{ + char *fname; + char *ext_test_name; + pixman_image_t *diff; + int ret; + + ret = asprintf(&ext_test_name, "%s-diff", test_name); + assert(ret >= 0); + + fname = screenshot_output_filename(ext_test_name, seq_no); + diff = visualize_image_difference(ref_image, shot->image, clip, fuzz); + write_image_as_png(diff, fname); + + pixman_image_unref(diff); + free(fname); + free(ext_test_name); +} + +int +check_screen(struct client *client, + const char *ref_image, + int ref_seq_no, + const struct rectangle *clip, + int seq_no) +{ + const char *test_name = get_test_name(); + const struct range gl_fuzz = { 0, 1 }; + struct buffer *shot; + pixman_image_t *ref; + char *ref_fname; + char *shot_fname; + bool match; + + ref_fname = screenshot_reference_filename(ref_image, ref_seq_no); + shot_fname = screenshot_output_filename(test_name, seq_no); + + ref = load_image_from_png(ref_fname); + assert(ref); + + shot = capture_screenshot_of_output(client); + assert(shot); + + match = check_images_match(ref, shot->image, clip, &gl_fuzz); + testlog("ref %s vs. shot %s: %s\n", ref_fname, shot_fname, + match ? "PASS" : "FAIL"); + + write_image_as_png(shot->image, shot_fname); + if (!match) + write_visual_diff(ref, shot, clip, test_name, seq_no, &gl_fuzz); + + buffer_destroy(shot); + pixman_image_unref(ref); + free(ref_fname); + free(shot_fname); + + return match ? 0 : -1; +} diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 45e6db932..046c1cf97 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -246,4 +246,11 @@ load_image_from_png(const char *fname); struct buffer * capture_screenshot_of_output(struct client *client); +int +check_screen(struct client *client, + const char *ref_image, + int ref_seq_no, + const struct rectangle *clip, + int seq_no); + #endif From 7009806b94a16bd7b66a8b0bbf984923b5377d0e Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 21 Jan 2020 15:37:14 +0200 Subject: [PATCH 1351/1642] tests: rename check_screen() to verify_screen_content() + doc The old name felt too... short. The return type is changed to bool; fits better for a success/failure. Signed-off-by: Pekka Paalanen --- tests/subsurface-shot-test.c | 15 ++++++++++++ tests/weston-test-client-helper.c | 40 ++++++++++++++++++++++++------- tests/weston-test-client-helper.h | 12 +++++----- 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/tests/subsurface-shot-test.c b/tests/subsurface-shot-test.c index 9a9a7fd6c..7f42dbb99 100644 --- a/tests/subsurface-shot-test.c +++ b/tests/subsurface-shot-test.c @@ -115,6 +115,21 @@ color(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b) return tmp; } +static int +check_screen(struct client *client, + const char *ref_image, + int ref_seq_no, + const struct rectangle *clip, + int seq_no) +{ + bool match; + + match = verify_screen_content(client, ref_image, ref_seq_no, clip, + seq_no); + + return match ? 0 : -1; +} + static struct buffer * surface_commit_color(struct client *client, struct wl_surface *surface, pixman_color_t *color, int width, int height) diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 06cf5e076..b6eaa22d0 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -1543,12 +1543,36 @@ write_visual_diff(pixman_image_t *ref_image, free(ext_test_name); } -int -check_screen(struct client *client, - const char *ref_image, - int ref_seq_no, - const struct rectangle *clip, - int seq_no) +/** + * Take a screenshot and verify its contents + * + * Takes a screenshot and writes the image into a PNG file named with + * get_test_name() and seq_no. Compares the contents to the given reference + * image over the given clip rectangle, reports whether they match to the + * test log, and if they do not match writes a visual diff into a PNG file. + * + * The compositor output size and the reference image size must both contain + * the clip rectangle. + * + * This function uses the pixel value allowed fuzz approriate for GL-renderer + * with 8 bits per channel data. + * + * \param client The client, for connecting to the compositor. + * \param ref_image The reference image file basename, without sequence number + * and .png suffix. + * \param ref_seq_no The reference image sequence number. + * \param clip The region of interest, or NULL for comparing the whole + * images. + * \param seq_no Test sequence number, for writing output files. + * \return True if the screen contents matches the reference image, + * false otherwise. + */ +bool +verify_screen_content(struct client *client, + const char *ref_image, + int ref_seq_no, + const struct rectangle *clip, + int seq_no) { const char *test_name = get_test_name(); const struct range gl_fuzz = { 0, 1 }; @@ -1568,7 +1592,7 @@ check_screen(struct client *client, assert(shot); match = check_images_match(ref, shot->image, clip, &gl_fuzz); - testlog("ref %s vs. shot %s: %s\n", ref_fname, shot_fname, + testlog("Verify reference image %s vs. shot %s: %s\n", ref_fname, shot_fname, match ? "PASS" : "FAIL"); write_image_as_png(shot->image, shot_fname); @@ -1580,5 +1604,5 @@ check_screen(struct client *client, free(ref_fname); free(shot_fname); - return match ? 0 : -1; + return match; } diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 046c1cf97..cbb0512da 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -246,11 +246,11 @@ load_image_from_png(const char *fname); struct buffer * capture_screenshot_of_output(struct client *client); -int -check_screen(struct client *client, - const char *ref_image, - int ref_seq_no, - const struct rectangle *clip, - int seq_no); +bool +verify_screen_content(struct client *client, + const char *ref_image, + int ref_seq_no, + const struct rectangle *clip, + int seq_no); #endif From 636fc15f20becb634eb66830035fc8a56474b5bb Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 22 Jan 2020 14:20:46 +0200 Subject: [PATCH 1352/1642] tests: allow verify_screen_content() w/o ref image Allow the reference image to be NULL or missing so that it does not even attempt to load a reference image or compare it. You cannot just point the reference image to an arbitrary image because the comparison functions can abort due to size mismatch. This makes bootstrapping new tests easier when you do not yet have a reference image. Signed-off-by: Pekka Paalanen --- tests/weston-test-client-helper.c | 42 ++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index b6eaa22d0..5f76914a9 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -1566,6 +1566,10 @@ write_visual_diff(pixman_image_t *ref_image, * \param seq_no Test sequence number, for writing output files. * \return True if the screen contents matches the reference image, * false otherwise. + * + * For bootstrapping, ref_image can be NULL or the file can be missing. + * In that case the screenshot file is written but no comparison is performed, + * and false is returned. */ bool verify_screen_content(struct client *client, @@ -1577,31 +1581,39 @@ verify_screen_content(struct client *client, const char *test_name = get_test_name(); const struct range gl_fuzz = { 0, 1 }; struct buffer *shot; - pixman_image_t *ref; - char *ref_fname; + pixman_image_t *ref = NULL; + char *ref_fname = NULL; char *shot_fname; bool match; - ref_fname = screenshot_reference_filename(ref_image, ref_seq_no); + shot = capture_screenshot_of_output(client); + assert(shot); shot_fname = screenshot_output_filename(test_name, seq_no); + write_image_as_png(shot->image, shot_fname); - ref = load_image_from_png(ref_fname); - assert(ref); + if (ref_image) { + ref_fname = screenshot_reference_filename(ref_image, ref_seq_no); + ref = load_image_from_png(ref_fname); + } - shot = capture_screenshot_of_output(client); - assert(shot); + if (ref) { + match = check_images_match(ref, shot->image, clip, &gl_fuzz); + testlog("Verify reference image %s vs. shot %s: %s\n", + ref_fname, shot_fname, match ? "PASS" : "FAIL"); - match = check_images_match(ref, shot->image, clip, &gl_fuzz); - testlog("Verify reference image %s vs. shot %s: %s\n", ref_fname, shot_fname, - match ? "PASS" : "FAIL"); + if (!match) { + write_visual_diff(ref, shot, clip, + test_name, seq_no, &gl_fuzz); + } - write_image_as_png(shot->image, shot_fname); - if (!match) - write_visual_diff(ref, shot, clip, test_name, seq_no, &gl_fuzz); + pixman_image_unref(ref); + } else { + testlog("No reference image, shot %s: FAIL\n", shot_fname); + match = false; + } - buffer_destroy(shot); - pixman_image_unref(ref); free(ref_fname); + buffer_destroy(shot); free(shot_fname); return match; From dd84ecf44d69bdd91470bdfcf7356fa4b2fcf834 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 22 Jan 2020 11:33:34 +0200 Subject: [PATCH 1353/1642] compositor: add scale cmdline option for headless The test suite wants to start using different output scales, and this is the easiest API to configure it. Signed-off-by: Pekka Paalanen --- compositor/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index d5a7f04ef..7fbbb574d 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -687,6 +687,7 @@ usage(int error_code) "Options for headless-backend.so:\n\n" " --width=WIDTH\t\tWidth of memory surface\n" " --height=HEIGHT\tHeight of memory surface\n" + " --scale=SCALE\t\tScale factor of output\n" " --transform=TR\tThe output transformation, TR is one of:\n" "\tnormal 90 180 270 flipped flipped-90 flipped-180 flipped-270\n" " --use-pixman\t\tUse the pixman (CPU) renderer (default: no rendering)\n" @@ -2567,6 +2568,7 @@ load_headless_backend(struct weston_compositor *c, const struct weston_option options[] = { { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, + { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, { WESTON_OPTION_BOOLEAN, "use-gl", 0, &config.use_gl }, { WESTON_OPTION_STRING, "transform", 0, &transform }, From 0ce5a19b7e5cc64c3cb2d37fac9b485498a544b0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 22 Jan 2020 11:53:12 +0200 Subject: [PATCH 1354/1642] tests: add scale and transform compositor options With these, a test can initialize the headless-backend with non-default scale and transform which allows testing output scales and transforms. Signed-off-by: Pekka Paalanen --- tests/weston-test-fixture-compositor.c | 31 ++++++++++++++++++++++++++ tests/weston-test-fixture-compositor.h | 7 ++++++ 2 files changed, 38 insertions(+) diff --git a/tests/weston-test-fixture-compositor.c b/tests/weston-test-fixture-compositor.c index 0c8004b4c..da0fa26a9 100644 --- a/tests/weston-test-fixture-compositor.c +++ b/tests/weston-test-fixture-compositor.c @@ -107,6 +107,8 @@ compositor_setup_defaults_(struct compositor_setup *setup, .xwayland = false, .width = 320, .height = 240, + .scale = 1, + .transform = WL_OUTPUT_TRANSFORM_NORMAL, .config_file = NULL, .extra_module = NULL, .logging_scopes = NULL, @@ -163,6 +165,24 @@ shell_to_str(enum shell_type t) return names[t]; } +static const char * +transform_to_str(enum wl_output_transform t) +{ + static const char * const names[] = { + [WL_OUTPUT_TRANSFORM_NORMAL] = "normal", + [WL_OUTPUT_TRANSFORM_90] = "rotate-90", + [WL_OUTPUT_TRANSFORM_180] = "rotate-180", + [WL_OUTPUT_TRANSFORM_270] = "rotate-270", + [WL_OUTPUT_TRANSFORM_FLIPPED] = "flipped", + [WL_OUTPUT_TRANSFORM_FLIPPED_90] = "flipped-rotate-90", + [WL_OUTPUT_TRANSFORM_FLIPPED_180] = "flipped-rotate-180", + [WL_OUTPUT_TRANSFORM_FLIPPED_270] = "flipped-rotate-270", + }; + + assert(t < ARRAY_LENGTH(names) && names[t]); + return names[t]; +} + /** Execute compositor * * Manufactures the compositor command line and calls wet_main(). @@ -259,6 +279,17 @@ execute_compositor(const struct compositor_setup *setup, asprintf(&tmp, "--height=%d", setup->height); prog_args_take(&args, tmp); + if (setup->scale != 1) { + asprintf(&tmp, "--scale=%d", setup->scale); + prog_args_take(&args, tmp); + } + + if (setup->transform != WL_OUTPUT_TRANSFORM_NORMAL) { + asprintf(&tmp, "--transform=%s", + transform_to_str(setup->transform)); + prog_args_take(&args, tmp); + } + if (setup->config_file) { asprintf(&tmp, "--config=%s", setup->config_file); prog_args_take(&args, tmp); diff --git a/tests/weston-test-fixture-compositor.h b/tests/weston-test-fixture-compositor.h index 6e8d680e2..fd8d0e5c6 100644 --- a/tests/weston-test-fixture-compositor.h +++ b/tests/weston-test-fixture-compositor.h @@ -26,6 +26,7 @@ #ifndef WESTON_TEST_FIXTURE_COMPOSITOR_H #define WESTON_TEST_FIXTURE_COMPOSITOR_H +#include #include #include "weston-testsuite-data.h" @@ -82,6 +83,10 @@ struct compositor_setup { unsigned width; /** Default output height. */ unsigned height; + /** Default output scale. */ + int scale; + /** Default output transform, one of WL_OUTPUT_TRANSFORM_*. */ + enum wl_output_transform transform; /** The absolute path to \c weston.ini to use, * or NULL for \c --no-config . */ const char *config_file; @@ -109,6 +114,8 @@ compositor_setup_defaults_(struct compositor_setup *setup, * - xwayland: no * - width: 320 * - height: 240 + * - scale: 1 + * - transform: WL_OUTPUT_TRANSFORM_NORMAL * - config_file: none * - extra_module: none * - logging_scopes: compositor defaults From 1eb30468ea2c4e7558b04443c20133dd87f36fe2 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 22 Jan 2020 13:37:20 +0200 Subject: [PATCH 1355/1642] tests: add get_test_fixture_index() A future test wants to access the fixture data array for the currently running fixture index to log the test description. This patch provides access to the array index. Rather than adding more gloabl variables, I changed the type of the existing one which feels slightly cleaner. Signed-off-by: Pekka Paalanen --- tests/weston-test-runner.c | 37 ++++++++++++++++++++++++++++++------- tests/weston-test-runner.h | 3 +++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/tests/weston-test-runner.c b/tests/weston-test-runner.c index 8f945f94d..c10f286f4 100644 --- a/tests/weston-test-runner.c +++ b/tests/weston-test-runner.c @@ -46,7 +46,12 @@ extern const struct weston_test_entry __start_test_section, __stop_test_section; -static const char *test_name_; +struct weston_test_run_info { + char name[512]; + int fixture_nr; +}; + +static const struct weston_test_run_info *test_run_info_; /** Get the test name string with counter * @@ -62,7 +67,23 @@ static const char *test_name_; const char * get_test_name(void) { - return test_name_; + return test_run_info_->name; +} + +/** Get the current fixture index + * + * Returns the current fixture index which can be used directly as an index + * into the array passed as an argument to DECLARE_FIXTURE_SETUP_WITH_ARG(). + * + * This is only usable from code paths inside TEST(), TEST_P(), PLUGIN_TEST() + * etc. defined functions. + * + * \ingroup testharness + */ +int +get_test_fixture_index(void) +{ + return test_run_info_->fixture_nr - 1; } /** Print into test log @@ -100,18 +121,20 @@ static enum test_result_code run_test(int fixture_nr, const struct weston_test_entry *t, void *data, int iteration) { - char str[512]; + struct weston_test_run_info info; if (data) { - snprintf(str, sizeof(str), "f%d-%s-e%d", + snprintf(info.name, sizeof(info.name), "f%d-%s-e%d", fixture_nr, t->name, iteration); } else { - snprintf(str, sizeof(str), "f%d-%s", fixture_nr, t->name); + snprintf(info.name, sizeof(info.name), "f%d-%s", + fixture_nr, t->name); } + info.fixture_nr = fixture_nr; - test_name_ = str; + test_run_info_ = &info; t->run(data); - test_name_ = NULL; + test_run_info_ = NULL; /* * XXX: We should return t->run(data); but that requires changing diff --git a/tests/weston-test-runner.h b/tests/weston-test-runner.h index c47deba90..9e545cd92 100644 --- a/tests/weston-test-runner.h +++ b/tests/weston-test-runner.h @@ -143,6 +143,9 @@ testlog(const char *fmt, ...) WL_PRINTF(1, 2); const char * get_test_name(void); +int +get_test_fixture_index(void); + /** Fixture setup array record * * Helper to store the attributes of the data array passed in to From 444f1a8e225f39526937e10763f006e7602e2163 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 26 Feb 2020 13:04:48 +0200 Subject: [PATCH 1356/1642] tests: re-order test naming pattern The string from get_test_name() can be used for writing screenshot files and others. Starting the name with the fixture number makes an alphabetized listing of output files look unorganized. Let's change the test name to begin with the test (source) name with fixture and element numbers as suffixes. That makes a file listing easier to look through, when you have multiple tests each saving multiple screenshot files. Signed-off-by: Pekka Paalanen --- tests/weston-test-runner.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/weston-test-runner.c b/tests/weston-test-runner.c index c10f286f4..8e9217071 100644 --- a/tests/weston-test-runner.c +++ b/tests/weston-test-runner.c @@ -55,9 +55,9 @@ static const struct weston_test_run_info *test_run_info_; /** Get the test name string with counter * - * \return The test name with fixture number \c f%%d- prefixed. For an array - * driven test, e.g. defined with TEST_P(), the name has a \c -e%%d suffix to - * indicate the array element number. + * \return The test name with fixture number \c -f%%d added. For an array + * driven test, e.g. defined with TEST_P(), the name has also a \c -e%%d + * suffix to indicate the array element number. * * This is only usable from code paths inside TEST(), TEST_P(), PLUGIN_TEST() * etc. defined functions. @@ -124,11 +124,11 @@ run_test(int fixture_nr, const struct weston_test_entry *t, void *data, struct weston_test_run_info info; if (data) { - snprintf(info.name, sizeof(info.name), "f%d-%s-e%d", - fixture_nr, t->name, iteration); + snprintf(info.name, sizeof(info.name), "%s-f%02d-e%02d", + t->name, fixture_nr, iteration); } else { - snprintf(info.name, sizeof(info.name), "f%d-%s", - fixture_nr, t->name); + snprintf(info.name, sizeof(info.name), "%s-f%02d", + t->name, fixture_nr); } info.fixture_nr = fixture_nr; From 080d85b8fbed2d55873371a36b460923cd5e46d0 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 24 Jan 2020 13:14:27 +0200 Subject: [PATCH 1357/1642] tests: implement client_destroy() It turns out that if the client is not explicitly destroyed, it will remain connected until the compositor shuts down because there is no more a client process that would terminate. Usually this is not a problem, but if a test file has multiple screenshooting tests, the windows from earlier tests in the file will remain on screen. That is not wanted, hence implement client destruction. To properly destroy a client, we also need a list of outputs. They used to be simply leaked. This does not fix wl_registry.global_remove for wl_outputs, that is left for a time when a test will actually need that. This patch makes only ivi-shell-app test use the new client_destroy() to show that it actually works. The added log scopes prove it: destroy requests get sent. Sprinkling client_destroy() around in all other tests is left for a time when it is actually necessary. ivi-shell-app is a nicely simple test doing little else, hence I picked it. Signed-off-by: Pekka Paalanen --- tests/ivi-shell-app-test.c | 3 ++ tests/weston-test-client-helper.c | 82 +++++++++++++++++++++++++++++-- tests/weston-test-client-helper.h | 10 +++- 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/tests/ivi-shell-app-test.c b/tests/ivi-shell-app-test.c index b2550e945..5652c0de7 100644 --- a/tests/ivi-shell-app-test.c +++ b/tests/ivi-shell-app-test.c @@ -41,6 +41,7 @@ fixture_setup(struct weston_test_harness *harness) compositor_setup_defaults(&setup); setup.shell = SHELL_IVI; setup.config_file = TESTSUITE_IVI_CONFIG_PATH; + setup.logging_scopes = "log,test-harness-plugin,proto"; return weston_test_harness_execute_as_client(harness, &setup); } @@ -84,4 +85,6 @@ TEST(ivi_application_exists) client_roundtrip(client); testlog("Successful bind: %p\n", iviapp); + + client_destroy(client); } diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 5f76914a9..be9c8bce1 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -724,6 +724,15 @@ static const struct wl_output_listener output_listener = { output_handle_scale, }; +static void +output_destroy(struct output *output) +{ + assert(wl_proxy_get_version((struct wl_proxy *)output->wl_output) >= 3); + wl_output_release(output->wl_output); + wl_list_remove(&output->link); + free(output); +} + static void handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) @@ -741,6 +750,13 @@ handle_global(void *data, struct wl_registry *registry, global->version = version; wl_list_insert(client->global_list.prev, &global->link); + /* We deliberately bind all globals with the maximum (advertised) + * version, because this test suite must be kept up-to-date with + * Weston. We must always implement at least the version advertised + * by Weston. This is not ok for normal clients, but it is ok in + * this test suite. + */ + if (strcmp(interface, "wl_compositor") == 0) { client->wl_compositor = wl_registry_bind(registry, id, @@ -766,6 +782,7 @@ handle_global(void *data, struct wl_registry *registry, &wl_output_interface, version); wl_output_add_listener(output->wl_output, &output_listener, output); + wl_list_insert(&client->output_list, &output->link); client->output = output; } else if (strcmp(interface, "weston_test") == 0) { test = xzalloc(sizeof *test); @@ -805,6 +822,14 @@ client_find_input_with_name(struct client *client, uint32_t name) return NULL; } +static void +global_destroy(struct global *global) +{ + wl_list_remove(&global->link); + free(global->interface); + free(global); +} + static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { @@ -824,9 +849,9 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) } } - wl_list_remove(&global->link); - free(global->interface); - free(global); + /* XXX: handle wl_output */ + + global_destroy(global); } static const struct wl_registry_listener registry_listener = { @@ -916,6 +941,7 @@ create_client(void) assert(client->wl_display); wl_list_init(&client->global_list); wl_list_init(&client->inputs); + wl_list_init(&client->output_list); /* setup registry so we can bind to interfaces */ client->wl_registry = wl_display_get_registry(client->wl_display); @@ -965,6 +991,16 @@ create_test_surface(struct client *client) return surface; } +void +surface_destroy(struct surface *surface) +{ + if (surface->wl_surface) + wl_surface_destroy(surface->wl_surface); + if (surface->buffer) + buffer_destroy(surface->buffer); + free(surface); +} + struct client * create_client_and_test_surface(int x, int y, int width, int height) { @@ -999,6 +1035,46 @@ create_client_and_test_surface(int x, int y, int width, int height) return client; } +void +client_destroy(struct client *client) +{ + if (client->surface) + surface_destroy(client->surface); + + while (!wl_list_empty(&client->inputs)) { + input_destroy(container_of(client->inputs.next, + struct input, link)); + } + + while (!wl_list_empty(&client->output_list)) { + output_destroy(container_of(client->output_list.next, + struct output, link)); + } + + while (!wl_list_empty(&client->global_list)) { + global_destroy(container_of(client->global_list.next, + struct global, link)); + } + + if (client->test) { + weston_test_destroy(client->test->weston_test); + free(client->test); + } + + if (client->wl_shm) + wl_shm_destroy(client->wl_shm); + if (client->wl_compositor) + wl_compositor_destroy(client->wl_compositor); + if (client->wl_registry) + wl_registry_destroy(client->wl_registry); + + client_roundtrip(client); + + if (client->wl_display) + wl_display_disconnect(client->wl_display); + free(client); +} + static const char* output_path(void) { diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index cbb0512da..f4ed919f8 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -57,6 +57,7 @@ struct client { int has_argb; struct wl_list global_list; bool has_wl_drm; + struct wl_list output_list; /* struct output::link */ }; struct global { @@ -145,6 +146,7 @@ struct touch { struct output { struct wl_output *wl_output; + struct wl_list link; /* struct client::output_list */ int x; int y; int width; @@ -161,7 +163,7 @@ struct buffer { struct surface { struct wl_surface *wl_surface; - struct output *output; + struct output *output; /* not owned */ int x; int y; int width; @@ -184,9 +186,15 @@ struct range { struct client * create_client(void); +void +client_destroy(struct client *client); + struct surface * create_test_surface(struct client *client); +void +surface_destroy(struct surface *surface); + struct client * create_client_and_test_surface(int x, int y, int width, int height); From 20026a55d66b529ffc33dc988c2b5c9adb10b450 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 19 Feb 2020 16:18:22 +0200 Subject: [PATCH 1358/1642] tests: expand allowed pixel fuzz for GL Running with Mesa 20.1.0-devel (git-c7617d8908) GL renderer: Radeon RX 550 Series (POLARIS11, DRM 3.27.0, 4.19.0-2-amd64, LLVM 8.0.1) I found output-tranform test (a future patch) to produce exactly this much more difference between Pixman and GL rendererers. Signed-off-by: Pekka Paalanen --- tests/weston-test-client-helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index be9c8bce1..27ea0f1fc 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -1655,7 +1655,7 @@ verify_screen_content(struct client *client, int seq_no) { const char *test_name = get_test_name(); - const struct range gl_fuzz = { 0, 1 }; + const struct range gl_fuzz = { -3, 4 }; struct buffer *shot; pixman_image_t *ref = NULL; char *ref_fname = NULL; From 97359ba5c5a6588cbfe824f6c5642d5fa8cf3d13 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Tue, 21 Jan 2020 12:00:28 +0200 Subject: [PATCH 1359/1642] tests: add output transform tests This goes through all output transforms with two different buffer transforms and verifies the visual output against reference images. This commit introduces a new test input image 'basic-test-card.png'. It is a small image with deliberately odd and indivisible dimensions to provoke bad assumptions about image sizes. It contains red, green and blue areas which are actually text that makes it very obvious if you have e.g. color channels swapped. It has a white thick circle to highlight aspect ratio issues, and an orange cross to show a mixed color. The white border is for contrast and a 1px wide detail. The whole design makes it clear if the image happens to be rotated or flipped in any way. The image has one pixel wide transparent border so that bilinear sampling filter near the edges of the image would produce the same colors with both Pixman- and GL-renderers which handle the out-of-image samples fundamentally differently: Pixman assumes (0, 0, 0, 0) samples outside of the image, while GL-renderer clamps sample coordinates to the edge essentially repeating the edge pixels. It would have been "easy" to create a full matrix of every output scale & transform x every buffer scale & transform, but that would have resulted in 2 renderers * 8 output transforms * 3 output scales * 8 buffer transforms * 3 buffer scales = 1152 test cases that would have all ran strictly serially because our test harness has no parallelism inside one test program. That would have been slow to run, and need a lot more reference images too. Instead, I chose to iterate separately through all output scales & transforms (this patch) and all buffer scales & transforms (next patch). This limits the number of test cases in this patch to 56, and allows the two test programs to run in parallel. I did not even pick all possible scale & transform combinations here, but just what I think is a representative sub-set to hopefully exercise all the code paths. https://gitlab.freedesktop.org/wayland/weston/issues/52 Signed-off-by: Pekka Paalanen --- tests/meson.build | 1 + tests/output-transforms-test.c | 137 ++++++++++++++++++ tests/reference/basic-test-card.png | Bin 0 -> 8347 bytes .../output_1-180_buffer_1-NORMAL-00.png | Bin 0 -> 4642 bytes .../reference/output_1-180_buffer_2-90-00.png | Bin 0 -> 4869 bytes .../output_1-270_buffer_1-NORMAL-00.png | Bin 0 -> 4783 bytes .../reference/output_1-270_buffer_2-90-00.png | Bin 0 -> 4638 bytes .../output_1-90_buffer_1-NORMAL-00.png | Bin 0 -> 4880 bytes .../reference/output_1-90_buffer_2-90-00.png | Bin 0 -> 4655 bytes ...utput_1-FLIPPED_180_buffer_1-NORMAL-00.png | Bin 0 -> 4669 bytes .../output_1-FLIPPED_180_buffer_2-90-00.png | Bin 0 -> 4884 bytes ...utput_1-FLIPPED_270_buffer_1-NORMAL-00.png | Bin 0 -> 4789 bytes .../output_1-FLIPPED_270_buffer_2-90-00.png | Bin 0 -> 4570 bytes ...output_1-FLIPPED_90_buffer_1-NORMAL-00.png | Bin 0 -> 4914 bytes .../output_1-FLIPPED_90_buffer_2-90-00.png | Bin 0 -> 4667 bytes .../output_1-FLIPPED_buffer_1-NORMAL-00.png | Bin 0 -> 4570 bytes .../output_1-FLIPPED_buffer_2-90-00.png | Bin 0 -> 4892 bytes .../output_1-NORMAL_buffer_1-NORMAL-00.png | Bin 0 -> 4655 bytes .../output_1-NORMAL_buffer_2-90-00.png | Bin 0 -> 4854 bytes .../output_2-180_buffer_1-NORMAL-00.png | Bin 0 -> 14395 bytes .../reference/output_2-180_buffer_2-90-00.png | Bin 0 -> 5270 bytes .../output_2-90_buffer_1-NORMAL-00.png | Bin 0 -> 14832 bytes .../reference/output_2-90_buffer_2-90-00.png | Bin 0 -> 5179 bytes .../output_2-FLIPPED_buffer_1-NORMAL-00.png | Bin 0 -> 14407 bytes .../output_2-FLIPPED_buffer_2-90-00.png | Bin 0 -> 5360 bytes .../output_2-NORMAL_buffer_1-NORMAL-00.png | Bin 0 -> 14482 bytes .../output_2-NORMAL_buffer_2-90-00.png | Bin 0 -> 5313 bytes ...utput_3-FLIPPED_270_buffer_1-NORMAL-00.png | Bin 0 -> 12107 bytes .../output_3-FLIPPED_270_buffer_2-90-00.png | Bin 0 -> 12541 bytes .../output_3-NORMAL_buffer_1-NORMAL-00.png | Bin 0 -> 15856 bytes .../output_3-NORMAL_buffer_2-90-00.png | Bin 0 -> 11702 bytes tests/weston-test-client-helper.c | 62 ++++++++ tests/weston-test-client-helper.h | 8 + 33 files changed, 208 insertions(+) create mode 100644 tests/output-transforms-test.c create mode 100644 tests/reference/basic-test-card.png create mode 100644 tests/reference/output_1-180_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-180_buffer_2-90-00.png create mode 100644 tests/reference/output_1-270_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-270_buffer_2-90-00.png create mode 100644 tests/reference/output_1-90_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-90_buffer_2-90-00.png create mode 100644 tests/reference/output_1-FLIPPED_180_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-FLIPPED_180_buffer_2-90-00.png create mode 100644 tests/reference/output_1-FLIPPED_270_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-FLIPPED_270_buffer_2-90-00.png create mode 100644 tests/reference/output_1-FLIPPED_90_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-FLIPPED_90_buffer_2-90-00.png create mode 100644 tests/reference/output_1-FLIPPED_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-FLIPPED_buffer_2-90-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_2-90-00.png create mode 100644 tests/reference/output_2-180_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_2-180_buffer_2-90-00.png create mode 100644 tests/reference/output_2-90_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_2-90_buffer_2-90-00.png create mode 100644 tests/reference/output_2-FLIPPED_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_2-FLIPPED_buffer_2-90-00.png create mode 100644 tests/reference/output_2-NORMAL_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_2-NORMAL_buffer_2-90-00.png create mode 100644 tests/reference/output_3-FLIPPED_270_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_3-FLIPPED_270_buffer_2-90-00.png create mode 100644 tests/reference/output_3-NORMAL_buffer_1-NORMAL-00.png create mode 100644 tests/reference/output_3-NORMAL_buffer_2-90-00.png diff --git a/tests/meson.build b/tests/meson.build index 87dad82ef..705319c07 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -132,6 +132,7 @@ tests = [ linux_explicit_synchronization_unstable_v1_protocol_c, ], }, + { 'name': 'output-transforms', }, { 'name': 'plugin-registry', }, { 'name': 'pointer', diff --git a/tests/output-transforms-test.c b/tests/output-transforms-test.c new file mode 100644 index 000000000..3ed19e396 --- /dev/null +++ b/tests/output-transforms-test.c @@ -0,0 +1,137 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * + * 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 "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +#define TRANSFORM(x) WL_OUTPUT_TRANSFORM_ ## x, #x +#define RENDERERS(s, t) \ + { RENDERER_PIXMAN, s, TRANSFORM(t) }, \ + { RENDERER_GL, s, TRANSFORM(t) } + +struct setup_args { + enum renderer_type renderer; + int scale; + enum wl_output_transform transform; + const char *transform_name; +}; + +static const struct setup_args my_setup_args[] = { + RENDERERS(1, NORMAL), + RENDERERS(1, 90), + RENDERERS(1, 180), + RENDERERS(1, 270), + RENDERERS(1, FLIPPED), + RENDERERS(1, FLIPPED_90), + RENDERERS(1, FLIPPED_180), + RENDERERS(1, FLIPPED_270), + RENDERERS(2, NORMAL), + RENDERERS(3, NORMAL), + RENDERERS(2, 90), + RENDERERS(2, 180), + RENDERERS(2, FLIPPED), + RENDERERS(3, FLIPPED_270), +}; + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) +{ + struct compositor_setup setup; + + /* The width and height are chosen to produce 324x240 framebuffer, to + * emulate keeping the video mode constant. + * This resolution is divisible by 2 and 3. + * Headless multiplies the given size by scale. + */ + + compositor_setup_defaults(&setup); + setup.renderer = arg->renderer; + setup.width = 324 / arg->scale; + setup.height = 240 / arg->scale; + setup.scale = arg->scale; + setup.transform = arg->transform; + setup.shell = SHELL_TEST_DESKTOP; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args); + +struct buffer_args { + int scale; + enum wl_output_transform transform; + const char *transform_name; +}; + +static const struct buffer_args my_buffer_args[] = { + { 1, TRANSFORM(NORMAL) }, + { 2, TRANSFORM(90) }, +}; + +TEST_P(output_transform, my_buffer_args) +{ + const struct buffer_args *bargs = data; + const struct setup_args *oargs; + struct client *client; + bool match; + char *refname; + int ret; + + oargs = &my_setup_args[get_test_fixture_index()]; + + ret = asprintf(&refname, "output_%d-%s_buffer_%d-%s", + oargs->scale, oargs->transform_name, + bargs->scale, bargs->transform_name); + assert(ret); + + testlog("%s: %s\n", get_test_name(), refname); + + /* + * NOTE! The transform set below is a lie. + * Take that into account when analyzing screenshots. + */ + + client = create_client(); + client->surface = create_test_surface(client); + client->surface->width = 10000; /* used only for damage */ + client->surface->height = 10000; + client->surface->buffer = client_buffer_from_image_file(client, + "basic-test-card", + bargs->scale); + wl_surface_set_buffer_scale(client->surface->wl_surface, bargs->scale); + wl_surface_set_buffer_transform(client->surface->wl_surface, + bargs->transform); + move_client(client, 19, 19); + + match = verify_screen_content(client, refname, 0, NULL, 0); + assert(match); + + client_destroy(client); +} diff --git a/tests/reference/basic-test-card.png b/tests/reference/basic-test-card.png new file mode 100644 index 0000000000000000000000000000000000000000..027ca852e94d2c58ce735e6a042515a6171b1fe3 GIT binary patch literal 8347 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 G3;P%Myjjfv literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-180_buffer_1-NORMAL-00.png b/tests/reference/output_1-180_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..cd60739060c7fd950d4731ded23234190a487d0b GIT binary patch literal 4642 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 N(>2zqyz@BnzW|DDxuF06 literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-180_buffer_2-90-00.png b/tests/reference/output_1-180_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..8aa9a1fc2a2c5dca686931711dec8934d15a7a8b GIT binary patch literal 4869 zcmeHLXH*kiw+^6y0s?|m=}2!HIz$vglO``6qyz+{_Zo^6sUlT+g60)bP!Lc91cHJH zNC~|d61s#Ag7m;0zW4in+&}l%{d3p4XV#fHv(}z{c6sL6dnWb1u`a`9uFD`0h(TWu z`WFa90R`4uw3NVk|5cg}aG`cK(1n7|&W{%@6}ceL)h2!DUGwnLbwc!gR(B-LmZJ%d zj=skIzD`!581+^b)V`xpQe8Ivp(N$kg%ycQ+t#_HNQb?Rw0rL{FXw!^YB3IkfK{Kg zplwB~>+XaOyn>W0CWPcW_t!P3L8BOVJO_)Ze!>fWIjWtZ=vc$Vm&pZj%M?pX`(`_x zyIQR$?|vwmgyDuw=-#SL^Mh!kn>u7bppS8KRC516|8KN`irMs?fRwMN9{xj`yXb44#>^Q+&m9~K$Mnx*IGNfxb(Tze|6#) ztQhvFpQNUy7S?YHT>SCYtfI2AlC4TlAY_RjSaM9F^ zFDnTS3oFxz#n#o;VX-mQ$!4q<+f^v!EzQk8`b}eddwUyC`Gf`jHI$Ul<>iZA*+O)a zrb4|S&y0+W$;rvMQd-*h53W7=fNWb^TWRTa2Cotd-^Ub&V&dYB5j(&3*6|7|vC!9gOCI5rl#J*O@BqpM5L#KgqF zz(8N$rrs^1!+Dz^Cm=Apj-EC)G9rBPvbw*jTNw9z(JzfLTFWnG^b#!hr|i4WxMrA- zkB@@`(ht+K=;`ZQ{r0W6tn5CyN?0GTcRszNqvPVmi z&d#KRo(y}>xb(r3bX(bZvG>eb0~y7LWkpJ*GTlZ-M!;isXkWywZtRbq9<(pP_Wu2W zk&%Vt@#f~{pFeG5_rMyYqPWZ(v7T$kj-88$XDU7x_V()~5A}1N@j@cEX1+$`4Gx@- z`?j`C?l|7j)G!AJhmeqvtbX($n2Ikx6?;${WL%P$m&eM+R>>u6-}Ettd~|ptV(M!G z&3KJt?DEhJ3pfl0%i2g5=>5`3TH0u+!L4g7laKeSHo_5VVKPEOS~;)2tY!;~3kl6n zH3ktg;`!I6TQ#;2$C`z4nYer3=ddunBVE%6i{nQ?Kq_C_7s%NEY>ue^Y~e#}wi(qB z6&4PkRvf-u4fyiO!c8#Ea9ospqj5dgH}ccBj5n5h^yod`Y~gMk1@Dj-%e3@nFE+;5 z*x1XfYX47AXlSUZY4+#&b|AfcT6~zK+o25dYBc@Ikm*hhtmGyhZ4we6y-PAR>aUKD z@U4hI1K9DKzU`A4kK{Z!SXgz5sV*uhDJd#iCJc>?u+H$*6!EBs`{O_R*x1<2OBWRt z0r1@k=XFsf70urL7`ExK={va?$fFu8HbDakCS~;~1U%!XZ6mh86{a-W=-*jm|Ax=a ze~?BCop!M;d)ORvo9&iW=|1wEY8Zsq0?J;#6qxv=ATKYkq=dV#^O(tPMrhV&dr+yb z$se}W`>m$$8UA;gA7_h|q=dv@gl_-+?^7Z$e4ZnW}jzSbIZpPgyjAL|Zo(QpqP_AvL*zkduTXz-~ z9&UE@_xHcBQY6oscU%Bo{@bhtSMy(C)t?bjxjeZsL6#W>8zd5Kr0X!B?~e+RB^p3P z2QwwP$Jvq`yKFU(=EtSm>JP7BNNKe^U2F*f!KBe{9g0bCMcG`$V0Y=4_}=|UTQ)GklH_8@XA zO?=E#7ZApeM+>r4=tr;<$Exo*VOwdQCXaHCMF0(f}aw|P}gL&R6v$5_on7&SsY&tl{jR2M!!Jqq>!n3V`0^!&6j zQ7bgDxIR(a?)j>!)=_a1x5z>u8OH&`sTyu1T* zLsGKp<45`MC(rZqBTYa$P-$stE@O}mE!aON=I6qpV63LlChk`L4#4C@1eo~{YosH| zmhOr}LWt67sOu_$Q2gJ3*SaGtkp!31>IW7Ui*P$Uu5np<(=h5}aAJZ4XR*WU*I$BB z8?QMwNXw?y*MylE8$Vn5yw?=6+K+@(`B=E)koEvuh8cu~q08#mahCu<1!EOH%a3nu z!CG#*>^+A_NJyBA;!6~Ln!>kHB1?F-^u@W3bm0ECuQ_Wu3MKe&(%QWjG|@hn27x)M zu{17*Lfxjnxi=a8`0?WzkJ@X`2WVe=FE6jh`|bA(4D9UeETV5Q^QeWsd-qN%3t~cJ zVno4bhD~y_#DuP=rKXy;peCJbn-71L0F5QYIG2gF-tgNiW?*_U7 zRLOGYX!3VXS(&4q-Rkj=KY+HX(VtUO56sNwaly_1sUoAt`wIa8gC8Cq?o<(T!)#HAz0VPs?&;ax{+Na2(^5 znU89BwLRuGd3X4{8W6)~VYSv$Y(06RIPmJmM)jLFv_m0vN>iYOEcUbJyYwv8r-WA& z6VakPNE|67Y-;#4)YjG2H8nL=)C6fxm_zbun;Ik4iwX-HK>8wyoE#~J!_$>vZ@$Cr zW4_NG>Yc6AniRKZ;-GoCx%EIl0e0{2RU1k}m8t}}0o}#d14x7_EjUp<`s0A3+Pcl| zjE4>1@@B_gLuI8z)o3vgSSzavF6&$M8ymj344JCY%0rHtqS<0jO=Q3D(-@VvbS+yW zT?b+AWr7?_*WEIYjv{gOOxoJoK%>yz-OZMMLs4qE!&%lHmaPOiDjEb%E&0li{3ZHXNo|BFzu@G{ zrw%~|t^W7Q8NaD00tGzDd?o)LfmQoEL;T%W*+G{Xq(hD9=wQ?iNnHW95&SaQ(KFN> zAQoEiKeNG&x)5k}%N;U4l#y zqw4INuhenfHA5(`zbZ=yYEj`6%{nLRN>5h7p=-sshcCVYp7pjVh&;J*%9+|DQoMT; zwrzs>{!7l|E9Do0_hXx<`V7pO&0Np-%nH`In2UJHG+N&z5FC)Gbw_s0M}43%=xu|9 zN4;8guk-rG@!S{+yUE?X$gzA>q`r0PT%dR<(v4QXf-iamr*O8ggQLI|W?syxgLO@ul&@Gs-6R`c)Jk-+3) z%|ZcTS9=7ByG)(~Tb3t36Uei^{+^3h1cmDAg7oBfL;W0Ec@JuP#fSS(U01~}N1fSz zRRAY8x7uK%QXS5Au(wSBYV{01h&?(kApMy}t`Jy*%7-~yZi@^uxzIYkWDyEhb(v8c zX9DbwU@S3&?%I{~VYU;pvmsD@9fseEQ>XEj+Uy8+--xkOSEGLE= zY>s8@ov5lWcg*lO^PVHu-o*vF>-=DfZM$lGz?->)eQqx0jaRU|9?kRhvSkLwiylim z3B%sB5u5jhxEVD{AIV$3vkmE47Dkm*CzvwnJUk>Ikxti52P%?kCZsXCZjK=o0sydM zmi?zzR(^7`7hGp^yUCIAsB0aotZF+szMrj?u>3`E{B#O#Io6%}J3CXER%);|jjh_WPT-Zz&akz%=_7*|itpI01=;r3 zGvAyGRG9Ei*yloX)63dPEzizUoXVA_6I9yg$kec;`=6eY%C55TWu|PEI-GvOllfX& zu4!p$q-NsMo{iDPF9h>$uF@?qnPx`8lRAzW+UQ@dz)1ZApC5-Pn&{=_%k0sR4tC!t#oSLM$wD#Q+?zNZ%Iju){4o(%X9x6(}M+93mEQXV*GW-q}a$Bh7lC6lN;#M zS$y^OaT?mdQ_N`OXPPqS<8ck07-3BulOUK60;z!VnyP*3!P9<4nhQ$5rm9K@lMbS) zFK!9b@{v1h1&mwULe3jJP%aQ_R9OZM1VXX2aj2a_@jvBeg@HuD&Y(w^h@e+$nM~r( zWS9+(Ik3Dr{=e>!4Gj1t&A-*Cso%!@bg91Si9IV6e;dnJnQN32@E3)AL=qyKQ|**c z7L)pFgULq>ZmjiJ>&3SFT+UB_T$`ycT1Lw-hY!^1>pV~h($MT;ix7PqE5nSkEgh1i zEQiiREZ$)W0~R19y!lTEc*>BqM(xyz&Y)$X2w>2xg) z;a4@Qs_;<86US2!M`3-}u{JrFhIruDJ5r6w91Fcfle%o7_y*L+$_Vs=0ii)F(Bod4D2GWBZkMh^kJBX%|8Yrlg zMmoKiP6teQ-{(K-dHw`svYQ$sb8mM2CH{8xSVp12iE1FCvvIBg>pikO?Vv~^yb{%?)6~>EbLn{&~ypzBr)Vo`uI`cS_v(_ z+nTI~S}R;9v}881W~H{)Ew?C6@rEByLAH-i@#L&*HU;^FwcOo~@ILzgAGxxbE&W?8 zabZtg)BVRIvG%ZXM(V()d~jg`X-iS{O#Ql?`zA@=)0~8xiGJhErylpPXq1O%BgOWR z_~SxKCYVL~e)eUS{a#7B;^TLKL&lJ{5nBB7JrcJT>T zBwUOh^%qHXBjfCNJz`*?X5K&Qv$Jm80ke`&7DPRSc0eq?F)vIoDCNlSf>75^w*4%n zzX;SF-;Of_<2`v|lbI{SbGkHG|4#mDDJ{{rV{HrDrm1`mTp7lmM8yiVaisRZ_RucD z9Rm{6!fR`Xyy3mFmQs+}Zekeu@U|>n2-4>su(nA$RWbs4OU(fhl#u!FjwhkP+`H5s zyRWJImtWETA)lvohHSKdk!*#sA_RKnTk!YRPj953QC4GZnDJ*Zr4}Bs<<`cQ<~D+5 zwiy>G(wn1~3IX(Jzs|MK8|^2V!=dwHo`>^&WB#Tyyr zLq;s>>SlV!w;;l|_uh=5JD}0>PVdg^# z)z4C%Q358&7`++7xby144an>Q>UjF2fX+F|i+nq@$;qITCwoq^(CV$PW-Klg>6Tsq z9Zk`>U(~(k6)UMfxO58Qla#2VtL|$E1`LIut}Obq$;HduLuCtv8WpUm43(+RW!Eoe z(z)m4vnl+{6W-!8G@;X_F@y;t_e!X~#mhvYeklMc8UM#Yna z`7(E<<1h1Con(!ce_?2$vF-9r5a(EIcDAK(r^x9(SJSZpME4~kxM8sB&dn>!CQCf5 z4sr9FiXokAZd3ZLr}4kFe8ySv+)-2606g7$CxZ%>3?eeQ34@F+-1?a1Xx->3NwCO=x>B2ZAF zpabo9M(!v~{}CHG`r_)OE#A)Kdr)5DZM74iyX$Q7_ggJY36*2e;DegW`=^9y&i7F3 zI;Y(ogA_YkvxUY!(d`D&o9^9PA~UXl(b8LC@-GuOtlQof}AxBX-?n_o?~?^{heg8f^7Nv`dnjqDM(5n4mz zXKHp&4oPAls9>AInc^=6ej7t<_}fR4Z{9sNIuv^gtDV~{%Ia810QX9Eq+VWB$xG(d+`m_MYZZOa4*MWK!>1dlR}d*euq+C-GU@u(W*`*rvGN z*3{e-6BAQX%9>HE9`U>>Pzwmgx0S%Lm94%$!+a`(!5|gfnfUPotaDG28Meh(sCJEz zpK;C0|KqV7ZD#XsP{)}S_M`mGn>XdWLx7aE32U%K)M2PKrSB+jm)7TKf6(<%6BmE0wM1DlAj;$S zml%@2y0o;kva&*>(H0jM=jSh>-j|e=6crT}7kiJ^NQjEY=6JlgpV0o;$f!DG<9AwG znkGYLuS}-TO9UD4_Oro=37O0J%{RtstDU)Gr=+iNa!Ex+MMJ~i9r{f5VfRM3wsy~; zv!-S{K7xV6;cjO0_u2FP2H=#HmDSYLOxXIiRr%Pgo&D?jO|USpZN4-9%ICT|;oE`B z%Olm1wHXBkb`};{d3h=f{Sy5GTSbypq{3=^MZrP)W$lg=4a%IAS&(HpdHL^pH%J?D za&k>Ut6K_PsFZ3K675$@Q*kkx8ZuJA#?CG%D0qIy>yJowDXGj$0ErpSE!yJg9p^MZr1# z>AGU$5M9r@;=_Ur%dP;nmqhTd?w%fa$%I*@^+SFAGdH?al}P}Sb`5@-mIN7{kdVLJ z9=-u7R-`amKBA=g#_B@Q2S9l`0H-_MYr1v&cF@Y`xlz-=mC>P*5eca(^XvU%&I!|p zMlznp?hm%k0_U>~pZeMw6!ov@>9xs%tb^dsC+(EKVMlw%7O1U(9XW6(Ug~&Z*OdcA6*VTOD2Ml65`?n0wD!c z1K_R_YTUq^EUYp*Iw~pEMIeZai)XgDsK>fneRgp17!=VhPV6xpQ$;9BkV{OTOuCKfU z0|I(~xxBd7#7jwMls0P&=a$2^qeLQibFdxHAC#_L&PuCAXfEBezS7+X^M12}ZK=|@kv($UcRVPQ>wqzL8Z<$HVKMRsoANhDHt zcUr)`I?{jH)DI}M1|swvU($Ue3yV_!cNrNO{csNrLuJG?sy|8k0uZYhK*Es={Y8Mv zmZmK)FE1{-&lNL$tgOUHnIE-}u;WfvE9<5J3^&`{+{D!mp1G)MZDv;T?RcJ0=LOYp zbIZ%|Pa-1rmsQ|BKR$4BIKZ;jJ!XbJ&rm*kdU%7hqew_xa69H}AThCVsQ^)@defY5JSZl<48z{xeKT-Z=U&jYp zeuo_F3-{S`=kin|LIXnr1CK&7mi;_N@mFoB6V0WZqFH70r@NlKoXWDoJ5@$93j7yUa)ByMyzs0gH5!H&C zo10@!p0RBXmYsZ%P#8>~ZU;W4=^^V1Ol=(g?yjzc?(TI8UpnV?i_9VSP0D<)@;Kja+f-MbG7FZ(}qGV8|`cG_rNxmag)f-kw`3FeRP$Aa)gLzn$pjr*@sdbRM; z1lTxS0UF@nbwv+<&YZ9HtQ-0LJp7$>OltiJX?-m@Lw>>64xVX?OY2kw5AjO)2_*Bu zQ2;MiiLAEmDmjTU@#~kXq5qY^&U8_O6kN{46wYkVEOJuz6xWNi6H%HyOxhwyrfXav zrt?t(&sp2pHBbL%>i6jzyw7#Y7asvmPhReldDDAT7bS4-`E2c>&6%?fkF&c6@EIKc zl&@x6_T?|{F12l z_IAAlAp;YWoF(kpo5@`ccjxEleSLf~mAqzTCm&o#ra6_CmX3^!phv3FLN!kP**iNs zEiEmXnVDQ=V>4l=p5F2J_U#)p3(NjGSum;5Ct-GPovf;)WND#5RU_$eSR>PfcXxMR zy?T}S&5|p;si|pVVj?>`dwqSqug{ohX=P<)XlUr|?QLfE^H-^Lye?vj+vw&)N5}cS zwXxWkm=-FVBsfZ7;p@^s;r90SsluSfiS5OHMFj;57>uQl_w@U!DqotNv$M07mX@w= zgzolt4Mf7MO~8E0@bEC5x*r+b%XfDb3CU9{p4;2o3kwVT^y!lUl=BfBo|Tef{@}s) ziHR#piW(Y>ouvUZd`RbvEJT|$G{llB$$HPx(b2-f0t)TEZ~HkvUsOV(q@u!%>va1k z8jW^zbTm@FV*fJjsy7gUvutd<+=Tji%<8bIjg2}ECM_c~iAU(cU^x3gJG<#wG?-l$ zGWX`qn_CI~@Oq!yQc_Z$xhia7B6<1wz`hH6t;0)HR1}S+=$w#+gny~77MEUIUS1w; z4Dx%XH_{kXV^4Lvn@&5IaXupL>V;sOHPz(&>8qV=;P9cFp<%C+lXHg$0kZJ%@v$^k z#PZrl=qq{8McK1Z=em-wju1F+u(ot1i_r@`h5Y6^Uwg(?y%RyQ5*ivBc6NLix7XIz z(BK*Qs;w>W_QPTTWI(YQU(5BkO>5*isz++0%?4$*qgCxUAc6G(Bw?0QSY2pcYmv`B z;`3M?x{%QdAk#k+ymi0o%a=?IY-kMW9g8f)pO=qMg3}&=Oc(jAUGrvg^q$?FHuh;= ze=gO|68Xil`g)bk&lrb@Y`!Rg771C|9}GKNTiY9YF!n-A0edb`IBw=;Ok-o?YazJo zcwWxi8>axL0CNvDR+pBd=YGdb^GHD0)%)3}9q;hdgfV`8EYjR_`S1a0=U@|M9obBK z$0)5HE}JHguFH$mzCWW(0mtc@;;kT+*%R@hxrUdeK;=V@7*2U;YGN>&!@yUZ*MqU; zcjP-c@C>H^p;vhezqm)A4S?pj`93bIMKw(aeSCaajB4L@QBQcN6W`o~$m>CWER$%TR&D$?qR#0|7mx1jgHn;r2;QmAnb`r~#mv2&2 z3oO%%aDJi3xfG4=R2%B>NZQ)Q3JmMCs@3BXn!ekuvSh=J=<(_<2${i+ru5gL#1X%w zOmL3nv$1|@i-D}6@lyb_G6eFvFd9g}DV7VTCRdKOu{q$~q!(spW;_z0%Ecf(wdxg9 zylOh8QU==Px|`C&pWHZf<43_yX|~{O!+e~M-VG=c0Ik;^e;^DoHa1T98x6^e-Gkj7 z>b|I-OPSwpRIbFyKK3~>!HJd|wzEjf1WI0_*HaiLubnC^5NY61B^ekP{2SRXK?fCc z80)gQ7)syT89NQt3pS z1`5Y@<$C+tMI( zmDQ0*PELV%;?JKyJK5n@R>ie*Q2gB&Py86r-i{Rodgd(SJ}=$9HMK_B_Y3-?GZEpc zD0tzl;}3bvEG(DIqRZH6*p0RAJJ3P00(>NqNa?*u#_cm_&b$_|lKpeUEqkxed#2OF zZDG=7R}hE7sv^4+1q$&YlydIH{IG(@6!#Ql(Mk!%YX`D$IcsUaiG?u2g~7?#?lE{A zbai!6C{(S+&&-UBjMuNvljla2g3p$$2M4U6m2T@*1!sKYecjKB%m^ z+9FjfLC{(5ZLVHoM9_CY9!*y3rp}L!j(VO`F&0vmiN(N!3sm+*3v77Pwa$aW;b=5k zPcM@3rl+STCMISx-qO_cb$a@eMt*MY^&~|HT)RmJ>Ns|;Lc3fh!HDj@`f#x&{lLh`NU4k&bSXZP^YT{CweBrIbx-}zmPQldqlKqSHp%pfp2W=F z6V*LjfB5?PzKE=PNkawB261bAedg}Y0gh5zdoOd63#6?N5NPHz#>Hj1J(drm`VEi| zA3mfPru8m}G`nN*kRC2&d3pI`y%v510kgKey2{?g_nbeT*w@zwCtwau-Z<736d>1S znr?|8qcV$&iz6bK+uH^Q2TNe(eH4F79i4Vk35<8lfoKVVK+MfaPiz9t1Es-KM@PrV z=v(VO-CW8r9^vlpURPHK3i&!dK3-j2jp#@?gPO>A`xXchvbijl1E{TI%~ShcOG-((ySlo%xjo8!wdL&MGTMw~JQ?yeovs#+8hl*dttZK<_4i1i3uMEj< z?=7~1`vVp z@Nke*-z`qB{shTxr@l-BeSLrM-$}awH#YA3`jSQFN$s7LJjcgPp3^PBRycXPD={q1 z?ie7nql3c%XWVUR$#lT+-sRulIy?P^$|!FyO@LbO{TLr#rAlghd3j+l7<>BGl0{$LD&Y>#IR`#w3zNPChe0B&PvGls)2i)Wo#MgG%8O{syjM+b6NbYf{s zebFAtCe4EG^g-%K&67BMzKoYEwo^~6NFFcVC8gTT)l91jnp5klnHxllg?uG>6wmHE z@2m{I||bIJBT`rwp&UrYr+-Q?GDZWaj>Jf8;9sh z8{`Tr=eQ`QaV|v^UmoboI^DsKKn!Vbn`~g=3!a5?v@(ez1Do{n%2en*od>qLhm-;% z@q`k7et)uqRmXN5-r9JP1p>i(5<=MKwrOk>N=EL7)MAoQM|>a{=3bo>A2)tphd3;( z=w?|y&=^F;l#-Si6}~qI`qI*@wEXuc8buhP1<&_;KEDfSc0l9=?5>bT3!T7zXBEGV zCs`{rDfOCI4h%KZ5RtjyOY75CJjUP3*fS{5^|RS1;6!s60)P6 zPObNKH5VsZwr+mz8HQ3QAnR@cppsln^%zCm%kEt785k)3dZ#7RMQcfgB}km zMdKb9J{}gcD6RHy6pM^F9zzDHh%XZ;ls~{&CG}(O%5+%;@tC2~Uu+gD=7kT%x(VzN zA>BBS!KDeyx~;1B@0kf|{nPrSmF}6D;sQbd`O{Tol-x*{61h!Gn3R!jr9pY%{VkUd ztVSK5eVEk0=_SyR3(Llooy#FUGCdbkaW_;(*ke%u0XuG4aBFVPQxY%nmk#7}QsrBf zpBCE~FA@|)#Q7$~!_i7E3LeIBFrmTL4WD_g{Q6i^vl$_T zIz0Etgr;uT_y*LxaTZzTBKU7J7n<#+sT@?fONvk){`C41D+)?I`u;s1kM~s}XSfa> zZ&bUJ$Ew%j(rNJ4)u-L;uQ3F(I0G*!Y=Z4^!><4ct)XW(UZwTm$@F59OEjX_?@&x* z&ar&8L39tNx_(>*M%Ig{LUU3@u+LIHR9s$|9CO^K-bCu2KD`xfQuyCfLIW+SJgE!gSM`=y~T z4KhOj>g=l;gm|;?4As{{qWDyl(&i literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-90_buffer_1-NORMAL-00.png b/tests/reference/output_1-90_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..f17d21d76a333f328830d7d8373ac0f9a699d904 GIT binary patch literal 4880 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 a!N-kUKUcm;>n4A30Wb|+XyyH<5&r=JBP<*M literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-90_buffer_2-90-00.png b/tests/reference/output_1-90_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..f3bb62c8eed8ac8584595afecc791c53f78f9c17 GIT binary patch literal 4655 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-FLIPPED_180_buffer_2-90-00.png b/tests/reference/output_1-FLIPPED_180_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..381ef746671be79dd6a8f3ff46d90bac4c4ffa6d GIT binary patch literal 4884 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-FLIPPED_270_buffer_1-NORMAL-00.png b/tests/reference/output_1-FLIPPED_270_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..91c5cdb925841c6db739bbc15112f2ef8908f34f GIT binary patch literal 4789 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 Z(J`64iF3utvA};Qkby1)T&?XK`!C&;6Sn{W literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-FLIPPED_270_buffer_2-90-00.png b/tests/reference/output_1-FLIPPED_270_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..15333fc17098aa9ef78ab5665d0b195871d5ef20 GIT binary patch literal 4570 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 PV1U+ReYF}D`S!dmQoqg?dU)R|Ob>Gs2=d{>q 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 Dc+|Ba literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-FLIPPED_buffer_2-90-00.png b/tests/reference/output_1-FLIPPED_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..0e32a86a4fc4561d11a05a4df10d10794fb53c70 GIT binary patch literal 4892 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-NORMAL_buffer_1-NORMAL-00.png b/tests/reference/output_1-NORMAL_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..83ff8859153e7dad2e258e47adfb553789895eec GIT binary patch literal 4655 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-180_buffer_2-90-00.png b/tests/reference/output_2-180_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..edd425e569c24d9475d72431a9fe9675e355eb11 GIT binary patch literal 5270 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-NORMAL-00.png b/tests/reference/output_2-90_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..127cd516ece5479a9f169e718dc5dd3e0fc11bf8 GIT binary patch literal 14832 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-FLIPPED_buffer_1-NORMAL-00.png b/tests/reference/output_2-FLIPPED_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..8c456b51434b95d4d02a7a4bceb25ed31c5b8284 GIT binary patch literal 14407 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-FLIPPED_buffer_2-90-00.png b/tests/reference/output_2-FLIPPED_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..58206d1eac41b1849c2fe9037a8fcd43afb71197 GIT binary patch literal 5360 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 OfYxn2wNe%9r~d`06IdAl literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-NORMAL_buffer_1-NORMAL-00.png b/tests/reference/output_2-NORMAL_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..b1d2a3774b94ca3b1e7604ae5753e614d319ff95 GIT binary patch literal 14482 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 I@#6LW06h`QH2?qr literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-NORMAL_buffer_2-90-00.png b/tests/reference/output_2-NORMAL_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..26b5789d2002a7df71bcd38120ba22327808fd41 GIT binary patch literal 5313 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_3-FLIPPED_270_buffer_1-NORMAL-00.png b/tests/reference/output_3-FLIPPED_270_buffer_1-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..878307d7e01de9104ed1faf2efef19703fb31cfd GIT binary patch literal 12107 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

    p3eGp`f^+w8~dOhmwz+!}gF$pcy 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_3-NORMAL_buffer_2-90-00.png b/tests/reference/output_3-NORMAL_buffer_2-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..b1133ef66834d5b75086273e8172d058a00711e1 GIT binary patch literal 11702 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 bZlZRDxsLyw_xfQkVgqzE41x7(cCr5p=p_>5 literal 0 HcmV?d00001 diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 27ea0f1fc..6e270d379 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -1118,6 +1118,16 @@ screenshot_reference_filename(const char *basename, uint32_t seq) return filename; } +char * +image_filename(const char *basename) +{ + char *filename; + + if (asprintf(&filename, "%s/%s.png", reference_path(), basename) < 0) + assert(0); + return filename; +} + struct format_map_entry { cairo_format_t cairo; pixman_format_code_t pixman; @@ -1694,3 +1704,55 @@ verify_screen_content(struct client *client, return match; } + +/** + * Create a wl_buffer from a PNG file + * + * Loads the named PNG file from the directory of reference images, + * creates a wl_buffer with scale times the image dimensions in pixels, + * and copies the image content into the buffer using nearest-neighbor filter. + * + * \param client The client, for the Wayland connection. + * \param basename The PNG file name without .png suffix. + * \param scale Upscaling factor >= 1. + */ +struct buffer * +client_buffer_from_image_file(struct client *client, + const char *basename, + int scale) +{ + struct buffer *buf; + char *fname; + pixman_image_t *img; + int buf_w, buf_h; + pixman_transform_t scaling; + + assert(scale >= 1); + + fname = image_filename(basename); + img = load_image_from_png(fname); + free(fname); + assert(img); + + buf_w = scale * pixman_image_get_width(img); + buf_h = scale * pixman_image_get_height(img); + buf = create_shm_buffer_a8r8g8b8(client, buf_w, buf_h); + + pixman_transform_init_scale(&scaling, + pixman_fixed_1 / scale, + pixman_fixed_1 / scale); + pixman_image_set_transform(img, &scaling); + pixman_image_set_filter(img, PIXMAN_FILTER_NEAREST, NULL, 0); + + pixman_image_composite32(PIXMAN_OP_SRC, + img, /* src */ + NULL, /* mask */ + buf->image, /* dst */ + 0, 0, /* src x,y */ + 0, 0, /* mask x,y */ + 0, 0, /* dst x,y */ + buf_w, buf_h); + pixman_image_unref(img); + + return buf; +} diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index f4ed919f8..a8253137a 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -235,6 +235,9 @@ screenshot_output_filename(const char *basename, uint32_t seq); char * screenshot_reference_filename(const char *basename, uint32_t seq); +char * +image_filename(const char *basename); + bool check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, const struct rectangle *clip, @@ -261,4 +264,9 @@ verify_screen_content(struct client *client, const struct rectangle *clip, int seq_no); +struct buffer * +client_buffer_from_image_file(struct client *client, + const char *basename, + int scale); + #endif From 147e67c4256ebd75491f40ce9d03c198ed5fe63c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 27 Feb 2020 17:04:45 +0200 Subject: [PATCH 1360/1642] tests: add buffer transform tests This patch continues the buffer and output transforms testing by iterating through a representative selection of buffer transforms and scales. For more details, see the previous patch "tests: add output transform tests". https://gitlab.freedesktop.org/wayland/weston/issues/52 Signed-off-by: Pekka Paalanen --- tests/buffer-transforms-test.c | 137 ++++++++++++++++++ tests/meson.build | 1 + .../output_1-NORMAL_buffer_1-180-00.png | Bin 0 -> 4674 bytes .../output_1-NORMAL_buffer_1-270-00.png | Bin 0 -> 4877 bytes .../output_1-NORMAL_buffer_1-90-00.png | Bin 0 -> 4854 bytes .../output_1-NORMAL_buffer_1-FLIPPED-00.png | Bin 0 -> 4598 bytes ...utput_1-NORMAL_buffer_1-FLIPPED_180-00.png | Bin 0 -> 4667 bytes ...utput_1-NORMAL_buffer_1-FLIPPED_270-00.png | Bin 0 -> 4883 bytes ...output_1-NORMAL_buffer_1-FLIPPED_90-00.png | Bin 0 -> 4914 bytes .../output_1-NORMAL_buffer_2-180-00.png | Bin 0 -> 4674 bytes .../output_1-NORMAL_buffer_2-FLIPPED-00.png | Bin 0 -> 4598 bytes .../output_1-NORMAL_buffer_2-NORMAL-00.png | Bin 0 -> 4655 bytes ...output_1-NORMAL_buffer_3-FLIPPED_90-00.png | Bin 0 -> 4914 bytes .../output_1-NORMAL_buffer_3-NORMAL-00.png | Bin 0 -> 4655 bytes .../reference/output_2-90_buffer_1-180-00.png | Bin 0 -> 14282 bytes .../reference/output_2-90_buffer_1-270-00.png | Bin 0 -> 14385 bytes .../reference/output_2-90_buffer_1-90-00.png | Bin 0 -> 14482 bytes .../output_2-90_buffer_1-FLIPPED-00.png | Bin 0 -> 14336 bytes .../output_2-90_buffer_1-FLIPPED_180-00.png | Bin 0 -> 14762 bytes .../output_2-90_buffer_1-FLIPPED_270-00.png | Bin 0 -> 14394 bytes .../output_2-90_buffer_1-FLIPPED_90-00.png | Bin 0 -> 14446 bytes .../reference/output_2-90_buffer_2-180-00.png | Bin 0 -> 5217 bytes .../output_2-90_buffer_2-FLIPPED-00.png | Bin 0 -> 5258 bytes .../output_2-90_buffer_2-NORMAL-00.png | Bin 0 -> 5288 bytes .../output_2-90_buffer_3-FLIPPED_90-00.png | Bin 0 -> 5167 bytes .../output_2-90_buffer_3-NORMAL-00.png | Bin 0 -> 5288 bytes 26 files changed, 138 insertions(+) create mode 100644 tests/buffer-transforms-test.c create mode 100644 tests/reference/output_1-NORMAL_buffer_1-180-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-270-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-90-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED_180-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED_270-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_1-FLIPPED_90-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_2-180-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_2-FLIPPED-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_2-NORMAL-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_3-FLIPPED_90-00.png create mode 100644 tests/reference/output_1-NORMAL_buffer_3-NORMAL-00.png create mode 100644 tests/reference/output_2-90_buffer_1-180-00.png create mode 100644 tests/reference/output_2-90_buffer_1-270-00.png create mode 100644 tests/reference/output_2-90_buffer_1-90-00.png create mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED-00.png create mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED_180-00.png create mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED_270-00.png create mode 100644 tests/reference/output_2-90_buffer_1-FLIPPED_90-00.png create mode 100644 tests/reference/output_2-90_buffer_2-180-00.png create mode 100644 tests/reference/output_2-90_buffer_2-FLIPPED-00.png create mode 100644 tests/reference/output_2-90_buffer_2-NORMAL-00.png create mode 100644 tests/reference/output_2-90_buffer_3-FLIPPED_90-00.png create mode 100644 tests/reference/output_2-90_buffer_3-NORMAL-00.png diff --git a/tests/buffer-transforms-test.c b/tests/buffer-transforms-test.c new file mode 100644 index 000000000..fdc86bbad --- /dev/null +++ b/tests/buffer-transforms-test.c @@ -0,0 +1,137 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * + * 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 "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +#define TRANSFORM(x) WL_OUTPUT_TRANSFORM_ ## x, #x +#define RENDERERS(s, t) \ + { RENDERER_PIXMAN, s, TRANSFORM(t) }, \ + { RENDERER_GL, s, TRANSFORM(t) } + +struct setup_args { + enum renderer_type renderer; + int scale; + enum wl_output_transform transform; + const char *transform_name; +}; + +static const struct setup_args my_setup_args[] = { + RENDERERS(1, NORMAL), + RENDERERS(2, 90), +}; + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) +{ + struct compositor_setup setup; + + /* The width and height are chosen to produce 324x240 framebuffer, to + * emulate keeping the video mode constant. + * This resolution is divisible by 2 and 3. + * Headless multiplies the given size by scale. + */ + + compositor_setup_defaults(&setup); + setup.renderer = arg->renderer; + setup.width = 324 / arg->scale; + setup.height = 240 / arg->scale; + setup.scale = arg->scale; + setup.transform = arg->transform; + setup.shell = SHELL_TEST_DESKTOP; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args); + +struct buffer_args { + int scale; + enum wl_output_transform transform; + const char *transform_name; +}; + +static const struct buffer_args my_buffer_args[] = { + /* { 1, TRANSFORM(NORMAL) }, done in output-transforms-test.c */ + { 1, TRANSFORM(90) }, + { 1, TRANSFORM(180) }, + { 1, TRANSFORM(270) }, + { 1, TRANSFORM(FLIPPED) }, + { 1, TRANSFORM(FLIPPED_90) }, + { 1, TRANSFORM(FLIPPED_180) }, + { 1, TRANSFORM(FLIPPED_270) }, + { 2, TRANSFORM(NORMAL) }, + /* { 2, TRANSFORM(90) }, done in output-transforms-test.c */ + { 2, TRANSFORM(180) }, + { 2, TRANSFORM(FLIPPED) }, + { 3, TRANSFORM(NORMAL) }, + { 3, TRANSFORM(FLIPPED_90) }, +}; + +TEST_P(buffer_transform, my_buffer_args) +{ + const struct buffer_args *bargs = data; + const struct setup_args *oargs; + struct client *client; + bool match; + char *refname; + int ret; + + oargs = &my_setup_args[get_test_fixture_index()]; + + ret = asprintf(&refname, "output_%d-%s_buffer_%d-%s", + oargs->scale, oargs->transform_name, + bargs->scale, bargs->transform_name); + assert(ret); + + testlog("%s: %s\n", get_test_name(), refname); + + /* + * NOTE! The transform set below is a lie. + * Take that into account when analyzing screenshots. + */ + + client = create_client(); + client->surface = create_test_surface(client); + client->surface->width = 10000; /* used only for damage */ + client->surface->height = 10000; + client->surface->buffer = client_buffer_from_image_file(client, + "basic-test-card", + bargs->scale); + wl_surface_set_buffer_scale(client->surface->wl_surface, bargs->scale); + wl_surface_set_buffer_transform(client->surface->wl_surface, + bargs->transform); + move_client(client, 19, 19); + + match = verify_screen_content(client, refname, 0, NULL, 0); + assert(match); + + client_destroy(client); +} diff --git a/tests/meson.build b/tests/meson.build index 705319c07..463d71293 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -112,6 +112,7 @@ dep_zucmain = declare_dependency( tests = [ { 'name': 'bad-buffer', }, + { 'name': 'buffer-transforms', }, { 'name': 'devices', }, { 'name': 'event', }, { 'name': 'internal-screenshot', }, diff --git a/tests/reference/output_1-NORMAL_buffer_1-180-00.png b/tests/reference/output_1-NORMAL_buffer_1-180-00.png new file mode 100644 index 0000000000000000000000000000000000000000..3d3a0653a46f08d3a7e250f453e927698d6fe0de GIT binary patch literal 4674 zcmeHLXH-*bmkvl#0TJO!M--7Fohu+ENE0JXfeTzZL;-0ED7^^SfCQvU35k~~HS``p zArvVQDWQapASuKUS|GsW&iAcZYyQoA-;en->zuXEdCxv?IeR~6KYPCk56q3Z&I_Ff z003N|dxn+(0Fxo({?}P%hTJboyvw+-xtkap0#5%vKB7v~0RWzTkfEM+D2y~8^?)w~ z-cNDC7;vk8yl5oKjxb8)dM{v+ii*>g3v6U^G_4Mtk(BfFd#)o}HotnJ^qY+uJNm|! zb3n+9+>d3+2oW^U+KKaK&5O5LA+c@J_IS1yan>2v6-F*|2prD}i^mOQ5u)coWXV-7ujS%IOesJ@g{N*I^v+?oqo*1~FmzUko zOjbL`mS+laiy`GVu90&%&Y!<<1=!((RY)J?Td^AUp#B*8Kp+qt92|OFVp*j?moB{2 z%f1{s z1;(2zD=Qrx9cQbByVL5G*t=zAW#7DgyHA?qPenaXUfm#JIzK-mFf=f5&&0&U%*+e~nrZUO8}s6fE*N}NUR}LV z3pNIU66#_A-35H4YzqX?$<9tdd!Dch*Ny3HZEbC8QVXpXKT9H!(z%C*hLRE!>+0%$ z&qY@0_6I1Ub}SFMKBQ)*a#Umu^L!NIcX>dP#8Y85p# zHMO+?Cxr7gEJiZxiHV6TEABJBa5y|)E5yde<_-00W@e^V$QE&PGu2E{L7|VL6#B_f zX1%UXWrhc7@8zJfp?j5e$JnnrbHr5v4~-A=ZECnzGKoj6g=7*Ho- z&oDTHAqSc;H_~N()d&Lo68nj|^P0~R#m`=aFJ0|W+2u{_=+K@i0jjGvH#Gdd#(=^X zz7*O(szjYAFZV}QH_EH3NOT%iwUP}0I^V}Yn5@>jc^s}1_e_=ZkxI@p0nPy{ zrS+xw>8tQ6!Oq#)*`=P@F+m~MzoZ3%0s{k=dSYXle)OHcA#C2H0@#rjs3#^R9v>fn zh5lx$hHF^NW%+3MIh*&=CFU!8ySp;#HcB(k>N1%=8h<9M3Uj{cev3k(J`cSL^~P43 z2rZ^sq;g3)2m-?SATu$UIM9jD8$+nC#>`h1D?#{#Ah*{}o;BVsAq=HZA5=8yN$b|d zzW@HwlCp=1DRnv3fd&^_G~Dh7?%+=Fp$psnvrbtztMibwE8aG!N(UMw*J8<9IUPwB zAUxwn@sxF45T9OY^WS4$!2&m^kU9Tj`#|Sa_i^_rZQ*9hQ(qCqXxb4nYpLO|(P(se zKPuT`mvBMIM%PD_yf=gS>TF!UJ~`dUj%(8EhwevCDfCsAzdAFdh7uxmv5NuE_YoRZ`K$)WU8)*p%gC-SKMKXQIIK9gxEBdH+rbU#D$3}XtL=StP@DMh*IwRyE%Kh?r zdt)x%wmPIXnUp`9uUVyaBx4rb;RK!oRe*kp>u_+pj?#KVL&K8db+#@n7vBLeY+W+G zxVU)G#={@8pB&+f1+RoClP2HjH`P%k_E|&I zb+k&&?hfge$6_KPxb_h8T94mc(?8(A2#gH7ynue%C6tI&*!pk^qZDFk{+K6@0raT3 z_g`zaj_6ag#a<3qF+Q9MLg%P4-r#OwZAXV=>oRlZhYv&Mw6rQyCG7mEB{iXKM0evr z*9sjySF>woE=mx2l0%)Im48#@4tj2eJD3b5$5M24z{kW7LD4)fTN%)P^MCs9^GG{Dr zT@*nWXIeL#!Q?2qbc!6v4^&oG(&>@KUEg5{1R^t2(gFGa{!=t6#C>P}iRaAGkHwF$ zgEhs{nBNcrU+n-qr9|{OJb@tj1mBW+xA1oO&ZhzimA+ ih-UAy67al_~Z-O(WR( z5~;1%_XVB96s76C$}BA$D9Iy!m*_I+~lw-Mnwla+p6 z^;7glU9Z11)Z)~SzPj=3kl*m_N@);WBS1e`ao6DOg~`cDnEdfuJE44C6aEGU2EW`$7m;R9BR%m_e2ifJnZ4FiXNUEU z!S7pMsUJQ_X;tRK;n9>8^gwbTL+W?w6wIW%QUyEg=3+_>DuJvWICPR+UXh{9c^sfUkmP!Qzy&I z%S%d1QeA9cN=k<-tM=`Y7q8${!E#z!?F@PM)2&OH(s9FYhrzpVrvWKwRw7 z#l2v)^KKDkUq6j{*=cpFF7M0hf`3w^1s{u>tLw+Et}Y}J+1!kc*+nq;f$?KbOiW~C zWH8{Z^E1EcI*7rSCINYJAWM8u%Ps9!kC_yNtt~ABNwbZAGt9999dfsD%nKHruBBQ( z<7jL9c7O2Kw?w@EUJ=6vMMXubo}&X70x&7pL7t;9(Ea=RgTIsC;tm4sU$8eU7P1b35HnBG?CuhXtH1==Gf~~T4Umh$DW@Hw} zi?5~SRY0;9uF?8M}nD!I>IcQ1Ndo*(Q>_L4DO zYr{dMQJ-%iD|iu;Kd}|J0z~?QfN$4YWRhWlDIGE~mjO&kKLOK>&RdF+61r4e`=5d-4UJif>=<{|ED|nzF{~LPBm#z*Ir5I^9^(UZ3g3&BkoiE(MPWf&{2a;UesEUnUa(h{)BmcK=sYbI1pb${?v$_YlJCpo^%?Qgw4AfHjHM<2mwj&A22 z^j0i>oB}Fuw5ql|T;6MyT&33Is&BtfTurWW=`t!E4}@i{HsN=XPp5y49PRf;pdZbO z@;6%+js?Qr)MncI`Rjk_Lb`OB7rR4lL6h%8Q17!fnj2ZN+l?9?pOonM&u-H1js_X! zhHb4G_&1&A*a|2NE?Tag>(stQ@?s09$xBvZ|`-uE-d>Xb!J#V zm>*5BA+T2LmXDXE7tOQv)Zeu6{l4k!(1_1WO}){M>R!HwCEUDp$=jnnO!SUMfl&S0 zBbYxvF9k1eT7?Mz8FYV@UporZCRJJlU*@d~HSYSI;HV3XFLUqQ8~NHB6o0!<+?Qyc~cu_n%S_NP7G z$30V3g0;0LGURh#6xIA>?ON-?BI&2sDCkQspBUZpNI{n_7QiFDWUcC7?!XKRfMMR% z;T%bGe+4Y?MInZPSj<6unQnjZ{lftzWs>(j1`xvZJE^aFHmDznA$okqI%0)3Ljpq^ zwADN{vm-f5bhD>6d^BPoo}6;}mP=U3*m#Z@jB8-axH!2#3_jhQ?5k*s>84r)mdNf( z+_oPBHWpvX#kDo!Mg2OirZ`Z}Ocr*fJXG8mCb#M5RLqW={((2HyA`W=AfEdJhLLYY z8oEksp7k?1!Ve4-CnW4@&Jzez$K*W{YOa}_n0MlIrn|%F#ZY9sJ;WraDm2%H0+|Xk zpf`Nkg|hXmIWBJiCsq1q$=fL?jEVDLn2AW`bKJ4#Vr9@ca6&%9arMAds*@o7e{b>`e z1gLt`p^5mgNA4!u#S*V80O5n1++ZsNC0KHnc_TL^VI9nU@M-gNPz2(+^4{c)Hp0XN zM@hpkYz%(Skv99qVPWkLZK)33W=sxumGkkt`!7cQ7X^8#s9r<(YFoZ}-?Ro;-Km-s zeyrs)1v^|x;z90XR#!Uf-Cye!w}qb8e+upE>g`I2<1&MLDpN>tyrkGqk zl74yx_oAZ>jzg2&8YXpu?Vm4Xtpr*3Z8Q!cz9l!c=+ocNJay!Yf}nhV?~IH|9NDPo ziol*8J>6Q+$<5fYj-*>rDCri;?T$~O$%io*b%B)$yj$~8f5?{M8ksr!@{ep}*RmtE zAovF`dV~tQ-=7dX_`;;a*^hrAcWx5~1cigWej58={l9GrfSVnw_gR(;kEEdVQGZ)* z?yl_Y?4t29!TLn^l=+jCkgdq*7Y9FJDy>qW7kL$l`V4kgr z^>;YA^MEh>Q}Rwsu9R+nwf}9Q{O^_7|KIyx*_`;lw%S3A`We}%gI}W;8Jj`?kde7z JwZ3cIe*h4`{ht5; literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-NORMAL_buffer_1-270-00.png b/tests/reference/output_1-NORMAL_buffer_1-270-00.png new file mode 100644 index 0000000000000000000000000000000000000000..332b79cae8a0d4c2ab26732c7657f6e49b8223a2 GIT binary patch literal 4877 zcmeHLcQo8jwEwD!mTaO$jZT!+J5jQTC>voBq6<-?L|H7WB~haM!cU79ktoqERA6}lF9{~KB?sArIdb${)5i5rz^*Peh<*Lt@pFzn%MO04*W`0UyW?ELmS1wv zo{9~x=UnfMvb1b_$hzOsJ$8u62MM0IYpfQ=@vjAjn1@V`Nb*;n8#LdV~c49t#Ws1-iLZYO!)O0n{$^i?rR$T?|jTVpuOK z0^%SK!2G|xq2%&%-?sQbledwC38KusBp;AN#dMoO;shRzrv3F!VnAP&%Gr7Z_g|bL zG-52wR8dhOp{c1UD%$JajP32cFU=AEk1oUyJ@Y+)nVI=^M)1l&mSwp)3iV^2i)-Oo}HS?EiB}_ZKMl&Dr4W`?%~0~ z!Lho${Afxg<}Qey##vNURMmfe_G47Lm_vjfsKtK)xxb3bxEfp(D^-H@sI9F9OG_6> zVlzZSLPJA?gPYK3baS&qN7&0m1qFrCv9Yny(R-GbHg%4Vo9)EOa`N(f+vo*Hln1WX zp?&I{3i#)0ikxiJt;ltrh+75biMe-?E?GgR*C!_@S65cdz(-+PI2=w{S$Sn;C6f%x zc#Fki8*ocYOL1}3`M5GX^w^C~FKn-u)6LCoe}CVTj0T=cug?9PAaGen4*NRZkYo(M zz(yWE&?%7YH#0M%qocD&`(gYu#HD0q7urI6^z`&BEytFlaf5?ZH8q8}KtP{&UvdA9 zj_M()ZtIshz5oexaL`uo&k*6$jyPESCw~8t;jMqk%WI=Z8)3yq=;-L!-1I&`3(m^x zC^CtiF7@rGlipIOzWy#OY-9A@?=AeWY`@zjeyvs{>C_|#TsgqR#3av8|2-`{*XZUE zHag0$`R78Xlk!r?&g_Sj6lUWLYX5W*V=gYPwJ(MHvTU?2zz(oqv&4N=GB&VYo{pLE+ zt%0Rv4+^kv-J;oQQC3n?QdNbHF~kLOS3C2o(VvS1my(pc z$Kr|~AFubDiITcwYAP)$$!IbdwiCj!IWhcS&wGy=l zr(eZVKx32IP{~aL;>b;y&J_sdvmOT85$2P9<`o(gTaHGdP?;e;WtHX=*JA3sw1$$6 zl5Sf&@%Qr|mznCw zeu063=lR>)+toZOW+Csfz*D-_qdfnPtvffA{nZ|GosZvw_;9CK`22ou(7lqua9Pn_ znuCmMX=&+C_*MKv8MARzprvC9D|{~xxWtC%5-$zDSvQTGX7g0M`t{9YL(iO}Q+r_& z;q!U>iFH5U4|_$CM^+R6sukBTdK>y-)^caOMl|UCVq00OENzK-%mwq^Ngz{4kSK0h z3tkFsPsQ$O4%=z(s+mwA`W{PL;|Pfv27&tEc0CcOD!Blg$!(Ph;-pQK`tuEq3N*XJgx3#;nS{ znRRV1W^p;cHxKdlmfU)7X6Yb@I0oz(C_J0m2YU+-FcHds*#D|2Xyp-8s1Y&{J?7oa z!O4m8%ZCOAHh2%@{JDcaixgKH0*4cG*x1o8nuE{@;9C_%uW7{pQp7G<{WOlyJaB&Y_-Jxb69wO!-=^ zf(p%$=9>pmgO#tS3?W%rS;-3XN0VE>FI?hvp6j5waOt?S#n9sKfq^@dS=9BQlq|!D zft5*}&=z0*^VSh0*r!z)H*HyKu5e`nk4HB(9p|_|e3+G;-RNWMge- z37E=X4m$P9B)m9dm|iG$0k6FjHadfTfhe6kR^CnTlxG_SbCHCBK4|E z8uCFMBDh7<1_m?ZpDbGWb}$LY>Y%<(O{s;0cy2j?K2m^u@(h3aMr|K?w7k4r@w^#d zV-G+4dH=C}$l=fFkgds+oM1S7_(^kA%*ZFLp+f&u@E2vRNL&A*+;Gq083$w)a8|Lq(1Vi-kXFCU+(%>q+MUmBm3loZqQXMic~#M1I8iUpSvy8Vrv zho>sS05#zq^&?fa9*epcF-3-=Iy#*#Eup`p z@TkT{Uln_bqcAhW`DXu+0fWI367|!*=SP`+;}a7rYiq-G&j3LKTh#vVaB?dNlDDbt zKb9DY^@l$DNq~O18{0_{+}+(>>HSiql49tkrKK1QrltmmK7|?@8b(A!*xA{6dv9(| zRF$`lTC=T8HMl7jWwIL>Bj^b(336SVL%e7c+Y^tcBvyZ;d7Ckn>%?IO&i-OjDQb;4 zPb3A$IkbmHoo0KR4%KVG+CKzQ|BTRsVF2+4X6_@9Scw zT|1f^zoHCl5kHDwZIgu`K743wY%C5fvSJAwPJbgonVH_Xqn{D0Sl!K=Rn`e3+`}EU zoiz;}y*!_JXkOHV^AsBB@1MpXj`9>eSNXgQEWQ+|qYy`avru|)10(9D;d)(RBd2zl z-J}uj&=p|{gJt1728W01s;Vr_r1Ko%aJ2vHK@XHm8s~2Egfi-jNJ^yoceaS{#>6Q& zyjbYk8m~w3>1o6Q3PcGhl)!T1#trK#Wia@QYs2wmduaRIr35!;XWfjqQZ}{D8Dd%j zw$G-w?z*BkPr)fVe9EIrMcY%4HKw`N=>lLd_Ir$S&ta;Fra_;m(19a;5iOqp$NX>! z9>60VmWx1cBu_irjyYSI^{aT?^)8fO^?6>zx|wAN2!N{FrbjQYtoIpluH`M6^AzAQ zMeWJ2|GFlXn`uyJ(F#-jwjWQdh62DoHbBF3Eoi4CzVY|{`0x)Eii{AE_P<~HIBw+l zRkB6-0;vs!`7&7%@9xPOWCq4NgZh!?C4yz_)LN}^zm{4BpA=dLBr$hc$#ERe#0dOn z<|z~=YmYBD9$_jw7XMvFtLMpk)+wGS;X2`h&k=X;+j!>3-9>#7uZTkK4O$F3j&7Ll zv_E!xmW&v2pZgF~Lv5@3oqIL07n(MUk()g)*nfe?GS0!jY~j+SACv+d_gbIdA1A-u zS`CDiDEz84FL?453VeL#lgpk#*W(H+0p-<>L~_v3E$X&DpH`GK5FybenP6WWG+beT zh8wQcKcWJl-62W{EAPa}<*fCl;&5Jk;^(HNV&<1pA+AHa&Cw3)WmM8B0osa)3+XfM zNHF3jc0E`qIdSebe-37A?N=9(X-+BTS;;^(l1{AL3g=h$IA4EKqu^!?RkFHEXR<2M{b!TR~ zd}-4_`bBm-hbgLucl}WRHe;`>WePE{us~!>zf79{h}7O~ldiO^JzrUuU_3hpToNXf z_ZMUw+HTj84V;Q)`yp1!T4y>W(u;#r{{_MvThO~PF49f0uOJ^e*|DUT8in$^@ib3` ztxKmb=bJ(fG!uF{cWK(AE?YEO#CNcub?PL)_Acku46AikGn_2~-xIeV9iAp$B;#0E zRhvZ@YIrtIQq@oBLSevD1#6cbZa&dBoJ&8@iB6d39xo(rUcU~7I0y@;NM;olZ?IgJ z*HEJb{Rte>Cc`oM_`67HgX_wGy`Sn88I@}(an3m_a72XdxWqa>H)lH=bC?IGTf9;w zM;=&do0w=;y?IkqsM}i(^z|=1BGCP9V;w;_hkh{Dpoa8}T=sfw>(gzaf4&m8x-MS= zrCl>$JNvwH`UqSH&u8sbT|AEp~1JS_Am0i{QQ`uuL4 zXUWfvXcmDUIUUc24$$?^S@)}Sv5DMZr<+9uyi&=8U^w23+ZPZBbpbcoI-1-WW$J!d zt~lX)$bN{>^1-coF8$bAogt-#R|jIFc91FX%AWW{SZ?u)bk&R93GwlDhX+mCrxkU( zLnC*sgAWFP9hLM;4@N3tfesh;Rgui3MDy8?+fGv&3i=TFqaCf`p7mw|{gfQw*zihe zY0xL#vs>t%Tf;vc+Ft#xhhzvYF5et$9{LXvZUy z*Zr4EvKyka66|RCddj7#^zYZ(tVuW7A~BKSzy;9f8RCS^u)vioEGML`k+&o&em2uD zL-vqB2luygKfD6e=n3q8s@EJF?Z$AcfWPU+Qm8qKVYh2kZOA0?T=d39!{^%zTx5UD zJb8V)<{I~@m%hHWX^JCA1WgQcauR?+bbf7nsZjCTawnW1OtP!fQ$*hIfOB;Jc2`0w z3c6SZACy&82-cxLc6RzYJ7eQbd#1^VR6E8lp46TWL4}M&a~9^aD1X^2q_F;spRKKC z78ZCTBd4v+(_39q9__WATw%ggt#9`L+iFKBL>azwtl~q6s{9xtwJSo?rKEHTz8EpOXBlffi_b z7nmz0YCJSTM1nu2^k{1mN8eo)!zX{iG){vP=)MacjF6G}&2#l$~*bJq1KcmjHL%j?hs zclBKtca^1%Igjl%_65rlN(U+n-!T1lwWLwGLGJ$E@-LCwDE*=aCt>NQFxSronLqEV z74p-5m^AoYT_q5}_Nn2029;v8<7FvZpsK2+jd1B3+w|bsmh;NG1~6oRV0}QqUAP}r z9=j45TNQ2xz5ymxrLVZ~Vh--ulv_^0i-f-yP?-lff@gl~=s1;)^HE=bWREeE=BD5o zn+mJ?Fvn{=5LKl*9OQgw0N{F+TG}xplR;c^r_mn--vuouHDHG*!Mip1i5rML4CXBS z_h92JBB8suc)Yln_ifvk;k`-N9e<@?{g>JeJfH^#&7k^UnP`EqsN=>_Tp->IH7hEb z&A;>)S^&VP>j4$yk4v~e?5rxW-3ij6=2`baP=?eWpH7rtnUw}CKQqg?QlWM$eKSXH zb=suMky-U;cTmUJJ>;I9tCwo>8|#`rLr=o0ysZG*&d}eim)?l4hla zY8P^=z$>uD8z84K)4@rY53i7gpac-$+}FgD(4wL|0e*urx^E$Gww{f@ z^wmBV1b7bStlqOI;6eO47JsU2p|z6n+Q!P4YqjrQD?yH7Hcd&Bm9@Io~iQ>TX=;j}$KaX1b$jq^Dh2D7koqOm0Lky2k|FWE<_~I!TBreWJZ4Ezk zFmIyd=Ie^EaO|BZ4^}UH^0e681HD<(npl+z5>1b-C0BBFd#SaJoB3g|Dy5h^bMwv+ z^VZ$8GJRd$(Y2tVkDvWRdKYArlt@|*?7VqotX|Nh;@D%a^P=z~n}hzt>W!HD_cc@d z1*o&9pHN$%#z+A$SY+E)|303`JH)V%vY%~HC^a~96#tYzlVQifv)ZVzam~||mOj+a zVX29a&$OdM+FG^+|P%XEoWKmagyqnnqNl&jwJHxjKIu#RZ z`;7z=rOh$k4DS}dTEWEp@S8>CXI`lTvE(H9YlX+hyjv5WF5L{rHC}YRX`MOrRii8$ znn}zmv16I%-~&O#c+@MNF{%m%%*p!^PMa4Nn)0rwe7D0hFsArqDe58nzX*q)dM&1I zW~|+!tl$n3$TI4TTayXnjlNMMw-9x*)Z$_-&O^i4%qg_WBHwkfHqkb5wl#)odrpyW z-rwkCmU+N#oXg8L{s^Z*?eS#4L_*WP-+29~r(;$UP)M#zpS~{6oihBvws*B5Rv2fI-Nw=6neF$eaS^kqy$R7!z_2_I!_So&*{{U?IBCos@^AU)Q|AKw|W1V1%GLK|f^tl%2 zs^B+*9b1^apRe*2A$u;eIhDjxp_hRk+)WPu&VXgp%=>6iHrzQ&rFUq^P)`qp z#0n<&*#ckGTB^RA-mIJ6WM*dO=%J;h&5`#jN26skB25CFb`EQthZ_~ThabtWO)Lde zG%DFSiC;F?XK4qMFAEE_eGgqsSM@JC;el#lSMf|K_qpukWUpV}!=6I|3Q;Kh(f02m z!#uitjW&T7$t)An%!lxcvhDat-2S)^p0&c*d+Jvqkd3jvWb2RRO_^A0oK>oudc3M1xbo^0A*!m zaX6f#qhmzGL-Vv?rxfYOZQUds1#}!PMwTN+S*!a_c`Hv_YxeF z+#;dZuV07HHCtHWiQePCiLbqpKXtB@61C1yKe=j})mUEN7|P^Xj5zC^85wzIW>#k& zD;vko!NI}8@&}K{Z*Fc92!aR)a$qMXCkF=@u3A7*aklvf(_gn^3cq~|!H<_MFE0-q zKsRoMG!ALrUT;^vX6AWC7a@fqwj4rq`Y2G}0~B%x2M6ow>g;>suoogd9%Qe6(kK+_ zaC15xi9`)!)i~;rKP2f{%BW$yn~>jSdg6nc4XAKzdgMO^1o8>9NII6cQXd=!O3( zf4;0?*m{XK?hFcdcTcC;Twhzm)w-rPmu*bdqKb;Nz;;#Uo%PtXu5+P^`B)yDGJR&p z20L6tbU|it=o075Xw73%M(9XxUY?DS(Sq~ymb}FKv9~To_j0UsMA7RRR2pW>@C@Mg zgE)F}WIR;C?Lf>zM^ySzw6FXlg0snCXbbKXSe_Zi=^2S`XpoVYw*rq04|^>2GO{8Q zWIUI&wX~ud_~qrB8XEjbxqV|LQ73!y@^)M>Nhh1=;6Kd4o<1hcq-(=!gQ0)=`*pXW zxZm3xih(BGCwIK}{usqOAHXu98RVHhrT2!YXEx&~AI#=_6EzQWjwqIG#YzmHoU4{g z4o{LMPFATklaD&<_uZvbIjS#AVOp?50kD1K+-Ttie<+a?U(5w&?Rbo7^@ zT{vTY6bwo({e+oLxT|Y2YNt?#t=e2TY1+0CyUyO~9RSCqZFMYZChJ9A(|p5Q9T7l! zvT>!yI;y?OzGr2A{I+|E`O4sxwV>cQqp)GyfA?pgr^oD0M@QFhTJxA6t0VV2bFUnC z|6I>FceJq^XfO3ctq!OMF(KDU0?H{=J1g0{Tk5-zg7|CAoNcA1b5L+Q{2)epo5c4w z5UDS??POz<2bMfl8_E6zk|xb|&4EtSyH{VdfZE3boAw1b$l0%Mc>x8obRt_?T6Pb& zyr87!1nE#>V9Sxu|I1Kl1#R(UyVM;l?i7Yqb;URD*78v@GvWH?y`DVcii}qtSe$ zB9S11!D6vyC{u8?`@9nRflbyY9@N+a3ky2F@7!#?$JuBUO7HBp0~`)FGBWyN^HTJ| zgMKVF)myEa{@>Q`s^GVO_gsPc-eUPn)7aRU&;I;INpUfwm=zy&J9$QJ_TC$cz5oZM z;FCRallJoTB&01ZFOQ9lMZnoA8yn^N!X@vBL9$!9Ujqk50qWN?qr9XfC3VK1J@)?d z)ZX6yp5Pw|BsPlfE>k*n_>Z4IB|;7R#3fu5)mYl6gM}EWK~GgzfYQ>^>8Yu=Zr+>` zd4$B0dm<^80ABl2ad2>OEL|l~|1F4rVDyXe`$FBEN~_oVJ=)?w)JSc8J|@i*+@);;*z&cs=OkXhU^nNl@gX*5&^1j> zuiG3)pZCM0{QUfgGiYP*J!(~Z``^*)tQR@Y4@GEJDFYGw7&1$im6av6yG_?wqdg1D z%SAJ?@aya4U^K6+AhW8amDL?93eQK#you{F160w7_28AAot>j2|2`(HQ_XQk2b0c@$yCvm1h%wn^rm%T<|9E_#6cLFU>~QgC_-T?HG@h<*{Kc9(Lk`fw07Ei zWJO_NVQDF>YF6eS#I=tlC8wsQW@ct4CRRBPr0wnPb(Md;qVmGc&H5yRLqUZ>oD)U+ zk{M746>EbB!L>6nw)GH!8-t=%etFo$(do7g2}u- zC|$bkXG^IRAuGXueP^$M8VWDBjMcOhF1&Z*AOK`s&@}ias}32 zUS4o`Ik7v#!M1Ot)C@(`d=7^b!tI*@+%(3Uxk|xPhob6ah6@P^v9!#>!K5bv`bW4C=oaIx=;QiJ(K>}rMY^>&$NJGF6pp%9;ak#yG0ekw54#d^}z^RSDmDLRj zlP9n68RS^-k)y1-z7AmMfcvhWR}sAf^M!)e3DOV3OJevfKFd%Ai3RCBt3Hp!Yf%LZ>#KARHU}WX$s%3 zJ<71U2WjwqWhc`@M25`A1}u@g*bP9)U((**>xWv>&8*lO1#B(-C(L% z5f(L@#@O_=MWVrqnd%0ru zFWvlsOCs*Wfv?-F%gL7w7X5x4%t%rDZk{&Jz;${pv9QnyNIagKvN$?y#YHn^RVDH^eAR+)NsbXh38hOAe zQ|U4N*}7i7A=FmS)U@HRJr*HZdy@L_^QSiiQjD50qdl^-_vFPPeWYh|^_*QVC(W>w zA`g2j2~&ApH^{EVC5Ob;n-TY%x)Nz7SBV(#YZGZ0D5@k-$-;j2j|#s4`DM&XT*{!o z&B*UJK?85As|AqT1^fH{w{G!U+PPgpVl{(~c1I48=jvKS4&EgC35!U7c78A*RC9$H zI`!PKBSCL(Gs;<54wehL8ZGHO@ET-CQkKcgRuF+pKkliKlT5CVF>e;Z(VjK z026JRtjDIKU4v9@#9l#Aeh_r2c(CUx4fe}C zCfkQc6#}|6Yt5nCY@#BDu4fE>T?5a2LG-qsV83_`iz;GXV-c)+?57Ls-CjW)zh;%WgXlliCr7pnpG|cc%dbk+eIV1PccT2Xk}turT~tL08<6`<2B2C;?5JDIfBxtEDa5B9z4MJ;zuS58~Xq&EfECSFRk?j`DaVYJUvxF$Ov%E>0i9$tfv$ zXK_rvStyDpUXYW^tE^;>H@%=d*EWn~o+5fK=;CDtWu zKbVFUwZCxTLX{6~tiq%3gH(>cRFy|x?nsqS>WA?=C$YAD-`q-yi#xl!Wt5yAiq&#U zO$}62Qxggu9v%id6%-V7baWUP7{tfNA3Ag>`Yg-Gz0Belkq9rcc=+JKXSxhPRW=4X zo2~zPc6N40C8(AvXN)Z>EG#T4+I*~#;Ampfx7s;5If=*PH#Y-{x2!kjz;q`0@wK%3 z)>X)i^)z0R?>Q}!bn!F_h2m}^kw|%{<So*k>qHV1xPU~|8 zR1`?#wr2+G@991iTIG|T9Ff(IYVYbYUtYwOmmfZIgcqyWsF?eV6uPziP|98g8sL66 zrDQKYD|r6*IobuOwv{2AesWpZ?K+CH#rpoLp`mGs+Qd};2fe<(X=&!+^u3<8y27n1 z;}UsD5(EPA@bIX=$MtzMY)a#MZ||d4=HC?+73t|xOg+po{f9UMs_u_1BECFi8AThv)!>61+a$k(89wldE=mW%pXkYy%0A`97^} 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 LQy*BT?hyTNC(A*F literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-NORMAL_buffer_1-FLIPPED_90-00.png b/tests/reference/output_1-NORMAL_buffer_1-FLIPPED_90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..bdb129bbeb23e70e0c122af182e32b3e7230ca94 GIT binary patch literal 4914 zcmeHLXHXRHlO8~c5>#?@S;;C%aukr9GYd%065J&R34-7v!2ps`at{r&6e?%UPX-F>{7s+sQheW&N`uI}fV=~ssO8q}1mlmGxwYiX((0|1d4 zLEa}PCR|U8;Oc}MsiTgD8gTW``LUz)9RS=yYpFdl4bH(Vco~{n#E|X7F7%{Q%t1Cr zuUHctqfGr7G6ctwKsg7eXr`5TL!U7(kC)Le=kzRF@n@=m^_oA!>pZ0d`g=2HT`X@& zHM$y#%8}^u@-&icRZye?(R2(RY-(#Dy>C8CxDg7Q2QecHWape$-&RzYdN42=F8utI z3>uo9AC?P59hRrJlK^zg%P%N9?KGS^S`~o z{qhWo&{&V&(h$)J#_vOei2i)Pq!b6muC_!Sf!$vfw9MNAmUKC;BqH5Q;0EMycMsF& zxo^oy*PCnsa=(nk;OC%r5E39_`Xdr%+Zp7$g>4!!w4wk;lzEVGj05B769m_XG(x;} z{7LRPdSnp=CtR0C`WW4f-I?_4$X>eIRc-psJpB?-=IwIe3}~kSg_&3F`=BCDKiv`B zqL|FrB)L}$*ooU^X7oMG7Du9%*pk>mUjDM#2yEP7lfuKJDK@Feg;o%?!}8Y5X?K?F-UGQ9n^sCxtz46s3P9S-#;|?uK`!c+9rdLB0FmZ zJJ)G7KGjT)t0kpjxVeq!{e8k_?iPLgres#FFP6zWzYCL`;|Im2T9A^8#mC#No&88n z&98offn`vDR;Lzo&)J*s44Wm*KhRl3^hy1N<&2NqGbQd7eP8$7*dCB##TFJ^md}eH z%Ke%Y5|^R?k&DY0rH4KZOd34t*`C6cDc{}F=){#BDDG3fyOex7i3ez@1T|c18wDr4 zjEoF2bcH57Rr<>Ae9_mv+&aNzbEXUjz+-GV+N_yzsdNRBE_5oU4gz@x>U+Ls4gv)T z&V)`9e%fjgr5fXoW*^(vPkgb#f_3t6=i6R1 z2uSYku~}t!&7`6qq?hIOWrI52O24>%;j&utu4MSYLEVqBW$WZ^-Dk;i`PVjt0Yhep z2I_9%8#-ucaPZb0dCfjVPfu^9g_4WdnN79Z&d%QtQsb4Q3EG%WtkRDOx#fe6!pO48 ztn28HPJd%APHIW8WB@;PwMcyVL~QC+u8h^z+d*~mSATt8m-?zL!%qL8O|rW;1m-pb zA_cG6lT?pA-CLH1h_B|yy;vn@Tp{}SV)9Mv((<&ui2OI`Iv)v%Nt^-8jo4ICyS?W( zRgO~dThSNNHQ!J$9MJz*2+0@!@_D`MU`;}TiR^-@gQq1ksAy{UVhr37vQ2XO>@Sv? zO}XAZuJxzu?emK(SuwF8`wT&w;@?Mdxm?uLXEvB=v{m#52k?TQhE>L+A@}Pua7?N< z#G4#(Q|08uf#w=;pT1{DLc5RefkjuPgo5Jg&G(4aqn6z^pQ~R^UN?fR0l2a#a%pT6 z&JA9@LAvKu@$At?{hUzzOZ<-ixt&a@sE(9`U{XK5uSn@M={wuz^sFe{)Y=i8= zc6~i>TE76?{6iw$t>Ov%V|VwM-+w2Q*h+Cj*bd>ydqT?(vsE4KB&%lQM0$E&O~O!2 zA&W)E$8x#79Ua3q?I*}aFM@>AFV!jz6_w3WPb_GM9iqNfj(5@ulNdgPI3!POQ-Jnp zsT;#jOO&ze;b&#$uvvs!N?gk&j*RG|d-M%#%^o9z73&s*FmnM+A~erW zP(|&#qKJ$iq~*d+XND~5PyKi$j1(^GSSxVCllF6s-Ip(RT0{7D(h&W{+y2XWXb`o1 z2)_g}RB=vWMr!e(`CW$+U#xWJS$tToa4RO_E^F7Jp=X9f=;Gqlk{WZ)18B-D znd0PzQ+lllFrZcLfJ)HzUr1+w-^Q@xLhOO>gdxWHpa8FveoC1h*A$WZdpIJ?Ky8KO z8Q-h#^jH(`ZY_sHlY*@TJC0v~?Pui@{1I*s62>JPh z=pMNEWhNc>e8^Iu+s`kQ7gMzlEZl(fT;tkIB4yHlrY)sD3Lc{-J|4`+^e1rQ<9>D-Ll$*_k|E--=fXIKhu@KFxhF#q*AOy{*g!H!U0& z-K+q!0Dwf*ZBoml6n`fRo7@@kx4Pudn%>sfpr@}|3-YH156po5>(wILP7{+RusluL z9B7<;MalTXE<(b4G|l!U<>fzP^wvc0=q=R+9<)D*A_;fEcc7kudfG2MILySY?bi?#1Tt&#=Ye zSxtR^I4t~_2nvP5;qWH!ImX+ttMUQaT>a9q%t6%g_Ec#}$?jb9UUl?mFc>yd+zkqYp|uYqdVl;_L!t6yU?r`s zt#xz*p~Ttl9GQCoErn2job^JD&{$kbKK`V{V#D>=d1-}HVrpv2&Dq&6XO9ofjwmiE zDJd#iL!;eXmWwf6tOQ(+e)r(}1D{JYOLG-iJYbQlNA`FGv3uP-;P3u29*;*NbtB)( z$E~&?kw}8t6Hj|#$ELjhF~FI#oUzu_KwC#~iO-5e?e6Vw)*~K)0RaKepW|&rn;_K- z3k$Zkwj3NB7!0Pmx;i#>ZEfwxj~}tIv3l8(y$A$WUg;uHR8&+WnSJJK2<*|L@7-7Y zQT9##*i1e}$kF4^r-#9Tt#cS3AD^1)YK6cZ#ac&#+2iA5lWZOyp0JZ0EghXPch}2$ zb_8awS(uxfTUgk)o`Zy3Wgdloa(Xtv!Ih@8KOQu|bqvF84RA@L&Q@%+d^|m6LytCE zVQ9u_t=gTP9o2Z6;?mM#{KQOy8~RrRKb&HCHI|(|>YTo>j!(kDz!T{Kfn`Zv3M2)M zU5F1iS5#KI@y&QPa>_uR(L=e3dSalp`Bs=*(5^gmEn&hl4JRli)!fz=TF*dyjcu!R z2J^V7C31Yp8)_Z7nof<$8CdR#$6~QAao*nER#sLGg)RKW#l;K^3u%ZqH&w3+oGQJXT^l&}{AOd?IPDQq%p|gZBSCV)Z7v7hP;QBHX5SJkA6XU{cMm z67}|_jHm$LZCg{spw`xkii$LKva+(Yb#yiZs=j>rQeLhFc}qCny*Sm3;U+k+R#`#q z`_jF;^^GeZ$oa2H>1>yGxVZDgz+p)u99|Uf(mWS&v|%*6AQyU6Hm9Ia_kOjdpn!We ziFia=0qJEoKGS)tX6JK&vn&NDcV=jK)Vqn(0rUJ!8$8$K{mw^HO6p=?KBRsqS8i&4 zUfw_ocpnp*L0!LO-*e7#`z{p~m5WrpmzS5vDW;Q}P=bFXnfY%X zi@;ci7Wuf+6ed>=9(2mNoapUbB)69=PuDv&_<9NJjS>Dd6p%wipcAv|Dv|K^eg#=4 zqfad@(wnY9K~k9H!LghwaRSvVAe$BaYNpHQ&doHt5o1ihg!0S{{0Et}xUpFBpZTPaMRJ%eeW~-w5d0O>kj#~0rI*xZ*Fe7s;ChEhiSTd;4WvUI!W(Y zwSU5*;W5esn0*%`t|~3nXk+2xGWchdZ~I0R_dk#S{HealJB%g3#g_||nXxf9+ab?C zbB44H>(tBHV9DVk=_~TOiVDHRUhew@0HDQmE;NG@dt)rRVs72<)do)n$>%G0_*%(` z9&1ktNB&{HFnkV*I6HLNbcH}5fq{~kP8T<~(&FO5F>0XL5-uWa=;Bh$ug_;w_?unU zKOwL%o`&OXXOlX8Z-2k`zj6~NgYC96FtM-*TJDY;A0ID&dvtX4`LkH)KvY^<+OH>; zu;uRSUsE~g;h`BFwV#aGWVmlR7zcXZ&-ZzD`S;-cd-_dndomjKva+&|2UQ8^GZHcy zO&v8O`RA9xU2MdbtmAgyl#8qX1GCOf4^BS)6j>f*Nq91U}BsSF#WyFn)V`du3&1VPRoM$3pwL0fE#V9UWCvx=Zx3 zS5{ZiZKI5j>q|;d(Fo`HLDFK&Hi@oq6GOw1nHeh^n|onPaZJNl`QfG0R5_)@USlJp z=xt|vEjns+j!sC(S(bzsD=VvWb4vsm?x?ETL(o&5?Ozjj?-25!G4cwZp3ayLz7KcQ z(HX$wE)L{bGKw-pZmU#ORE&;}ewF&^OrnEwtGE0r)ke@oI&NAr>`WmWCMG6A&?@wd zjIPekKYDu))J$_`m1|;klk_(pM&$F>eu? PV1U+ReYF}D`zuXEdCxv?IeR~6KYPCk56q3Z&I_Ff z003N|dxn+(0Fxo({?}P%hTJboyvw+-xtkap0#5%vKB7v~0RWzTkfEM+D2y~8^?)w~ z-cNDC7;vk8yl5oKjxb8)dM{v+ii*>g3v6U^G_4Mtk(BfFd#)o}HotnJ^qY+uJNm|! zb3n+9+>d3+2oW^U+KKaK&5O5LA+c@J_IS1yan>2v6-F*|2prD}i^mOQ5u)coWXV-7ujS%IOesJ@g{N*I^v+?oqo*1~FmzUko zOjbL`mS+laiy`GVu90&%&Y!<<1=!((RY)J?Td^AUp#B*8Kp+qt92|OFVp*j?moB{2 z%f1{s z1;(2zD=Qrx9cQbByVL5G*t=zAW#7DgyHA?qPenaXUfm#JIzK-mFf=f5&&0&U%*+e~nrZUO8}s6fE*N}NUR}LV z3pNIU66#_A-35H4YzqX?$<9tdd!Dch*Ny3HZEbC8QVXpXKT9H!(z%C*hLRE!>+0%$ z&qY@0_6I1Ub}SFMKBQ)*a#Umu^L!NIcX>dP#8Y85p# zHMO+?Cxr7gEJiZxiHV6TEABJBa5y|)E5yde<_-00W@e^V$QE&PGu2E{L7|VL6#B_f zX1%UXWrhc7@8zJfp?j5e$JnnrbHr5v4~-A=ZECnzGKoj6g=7*Ho- z&oDTHAqSc;H_~N()d&Lo68nj|^P0~R#m`=aFJ0|W+2u{_=+K@i0jjGvH#Gdd#(=^X zz7*O(szjYAFZV}QH_EH3NOT%iwUP}0I^V}Yn5@>jc^s}1_e_=ZkxI@p0nPy{ zrS+xw>8tQ6!Oq#)*`=P@F+m~MzoZ3%0s{k=dSYXle)OHcA#C2H0@#rjs3#^R9v>fn zh5lx$hHF^NW%+3MIh*&=CFU!8ySp;#HcB(k>N1%=8h<9M3Uj{cev3k(J`cSL^~P43 z2rZ^sq;g3)2m-?SATu$UIM9jD8$+nC#>`h1D?#{#Ah*{}o;BVsAq=HZA5=8yN$b|d zzW@HwlCp=1DRnv3fd&^_G~Dh7?%+=Fp$psnvrbtztMibwE8aG!N(UMw*J8<9IUPwB zAUxwn@sxF45T9OY^WS4$!2&m^kU9Tj`#|Sa_i^_rZQ*9hQ(qCqXxb4nYpLO|(P(se zKPuT`mvBMIM%PD_yf=gS>TF!UJ~`dUj%(8EhwevCDfCsAzdAFdh7uxmv5NuE_YoRZ`K$)WU8)*p%gC-SKMKXQIIK9gxEBdH+rbU#D$3}XtL=StP@DMh*IwRyE%Kh?r zdt)x%wmPIXnUp`9uUVyaBx4rb;RK!oRe*kp>u_+pj?#KVL&K8db+#@n7vBLeY+W+G zxVU)G#={@8pB&+f1+RoClP2HjH`P%k_E|&I zb+k&&?hfge$6_KPxb_h8T94mc(?8(A2#gH7ynue%C6tI&*!pk^qZDFk{+K6@0raT3 z_g`zaj_6ag#a<3qF+Q9MLg%P4-r#OwZAXV=>oRlZhYv&Mw6rQyCG7mEB{iXKM0evr z*9sjySF>woE=mx2l0%)Im48#@4tj2eJD3b5$5M24z{kW7LD4)fTN%)P^MCs9^GG{Dr zT@*nWXIeL#!Q?2qbc!6v4^&oG(&>@KUEg5{1R^t2(gFGa{!=t6#C>P}iRaAGkHwF$ zgEhs{nBNcrU+n-qr9|{OJb@tj1mBW+xA1oO&ZhzimA+ ih-UAy67al_~Z-O(WR( z5~;1%_XVB96s76C$}BA$D9Iy!m*_I+~lw-Mnwla+p6 z^;7glU9Z11)Z)~SzPj=3kl*m_N@);WBS1e`ao6DOg~`cDnEdfuJE44C6aEGU2EW`$7m;R9BR%m_e2ifJnZ4FiXNUEU z!S7pMsUJQ_X;tRK;n9>8^gwbTL+W?w6wIW%QUyEg=3+_>DuJvWICPR+UXh{9c^sfUkmP!Qzy&I z%S%d1QeA9cN=k<-tM=`Y7q8${!E#z!?F@PM)2&OH(s9FYhrzpVrvWKwRw7 z#l2v)^KKDkUq6j{*=cpFF7M0hf`3w^1s{u>tLw+Et}Y}J+1!kc*+nq;f$?KbOiW~C zWH8{Z^E1EcI*7rSCINYJAWM8u%Ps9!kC_yNtt~ABNwbZAGt9999dfsD%nKHruBBQ( z<7jL9c7O2Kw?w@EUJ=6vMMXubo}&X70x&7pL7t;9(Ea=RgTIsC;tm4sU$8eU7P1b35HnBG?CuhXtH1==Gf~~T4Umh$DW@Hw} zi?5~SRY0;9uF?8M}nD!I>IcQ1Ndo*(Q>_L4DO zYr{dMQJ-%iD|iu;Kd}|J0z~?QfN$4YWRhWlDIGE~mjO&kKLOK>&RdF+61r4e`=5d-4UJif>=<{|ED|nzF{~LPBm#z*Ir5I^9^(UZ3g3&BkoiE(MPWf&{2a;UesEUnUa(h{)BmcK=sYbI1pb${?v$_YlJCpo^%?Qgw4AfHjHM<2mwj&A22 z^j0i>oB}Fuw5ql|T;6MyT&33Is&BtfTurWW=`t!E4}@i{HsN=XPp5y49PRf;pdZbO z@;6%+js?Qr)MncI`Rjk_Lb`OB7rR4lL6h%8Q17!fnj2ZN+l?9?pOonM&u-H1js_X! zhHb4G_&1&A*a|2NE?Tag>(stQ@?s09$xBvZ|`-uE-d>Xb!J#V zm>*5BA+T2LmXDXE7tOQv)Zeu6{l4k!(1_1WO}){M>R!HwCEUDp$=jnnO!SUMfl&S0 zBbYxvF9k1eT7?Mz8FYV@UporZCRJJlU*@d~HSYSI;HV3XFLUqQ8~NHB6o0!<+?Qyc~cu_n%S_NP7G z$30V3g0;0LGURh#6xIA>?ON-?BI&2sDCkQspBUZpNI{n_7QiFDWUcC7?!XKRfMMR% z;T%bGe+4Y?MInZPSj<6unQnjZ{lftzWs>(j1`xvZJE^aFHmDznA$okqI%0)3Ljpq^ zwADN{vm-f5bhD>6d^BPoo}6;}mP=U3*m#Z@jB8-axH!2#3_jhQ?5k*s>84r)mdNf( z+_oPBHWpvX#kDo!Mg2OirZ`Z}Ocr*fJXG8mCb#M5RLqW={((2HyA`W=AfEdJhLLYY z8oEksp7k?1!Ve4-CnW4@&Jzez$K*W{YOa}_n0MlIrn|%F#ZY9sJ;WraDm2%H0+|Xk zpf`Nkg|hXmIWBJiCsq1q$=fL?jEVDLn2AW`bKJ4#Vr9@ca6&%9arMAds*@o7e{b>`e z1gLt`p^5mgNA4!u#S*V80O5n1++ZsNC0KHnc_TL^VI9nU@M-gNPz2(+^4{c)Hp0XN zM@hpkYz%(Skv99qVPWkLZK)33W=sxumGkkt`!7cQ7X^8#s9r<(YFoZ}-?Ro;-Km-s zeyrs)1v^|x;z90XR#!Uf-Cye!w}qb8e+upE>g`I2<1&MLDpN>tyrkGqk zl74yx_oAZ>jzg2&8YXpu?Vm4Xtpr*3Z8Q!cz9l!c=+ocNJay!Yf}nhV?~IH|9NDPo ziol*8J>6Q+$<5fYj-*>rDCri;?T$~O$%io*b%B)$yj$~8f5?{M8ksr!@{ep}*RmtE zAovF`dV~tQ-=7dX_`;;a*^hrAcWx5~1cigWej58={l9GrfSVnw_gR(;kEEdVQGZ)* z?yl_Y?4t29!TLn^l=+jCkgdq*7Y9FJDy>qW7kL$l`V4kgr z^>;YA^MEh>Q}Rwsu9R+nwf}9Q{O^_7|KIyx*_`;lw%S3A`We}%gI}W;8Jj`?kde7z JwZ3cIe*h4`{ht5; literal 0 HcmV?d00001 diff --git a/tests/reference/output_1-NORMAL_buffer_2-FLIPPED-00.png b/tests/reference/output_1-NORMAL_buffer_2-FLIPPED-00.png new file mode 100644 index 0000000000000000000000000000000000000000..6f55a9f84029de78caf5ed03a241b90cdd8a367b GIT binary patch literal 4598 zcmeHLXHXN`x{V-31yMl^f}nB~r9(uM5JV9{R6wPKF1-Z;2vVd83exQ%7zhf|4owIM zLg*5}1dy6gLg<}@7KjAM&6#(9yq|C0oq50B-ZQ(*F0bv`bW4C=oaIx=;QiJ(K>}rMY^>&$NJGF6pp%9;ak#yG0ekw54#d^}z^RSDmDLRj zlP9n68RS^-k)y1-z7AmMfcvhWR}sAf^M!)e3DOV3OJevfKFd%Ai3RCBt3Hp!Yf%LZ>#KARHU}WX$s%3 zJ<71U2WjwqWhc`@M25`A1}u@g*bP9)U((**>xWv>&8*lO1#B(-C(L% z5f(L@#@O_=MWVrqnd%0ru zFWvlsOCs*Wfv?-F%gL7w7X5x4%t%rDZk{&Jz;${pv9QnyNIagKvN$?y#YHn^RVDH^eAR+)NsbXh38hOAe zQ|U4N*}7i7A=FmS)U@HRJr*HZdy@L_^QSiiQjD50qdl^-_vFPPeWYh|^_*QVC(W>w zA`g2j2~&ApH^{EVC5Ob;n-TY%x)Nz7SBV(#YZGZ0D5@k-$-;j2j|#s4`DM&XT*{!o z&B*UJK?85As|AqT1^fH{w{G!U+PPgpVl{(~c1I48=jvKS4&EgC35!U7c78A*RC9$H zI`!PKBSCL(Gs;<54wehL8ZGHO@ET-CQkKcgRuF+pKkliKlT5CVF>e;Z(VjK z026JRtjDIKU4v9@#9l#Aeh_r2c(CUx4fe}C zCfkQc6#}|6Yt5nCY@#BDu4fE>T?5a2LG-qsV83_`iz;GXV-c)+?57Ls-CjW)zh;%WgXlliCr7pnpG|cc%dbk+eIV1PccT2Xk}turT~tL08<6`<2B2C;?5JDIfBxtEDa5B9z4MJ;zuS58~Xq&EfECSFRk?j`DaVYJUvxF$Ov%E>0i9$tfv$ zXK_rvStyDpUXYW^tE^;>H@%=d*EWn~o+5fK=;CDtWu zKbVFUwZCxTLX{6~tiq%3gH(>cRFy|x?nsqS>WA?=C$YAD-`q-yi#xl!Wt5yAiq&#U zO$}62Qxggu9v%id6%-V7baWUP7{tfNA3Ag>`Yg-Gz0Belkq9rcc=+JKXSxhPRW=4X zo2~zPc6N40C8(AvXN)Z>EG#T4+I*~#;Ampfx7s;5If=*PH#Y-{x2!kjz;q`0@wK%3 z)>X)i^)z0R?>Q}!bn!F_h2m}^kw|%{<So*k>qHV1xPU~|8 zR1`?#wr2+G@991iTIG|T9Ff(IYVYbYUtYwOmmfZIgcqyWsF?eV6uPziP|98g8sL66 zrDQKYD|r6*IobuOwv{2AesWpZ?K+CH#rpoLp`mGs+Qd};2fe<(X=&!+^u3<8y27n1 z;}UsD5(EPA@bIX=$MtzMY)a#MZ||d4=HC?+73t|xOg+po{f9UMs_u_1BECFi8AThv)!>61+a$k(89wldE=mW%pXkYy%0A`97^} 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 PV1U+ReYF}D`TfRJ?eFh@q{S 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 tfctkE6ZR( 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-270-00.png b/tests/reference/output_2-90_buffer_1-270-00.png new file mode 100644 index 0000000000000000000000000000000000000000..86e4ba0eaa6a35325c1725bebe2fc28ec9d53a04 GIT binary patch literal 14385 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

    5w)5#rSQuKu=!?#^J`r-CCf@NumWR824}enV9m2 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 A7XSbN literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_3-FLIPPED_90-00.png b/tests/reference/output_2-90_buffer_3-FLIPPED_90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..fe5507dc639806e744e1b37bcca19dcc52645c80 GIT binary patch literal 5167 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

    5w)5#rSQuKu=!?#^J`r-CCf@NumWR824}enV9m2 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 A7XSbN literal 0 HcmV?d00001 From 7f42b350de4d334793e7131d7c8753b409e4ad85 Mon Sep 17 00:00:00 2001 From: Guillaume Champagne Date: Mon, 10 Feb 2020 11:34:25 -0500 Subject: [PATCH 1361/1642] backend-rdp: fix unresolved symbols errors The RDP backend uses functions defined by the Windows Portable Runtime library (WinPR). The library's code is contained within FreeRDP repository, but it is packaged as its own library (seperate pkg-config file). WinPR is added as a dependency to the RDP backend. The version 2.0 is choosen as the version to on since the backend depends on FreeRDP 2.0. Signed-off-by: Guillaume Champagne --- libweston/backend-rdp/meson.build | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index 237cd0a7a..a033969e0 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -9,6 +9,11 @@ if not dep_frdp.found() error('RDP-backend requires freerdp2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') endif +dep_wpr = dependency('winpr2', version: '>= 2.0.0', required: false) +if not dep_wpr.found() + error('RDP-backend requires winpr2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') +endif + if cc.has_header('freerdp/version.h', dependencies: dep_frdp) config_h.set('HAVE_FREERDP_VERSION_H', '1') endif @@ -24,6 +29,7 @@ endif deps_rdp = [ dep_libweston_private, dep_frdp, + dep_wpr, ] plugin_rdp = shared_library( 'rdp-backend', From 467e6b98831f76c066c10dfb9d8a8688307feea6 Mon Sep 17 00:00:00 2001 From: Guillaume Champagne Date: Mon, 10 Feb 2020 11:35:21 -0500 Subject: [PATCH 1362/1642] backend-rdp: enable undefined functions errors. b_lundef was overriden for the RDP backend since it triggered linking errors due to functions that were defined in a missing dependency. This issue was fixed, so the override is removed. The global project's linker parameters are now applied to the RDP backend. Signed-off-by: Guillaume Champagne --- libweston/backend-rdp/meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index a033969e0..95d9a83b4 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -37,7 +37,6 @@ plugin_rdp = shared_library( include_directories: common_inc, dependencies: deps_rdp, name_prefix: '', - override_options: [ 'b_lundef=false' ], install: true, install_dir: dir_module_libweston ) From 31af69d8684e524aba88e06ed75e8d89ba64bf53 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 25 Nov 2019 23:35:36 +0000 Subject: [PATCH 1363/1642] compositor: Fix default transforms when output section declared Regardless of the default transform passed in, weston_parse_transform would always return 'normal' if there was an output section. This is because, if a section was declared for that output, it would ask weston_config for the transform, with the default being 'normal'. Fix it so we return the passed-in default transform when we have a matching output section without a transform key. If the transform is declared but invalid, we can remove the line resetting to the default transform, because we've already set the default transform up top. Signed-off-by: Daniel Stone --- compositor/main.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 7fbbb574d..d386389ac 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1214,17 +1214,18 @@ wet_output_set_transform(struct weston_output *output, uint32_t default_transform, uint32_t parsed_transform) { - char *t; + char *t = NULL; uint32_t transform = default_transform; if (section) { weston_config_section_get_string(section, - "transform", &t, "normal"); + "transform", &t, NULL); + } + if (t) { if (weston_parse_transform(t, &transform) < 0) { weston_log("Invalid transform \"%s\" for output %s\n", t, output->name); - transform = default_transform; } free(t); } From aaf35586f471ed83d3429c16093d4aa26bb39fb5 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 6 Mar 2020 21:45:50 +0000 Subject: [PATCH 1364/1642] compositor: Fail output configuration on invalid transform If an invalid transformation is provided for an output, fail the output configuration rather than continuing on using whatever we chose as the default transform. After !383, this will result in configurations using the old definition failing and exiting, rather than continuing on the wrong way around. Signed-off-by: Daniel Stone --- compositor/main.c | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index d386389ac..799ab8379 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1208,7 +1208,7 @@ wet_output_set_scale(struct weston_output *output, /* UINT32_MAX is treated as invalid because 0 is a valid * enumeration value and the parameter is unsigned */ -static void +static int wet_output_set_transform(struct weston_output *output, struct weston_config_section *section, uint32_t default_transform, @@ -1226,6 +1226,7 @@ wet_output_set_transform(struct weston_output *output, if (weston_parse_transform(t, &transform) < 0) { weston_log("Invalid transform \"%s\" for output %s\n", t, output->name); + return -1; } free(t); } @@ -1234,6 +1235,8 @@ wet_output_set_transform(struct weston_output *output, transform = parsed_transform; weston_output_set_transform(output, transform); + + return 0; } static void @@ -1295,7 +1298,10 @@ wet_configure_windowed_output_from_config(struct weston_output *output, height = parsed_options->height; wet_output_set_scale(output, section, defaults->scale, parsed_options->scale); - wet_output_set_transform(output, section, defaults->transform, parsed_options->transform); + if (wet_output_set_transform(output, section, defaults->transform, + parsed_options->transform) < 0) { + return -1; + } if (api->output_set_size(output, width, height) < 0) { weston_log("Cannot configure output \"%s\" using weston_windowed_output_api.\n", @@ -1726,7 +1732,11 @@ drm_backend_output_configure(struct weston_output *output, free(modeline); wet_output_set_scale(output, section, 1, 0); - wet_output_set_transform(output, section, WL_OUTPUT_TRANSFORM_NORMAL, UINT32_MAX); + if (wet_output_set_transform(output, section, + WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX) < 0) { + return -1; + } weston_config_section_get_string(section, "gbm-format", &gbm_format, NULL); @@ -2221,8 +2231,11 @@ drm_backend_remoted_output_configure(struct weston_output *output, } wet_output_set_scale(output, section, 1, 0); - wet_output_set_transform(output, section, WL_OUTPUT_TRANSFORM_NORMAL, - UINT32_MAX); + if (wet_output_set_transform(output, section, + WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX) < 0) { + return -1; + }; weston_config_section_get_string(section, "gbm-format", &gbm_format, NULL); @@ -2369,8 +2382,11 @@ drm_backend_pipewire_output_configure(struct weston_output *output, } wet_output_set_scale(output, section, 1, 0); - wet_output_set_transform(output, section, WL_OUTPUT_TRANSFORM_NORMAL, - UINT32_MAX); + if (wet_output_set_transform(output, section, + WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX) < 0) { + return -1; + } weston_config_section_get_string(section, "seat", &seat, ""); @@ -2712,7 +2728,12 @@ fbdev_backend_output_configure(struct weston_output *output) section = weston_config_get_section(wc, "output", "name", "fbdev"); - wet_output_set_transform(output, section, WL_OUTPUT_TRANSFORM_NORMAL, UINT32_MAX); + if (wet_output_set_transform(output, section, + WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX) < 0) { + return -1; + } + weston_output_set_scale(output, 1); return 0; From a69cb711cce25d10f8f50907f0f44c02baf527bd Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Mon, 25 Nov 2019 23:29:31 +0000 Subject: [PATCH 1365/1642] libweston: Add transform to weston_head Like physical size, subpixel arrangement, etc, transform advises of a physical transform of a head, if present. This commit adds the transform member and setter to weston_head, however it is currently unused. Signed-off-by: Lucas Stach [daniels: Extracted from one of Lucas's patches.] Signed-off-by: Daniel Stone --- include/libweston/libweston.h | 7 ++++++ libweston/backend.h | 4 ++++ libweston/compositor.c | 40 +++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 1439775ef..d036c5bba 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -207,6 +207,10 @@ struct weston_head { int32_t mm_width; /**< physical image width in mm */ int32_t mm_height; /**< physical image height in mm */ + + /** WL_OUTPUT_TRANSFORM enum to apply to match native orientation */ + uint32_t transform; + char *make; /**< monitor manufacturer (PNP ID) */ char *model; /**< monitor model */ char *serial_number; /**< monitor serial */ @@ -1954,6 +1958,9 @@ weston_head_get_name(struct weston_head *head); struct weston_output * weston_head_get_output(struct weston_head *head); +uint32_t +weston_head_get_transform(struct weston_head *head); + void weston_head_detach(struct weston_head *head); diff --git a/libweston/backend.h b/libweston/backend.h index ff10b3631..8e91746b4 100644 --- a/libweston/backend.h +++ b/libweston/backend.h @@ -138,6 +138,10 @@ weston_head_set_physical_size(struct weston_head *head, void weston_head_set_subpixel(struct weston_head *head, enum wl_output_subpixel sp); + +void +weston_head_set_transform(struct weston_head *head, uint32_t transform); + /* weston_output */ void diff --git a/libweston/compositor.c b/libweston/compositor.c index 29c0ab18a..35da1e658 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5422,6 +5422,28 @@ weston_head_set_non_desktop(struct weston_head *head, bool non_desktop) weston_head_set_device_changed(head); } +/** Store display transformation + * + * \param head The head to modify. + * \param transform The transformation to apply for this head + * + * This may set the device_changed flag. + * + * \ingroup head + * \internal + */ +WL_EXPORT void +weston_head_set_transform(struct weston_head *head, uint32_t transform) +{ + if (head->transform == transform) + return; + + head->transform = transform; + + weston_head_set_device_changed(head); +} + + /** Store physical image size * * \param head The head to modify. @@ -5683,6 +5705,24 @@ weston_head_get_output(struct weston_head *head) return head->output; } +/** Get the head's native transformation + * + * \param head The head to query. + * \return The head's native transform, as a WL_OUTPUT_TRANSFORM_* value + * + * A weston_head may have a 'native' transform provided by the backend. + * Examples include panels which are physically rotated, where the rotation + * is recorded and described as part of the system configuration. This call + * will return any known native transform for the head. + * + * \ingroup head + */ +WL_EXPORT uint32_t +weston_head_get_transform(struct weston_head *head) +{ + return head->transform; +} + /** Add destroy callback for a head * * \param head The head to watch for. From 72e7a1ed48d003977edcd244301c0a3cbd15df2c Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Mon, 25 Nov 2019 23:31:57 +0000 Subject: [PATCH 1366/1642] backend-drm: Parse KMS panel orientation property, apply to weston_head The KMS 'panel orientation' property allows the driver to statically declare a fixed rotation of an output device. Now that weston_head has a transform member, plumb the KMS property through to weston_head so the compositor can make a smarter choice out of the box. Signed-off-by: Lucas Stach [daniels: Extracted from one of Lucas's patches] Signed-off-by: Daniel Stone --- libweston/backend-drm/drm-internal.h | 9 +++++++++ libweston/backend-drm/kms.c | 12 ++++++++++++ libweston/backend-drm/modes.c | 26 ++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 2384a9acd..855baf151 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -200,6 +200,7 @@ enum wdrm_connector_property { WDRM_CONNECTOR_NON_DESKTOP, WDRM_CONNECTOR_CONTENT_PROTECTION, WDRM_CONNECTOR_HDCP_CONTENT_TYPE, + WDRM_CONNECTOR_PANEL_ORIENTATION, WDRM_CONNECTOR__COUNT }; @@ -224,6 +225,14 @@ enum wdrm_dpms_state { WDRM_DPMS_STATE__COUNT }; +enum wdrm_panel_orientation { + WDRM_PANEL_ORIENTATION_NORMAL = 0, + WDRM_PANEL_ORIENTATION_UPSIDE_DOWN, + WDRM_PANEL_ORIENTATION_LEFT_SIDE_UP, + WDRM_PANEL_ORIENTATION_RIGHT_SIDE_UP, + WDRM_PANEL_ORIENTATION__COUNT +}; + /** * List of properties attached to DRM CRTCs */ diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index 192435c77..7576c0060 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -116,6 +116,13 @@ struct drm_property_enum_info hdcp_content_type_enums[] = { }, }; +struct drm_property_enum_info panel_orientation_enums[] = { + [WDRM_PANEL_ORIENTATION_NORMAL] = { .name = "Normal", }, + [WDRM_PANEL_ORIENTATION_UPSIDE_DOWN] = { .name = "Upside Down", }, + [WDRM_PANEL_ORIENTATION_LEFT_SIDE_UP] = { .name = "Left Side Up", }, + [WDRM_PANEL_ORIENTATION_RIGHT_SIDE_UP] = { .name = "Right Side Up", }, +}; + const struct drm_property_info connector_props[] = { [WDRM_CONNECTOR_EDID] = { .name = "EDID" }, [WDRM_CONNECTOR_DPMS] = { @@ -135,6 +142,11 @@ const struct drm_property_info connector_props[] = { .enum_values = hdcp_content_type_enums, .num_enum_values = WDRM_HDCP_CONTENT_TYPE__COUNT, }, + [WDRM_CONNECTOR_PANEL_ORIENTATION] = { + .name = "panel orientation", + .enum_values = panel_orientation_enums, + .num_enum_values = WDRM_PANEL_ORIENTATION__COUNT, + }, }; const struct drm_property_info crtc_props[] = { diff --git a/libweston/backend-drm/modes.c b/libweston/backend-drm/modes.c index e12ed625d..69611b089 100644 --- a/libweston/backend-drm/modes.c +++ b/libweston/backend-drm/modes.c @@ -128,6 +128,29 @@ check_non_desktop(struct drm_head *head, drmModeObjectPropertiesPtr props) return drm_property_get_value(non_desktop_info, props, 0); } +static uint32_t +get_panel_orientation(struct drm_head *head, drmModeObjectPropertiesPtr props) +{ + struct drm_property_info *orientation = + &head->props_conn[WDRM_CONNECTOR_PANEL_ORIENTATION]; + uint64_t kms_val = + drm_property_get_value(orientation, props, + WDRM_PANEL_ORIENTATION_NORMAL); + + switch (kms_val) { + case WDRM_PANEL_ORIENTATION_NORMAL: + return WL_OUTPUT_TRANSFORM_NORMAL; + case WDRM_PANEL_ORIENTATION_UPSIDE_DOWN: + return WL_OUTPUT_TRANSFORM_180; + case WDRM_PANEL_ORIENTATION_LEFT_SIDE_UP: + return WL_OUTPUT_TRANSFORM_90; + case WDRM_PANEL_ORIENTATION_RIGHT_SIDE_UP: + return WL_OUTPUT_TRANSFORM_270; + default: + assert(!"unknown property value in get_panel_orientation"); + } +} + static int parse_modeline(const char *s, drmModeModeInfo *mode) { @@ -500,6 +523,9 @@ update_head_from_connector(struct drm_head *head, weston_head_set_physical_size(&head->base, head->connector->mmWidth, head->connector->mmHeight); + weston_head_set_transform(&head->base, + get_panel_orientation(head, props)); + /* Unknown connection status is assumed disconnected. */ weston_head_set_connection_status(&head->base, head->connector->connection == DRM_MODE_CONNECTED); From 1c0507b12a30e38c4e35e64b254dd5c1d298f3ae Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 25 Nov 2019 23:41:04 +0000 Subject: [PATCH 1367/1642] compositor: Use weston_head transform for output default If the output only has a single weston_head attached to it, take its declared transform as the default transform. With the previous patches, this allows a device declaring the KMS 'panel orientation' property (e.g. through DeviceTree) to autoconfigure to the correct display rotation when running Weston. Signed-off-by: Daniel Stone --- compositor/main.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 799ab8379..c9002bf43 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1700,6 +1700,7 @@ drm_backend_output_configure(struct weston_output *output, const struct weston_drm_output_api *api; enum weston_drm_backend_output_mode mode = WESTON_DRM_BACKEND_OUTPUT_PREFERRED; + uint32_t transform = WL_OUTPUT_TRANSFORM_NORMAL; char *s; char *modeline = NULL; char *gbm_format = NULL; @@ -1731,9 +1732,13 @@ drm_backend_output_configure(struct weston_output *output, } free(modeline); + if (count_remaining_heads(output, NULL) == 1) { + struct weston_head *head = weston_output_get_first_head(output); + transform = weston_head_get_transform(head); + } + wet_output_set_scale(output, section, 1, 0); - if (wet_output_set_transform(output, section, - WL_OUTPUT_TRANSFORM_NORMAL, + if (wet_output_set_transform(output, section, transform, UINT32_MAX) < 0) { return -1; } From 536873c58e91927a1eec90c8c58da8aad376676c Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 5 Mar 2020 16:11:33 +0200 Subject: [PATCH 1368/1642] tests: print image difference statistics When a test fails and it produces a difference image, also compute the min/max per-channel signed difference statistics. These numbers can be used to adjust the fuzz needed for fuzzy_match_pixels() to pass. Otherwise one would have to manually inspect the reference and result images and figure out the values. Signed-off-by: Pekka Paalanen --- tests/weston-test-client-helper.c | 44 ++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 6e270d379..f5f24025b 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -1262,21 +1262,47 @@ image_iter_get_row(struct image_iterator *it, int y) return (uint32_t *)(it->data + y * it->stride); } +struct pixel_diff_stat { + struct pixel_diff_stat_channel { + int min_diff; + int max_diff; + } ch[4]; +}; + +static void +testlog_pixel_diff_stat(const struct pixel_diff_stat *stat) +{ + int i; + + testlog("Image difference statistics:\n"); + for (i = 0; i < 4; i++) { + testlog("\tch %d: [%d, %d]\n", + i, stat->ch[i].min_diff, stat->ch[i].max_diff); + } +} + static bool -fuzzy_match_pixels(uint32_t pix_a, uint32_t pix_b, const struct range *fuzz) +fuzzy_match_pixels(uint32_t pix_a, uint32_t pix_b, + const struct range *fuzz, + struct pixel_diff_stat *stat) { + bool ret = true; int shift; + int i; - for (shift = 0; shift < 32; shift += 8) { + for (shift = 0, i = 0; i < 4; shift += 8, i++) { int val_a = (pix_a >> shift) & 0xffu; int val_b = (pix_b >> shift) & 0xffu; int d = val_b - val_a; + stat->ch[i].min_diff = min(stat->ch[i].min_diff, d); + stat->ch[i].max_diff = max(stat->ch[i].max_diff, d); + if (d < fuzz->a || d > fuzz->b) - return false; + ret = false; } - return true; + return ret; } /** @@ -1305,6 +1331,7 @@ check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, const struct rectangle *clip_rect, const struct range *prec) { struct range fuzz = range_get(prec); + struct pixel_diff_stat diffstat = {}; struct image_iterator it_a; struct image_iterator it_b; pixman_box32_t box; @@ -1322,7 +1349,8 @@ check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, pix_b = image_iter_get_row(&it_b, y) + box.x1; for (x = box.x1; x < box.x2; x++) { - if (!fuzzy_match_pixels(*pix_a, *pix_b, &fuzz)) + if (!fuzzy_match_pixels(*pix_a, *pix_b, + &fuzz, &diffstat)) return false; pix_a++; @@ -1385,6 +1413,7 @@ visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, const struct range *prec) { struct range fuzz = range_get(prec); + struct pixel_diff_stat diffstat = {}; pixman_image_t *diffimg; pixman_image_t *shade; struct image_iterator it_a; @@ -1427,7 +1456,8 @@ visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, pix_d = image_iter_get_row(&it_d, y) + box.x1; for (x = box.x1; x < box.x2; x++) { - if (fuzzy_match_pixels(*pix_a, *pix_b, &fuzz)) + if (fuzzy_match_pixels(*pix_a, *pix_b, + &fuzz, &diffstat)) *pix_d = tint(*pix_d, 0x00008000); /* green */ else *pix_d = tint(*pix_d, 0x00c00000); /* red */ @@ -1438,6 +1468,8 @@ visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, } } + testlog_pixel_diff_stat(&diffstat); + return diffimg; } From 5450456da2309a91db874891d562fd9c8b8b97fc Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Mon, 9 Mar 2020 16:26:34 +0000 Subject: [PATCH 1369/1642] compositor: Fail on invalid transform for headless As in aaf35586f471, we want to fail when we are passed an invalid transform name, not just blindly configure on using the normal transform. The previous commit missed the callsite from the headless backend's command-line parsing. Fix this so that headless fails when an invalid transform is specified on the command line. Signed-off-by: Daniel Stone --- compositor/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compositor/main.c b/compositor/main.c index c9002bf43..c1a727017 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2602,7 +2602,7 @@ load_headless_backend(struct weston_compositor *c, if (transform) { if (weston_parse_transform(transform, &parsed_options->transform) < 0) { weston_log("Invalid transform \"%s\"\n", transform); - parsed_options->transform = UINT32_MAX; + return -1; } free(transform); } From 9f53edd461c578d365a2e7a9a35a51e6410de7dc Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 11 Mar 2020 15:21:04 +0200 Subject: [PATCH 1370/1642] pixman-renderer: half-fix bilinear sampling on edges When weston-desktop-shell uses a solid color for the wallpaper, it creates a 1x1 buffer and uses wp_viewport to scale that up to fullscreen. It's a very nice memory saving optimization. If you also have output scale != buffer scale, it means pixman-renderer chooses bilinear filter. Arguably pixman-renderer should choose bilinear filter also when wp_viewport implies scaling, but it does not. As w-d-s always sets buffer scale from output scale, triggering the bilinear filter needs some effort. What happens when you sample with bilinear filter from a 1x1 buffer, stretching it to cover a big area? Depends on the repeat mode. The default repeat mode is NONE, which means that samples outside of the buffer come out as (0,0,0,0). Bilinear filter makes it so that every sampling point on the 1x1 buffer except the very center is actually a mixture of the pixel value and (0,0,0,0). The resulting color is no longer opaque, but the renderer and damage tracking assume it is. This leads to the issue 373. Fix half of the issue by using repeat mode PAD which corresponds to OpenGL CLAMP_TO_EDGE. GL-renderer already uses CLAMP_TO_EDGE always. This is only a half-fix, because composite_clipped() cannot actually be fixed. It relies on repeat mode NONE to work. It would need a whole different approach to rendering potentially non-axis-aligned regions exactly like GL-renderer. Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/373 Signed-off-by: Pekka Paalanen --- libweston/pixman-renderer.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c index cae897411..51324e9a3 100644 --- a/libweston/pixman-renderer.c +++ b/libweston/pixman-renderer.c @@ -229,6 +229,12 @@ composite_whole(pixman_op_t op, pixman_image_set_transform(src, transform); pixman_image_set_filter(src, filter, NULL, 0); + /* bilinear filtering needs the equivalent of OpenGL CLAMP_TO_EDGE */ + if (filter == PIXMAN_FILTER_NEAREST) + pixman_image_set_repeat(src, PIXMAN_REPEAT_NONE); + else + pixman_image_set_repeat(src, PIXMAN_REPEAT_PAD); + pixman_image_composite32(op, src, mask, dest, 0, 0, /* src_x, src_y */ 0, 0, /* mask_x, mask_y */ @@ -254,9 +260,17 @@ composite_clipped(pixman_image_t *src, void *src_data; int i; - /* Hardcoded to use PIXMAN_OP_OVER, because sampling outside of + /* + * Hardcoded to use PIXMAN_OP_OVER, because sampling outside of * a Pixman image produces (0,0,0,0) instead of discarding the * fragment. + * + * Also repeat mode must be PIXMAN_REPEAT_NONE (the default) to + * actually sample (0,0,0,0). This may cause issues for clients that + * expect OpenGL CLAMP_TO_EDGE sampling behavior on their buffer. + * Using temporary 'boximg' it is not possible to apply CLAMP_TO_EDGE + * correctly with bilinear filter. Maybe trapezoid rendering could be + * the answer instead of source clip? */ dest_width = pixman_image_get_width(dest); From 9c267e5b5500b4c30311bb46341126afaa2c7992 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 11 Mar 2020 17:11:03 +0200 Subject: [PATCH 1371/1642] tests: move viewport creation into helpers There will be a new test program using viewports and would like to share this bit of code. There are two behavioral changes: - Compositor wp_viewporter interface version is no longer checked. - client_create_viewport() does not leak the viewporter object. test_viewporter_double_create needs to call bind_to_singleton_global() itself so that the viewporter object still exists when the error event arrives. Otherwise error verification fails. Signed-off-by: Pekka Paalanen --- tests/meson.build | 14 +++---- tests/viewporter-test.c | 66 ++++++------------------------- tests/weston-test-client-helper.c | 60 ++++++++++++++++++++++++++++ tests/weston-test-client-helper.h | 9 +++++ 4 files changed, 87 insertions(+), 62 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index 463d71293..714903f2e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -30,6 +30,8 @@ lib_test_client = static_library( 'weston-test-fixture-compositor.c', weston_test_client_protocol_h, weston_test_protocol_c, + viewporter_client_protocol_h, + viewporter_protocol_c, ], include_directories: common_inc, dependencies: [ @@ -43,6 +45,9 @@ lib_test_client = static_library( ) dep_test_client = declare_dependency( link_with: lib_test_client, + sources: [ + viewporter_client_protocol_h, + ], dependencies: [ dep_wayland_client, dep_test_runner, @@ -179,14 +184,7 @@ tests = [ 'name': 'vertex-clip', 'dep_objs': dep_vertex_clipping, }, - { - 'name': 'viewporter', - 'sources': [ - 'viewporter-test.c', - viewporter_client_protocol_h, - viewporter_protocol_c, - ], - }, + { 'name': 'viewporter', }, ] tests_standalone = [ diff --git a/tests/viewporter-test.c b/tests/viewporter-test.c index ad4f37790..68e936476 100644 --- a/tests/viewporter-test.c +++ b/tests/viewporter-test.c @@ -34,7 +34,6 @@ #include "shared/helpers.h" #include "shared/xalloc.h" #include "weston-test-client-helper.h" -#include "viewporter-client-protocol.h" #include "weston-test-fixture-compositor.h" static enum test_result_code @@ -48,48 +47,6 @@ fixture_setup(struct weston_test_harness *harness) } DECLARE_FIXTURE_SETUP(fixture_setup); -static struct wp_viewporter * -get_viewporter(struct client *client) -{ - struct global *g; - struct global *global_wpr = NULL; - struct wp_viewporter *wpr; - - wl_list_for_each(g, &client->global_list, link) { - if (strcmp(g->interface, wp_viewporter_interface.name)) - continue; - - if (global_wpr) - assert(0 && "multiple wp_viewporter objects"); - - global_wpr = g; - } - - assert(global_wpr && "no wp_viewporter found"); - - assert(global_wpr->version == 1); - - wpr = wl_registry_bind(client->wl_registry, global_wpr->name, - &wp_viewporter_interface, 1); - assert(wpr); - - return wpr; -} - -static struct wp_viewport * -create_viewport(struct client *client) -{ - struct wp_viewporter *viewporter; - struct wp_viewport *viewport; - - viewporter = get_viewporter(client); - viewport = wp_viewporter_get_viewport(viewporter, - client->surface->wl_surface); - assert(viewport); - - return viewport; -} - static void set_source(struct wp_viewport *vp, int x, int y, int w, int h) { @@ -104,7 +61,8 @@ TEST(test_viewporter_double_create) client = create_client_and_test_surface(100, 50, 123, 77); - viewporter = get_viewporter(client); + viewporter = bind_to_singleton_global(client, + &wp_viewporter_interface, 1); wp_viewporter_get_viewport(viewporter, client->surface->wl_surface); wp_viewporter_get_viewport(viewporter, client->surface->wl_surface); @@ -135,7 +93,7 @@ TEST_P(test_viewporter_bad_source_rect, bad_source_rect_args) client = create_client_and_test_surface(100, 50, 123, 77); - vp = create_viewport(client); + vp = client_create_viewport(client); testlog("wp_viewport.set_source x=%d, y=%d, w=%d, h=%d\n", args->x, args->y, args->w, args->h); @@ -152,7 +110,7 @@ TEST(test_viewporter_unset_source_rect) client = create_client_and_test_surface(100, 50, 123, 77); - vp = create_viewport(client); + vp = client_create_viewport(client); set_source(vp, -1, -1, -1, -1); wl_surface_commit(client->surface->wl_surface); @@ -180,7 +138,7 @@ TEST_P(test_viewporter_bad_destination_size, bad_destination_args) client = create_client_and_test_surface(100, 50, 123, 77); - vp = create_viewport(client); + vp = client_create_viewport(client); testlog("wp_viewport.set_destination w=%d, h=%d\n", args->w, args->h); wp_viewport_set_destination(vp, args->w, args->h); @@ -196,7 +154,7 @@ TEST(test_viewporter_unset_destination_size) client = create_client_and_test_surface(100, 50, 123, 77); - vp = create_viewport(client); + vp = client_create_viewport(client); wp_viewport_set_destination(vp, -1, -1); wl_surface_commit(client->surface->wl_surface); @@ -225,7 +183,7 @@ TEST_P(test_viewporter_non_integer_destination_size, nonint_destination_args) client = create_client_and_test_surface(100, 50, 123, 77); - vp = create_viewport(client); + vp = client_create_viewport(client); testlog("non-integer size w=%f, h=%f\n", wl_fixed_to_double(args->w), wl_fixed_to_double(args->h)); @@ -294,7 +252,7 @@ setup_source_vs_buffer(struct client *client, struct wp_viewport *vp; surf = client->surface->wl_surface; - vp = create_viewport(client); + vp = client_create_viewport(client); testlog("surface %dx%d\n", get_surface_width(client->surface, @@ -487,7 +445,7 @@ TEST(test_viewporter_outside_null_buffer) surf = client->surface->wl_surface; /* If buffer is NULL, does not matter what the source rect is. */ - vp = create_viewport(client); + vp = client_create_viewport(client); wl_surface_attach(surf, NULL, 0, 0); set_source(vp, 1000, 1000, 20, 10); wp_viewport_set_destination(vp, 99, 99); @@ -516,7 +474,7 @@ TEST(test_viewporter_no_surface_set_source) struct wp_viewport *vp; client = create_client_and_test_surface(100, 50, 123, 77); - vp = create_viewport(client); + vp = client_create_viewport(client); wl_surface_destroy(client->surface->wl_surface); client->surface->wl_surface = NULL; @@ -533,7 +491,7 @@ TEST(test_viewporter_no_surface_set_destination) struct wp_viewport *vp; client = create_client_and_test_surface(100, 50, 123, 77); - vp = create_viewport(client); + vp = client_create_viewport(client); wl_surface_destroy(client->surface->wl_surface); client->surface->wl_surface = NULL; @@ -550,7 +508,7 @@ TEST(test_viewporter_no_surface_destroy) struct wp_viewport *vp; client = create_client_and_test_surface(100, 50, 123, 77); - vp = create_viewport(client); + vp = client_create_viewport(client); wl_surface_destroy(client->surface->wl_surface); client->surface->wl_surface = NULL; diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index f5f24025b..8711cf0e9 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -1788,3 +1788,63 @@ client_buffer_from_image_file(struct client *client, return buf; } + +/** + * Bind to a singleton global in wl_registry + * + * \param client Client whose registry and globals to use. + * \param iface The Wayland interface to look for. + * \param version The version to bind the interface with. + * \return A struct wl_proxy, which you need to cast to the proper type. + * + * Asserts that the global being searched for is a singleton and is found. + * + * Binds with the exact version given, does not take compositor interface + * version into account. + */ +void * +bind_to_singleton_global(struct client *client, + const struct wl_interface *iface, + int version) +{ + struct global *tmp; + struct global *g = NULL; + struct wl_proxy *proxy; + + wl_list_for_each(tmp, &client->global_list, link) { + if (strcmp(tmp->interface, iface->name)) + continue; + + assert(!g && "multiple singleton objects"); + g = tmp; + } + + assert(g && "singleton not found"); + + proxy = wl_registry_bind(client->wl_registry, g->name, iface, version); + assert(proxy); + + return proxy; +} + +/** + * Create a wp_viewport for the client surface + * + * \param client The client->surface to use. + * \return A fresh viewport object. + */ +struct wp_viewport * +client_create_viewport(struct client *client) +{ + struct wp_viewporter *viewporter; + struct wp_viewport *viewport; + + viewporter = bind_to_singleton_global(client, + &wp_viewporter_interface, 1); + viewport = wp_viewporter_get_viewport(viewporter, + client->surface->wl_surface); + assert(viewport); + wp_viewporter_destroy(viewporter); + + return viewport; +} diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index a8253137a..417c62e43 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -37,6 +37,7 @@ #include #include "weston-test-runner.h" #include "weston-test-client-protocol.h" +#include "viewporter-client-protocol.h" struct client { struct wl_display *wl_display; @@ -269,4 +270,12 @@ client_buffer_from_image_file(struct client *client, const char *basename, int scale); +void * +bind_to_singleton_global(struct client *client, + const struct wl_interface *iface, + int version); + +struct wp_viewport * +client_create_viewport(struct client *client); + #endif From f26d17fe03746afca1f08cd8c6c97d676112a9e1 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 11 Mar 2020 17:24:47 +0200 Subject: [PATCH 1372/1642] tests: move fill_color into helpers There will be a new test program that wants to share this code. No behavioral changes. Signed-off-by: Pekka Paalanen --- tests/subsurface-shot-test.c | 43 +++----------------------- tests/weston-test-client-helper.c | 51 ++++++++++++++++++++++++++++++- tests/weston-test-client-helper.h | 6 ++++ 3 files changed, 61 insertions(+), 39 deletions(-) diff --git a/tests/subsurface-shot-test.c b/tests/subsurface-shot-test.c index 7f42dbb99..4df9d3dcb 100644 --- a/tests/subsurface-shot-test.c +++ b/tests/subsurface-shot-test.c @@ -82,39 +82,6 @@ get_subcompositor(struct client *client) return sub; } -static void -fill_color(pixman_image_t *image, pixman_color_t *color) -{ - pixman_image_t *solid; - int width; - int height; - - width = pixman_image_get_width(image); - height = pixman_image_get_height(image); - - solid = pixman_image_create_solid_fill(color); - pixman_image_composite32(PIXMAN_OP_SRC, - solid, /* src */ - NULL, /* mask */ - image, /* dst */ - 0, 0, /* src x,y */ - 0, 0, /* mask x,y */ - 0, 0, /* dst x,y */ - width, height); - pixman_image_unref(solid); -} - -static pixman_color_t * -color(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b) -{ - tmp->alpha = 65535; - tmp->red = (r << 8) + r; - tmp->green = (g << 8) + g; - tmp->blue = (b << 8) + b; - - return tmp; -} - static int check_screen(struct client *client, const char *ref_image, @@ -137,7 +104,7 @@ surface_commit_color(struct client *client, struct wl_surface *surface, struct buffer *buf; buf = create_shm_buffer_a8r8g8b8(client, width, height); - fill_color(buf->image, color); + fill_image_with_color(buf->image, color); wl_surface_attach(surface, buf->proxy, 0, 0); wl_surface_damage(surface, 0, 0, width, height); wl_surface_commit(surface); @@ -160,10 +127,10 @@ TEST(subsurface_z_order) pixman_color_t cyan; pixman_color_t green; - color(&red, 255, 0, 0); - color(&blue, 0, 0, 255); - color(&cyan, 0, 255, 255); - color(&green, 0, 255, 0); + color_rgb888(&red, 255, 0, 0); + color_rgb888(&blue, 0, 0, 255); + color_rgb888(&cyan, 0, 255, 255); + color_rgb888(&green, 0, 255, 0); client = create_client_and_test_surface(100, 50, 100, 100); assert(client); diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 8711cf0e9..94b93eac4 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -1,6 +1,7 @@ /* * Copyright © 2012 Intel Corporation - * Copyright 2017 Collabora, Ltd. + * Copyright © 2015 Samsung Electronics Co., Ltd + * Copyright 2016, 2017 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -1848,3 +1849,51 @@ client_create_viewport(struct client *client) return viewport; } + +/** + * Fill the image with the given color + * + * \param image The image to write to. + * \param color The color to use. + */ +void +fill_image_with_color(pixman_image_t *image, pixman_color_t *color) +{ + pixman_image_t *solid; + int width; + int height; + + width = pixman_image_get_width(image); + height = pixman_image_get_height(image); + + solid = pixman_image_create_solid_fill(color); + pixman_image_composite32(PIXMAN_OP_SRC, + solid, /* src */ + NULL, /* mask */ + image, /* dst */ + 0, 0, /* src x,y */ + 0, 0, /* mask x,y */ + 0, 0, /* dst x,y */ + width, height); + pixman_image_unref(solid); +} + +/** + * Convert 8-bit RGB to opaque Pixman color + * + * \param tmp Pixman color struct to fill in. + * \param r Red value, 0 - 255. + * \param g Green value, 0 - 255. + * \param b Blue value, 0 - 255. + * \return tmp + */ +pixman_color_t * +color_rgb888(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b) +{ + tmp->alpha = 65535; + tmp->red = (r << 8) + r; + tmp->green = (g << 8) + g; + tmp->blue = (b << 8) + b; + + return tmp; +} diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 417c62e43..bda330ea8 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -278,4 +278,10 @@ bind_to_singleton_global(struct client *client, struct wp_viewport * client_create_viewport(struct client *client); +void +fill_image_with_color(pixman_image_t *image, pixman_color_t *color); + +pixman_color_t * +color_rgb888(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b); + #endif From 4505f8111125f861d8e039f26a0f062ead4aa043 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Wed, 11 Mar 2020 17:43:44 +0200 Subject: [PATCH 1373/1642] tests: add viewport test for repeat mode This test ensures that "pixman-renderer: half-fix bilinear sampling on edges" keeps on working. Unlike in the original report https://gitlab.freedesktop.org/wayland/weston/issues/373, here we use buffer scale 2 instead of output scale 2 to trigger bilinear filter. The effect is the same, the actual resulting image in the failing case is just a little different. This is so that it will be easy to add more viewport screenshooting tests in this program in the future. Signed-off-by: Pekka Paalanen --- tests/meson.build | 1 + tests/reference/viewport_upscale_solid-00.png | Bin 0 -> 829 bytes tests/viewporter-shot-test.c | 89 ++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 tests/reference/viewport_upscale_solid-00.png create mode 100644 tests/viewporter-shot-test.c diff --git a/tests/meson.build b/tests/meson.build index 714903f2e..231746c1c 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -185,6 +185,7 @@ tests = [ 'dep_objs': dep_vertex_clipping, }, { 'name': 'viewporter', }, + { 'name': 'viewporter-shot', }, ] tests_standalone = [ diff --git a/tests/reference/viewport_upscale_solid-00.png b/tests/reference/viewport_upscale_solid-00.png new file mode 100644 index 0000000000000000000000000000000000000000..71f438e79b8d6e1d5a8b89013516a6173a4a7b81 GIT binary patch literal 829 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX5Ps$$$P@Hb9Ck$=lt9;Xep2*t>i(0|V11 zPZ!6KiaBrZY~*YS5O8qx*Kt$M-dU#e;Fs~PvNsQvUo_9GNt}E%bFgnj@Q1m$Q*~61z7~+SUMIeqiu)^>bP0l+XkK&mQXW literal 0 HcmV?d00001 diff --git a/tests/viewporter-shot-test.c b/tests/viewporter-shot-test.c new file mode 100644 index 000000000..b4f389c60 --- /dev/null +++ b/tests/viewporter-shot-test.c @@ -0,0 +1,89 @@ +/* + * Copyright 2014, 2016, 2020 Collabora, Ltd. + * Copyright 2020 Zodiac Inflight Innovations + * + * 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 "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static const enum renderer_type renderers[] = { + RENDERER_PIXMAN, + RENDERER_GL, +}; + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness, + const enum renderer_type *renderer) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.renderer = *renderer; + setup.shell = SHELL_TEST_DESKTOP; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, renderers); + + +TEST(viewport_upscale_solid) +{ + struct client *client; + struct wp_viewport *viewport; + pixman_color_t color; + const int width = 256; + const int height = 100; + bool match; + + color_rgb888(&color, 255, 128, 0); + + client = create_client(); + client->surface = create_test_surface(client); + viewport = client_create_viewport(client); + + client->surface->buffer = create_shm_buffer_a8r8g8b8(client, 2, 2); + fill_image_with_color(client->surface->buffer->image, &color); + + /* Needs output scale != buffer scale to hit bilinear filter. */ + wl_surface_set_buffer_scale(client->surface->wl_surface, 2); + + wp_viewport_set_destination(viewport, width, height); + client->surface->width = width; + client->surface->height = height; + + move_client(client, 19, 19); + + match = verify_screen_content(client, "viewport_upscale_solid", 0, + NULL, 0); + assert(match); + + wp_viewport_destroy(viewport); + client_destroy(client); +} From cb481a66cd3e27845fa5cfe8a775e5d178c6c3b5 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 17 Mar 2020 21:52:28 +0000 Subject: [PATCH 1374/1642] wayland-backend: Fully damage initial SHM buffer In order to start the repaint loop, the Wayland backend tries to damage the full SHM buffer, but doesn't actually damage the full area if we have a frame. Store the buffer's width and height alongside the buffer itself, so we can damage the full area when required. Signed-off-by: Daniel Stone --- libweston/backend-wayland/wayland.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libweston/backend-wayland/wayland.c b/libweston/backend-wayland/wayland.c index 42af0c801..83145c936 100644 --- a/libweston/backend-wayland/wayland.c +++ b/libweston/backend-wayland/wayland.c @@ -180,6 +180,8 @@ struct wayland_shm_buffer { struct wl_buffer *buffer; void *data; size_t size; + int width; + int height; pixman_region32_t damage; /**< in global coords */ int frame_damaged; @@ -339,6 +341,8 @@ wayland_output_get_shm_buffer(struct wayland_output *output) sb->frame_damaged = 1; sb->data = data; + sb->width = width; + sb->height = height; sb->size = height * stride; pool = wl_shm_create_pool(shm, fd, sb->size); @@ -410,8 +414,7 @@ draw_initial_frame(struct wayland_output *output) wl_surface_attach(output->parent.surface, sb->buffer, 0, 0); wl_surface_damage(output->parent.surface, 0, 0, - output->base.current_mode->width, - output->base.current_mode->height); + sb->width, sb->height); } #ifdef ENABLE_EGL From 24c0f83778f3db8476cd875580876d1d1a27a13a Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 20 Mar 2020 14:50:52 +0000 Subject: [PATCH 1375/1642] xdg-shell: More helpful surface-state-mismatch error When libweston-desktop kills an xdg-shell client because it has failed to configure its surface as demanded, be more helpful by explaining exactly what the error is. Signed-off-by: Daniel Stone --- libweston-desktop/xdg-shell.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libweston-desktop/xdg-shell.c b/libweston-desktop/xdg-shell.c index d1fc2ec12..34e7be921 100644 --- a/libweston-desktop/xdg-shell.c +++ b/libweston-desktop/xdg-shell.c @@ -683,7 +683,11 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev wl_resource_post_error(client_resource, XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, - "xdg_surface buffer does not match the configured state"); + "xdg_surface buffer (%" PRIi32 " x %" PRIi32 ") " + "does not match the configured state (%" PRIi32 " x %" PRIi32 ")", + geometry.width, geometry.height, + toplevel->next.size.width, + toplevel->next.size.height); return; } From 76932e6b0f62ffdfea4d496e9b12fada92760012 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 6 Mar 2020 13:11:41 +0000 Subject: [PATCH 1376/1642] x11: Remove unnecessary NULL checks Output and mode can never be NULL. Signed-off-by: Daniel Stone --- libweston/backend-x11/x11.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/libweston/backend-x11/x11.c b/libweston/backend-x11/x11.c index 5fda05685..6d558edb8 100644 --- a/libweston/backend-x11/x11.c +++ b/libweston/backend-x11/x11.c @@ -807,16 +807,6 @@ x11_output_switch_mode(struct weston_output *base, struct weston_mode *mode) static uint32_t values[2]; int ret; - if (base == NULL) { - weston_log("output is NULL.\n"); - return -1; - } - - if (mode == NULL) { - weston_log("mode is NULL.\n"); - return -1; - } - b = to_x11_backend(base->compositor); output = to_x11_output(base); From 7fa97e66eb0d9b0cf3a0056ba234df9988eaf7af Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 6 Mar 2020 11:03:01 +0000 Subject: [PATCH 1377/1642] drm: Remove trailing whitespace Signed-off-by: Daniel Stone --- libweston/backend-drm/drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index db128286a..d495c24c4 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -768,7 +768,7 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane, WDRM_PLANE_TYPE__COUNT); zpos_range_values = - drm_property_get_range_values(&plane->props[WDRM_PLANE_ZPOS], + drm_property_get_range_values(&plane->props[WDRM_PLANE_ZPOS], props); if (zpos_range_values) { From 98d75e1b238bbf5da406ea53d42392749fc84dd3 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 6 Mar 2020 11:03:14 +0000 Subject: [PATCH 1378/1642] drm: Remove unnecessary condition in drm_output_render reuse This condition inside drm_output_render() checks if we can reuse the existing renderer buffer for the primary plane; this occurs in mixed-mode composition where a client buffer promoted to a plane has changed, but the primary plane is unchanged. We accomplish this by checking if there is no damage on the primary/renderer plane, and then if there is already a renderer buffer active on the primary plane: in that case, we can reuse the buffer we already have. There was a further condition checking if the width and height were identical. This was designed to prevent against issues on mode changes. However, runtime mode changes are already quite broken, and a mode change will also cause damage on the full plane. We can simply remove this condition. Signed-off-by: Daniel Stone --- libweston/backend-drm/drm.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index d495c24c4..94729d896 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -364,14 +364,16 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) if (scanout_state->fb) return; + /* + * If we don't have any damage on the primary plane, and we already + * have a renderer buffer active, we can reuse it; else we pass + * the damaged region into the renderer to re-render the affected + * area. + */ if (!pixman_region32_not_empty(damage) && scanout_plane->state_cur->fb && (scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE || - scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB) && - scanout_plane->state_cur->fb->width == - output->base.current_mode->width && - scanout_plane->state_cur->fb->height == - output->base.current_mode->height) { + scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB)) { fb = drm_fb_ref(scanout_plane->state_cur->fb); } else if (b->use_pixman) { fb = drm_output_render_pixman(state, damage); From f9a6162595a3324cdf412efeba17e3a0015e87d2 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 6 Mar 2020 11:07:15 +0000 Subject: [PATCH 1379/1642] drm: Get renderer buffer size from drm_fb The renderer buffer size is usually the same size as the current mode, so we were taking the dimensions from the currently-set mode. However, using current_mode is quite confusing in places when it comes to scale, and it also hampers our ability to do mode switches, as well as to introduce a future option which will let the renderer use a smaller buffer than the output and display scaled. Simply take the dimensions of the renderer's output buffer from the buffer itself. Signed-off-by: Daniel Stone --- libweston/backend-drm/drm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 94729d896..11936115b 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -391,13 +391,13 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) scanout_state->src_x = 0; scanout_state->src_y = 0; - scanout_state->src_w = output->base.current_mode->width << 16; - scanout_state->src_h = output->base.current_mode->height << 16; + scanout_state->src_w = fb->width << 16; + scanout_state->src_h = fb->height << 16; scanout_state->dest_x = 0; scanout_state->dest_y = 0; - scanout_state->dest_w = scanout_state->src_w >> 16; - scanout_state->dest_h = scanout_state->src_h >> 16; + scanout_state->dest_w = output->base.current_mode->width; + scanout_state->dest_h = output->base.current_mode->height; pixman_region32_copy(&scanout_state->damage, damage); if (output->base.zoom.active) { From c890c384c82111d49dacfcc886b89adf9819844f Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 6 Mar 2020 13:04:18 +0000 Subject: [PATCH 1380/1642] gl-renderer: Replace display-create args with struct gl_rendererer's output_create has a lot of arguments now. Add a structure for the options to make it more clear what is what. This is in preparation for adding bare-integer arguments which are ripe for confusion when passing positional arguments. Signed-off-by: Daniel Stone --- libweston/backend-drm/drm-gbm.c | 19 ++++++++-------- libweston/backend-headless/headless.c | 19 ++++++++-------- libweston/backend-wayland/wayland.c | 14 +++++++----- libweston/backend-x11/x11.c | 16 +++++++------- libweston/renderer-gl/gl-renderer.c | 21 +++++++++--------- libweston/renderer-gl/gl-renderer.h | 32 +++++++++++++++++---------- 6 files changed, 65 insertions(+), 56 deletions(-) diff --git a/libweston/backend-drm/drm-gbm.c b/libweston/backend-drm/drm-gbm.c index 324c2a83f..e06372f10 100644 --- a/libweston/backend-drm/drm-gbm.c +++ b/libweston/backend-drm/drm-gbm.c @@ -98,18 +98,19 @@ drm_backend_create_gl_renderer(struct drm_backend *b) fallback_format_for(b->gbm_format), 0, }; - unsigned n_formats = 2; + struct gl_renderer_display_options options = { + .egl_platform = EGL_PLATFORM_GBM_KHR, + .egl_native_display = b->gbm, + .egl_surface_type = EGL_WINDOW_BIT, + .drm_formats = format, + .drm_formats_count = 2, + }; if (format[1]) - n_formats = 3; - if (gl_renderer->display_create(b->compositor, - EGL_PLATFORM_GBM_KHR, - (void *)b->gbm, - EGL_WINDOW_BIT, - format, - n_formats) < 0) { + options.drm_formats_count = 3; + + if (gl_renderer->display_create(b->compositor, &options) < 0) return -1; - } return 0; } diff --git a/libweston/backend-headless/headless.c b/libweston/backend-headless/headless.c index e9d0ad8f0..39cd1fd29 100644 --- a/libweston/backend-headless/headless.c +++ b/libweston/backend-headless/headless.c @@ -387,20 +387,19 @@ headless_destroy(struct weston_compositor *ec) static int headless_gl_renderer_init(struct headless_backend *b) { + const struct gl_renderer_display_options options = { + .egl_platform = EGL_PLATFORM_SURFACELESS_MESA, + .egl_native_display = EGL_DEFAULT_DISPLAY, + .egl_surface_type = EGL_PBUFFER_BIT, + .drm_formats = headless_formats, + .drm_formats_count = ARRAY_LENGTH(headless_formats), + }; + b->glri = weston_load_module("gl-renderer.so", "gl_renderer_interface"); if (!b->glri) return -1; - if (b->glri->display_create(b->compositor, - EGL_PLATFORM_SURFACELESS_MESA, - EGL_DEFAULT_DISPLAY, - EGL_PBUFFER_BIT, - headless_formats, - ARRAY_LENGTH(headless_formats)) < 0) { - return -1; - } - - return 0; + return b->glri->display_create(b->compositor, &options); } static const struct weston_windowed_output_api api = { diff --git a/libweston/backend-wayland/wayland.c b/libweston/backend-wayland/wayland.c index 83145c936..d638fe953 100644 --- a/libweston/backend-wayland/wayland.c +++ b/libweston/backend-wayland/wayland.c @@ -2773,12 +2773,14 @@ wayland_backend_create(struct weston_compositor *compositor, } if (!b->use_pixman) { - if (gl_renderer->display_create(compositor, - EGL_PLATFORM_WAYLAND_KHR, - b->parent.wl_display, - EGL_WINDOW_BIT, - wayland_formats, - ARRAY_LENGTH(wayland_formats)) < 0) { + const struct gl_renderer_display_options options = { + .egl_platform = EGL_PLATFORM_WAYLAND_KHR, + .egl_native_display = b->parent.wl_display, + .egl_surface_type = EGL_WINDOW_BIT, + .drm_formats = wayland_formats, + .drm_formats_count = ARRAY_LENGTH(wayland_formats), + }; + if (gl_renderer->display_create(compositor, &options) < 0) { weston_log("Failed to initialize the GL renderer; " "falling back to pixman.\n"); b->use_pixman = true; diff --git a/libweston/backend-x11/x11.c b/libweston/backend-x11/x11.c index 6d558edb8..23d501f02 100644 --- a/libweston/backend-x11/x11.c +++ b/libweston/backend-x11/x11.c @@ -1794,20 +1794,20 @@ x11_destroy(struct weston_compositor *ec) static int init_gl_renderer(struct x11_backend *b) { - int ret; + const struct gl_renderer_display_options options = { + .egl_platform = EGL_PLATFORM_X11_KHR, + .egl_native_display = b->dpy, + .egl_surface_type = EGL_WINDOW_BIT, + .drm_formats = x11_formats, + .drm_formats_count = ARRAY_LENGTH(x11_formats), + }; gl_renderer = weston_load_module("gl-renderer.so", "gl_renderer_interface"); if (!gl_renderer) return -1; - ret = gl_renderer->display_create(b->compositor, EGL_PLATFORM_X11_KHR, - (void *) b->dpy, - EGL_WINDOW_BIT, - x11_formats, - ARRAY_LENGTH(x11_formats)); - - return ret; + return gl_renderer->display_create(b->compositor, &options); } static const struct weston_windowed_output_api api = { diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 462f422c6..3a000e51d 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -3374,11 +3374,7 @@ gl_renderer_create_pbuffer_surface(struct gl_renderer *gr) { static int gl_renderer_display_create(struct weston_compositor *ec, - EGLenum platform, - void *native_display, - EGLint egl_surface_type, - const uint32_t *drm_formats, - unsigned drm_formats_count) + const struct gl_renderer_display_options *options) { struct gl_renderer *gr; @@ -3386,7 +3382,7 @@ gl_renderer_display_create(struct weston_compositor *ec, if (gr == NULL) return -1; - gr->platform = platform; + gr->platform = options->egl_platform; if (gl_renderer_setup_egl_client_extensions(gr) < 0) goto fail; @@ -3401,7 +3397,7 @@ gl_renderer_display_create(struct weston_compositor *ec, gl_renderer_surface_get_content_size; gr->base.surface_copy_content = gl_renderer_surface_copy_content; - if (gl_renderer_setup_egl_display(gr, native_display) < 0) + if (gl_renderer_setup_egl_display(gr, options->egl_native_display) < 0) goto fail; log_egl_info(gr->egl_display); @@ -3412,13 +3408,16 @@ gl_renderer_display_create(struct weston_compositor *ec, goto fail_with_error; if (!gr->has_configless_context) { + EGLint egl_surface_type = options->egl_surface_type; + if (!gr->has_surfaceless_context) egl_surface_type |= EGL_PBUFFER_BIT; - gr->egl_config = gl_renderer_get_egl_config(gr, - egl_surface_type, - drm_formats, - drm_formats_count); + gr->egl_config = + gl_renderer_get_egl_config(gr, + egl_surface_type, + options->drm_formats, + options->drm_formats_count); if (gr->egl_config == EGL_NO_CONFIG_KHR) { weston_log("failed to choose EGL config\n"); goto fail_terminate; diff --git a/libweston/renderer-gl/gl-renderer.h b/libweston/renderer-gl/gl-renderer.h index 2bca2d00c..e1d35d415 100644 --- a/libweston/renderer-gl/gl-renderer.h +++ b/libweston/renderer-gl/gl-renderer.h @@ -58,18 +58,30 @@ enum gl_renderer_border_side { GL_RENDERER_BORDER_BOTTOM = 3, }; +/** + * Options passed to the \c display_create method of the GL renderer interface. + * + * \see struct gl_renderer_interface + */ +struct gl_renderer_display_options { + /** The EGL platform identifier */ + EGLenum egl_platform; + /** The native display corresponding to the given EGL platform */ + void *egl_native_display; + /** EGL_SURFACE_TYPE bits for the base EGLConfig */ + EGLint egl_surface_type; + /** Array of DRM pixel formats acceptable for the base EGLConfig */ + const uint32_t *drm_formats; + /** The \c drm_formats array length */ + unsigned drm_formats_count; +}; + struct gl_renderer_interface { /** * Initialize GL-renderer with the given EGL platform and native display * * \param ec The weston_compositor where to initialize. - * \param platform The EGL platform identifier. - * \param native_display The native display corresponding to the given - * EGL platform. - * \param egl_surface_type EGL_SURFACE_TYPE bits for the base EGLConfig. - * \param drm_formats Array of DRM pixel formats that are acceptable - * for the base EGLConfig. - * \param drm_formats_count The drm_formats array length. + * \param options The options struct describing display configuration * \return 0 on success, -1 on failure. * * This function creates an EGLDisplay and initializes it. It also @@ -96,11 +108,7 @@ struct gl_renderer_interface { * DRM format. */ int (*display_create)(struct weston_compositor *ec, - EGLenum platform, - void *native_display, - EGLint egl_surface_type, - const uint32_t *drm_formats, - unsigned drm_formats_count); + const struct gl_renderer_display_options *options); /** * Attach GL-renderer to the output with a native window From db6e6e1ec5e665cc45809cbce47b62edac9a2834 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 6 Mar 2020 13:04:18 +0000 Subject: [PATCH 1381/1642] gl-renderer: Replace window-create args with struct gl_rendererer's output_window_create has a lot of arguments now. Add a structure for the options to make it more clear what is what. This is in preparation for adding bare-integer arguments which are ripe for confusion when passing positional arguments. Signed-off-by: Daniel Stone --- libweston/backend-drm/drm-gbm.c | 17 +++++++++-------- libweston/backend-wayland/wayland.c | 12 +++++++----- libweston/backend-x11/x11.c | 26 +++++++++++++++----------- libweston/renderer-gl/gl-renderer.c | 13 +++++-------- libweston/renderer-gl/gl-renderer.h | 23 +++++++++++++---------- 5 files changed, 49 insertions(+), 42 deletions(-) diff --git a/libweston/backend-drm/drm-gbm.c b/libweston/backend-drm/drm-gbm.c index e06372f10..4b83e9949 100644 --- a/libweston/backend-drm/drm-gbm.c +++ b/libweston/backend-drm/drm-gbm.c @@ -185,7 +185,10 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) output->gbm_format, fallback_format_for(output->gbm_format), }; - unsigned n_formats = 1; + struct gl_renderer_output_options options = { + .drm_formats = format, + .drm_formats_count = 1, + }; struct weston_mode *mode = output->base.current_mode; struct drm_plane *plane = output->scanout_plane; unsigned int i; @@ -231,13 +234,11 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) return -1; } - if (format[1]) - n_formats = 2; - if (gl_renderer->output_window_create(&output->base, - (EGLNativeWindowType)output->gbm_surface, - output->gbm_surface, - format, - n_formats) < 0) { + if (options.drm_formats[1]) + options.drm_formats_count = 2; + options.window_for_legacy = (EGLNativeWindowType) output->gbm_surface; + options.window_for_platform = output->gbm_surface; + if (gl_renderer->output_window_create(&output->base, &options) < 0) { weston_log("failed to create gl renderer output state\n"); gbm_surface_destroy(output->gbm_surface); output->gbm_surface = NULL; diff --git a/libweston/backend-wayland/wayland.c b/libweston/backend-wayland/wayland.c index d638fe953..0224865cc 100644 --- a/libweston/backend-wayland/wayland.c +++ b/libweston/backend-wayland/wayland.c @@ -762,6 +762,10 @@ static int wayland_output_init_gl_renderer(struct wayland_output *output) { int32_t fwidth = 0, fheight = 0; + struct gl_renderer_output_options options = { + .drm_formats = wayland_formats, + .drm_formats_count = ARRAY_LENGTH(wayland_formats), + }; if (output->frame) { fwidth = frame_width(output->frame); @@ -778,12 +782,10 @@ wayland_output_init_gl_renderer(struct wayland_output *output) weston_log("failure to create wl_egl_window\n"); return -1; } + options.window_for_legacy = output->gl.egl_window; + options.window_for_platform = output->gl.egl_window; - if (gl_renderer->output_window_create(&output->base, - output->gl.egl_window, - output->gl.egl_window, - wayland_formats, - ARRAY_LENGTH(wayland_formats)) < 0) + if (gl_renderer->output_window_create(&output->base, &options) < 0) goto cleanup_window; return 0; diff --git a/libweston/backend-x11/x11.c b/libweston/backend-x11/x11.c index 23d501f02..dad0849f3 100644 --- a/libweston/backend-x11/x11.c +++ b/libweston/backend-x11/x11.c @@ -854,14 +854,16 @@ x11_output_switch_mode(struct weston_output *base, struct weston_mode *mode) } } else { Window xid = (Window) output->window; + const struct gl_renderer_output_options options = { + .window_for_legacy = (EGLNativeWindowType) output->window, + .window_for_platform = &xid, + .drm_formats = x11_formats, + .drm_formats_count = ARRAY_LENGTH(x11_formats), + }; gl_renderer->output_destroy(&output->base); - ret = gl_renderer->output_window_create(&output->base, - (EGLNativeWindowType) output->window, - &xid, - x11_formats, - ARRAY_LENGTH(x11_formats)); + ret = gl_renderer->output_window_create(&output->base, &options); if (ret < 0) return -1; } @@ -1030,13 +1032,15 @@ x11_output_enable(struct weston_output *base) /* eglCreatePlatformWindowSurfaceEXT takes a Window* * but eglCreateWindowSurface takes a Window. */ Window xid = (Window) output->window; + const struct gl_renderer_output_options options = { + .window_for_legacy = (EGLNativeWindowType) output->window, + .window_for_platform = &xid, + .drm_formats = x11_formats, + .drm_formats_count = ARRAY_LENGTH(x11_formats), + }; - ret = gl_renderer->output_window_create( - &output->base, - (EGLNativeWindowType) output->window, - &xid, - x11_formats, - ARRAY_LENGTH(x11_formats)); + ret = gl_renderer->output_window_create(&output->base, + &options); if (ret < 0) goto err; diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 3a000e51d..3c69d7792 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -3172,10 +3172,7 @@ gl_renderer_output_create(struct weston_output *output, static int gl_renderer_output_window_create(struct weston_output *output, - EGLNativeWindowType window_for_legacy, - void *window_for_platform, - const uint32_t *drm_formats, - unsigned drm_formats_count) + const struct gl_renderer_output_options *options) { struct weston_compositor *ec = output->compositor; struct gl_renderer *gr = get_renderer(ec); @@ -3183,10 +3180,10 @@ gl_renderer_output_window_create(struct weston_output *output, int ret = 0; egl_surface = gl_renderer_create_window_surface(gr, - window_for_legacy, - window_for_platform, - drm_formats, - drm_formats_count); + options->window_for_legacy, + options->window_for_platform, + options->drm_formats, + options->drm_formats_count); if (egl_surface == EGL_NO_SURFACE) { weston_log("failed to create egl surface\n"); return -1; diff --git a/libweston/renderer-gl/gl-renderer.h b/libweston/renderer-gl/gl-renderer.h index e1d35d415..434e8dc28 100644 --- a/libweston/renderer-gl/gl-renderer.h +++ b/libweston/renderer-gl/gl-renderer.h @@ -76,6 +76,17 @@ struct gl_renderer_display_options { unsigned drm_formats_count; }; +struct gl_renderer_output_options { + /** Native window handle for \c eglCreateWindowSurface */ + EGLNativeWindowType window_for_legacy; + /** Native window handle for \c eglCreatePlatformWindowSurface */ + void *window_for_platform; + /** Array of DRM pixel formats acceptable for the window */ + const uint32_t *drm_formats; + /** The \c drm_formats array length */ + unsigned drm_formats_count; +}; + struct gl_renderer_interface { /** * Initialize GL-renderer with the given EGL platform and native display @@ -114,12 +125,7 @@ struct gl_renderer_interface { * Attach GL-renderer to the output with a native window * * \param output The output to create a rendering surface for. - * \param window_for_legacy Native window handle for - * eglCreateWindowSurface(). - * \param window_for_platform Native window handle for - * eglCreatePlatformWindowSurface(). - * \param drm_formats Array of DRM pixel formats that are acceptable. - * \param drm_formats_count The drm_formats array length. + * \param options The options struct describing output configuration * \return 0 on success, -1 on failure. * * This function creates the renderer data structures needed to repaint @@ -138,10 +144,7 @@ struct gl_renderer_interface { * with \c EGL_WINDOW_BIT in \c egl_surface_type. */ int (*output_window_create)(struct weston_output *output, - EGLNativeWindowType window_for_legacy, - void *window_for_platform, - const uint32_t *drm_formats, - unsigned drm_formats_count); + const struct gl_renderer_output_options *options); /** * Attach GL-renderer to the output with internal pixel storage From 786490cb53439624fd3c20b9e19d3ea5ec316c00 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 6 Mar 2020 13:04:18 +0000 Subject: [PATCH 1382/1642] gl-renderer: Replace pbuffer-create args with struct gl_rendererer's output_pbuffer_create has a lot of arguments now. Add a structure for the options to make it more clear what is what. This is in preparation for adding bare-integer arguments which are ripe for confusion when passing positional arguments. Signed-off-by: Daniel Stone --- libweston/backend-headless/headless.c | 12 +++++++----- libweston/renderer-gl/gl-renderer.c | 13 +++++-------- libweston/renderer-gl/gl-renderer.h | 21 +++++++++++++-------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/libweston/backend-headless/headless.c b/libweston/backend-headless/headless.c index 39cd1fd29..76e529069 100644 --- a/libweston/backend-headless/headless.c +++ b/libweston/backend-headless/headless.c @@ -196,12 +196,14 @@ headless_output_enable_gl(struct headless_output *output) { struct weston_compositor *compositor = output->base.compositor; struct headless_backend *b = to_headless_backend(compositor); + const struct gl_renderer_pbuffer_options options = { + .width = output->base.current_mode->width, + .height = output->base.current_mode->height, + .drm_formats = headless_formats, + .drm_formats_count = ARRAY_LENGTH(headless_formats), + }; - if (b->glri->output_pbuffer_create(&output->base, - output->base.current_mode->width, - output->base.current_mode->height, - headless_formats, - ARRAY_LENGTH(headless_formats)) < 0) { + if (b->glri->output_pbuffer_create(&output->base, &options) < 0) { weston_log("failed to create gl renderer output state\n"); return -1; } diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 3c69d7792..4e5c6d441 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -3198,24 +3198,21 @@ gl_renderer_output_window_create(struct weston_output *output, static int gl_renderer_output_pbuffer_create(struct weston_output *output, - int width, - int height, - const uint32_t *drm_formats, - unsigned drm_formats_count) + const struct gl_renderer_pbuffer_options *options) { struct gl_renderer *gr = get_renderer(output->compositor); EGLConfig pbuffer_config; EGLSurface egl_surface; int ret; EGLint pbuffer_attribs[] = { - EGL_WIDTH, width, - EGL_HEIGHT, height, + EGL_WIDTH, options->width, + EGL_HEIGHT, options->height, EGL_NONE }; pbuffer_config = gl_renderer_get_egl_config(gr, EGL_PBUFFER_BIT, - drm_formats, - drm_formats_count); + options->drm_formats, + options->drm_formats_count); if (pbuffer_config == EGL_NO_CONFIG_KHR) { weston_log("failed to choose EGL config for PbufferSurface\n"); return -1; diff --git a/libweston/renderer-gl/gl-renderer.h b/libweston/renderer-gl/gl-renderer.h index 434e8dc28..1430bb14e 100644 --- a/libweston/renderer-gl/gl-renderer.h +++ b/libweston/renderer-gl/gl-renderer.h @@ -87,6 +87,17 @@ struct gl_renderer_output_options { unsigned drm_formats_count; }; +struct gl_renderer_pbuffer_options { + /** Width of the rendering surface in pixels */ + int width; + /** Height of the rendering surface in pixels */ + int height; + /** Array of DRM pixel formats acceptable for the pbuffer */ + const uint32_t *drm_formats; + /** The \c drm_formats array length */ + unsigned drm_formats_count; +}; + struct gl_renderer_interface { /** * Initialize GL-renderer with the given EGL platform and native display @@ -150,10 +161,7 @@ struct gl_renderer_interface { * Attach GL-renderer to the output with internal pixel storage * * \param output The output to create a rendering surface for. - * \param width Width of the rendering surface in pixels. - * \param height Height of the rendering surface in pixels. - * \param drm_formats Array of DRM pixel formats that are acceptable. - * \param drm_formats_count The drm_formats array length. + * \param options The options struct describing the pbuffer * \return 0 on success, -1 on failure. * * This function creates the renderer data structures needed to repaint @@ -168,10 +176,7 @@ struct gl_renderer_interface { * with \c EGL_PBUFFER_BIT in \c egl_surface_type. */ int (*output_pbuffer_create)(struct weston_output *output, - int width, - int height, - const uint32_t *drm_formats, - unsigned drm_formats_count); + const struct gl_renderer_pbuffer_options *options); void (*output_destroy)(struct weston_output *output); From 61abf35ec4b1f64056810bb0aa1623d971f8af16 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Fri, 6 Mar 2020 12:46:30 +0000 Subject: [PATCH 1383/1642] pixman-renderer: Replace output-create flags with struct pixman_renderer_output_create currently takes a flags enum bitmask for its options. Switch this to using a structure, so we can introduce other non-boolean options. Signed-off-by: Daniel Stone --- libweston/backend-drm/drm.c | 9 ++++----- libweston/backend-fbdev/fbdev.c | 6 ++++-- libweston/backend-headless/headless.c | 7 +++++-- libweston/backend-rdp/rdp.c | 9 ++++++--- libweston/backend-wayland/wayland.c | 6 ++++-- libweston/backend-x11/x11.c | 12 ++++++++---- libweston/pixman-renderer.c | 5 +++-- libweston/pixman-renderer.h | 8 +++++--- 8 files changed, 39 insertions(+), 23 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 11936115b..3c5ca65a9 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -1179,7 +1179,9 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) uint32_t format = output->gbm_format; uint32_t pixman_format; unsigned int i; - uint32_t flags = 0; + const struct pixman_renderer_output_options options = { + .use_shadow = b->use_pixman_shadow, + }; switch (format) { case DRM_FORMAT_XRGB8888: @@ -1207,10 +1209,7 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) goto err; } - if (b->use_pixman_shadow) - flags |= PIXMAN_RENDERER_OUTPUT_USE_SHADOW; - - if (pixman_renderer_output_create(&output->base, flags) < 0) + if (pixman_renderer_output_create(&output->base, &options) < 0) goto err; weston_log("DRM: output %s %s shadow framebuffer.\n", output->base.name, diff --git a/libweston/backend-fbdev/fbdev.c b/libweston/backend-fbdev/fbdev.c index ae289cc0c..a43f648e8 100644 --- a/libweston/backend-fbdev/fbdev.c +++ b/libweston/backend-fbdev/fbdev.c @@ -528,6 +528,9 @@ fbdev_output_enable(struct weston_output *base) struct fbdev_head *head; int fb_fd; struct wl_event_loop *loop; + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; head = fbdev_output_get_head(output); @@ -546,8 +549,7 @@ fbdev_output_enable(struct weston_output *base) output->base.start_repaint_loop = fbdev_output_start_repaint_loop; output->base.repaint = fbdev_output_repaint; - if (pixman_renderer_output_create(&output->base, - PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) + if (pixman_renderer_output_create(&output->base, &options) < 0) goto out_hw_surface; loop = wl_display_get_event_loop(backend->compositor->wl_display); diff --git a/libweston/backend-headless/headless.c b/libweston/backend-headless/headless.c index 76e529069..c312a0f22 100644 --- a/libweston/backend-headless/headless.c +++ b/libweston/backend-headless/headless.c @@ -214,6 +214,10 @@ headless_output_enable_gl(struct headless_output *output) static int headless_output_enable_pixman(struct headless_output *output) { + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; + output->image_buf = malloc(output->base.current_mode->width * output->base.current_mode->height * 4); if (!output->image_buf) @@ -225,8 +229,7 @@ headless_output_enable_pixman(struct headless_output *output) output->image_buf, output->base.current_mode->width * 4); - if (pixman_renderer_output_create(&output->base, - PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) + if (pixman_renderer_output_create(&output->base, &options) < 0) goto err_renderer; pixman_renderer_output_set_buffer(&output->base, output->image); diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index ce91cedd1..2b33d29cf 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -468,6 +468,7 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) rdpSettings *settings; pixman_image_t *new_shadow_buffer; struct weston_mode *local_mode; + const struct pixman_renderer_output_options options = { }; local_mode = ensure_matching_mode(output, target_mode); if (!local_mode) { @@ -484,7 +485,7 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) output->current_mode->flags |= WL_OUTPUT_MODE_CURRENT; pixman_renderer_output_destroy(output); - pixman_renderer_output_create(output, 0); + pixman_renderer_output_create(output, &options); new_shadow_buffer = pixman_image_create_bits(PIXMAN_x8r8g8b8, target_mode->width, target_mode->height, 0, target_mode->width * 4); @@ -560,6 +561,9 @@ rdp_output_enable(struct weston_output *base) struct rdp_output *output = to_rdp_output(base); struct rdp_backend *b = to_rdp_backend(base->compositor); struct wl_event_loop *loop; + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; output->shadow_surface = pixman_image_create_bits(PIXMAN_x8r8g8b8, output->base.current_mode->width, @@ -571,8 +575,7 @@ rdp_output_enable(struct weston_output *base) return -1; } - if (pixman_renderer_output_create(&output->base, - PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) { + if (pixman_renderer_output_create(&output->base, &options) < 0) { pixman_image_unref(output->shadow_surface); return -1; } diff --git a/libweston/backend-wayland/wayland.c b/libweston/backend-wayland/wayland.c index 0224865cc..60d42bf3b 100644 --- a/libweston/backend-wayland/wayland.c +++ b/libweston/backend-wayland/wayland.c @@ -799,8 +799,10 @@ wayland_output_init_gl_renderer(struct wayland_output *output) static int wayland_output_init_pixman_renderer(struct wayland_output *output) { - return pixman_renderer_output_create(&output->base, - PIXMAN_RENDERER_OUTPUT_USE_SHADOW); + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; + return pixman_renderer_output_create(&output->base, &options); } static void diff --git a/libweston/backend-x11/x11.c b/libweston/backend-x11/x11.c index dad0849f3..387e97a48 100644 --- a/libweston/backend-x11/x11.c +++ b/libweston/backend-x11/x11.c @@ -836,6 +836,9 @@ x11_output_switch_mode(struct weston_output *base, struct weston_mode *mode) output->mode.height = mode->height; if (b->use_pixman) { + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; pixman_renderer_output_destroy(&output->base); x11_output_deinit_shm(b, output); @@ -846,8 +849,7 @@ x11_output_switch_mode(struct weston_output *base, struct weston_mode *mode) return -1; } - if (pixman_renderer_output_create(&output->base, - PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) { + if (pixman_renderer_output_create(&output->base, &options) < 0) { weston_log("Failed to create pixman renderer for output\n"); x11_output_deinit_shm(b, output); return -1; @@ -1014,14 +1016,16 @@ x11_output_enable(struct weston_output *base) x11_output_wait_for_map(b, output); if (b->use_pixman) { + const struct pixman_renderer_output_options options = { + .use_shadow = true, + }; if (x11_output_init_shm(b, output, output->base.current_mode->width, output->base.current_mode->height) < 0) { weston_log("Failed to initialize SHM for the X11 output\n"); goto err; } - if (pixman_renderer_output_create(&output->base, - PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) { + if (pixman_renderer_output_create(&output->base, &options) < 0) { weston_log("Failed to create pixman renderer for output\n"); x11_output_deinit_shm(b, output); goto err; diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c index 51324e9a3..59b1f0ff2 100644 --- a/libweston/pixman-renderer.c +++ b/libweston/pixman-renderer.c @@ -924,7 +924,8 @@ pixman_renderer_output_set_hw_extra_damage(struct weston_output *output, } WL_EXPORT int -pixman_renderer_output_create(struct weston_output *output, uint32_t flags) +pixman_renderer_output_create(struct weston_output *output, + const struct pixman_renderer_output_options *options) { struct pixman_output_state *po; int w, h; @@ -933,7 +934,7 @@ pixman_renderer_output_create(struct weston_output *output, uint32_t flags) if (po == NULL) return -1; - if (flags & PIXMAN_RENDERER_OUTPUT_USE_SHADOW) { + if (options->use_shadow) { /* set shadow image transformation */ w = output->current_mode->width; h = output->current_mode->height; diff --git a/libweston/pixman-renderer.h b/libweston/pixman-renderer.h index f53ae2a34..2b81dde51 100644 --- a/libweston/pixman-renderer.h +++ b/libweston/pixman-renderer.h @@ -32,12 +32,14 @@ int pixman_renderer_init(struct weston_compositor *ec); -enum pixman_renderer_output_flags { - PIXMAN_RENDERER_OUTPUT_USE_SHADOW = (1 << 0), +struct pixman_renderer_output_options { + /** Composite into a shadow buffer, copying to the hardware buffer */ + bool use_shadow; }; int -pixman_renderer_output_create(struct weston_output *output, uint32_t flags); +pixman_renderer_output_create(struct weston_output *output, + const struct pixman_renderer_output_options *options); void pixman_renderer_output_set_buffer(struct weston_output *output, From 51048463dae4ace13776bb77675316007083a6ee Mon Sep 17 00:00:00 2001 From: Tomohito Esaki Date: Mon, 30 Mar 2020 17:10:54 +0900 Subject: [PATCH 1384/1642] drm: change timing to set color format for primary plane without universal plane Without universal plane, the weston crashes with null pointer access in set_gbm_format function because that function called before output enable function. By changing timing to set color format for primary plane in this case, this issue fixes. Signed-off-by: Tomohito Esaki --- libweston/backend-drm/drm.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 3c5ca65a9..2aea82f55 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -1389,12 +1389,6 @@ drm_output_set_gbm_format(struct weston_output *base, if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1) output->gbm_format = b->gbm_format; - - /* Without universal planes, we can't discover which formats are - * supported by the primary plane; we just hope that the GBM format - * works. */ - if (!b->universal_planes) - output->scanout_plane->formats[0].format = output->gbm_format; } static void @@ -1626,6 +1620,12 @@ drm_output_init_crtc(struct drm_output *output, drmModeRes *resources) goto err_crtc; } + /* Without universal planes, we can't discover which formats are + * supported by the primary plane; we just hope that the GBM format + * works. */ + if (!b->universal_planes) + output->scanout_plane->formats[0].format = output->gbm_format; + /* Failing to find a cursor plane is not fatal, as we'll fall back * to software cursor. */ output->cursor_plane = From bac1a7a71f5d49451ad5c6655ef4e79334ba9c38 Mon Sep 17 00:00:00 2001 From: Paul Menzel Date: Sun, 5 Apr 2020 22:19:49 +0200 Subject: [PATCH 1385/1642] man/weston-drm: Use third person singular conjugation Signed-off-by: Paul Menzel --- man/weston-drm.man | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/weston-drm.man b/man/weston-drm.man index ebf370ebf..28cdf3d1c 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -31,7 +31,7 @@ If seat identifiers are not set, it looks for the graphics device that was used in boot. If that is not found, it finally chooses the first DRM device returned by .BR udev (7). -Combining multiple graphics devices are not supported yet. +Combining multiple graphics devices is not supported yet. The DRM backend relies on .B weston-launch From ef5f3233f9c01303eb00716acbcc3d577b699809 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Wed, 29 Apr 2020 09:03:15 +0200 Subject: [PATCH 1386/1642] compositor: fix endless recursion in scene-graph printing If a surface has subsurfaces then the surface itself is in the subsurface list. To avoid printing it again there is a check to skip the child view, if it is the same as the current view. However, this fails when a surface with subsurfaces has two (or more) views: The check to skip the parent fails for the other view and the two views are printed again and again until a stack overflow occurs. So instead check if the parent view of the subsurface view is the current view. This way, any view that does not belong to a real subsurface is skipped. As a side effect, this ensures that each view of the subsurfaces is only printed once at the correct place in the hierarchy. Signed-off-by: Michael Olbrich --- libweston/compositor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 35da1e658..5fda94328 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -7162,8 +7162,8 @@ debug_scene_view_print_tree(struct weston_view *view, wl_list_for_each(sub, &view->surface->subsurface_list, parent_link) { wl_list_for_each(ev, &sub->surface->views, surface_link) { - /* do not print again the parent view */ - if (view == ev) + /* only print the child views of the current view */ + if (ev->parent_view != view) continue; (*view_idx)++; From 753bdfc1bcd223909cc5a08f8ee2da70cb8759a7 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 8 May 2020 07:25:48 +1000 Subject: [PATCH 1387/1642] gitlab ci: switch to freedesktop ci-templates The project was moved a while ago to make it look less waylandy. Same sha, so no actual changes here. Signed-off-by: Peter Hutterer --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c2054caa6..d9d1b95d9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,7 @@ variables: include: - - project: 'wayland/ci-templates' + - project: 'freedesktop/ci-templates' ref: b7030c2cd0d6ccc5f6d4f8299bafa4daa9240d71 file: '/templates/debian.yml' From 3002a38177c9189b3da4e543b88048eb77fe0d7b Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 8 May 2020 07:33:57 +1000 Subject: [PATCH 1388/1642] gitlab CI: update to recent fdo ci-templates Make use of the templating structure the templates provide. No functional changes in the end, container-build's default behavior is the previously called container-if-not-exists template. Signed-off-by: Peter Hutterer --- .gitlab-ci.yml | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d9d1b95d9..2831df42d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,17 +1,12 @@ # vim: set expandtab shiftwidth=2 tabstop=8 textwidth=0: variables: - UPSTREAM_REPO: wayland/weston - DEBIAN_VERSION: buster - DEBIAN_EXEC: 'bash .gitlab-ci/debian-install.sh' - - DEBIAN_TAG: '2020-02-18.1' - DEBIAN_CONTAINER_IMAGE: $CI_REGISTRY_IMAGE/debian/$DEBIAN_VERSION:$DEBIAN_TAG + FDO_UPSTREAM_REPO: wayland/weston include: - project: 'freedesktop/ci-templates' - ref: b7030c2cd0d6ccc5f6d4f8299bafa4daa9240d71 + ref: 59de540b620c45739871d1a073d76d5521989d11 file: '/templates/debian.yml' @@ -21,14 +16,25 @@ stages: - pages +.debian: + variables: + FDO_DISTRIBUTION_VERSION: buster + FDO_DISTRIBUTION_EXEC: 'bash .gitlab-ci/debian-install.sh' + FDO_DISTRIBUTION_TAG: '2020-05-08.0' + + container_prep: - extends: .debian@container-ifnot-exists + extends: + - .debian + - .fdo.container-build@debian stage: container_prep .build-native: + extends: + - .debian + - .fdo.distribution-image@debian stage: build - image: $DEBIAN_CONTAINER_IMAGE before_script: - git clone --depth=1 https://gitlab.freedesktop.org/wayland/wayland-protocols - export WAYLAND_PROTOCOLS_DIR="$(pwd)/prefix-wayland-protocols" From 6b64d39ab7bcbe749d44d303e18c2cdac3c93987 Mon Sep 17 00:00:00 2001 From: Ken C Date: Sat, 11 Apr 2020 15:05:59 -0700 Subject: [PATCH 1389/1642] set SURFACE_BITS_COMMAND cmdType explicitly --- libweston/backend-rdp/meson.build | 16 ++++++++++++++++ libweston/backend-rdp/rdp.c | 15 +++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index 95d9a83b4..e3b602507 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -26,6 +26,22 @@ if cc.has_member( config_h.set('HAVE_SURFACE_BITS_BMP', '1') endif +if cc.has_type( + 'enum SURFCMD_CMDTYPE', + dependencies : dep_frdp, + prefix : '#include ' +) + config_h.set('HAVE_SURFCMD_CMDTYPE', '1') +endif + +if cc.has_function( + 'nsc_context_set_parameters', + dependencies : dep_frdp, + prefix: '#include ' +) + config_h.set('HAVE_NSC_CONTEXT_SET_PARAMETERS', '1') +endif + deps_rdp = [ dep_libweston_private, dep_frdp, diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 2b33d29cf..9e414aa24 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -212,6 +212,9 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p cmd.skipCompression = TRUE; #else memset(&cmd, 0, sizeof(*cmd)); +#endif +#ifdef HAVE_SURFCMD_CMDTYPE + cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS; #endif cmd.destLeft = damage->extents.x1; cmd.destTop = damage->extents.y1; @@ -270,7 +273,9 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p #else memset(cmd, 0, sizeof(*cmd)); #endif - +#ifdef HAVE_SURFCMD_CMDTYPE + cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; +#endif cmd.destLeft = damage->extents.x1; cmd.destTop = damage->extents.y1; cmd.destRight = damage->extents.x2; @@ -326,6 +331,9 @@ rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_p update->SurfaceFrameMarker(peer->context, &marker); memset(&cmd, 0, sizeof(cmd)); +#ifdef HAVE_SURFCMD_CMDTYPE + cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; +#endif SURFACE_BPP(cmd) = 32; SURFACE_CODECID(cmd) = 0; @@ -758,8 +766,11 @@ rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) if (!context->nsc_context) goto out_error_nsc; +#ifdef HAVE_NSC_CONTEXT_SET_PARAMETERS + nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, DEFAULT_PIXEL_FORMAT); +#else nsc_context_set_pixel_format(context->nsc_context, DEFAULT_PIXEL_FORMAT); - +#endif context->encode_stream = Stream_New(NULL, 65536); if (!context->encode_stream) goto out_error_stream; From a2086bba6602408efda3b65ce3930111f049adcb Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 13 May 2020 15:24:27 +1000 Subject: [PATCH 1390/1642] libweston: replace 0 with the enum value for the xkb init flags No functional changes, this is cosmetics only. Signed-off-by: Peter Hutterer --- clients/window.c | 2 +- libweston/input.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/window.c b/clients/window.c index 5a75bd407..ca7e62d07 100644 --- a/clients/window.c +++ b/clients/window.c @@ -6244,7 +6244,7 @@ display_create(int *argc, char *argv[]) return NULL; } - d->xkb_context = xkb_context_new(0); + d->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (d->xkb_context == NULL) { fprintf(stderr, "Failed to create XKB context\n"); free(d); diff --git a/libweston/input.c b/libweston/input.c index 28dcb0b9a..e348a56b9 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -3120,7 +3120,7 @@ weston_compositor_set_xkb_rule_names(struct weston_compositor *ec, struct xkb_rule_names *names) { if (ec->xkb_context == NULL) { - ec->xkb_context = xkb_context_new(0); + ec->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (ec->xkb_context == NULL) { weston_log("failed to create XKB context\n"); return -1; From f6bd2129245b6c949dfeb50c722834050876f529 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Wed, 13 May 2020 16:33:03 +0300 Subject: [PATCH 1391/1642] xdg-shell: Allow fullscreen surfaces to not cover the whole screen The wording of the xdg-shell protocol allows surfaces to not cover the whole screen when they are made fullscreen. From the description of the fullscreen state in xdg-shell: The window geometry specified in the configure event is a maximum; the client cannot resize beyond it. For a surface to cover the whole fullscreened area, the geometry dimensions must be obeyed by the client. The last sentence is the condition for fullscreen coverage, not a requirement. This commit updates the code to not flag size mismatches for fullscreen surfaces as a protocol error when the surface fits within the screen. In such cases, the shell is responsible for centering surfaces appropriately and also for obscuring other screen content as described in the xdg_toplevel.set_fullscreen request description (and, indeed, desktop-shell does all this). For reference, contrast with the corresponding, stricter wording in the obsolete xdg-shell-unstable-v6 protocol for the fullscreen state: The window geometry specified in the configure event must be obeyed by the client. Signed-off-by: Alexandros Frantzis --- libweston-desktop/xdg-shell.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/libweston-desktop/xdg-shell.c b/libweston-desktop/xdg-shell.c index 34e7be921..4a9eb9770 100644 --- a/libweston-desktop/xdg-shell.c +++ b/libweston-desktop/xdg-shell.c @@ -673,7 +673,7 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev struct weston_geometry geometry = weston_desktop_surface_get_geometry(toplevel->base.desktop_surface); - if ((toplevel->next.state.maximized || toplevel->next.state.fullscreen) && + if (toplevel->next.state.maximized && (toplevel->next.size.width != geometry.width || toplevel->next.size.height != geometry.height)) { struct weston_desktop_client *client = @@ -684,7 +684,25 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev wl_resource_post_error(client_resource, XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, "xdg_surface buffer (%" PRIi32 " x %" PRIi32 ") " - "does not match the configured state (%" PRIi32 " x %" PRIi32 ")", + "does not match the configured maximized state (%" PRIi32 " x %" PRIi32 ")", + geometry.width, geometry.height, + toplevel->next.size.width, + toplevel->next.size.height); + return; + } + + if (toplevel->next.state.fullscreen && + (toplevel->next.size.width < geometry.width || + toplevel->next.size.height < geometry.height)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(toplevel->base.desktop_surface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, + "xdg_surface buffer (%" PRIi32 " x %" PRIi32 ") " + "is larger than the configured fullscreen state (%" PRIi32 " x %" PRIi32 ")", geometry.width, geometry.height, toplevel->next.size.width, toplevel->next.size.height); From 40c519a3e6130f4f5f41b564057507a1e993fb5d Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Wed, 27 Nov 2019 14:11:05 +0100 Subject: [PATCH 1392/1642] gl-renderer: query EGL to determine if GL_TEXTURE_EXTERNAL_OES should be used Using the number of planes to determine if GL_TEXTURE_EXTERNAL_OES should be used is incorrect with some modifiers: For example RGBA with a I915_FORMAT_MOD_Y_TILED_CCS modifier has two planes. Use eglQueryDmaBufModifiersEXT() to query if the current format/modifier only supports GL_TEXTURE_EXTERNAL_OES. Use the current code as fallback of modifiers are not supported. Signed-off-by: Michael Olbrich --- libweston/renderer-gl/gl-renderer-internal.h | 1 + libweston/renderer-gl/gl-renderer.c | 112 ++++++++++++++++++- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index 6e1b095c2..529cb2f99 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -89,6 +89,7 @@ struct gl_renderer { bool has_dmabuf_import; struct wl_list dmabuf_images; + struct wl_list dmabuf_formats; bool has_gl_texture_rg; diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 4e5c6d441..505d0559c 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -131,6 +131,15 @@ struct dmabuf_image { struct gl_shader *shader; }; +struct dmabuf_format { + uint32_t format; + struct wl_list link; + + uint64_t *modifiers; + unsigned *external_only; + int num_modifiers; +}; + struct yuv_plane_descriptor { int width_divisor; int height_divisor; @@ -2219,9 +2228,75 @@ import_yuv_dmabuf(struct gl_renderer *gr, return true; } +static void +gl_renderer_query_dmabuf_modifiers_full(struct gl_renderer *gr, int format, + uint64_t **modifiers, + unsigned **external_only, + int *num_modifiers); + +static struct dmabuf_format* +dmabuf_format_create(struct gl_renderer *gr, uint32_t format) +{ + struct dmabuf_format *dmabuf_format; + + dmabuf_format = calloc(1, sizeof(struct dmabuf_format)); + if (!dmabuf_format) + return NULL; + + dmabuf_format->format = format; + + gl_renderer_query_dmabuf_modifiers_full(gr, format, + &dmabuf_format->modifiers, + &dmabuf_format->external_only, + &dmabuf_format->num_modifiers); + + if (dmabuf_format->num_modifiers == 0) { + free(dmabuf_format); + return NULL; + } + + wl_list_insert(&gr->dmabuf_formats, &dmabuf_format->link); + return dmabuf_format; +} + +static void +dmabuf_format_destroy(struct dmabuf_format *format) +{ + free(format->modifiers); + free(format->external_only); + wl_list_remove(&format->link); + free(format); +} + static GLenum -choose_texture_target(struct dmabuf_attributes *attributes) +choose_texture_target(struct gl_renderer *gr, + struct dmabuf_attributes *attributes) { + struct dmabuf_format *tmp, *format = NULL; + + wl_list_for_each(tmp, &gr->dmabuf_formats, link) { + if (tmp->format == attributes->format) { + format = tmp; + break; + } + } + + if (!format) + format = dmabuf_format_create(gr, attributes->format); + + if (format) { + int i; + + for (i = 0; i < format->num_modifiers; ++i) { + if (format->modifiers[i] == attributes->modifier[0]) { + if(format->external_only[i]) + return GL_TEXTURE_EXTERNAL_OES; + else + return GL_TEXTURE_2D; + } + } + } + if (attributes->n_planes > 1) return GL_TEXTURE_EXTERNAL_OES; @@ -2253,7 +2328,7 @@ import_dmabuf(struct gl_renderer *gr, image->num_images = 1; image->images[0] = egl_image; image->import_type = IMPORT_TYPE_DIRECT; - image->target = choose_texture_target(&dmabuf->attributes); + image->target = choose_texture_target(gr, &dmabuf->attributes); switch (image->target) { case GL_TEXTURE_2D: @@ -2321,11 +2396,11 @@ gl_renderer_query_dmabuf_formats(struct weston_compositor *wc, } static void -gl_renderer_query_dmabuf_modifiers(struct weston_compositor *wc, int format, +gl_renderer_query_dmabuf_modifiers_full(struct gl_renderer *gr, int format, uint64_t **modifiers, + unsigned **external_only, int *num_modifiers) { - struct gl_renderer *gr = get_renderer(wc); int num; assert(gr->has_dmabuf_import); @@ -2343,16 +2418,38 @@ gl_renderer_query_dmabuf_modifiers(struct weston_compositor *wc, int format, *num_modifiers = 0; return; } + if (external_only) { + *external_only = calloc(num, sizeof(unsigned)); + if (*external_only == NULL) { + *num_modifiers = 0; + free(*modifiers); + return; + } + } if (!gr->query_dmabuf_modifiers(gr->egl_display, format, - num, *modifiers, NULL, &num)) { + num, *modifiers, external_only ? + *external_only : NULL, &num)) { *num_modifiers = 0; free(*modifiers); + if (external_only) + free(*external_only); return; } *num_modifiers = num; } +static void +gl_renderer_query_dmabuf_modifiers(struct weston_compositor *wc, int format, + uint64_t **modifiers, + int *num_modifiers) +{ + struct gl_renderer *gr = get_renderer(wc); + + gl_renderer_query_dmabuf_modifiers_full(gr, format, modifiers, NULL, + num_modifiers); +} + static bool gl_renderer_import_dmabuf(struct weston_compositor *ec, struct linux_dmabuf_buffer *dmabuf) @@ -3289,6 +3386,7 @@ gl_renderer_destroy(struct weston_compositor *ec) { struct gl_renderer *gr = get_renderer(ec); struct dmabuf_image *image, *next; + struct dmabuf_format *format, *next_format; wl_signal_emit(&gr->destroy_signal, gr); @@ -3304,6 +3402,9 @@ gl_renderer_destroy(struct weston_compositor *ec) wl_list_for_each_safe(image, next, &gr->dmabuf_images, link) dmabuf_image_destroy(image); + wl_list_for_each_safe(format, next_format, &gr->dmabuf_formats, link) + dmabuf_format_destroy(format); + if (gr->dummy_surface != EGL_NO_SURFACE) weston_platform_destroy_egl_surface(gr->egl_display, gr->dummy_surface); @@ -3432,6 +3533,7 @@ gl_renderer_display_create(struct weston_compositor *ec, gr->base.query_dmabuf_modifiers = gl_renderer_query_dmabuf_modifiers; } + wl_list_init(&gr->dmabuf_formats); if (gr->has_surfaceless_context) { weston_log("EGL_KHR_surfaceless_context available\n"); From 50aa3a76c0c6fa8598a48acb641f77a1e3916664 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 28 May 2020 11:34:04 +0300 Subject: [PATCH 1393/1642] timeline: convert vblank timestamp to MONOTONIC All timeline event timestamps are in CLOCK_MONOTONIC already. DRM KMS timestamps are practically guaranteed to be CLOCK_MONOTONIC too, even though presentation clock could theoretically be something else. For other backends, the presentation clock is likely CLOCK_MONOTONIC_RAW due to weston_compositor_set_presentation_clock_software(). This patch ensures that the recorded vblank timestamp is in CLOCK_MONOTONIC. Otherwise interpreting the timeline traces might be difficult to do accurately, since it would be hard to recover the relationship between the presentation clock and timeline event timestamps. The time conversion routine is the simplest possible, I don't think we need any more accurate conversion for timeline purposes. Besides, DRM-backend is the only backend where the timings actually matter, the other backends are software-timed anyway. Since the clock domain of the "vblank" attribute potentially changes, the attribute is renamed. Wesgr never used this attribute. Signed-off-by: Pekka Paalanen --- libweston/compositor.c | 40 ++++++++++++++++++++++++++++++++++++++-- libweston/timeline.c | 2 +- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 5fda94328..283f3f68c 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2938,6 +2938,39 @@ output_repaint_timer_handler(void *data) return 0; } +/** Convert a presentation timestamp to another clock domain + * + * \param compositor The compositor defines the presentation clock domain. + * \param presentation_stamp The timestamp in presentation clock domain. + * \param presentation_now Current time in presentation clock domain. + * \param target_clock Defines the target clock domain. + * + * This approximation relies on presentation_stamp to be close to current time. + * The further it is from current time and the bigger the speed difference + * between the two clock domains, the bigger the conversion error. + * + * Conversion error due to system load is biased and unbounded. + */ +static struct timespec +convert_presentation_time_now(struct weston_compositor *compositor, + const struct timespec *presentation_stamp, + const struct timespec *presentation_now, + clockid_t target_clock) +{ + struct timespec target_now = {}; + struct timespec target_stamp; + int64_t delta_ns; + + if (compositor->presentation_clock == target_clock) + return *presentation_stamp; + + clock_gettime(target_clock, &target_now); + delta_ns = timespec_sub_to_nsec(presentation_stamp, presentation_now); + timespec_add_nsec(&target_stamp, &target_now, delta_ns); + + return target_stamp; +} + /** * \ingroup output */ @@ -2949,9 +2982,9 @@ weston_output_finish_frame(struct weston_output *output, struct weston_compositor *compositor = output->compositor; int32_t refresh_nsec; struct timespec now; + struct timespec vblank_monotonic; int64_t msec_rel; - assert(output->repaint_status == REPAINT_AWAITING_COMPLETION); assert(stamp || (presented_flags & WP_PRESENTATION_FEEDBACK_INVALID)); @@ -2965,8 +2998,11 @@ weston_output_finish_frame(struct weston_output *output, goto out; } + vblank_monotonic = convert_presentation_time_now(compositor, + stamp, &now, + CLOCK_MONOTONIC); TL_POINT(compositor, "core_repaint_finished", TLP_OUTPUT(output), - TLP_VBLANK(stamp), TLP_END); + TLP_VBLANK(&vblank_monotonic), TLP_END); refresh_nsec = millihz_to_nsec(output->current_mode->refresh); weston_presentation_feedback_present_list(&output->feedback_list, diff --git a/libweston/timeline.c b/libweston/timeline.c index 9da8b5e3d..d5738f48c 100644 --- a/libweston/timeline.c +++ b/libweston/timeline.c @@ -324,7 +324,7 @@ emit_vblank_timestamp(struct timeline_emit_context *ctx, void *obj) { struct timespec *ts = obj; - fprintf(ctx->cur, "\"vblank\":[%" PRId64 ", %ld]", + fprintf(ctx->cur, "\"vblank_monotonic\":[%" PRId64 ", %ld]", (int64_t)ts->tv_sec, ts->tv_nsec); return 1; From fdc9b4bce5816cdafb9dc08f499ed76778945d36 Mon Sep 17 00:00:00 2001 From: Antonio Caggiano Date: Thu, 28 May 2020 16:28:08 +0200 Subject: [PATCH 1394/1642] compositor: Print error opening log file When failing to open the log file nothing is reported to the user, therefore we print a message on stderr when that happens. Signed-off-by: Antonio Caggiano Suggested-by: Pekka Paalanen --- compositor/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index c1a727017..af31c94e6 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -180,6 +180,8 @@ weston_log_file_open(const char *filename) weston_logfile = fopen(filename, "a"); if (weston_logfile) os_fd_set_cloexec(fileno(weston_logfile)); + else + fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno)); } if (weston_logfile == NULL) From 2eda978e95aaa18516cfc007aa91c8901fa251c5 Mon Sep 17 00:00:00 2001 From: Antonio Caggiano Date: Thu, 28 May 2020 17:45:57 +0200 Subject: [PATCH 1395/1642] compositor: Quit when failing to open log file If users ask explicitly to log to a file, it makes sense to quit when we fail opening that file. Continuing execution would mean wasting users' time if they expect to find the log file at the end of the session. Signed-off-by: Antonio Caggiano Suggested-by: Pekka Paalanen --- compositor/main.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index af31c94e6..1a8bf1155 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -171,23 +171,27 @@ custom_handler(const char *fmt, va_list arg) weston_log_scope_vprintf(log_scope, fmt, arg); } -static void +static bool weston_log_file_open(const char *filename) { wl_log_set_handler_server(custom_handler); if (filename != NULL) { weston_logfile = fopen(filename, "a"); - if (weston_logfile) + if (weston_logfile) { os_fd_set_cloexec(fileno(weston_logfile)); - else + } else { fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno)); + return false; + } } if (weston_logfile == NULL) weston_logfile = stderr; else setvbuf(weston_logfile, NULL, _IOLBF, 256); + + return true; } static void @@ -3196,7 +3200,9 @@ wet_main(int argc, char *argv[]) log_scope = weston_log_ctx_add_log_scope(log_ctx, "log", "Weston and Wayland log\n", NULL, NULL, NULL); - weston_log_file_open(log); + if (!weston_log_file_open(log)) + return EXIT_FAILURE; + weston_log_set_handler(vlog, vlog_continue); logger = weston_log_subscriber_create_log(weston_logfile); From e8033e3dd166449ef1a91941fe0d6f8a2e59da45 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Tue, 5 May 2020 16:09:03 -0300 Subject: [PATCH 1396/1642] tests: don't use width and height for drm/fbdev backend tests In the test suite we have some default options which are command line arguments used by most of the tests. Two of these are width==320 and height==240. But when we have DRM or fbdev backends, width and height are not possible command line arguments. This makes impossible to run tests that uses one of these types of backends, as the compositor won't open if the command line string is wrong. Fix this by not passing command line arguments width and height if the backend is DRM or fbdev. Signed-off-by: Leandro Ribeiro --- tests/weston-test-fixture-compositor.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/weston-test-fixture-compositor.c b/tests/weston-test-fixture-compositor.c index da0fa26a9..1b2df97f3 100644 --- a/tests/weston-test-fixture-compositor.c +++ b/tests/weston-test-fixture-compositor.c @@ -273,11 +273,14 @@ execute_compositor(const struct compositor_setup *setup, setup->extra_module ? setup->extra_module : ""); prog_args_take(&args, tmp); - asprintf(&tmp, "--width=%d", setup->width); - prog_args_take(&args, tmp); + if (setup->backend != WESTON_BACKEND_DRM && + setup->backend != WESTON_BACKEND_FBDEV) { + asprintf(&tmp, "--width=%d", setup->width); + prog_args_take(&args, tmp); - asprintf(&tmp, "--height=%d", setup->height); - prog_args_take(&args, tmp); + asprintf(&tmp, "--height=%d", setup->height); + prog_args_take(&args, tmp); + } if (setup->scale != 1) { asprintf(&tmp, "--scale=%d", setup->scale); From 7b37b4d3d723da2c041053472812748465c3ae27 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Tue, 5 May 2020 18:14:25 -0300 Subject: [PATCH 1397/1642] tests: properly select renderer for DRM-backend The test suite is dealing only with headless-backend tests. In order to make it able to run DRM-backend tests, we have to properly select the renderer that it will use. This patch add the command line option --use-pixman if the test defines the DRM-backend renderer as RENDERER_PIXMAN, and it will add nothing to the command line if it defines RENDERER_GL (the DRM-backend default renderer is already GL). Also, if the user defines the DRM-backend renderer as RENDERER_NOOP, the test will fail (as it should, since DRM-backend does not implement it). Signed-off-by: Leandro Ribeiro --- tests/weston-test-fixture-compositor.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/weston-test-fixture-compositor.c b/tests/weston-test-fixture-compositor.c index 1b2df97f3..ee7a5d1ef 100644 --- a/tests/weston-test-fixture-compositor.c +++ b/tests/weston-test-fixture-compositor.c @@ -139,12 +139,18 @@ renderer_to_arg(enum weston_compositor_backend b, enum renderer_type r) [RENDERER_PIXMAN] = "--use-pixman", [RENDERER_GL] = "--use-gl", }; - - assert(r >= 0 && r < ARRAY_LENGTH(headless_names)); + static const char * const drm_names[] = { + [RENDERER_PIXMAN] = "--use-pixman", + [RENDERER_GL] = NULL, + }; switch (b) { case WESTON_BACKEND_HEADLESS: + assert(r >= RENDERER_NOOP && r <= RENDERER_GL); return headless_names[r]; + case WESTON_BACKEND_DRM: + assert(r >= RENDERER_PIXMAN && r <= RENDERER_GL); + return drm_names[r]; default: assert(0 && "renderer_to_str() does not know the backend"); } From e57d8ae818c39fc434fcf3bfc97274195296c8d6 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Wed, 6 May 2020 18:10:19 -0300 Subject: [PATCH 1398/1642] drm-backend: add --continue-without-input command line option to DRM-backend In the test suite we may want to run a DRM-backend test on a non-default seat, which may not have a input device associated. Weston's default behavior is to not open if input devices are not found, as it may cause troubles. For instance, Weston can open but if no input device is set than the user can not interact or leave it. Add flag --continue-without-input to DRM-backend so we can run these types of tests with no input. Notice that this won't force the compositor to skip opening a input device if it finds it on the non-default seat. Signed-off-by: Leandro Ribeiro --- compositor/main.c | 4 +++- include/libweston/backend-drm.h | 3 +++ libweston/backend-drm/drm.c | 1 + man/weston-drm.man | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/compositor/main.c b/compositor/main.c index 1a8bf1155..65da9dbc4 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -676,7 +676,8 @@ usage(int error_code) " --tty=TTY\t\tThe tty to use\n" " --drm-device=CARD\tThe DRM device to use, e.g. \"card0\".\n" " --use-pixman\t\tUse the pixman (CPU) renderer\n" - " --current-mode\tPrefer current KMS mode over EDID preferred mode\n\n"); + " --current-mode\tPrefer current KMS mode over EDID preferred mode\n" + " --continue-without-input\tAllow the compositor to start without input devices\n\n"); #endif #if defined(BUILD_FBDEV_COMPOSITOR) @@ -2523,6 +2524,7 @@ load_drm_backend(struct weston_compositor *c, { WESTON_OPTION_STRING, "drm-device", 0, &config.specific_device }, { WESTON_OPTION_BOOLEAN, "current-mode", 0, &wet->drm_use_current_mode }, { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + { WESTON_OPTION_BOOLEAN, "continue-without-input", 0, &config.continue_without_input }, }; parse_options(options, ARRAY_LENGTH(options), argc, argv); diff --git a/include/libweston/backend-drm.h b/include/libweston/backend-drm.h index f6647e286..350eeb0de 100644 --- a/include/libweston/backend-drm.h +++ b/include/libweston/backend-drm.h @@ -223,6 +223,9 @@ struct weston_drm_backend_config { /** Use shadow buffer if using Pixman-renderer. */ bool use_pixman_shadow; + + /** Allow compositor to start without input devices. */ + bool continue_without_input; }; #ifdef __cplusplus diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 2aea82f55..ca0f3a866 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -2831,6 +2831,7 @@ drm_backend_create(struct weston_compositor *compositor, NULL, NULL, NULL); compositor->backend = &b->base; + compositor->require_input = !config->continue_without_input; if (parse_gbm_format(config->gbm_format, DRM_FORMAT_XRGB8888, &b->gbm_format) < 0) goto err_compositor; diff --git a/man/weston-drm.man b/man/weston-drm.man index 28cdf3d1c..01a336e1d 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -205,6 +205,9 @@ instead of the seat defined in the environment variable Launch Weston on tty .I x instead of using the current tty. +.TP +.B \-\-continue\-without\-input +Allow Weston to start without input devices. Used for testing purposes. . .\" *************************************************************** .SH ENVIRONMENT From e8a8c13e0d638b59bbdf81805e6555416a6a7e3e Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Wed, 6 May 2020 11:21:03 -0300 Subject: [PATCH 1399/1642] tests: add support to run drm-backend tests locally With this patch we add support to run DRM-backend tests locally in the test suite. For now this won't work in the CI, as there are no cards available. But the plan is to achieve this by using VKMS (virtual KMS) in the future. To run DRM-backend tests locally, first of all the user has to set the environment variable WESTON_TEST_SUITE_DRM_DEVICE to 'card0', 'card1' or any other device where he wants to run the tests. Also, for now it only works if it is run as root, but in the future this problem will be solved. The tests will run on a non-default seat. The reason for that is that we want to avoid opening input devices unnecessarily. Also, since DRM-backend usage requires gaining DRM master status on a DRM KMS device, nothing else must be using the device at the same time. To achieve this we use a lock to run the DRM-backend tests sequentially. Signed-off-by: Leandro Ribeiro --- doc/sphinx/toc/test-suite.rst | 28 +++++++ tests/meson.build | 1 + tests/weston-test-fixture-compositor.c | 100 ++++++++++++++++++++++++- 3 files changed, 127 insertions(+), 2 deletions(-) diff --git a/doc/sphinx/toc/test-suite.rst b/doc/sphinx/toc/test-suite.rst index 7fafe3e9d..f7803292f 100644 --- a/doc/sphinx/toc/test-suite.rst +++ b/doc/sphinx/toc/test-suite.rst @@ -213,6 +213,34 @@ clients: } +DRM-backend tests +----------------- + +DRM-backend tests require a DRM device, so they are a special case. To select a +device the test suite will simply look at the environment variable +``WESTON_TEST_SUITE_DRM_DEVICE``. So the first thing the user has to do in order +to run DRM-backend tests is to set this environment variable with the card that +should run the tests. For instance, in order to run DRM-backend tests with +``card0`` we need to run ``export WESTON_TEST_SUITE_DRM_DEVICE=card0``. + +Note that the card should not be in use by a desktop environment (or any other +program that requires master status), as there can only be one user at a time +with master status in a DRM device. Also, this is the reason why we can not run +two or more DRM-backend tests simultaneously. Since the test suite tries to run +the tests in parallel, we have a lock mechanism to enforce that DRM-backend +tests run sequentially, one at a time. Note that this will not avoid them to run +in parallel with other types of tests. + +Another specificity of DRM-backend tests is that they run using the non-default +seat ``seat-weston-test``. This avoids unnecessarily opening input devices that +may be present in the default seat ``seat0``. On the other hand, this will make +``launcher-logind`` fail, as we are trying to use a seat that is different from +the one we are logged in. In the CI we do not rely on ``logind``, so it should +fallback to ``launcher-direct`` anyway. It requires root, but this is also not a +problem for the CI, as ``virtme`` starts as root. The problem is that to run +the tests locally with a real hardware the users need to run as root. + + Writing tests ------------- diff --git a/tests/meson.build b/tests/meson.build index 231746c1c..d26bc5826 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -52,6 +52,7 @@ dep_test_client = declare_dependency( dep_wayland_client, dep_test_runner, dep_pixman, + dependency('libudev', version: '>= 136'), ] ) diff --git a/tests/weston-test-fixture-compositor.c b/tests/weston-test-fixture-compositor.c index ee7a5d1ef..0c9855f60 100644 --- a/tests/weston-test-fixture-compositor.c +++ b/tests/weston-test-fixture-compositor.c @@ -27,6 +27,10 @@ #include #include +#include +#include +#include +#include #include "shared/helpers.h" #include "weston-test-fixture-compositor.h" @@ -89,6 +93,71 @@ prog_args_fini(struct prog_args *p) prog_args_init(p); } +static char * +get_lock_path(void) +{ + const char *env_path, *suffix; + char *lock_path; + + suffix = "weston-test-suite-drm-lock"; + env_path = getenv("XDG_RUNTIME_DIR"); + if (!env_path) { + fprintf(stderr, "Failed to compute lock file path. " \ + "XDG_RUNTIME_DIR is not set.\n"); + return NULL; + } + + if (asprintf(&lock_path, "%s/%s", env_path, suffix) == -1) + return NULL; + + return lock_path; +} + +/* + * DRM-backend tests need to be run sequentially, since there can only be + * one user at a time with master status in a DRM KMS device. Since the + * test suite runs the tests in parallel, there's a mechanism to assure + * only one DRM-backend test is running at a time: tests of this type keep + * waiting until they acquire a lock (which is hold until they end). + * + * Returns -1 in failure and fd in success. + */ +static int +wait_for_lock(void) +{ + char *lock_path; + int fd; + + lock_path = get_lock_path(); + if (!lock_path) + goto err_path; + + fd = open(lock_path, O_RDWR | O_CLOEXEC | O_CREAT, 00700); + if (fd == -1) { + fprintf(stderr, "Could not open lock file %s: %s\n", lock_path, strerror(errno)); + goto err_open; + } + fprintf(stderr, "Waiting for lock on %s...\n", lock_path); + + /* The call is blocking, so we don't need a 'while'. Also, as we + * have a timeout for each test, this won't get stuck waiting. */ + if (flock(fd, LOCK_EX) == -1) { + fprintf(stderr, "Could not lock %s: %s\n", lock_path, strerror(errno)); + goto err_lock; + } + fprintf(stderr, "Lock %s acquired.\n", lock_path); + free(lock_path); + + return fd; + +err_lock: + close(fd); +err_open: + free(lock_path); +err_path: + return -1; +} + /** Initialize part of compositor setup * * \param setup The variable to initialize. @@ -204,8 +273,8 @@ execute_compositor(const struct compositor_setup *setup, { struct prog_args args; char *tmp; - const char *ctmp; - int ret; + const char *ctmp, *drm_device; + int ret, lock_fd = -1; if (setenv("WESTON_MODULE_MAP", WESTON_MODULE_MAP, 0) < 0 || setenv("WESTON_DATA_DIR", WESTON_DATA_DIR, 0) < 0) { @@ -271,6 +340,28 @@ execute_compositor(const struct compositor_setup *setup, asprintf(&tmp, "--backend=%s", backend_to_str(setup->backend)); prog_args_take(&args, tmp); + if (setup->backend == WESTON_BACKEND_DRM) { + + drm_device = getenv("WESTON_TEST_SUITE_DRM_DEVICE"); + if (!drm_device) { + fprintf(stderr, "Skipping DRM-backend tests because " \ + "WESTON_TEST_SUITE_DRM_DEVICE is not set. " \ + "See test suite documentation to learn how " \ + "to run them.\n"); + return RESULT_SKIP; + } + asprintf(&tmp, "--drm-device=%s", drm_device); + prog_args_take(&args, tmp); + + prog_args_take(&args, strdup("--seat=weston-test-seat")); + + prog_args_take(&args, strdup("--continue-without-input")); + + lock_fd = wait_for_lock(); + if (lock_fd == -1) + return RESULT_FAIL; + } + asprintf(&tmp, "--socket=%s", setup->testset_name); prog_args_take(&args, tmp); @@ -327,5 +418,10 @@ execute_compositor(const struct compositor_setup *setup, prog_args_fini(&args); + /* We acquired a lock (if this is a DRM-backend test) and now we can + * close its fd and release it, as it has already been run. */ + if (lock_fd != -1) + close(lock_fd); + return ret; } From b1c529e9d719b4a1afa713eb739b9f2b31dc0743 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Wed, 6 May 2020 11:21:54 -0300 Subject: [PATCH 1400/1642] tests: add drm-backend smoke test This adds the first DRM-backend test. It is very simple and was made in order to make easier to add more complex DRM-backend tests in the future. Signed-off-by: Leandro Ribeiro --- tests/drm-smoke-test.c | 72 ++++++++++++++++++++++++++++++++++++++++++ tests/meson.build | 1 + 2 files changed, 73 insertions(+) create mode 100644 tests/drm-smoke-test.c diff --git a/tests/drm-smoke-test.c b/tests/drm-smoke-test.c new file mode 100644 index 000000000..4c5b6a598 --- /dev/null +++ b/tests/drm-smoke-test.c @@ -0,0 +1,72 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * + * 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 "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; + setup.backend = WESTON_BACKEND_DRM; + setup.renderer = RENDERER_PIXMAN; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +TEST(drm_smoke) { + + struct client *client; + struct buffer *buffer; + struct wl_surface *surface; + pixman_color_t red; + int i, frame; + + color_rgb888(&red, 255, 0, 0); + + client = create_client_and_test_surface(0, 0, 200, 200); + assert(client); + + surface = client->surface->wl_surface; + buffer = create_shm_buffer_a8r8g8b8(client, 200, 200); + + fill_image_with_color(buffer->image, &red); + + for (i = 0; i < 5; i++) { + wl_surface_attach(surface, buffer->proxy, 0, 0); + wl_surface_damage(surface, 0, 0, 200, 200); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + } + + client_destroy(client); +} diff --git a/tests/meson.build b/tests/meson.build index d26bc5826..1b9ecdc89 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -118,6 +118,7 @@ dep_zucmain = declare_dependency( tests = [ { 'name': 'bad-buffer', }, + { 'name': 'drm-smoke', }, { 'name': 'buffer-transforms', }, { 'name': 'devices', }, { 'name': 'event', }, From 7451f9956ea80437e5b561c6bc0ccbfe48db5a2c Mon Sep 17 00:00:00 2001 From: Guillaume Champagne Date: Mon, 1 Jun 2020 21:59:12 -0400 Subject: [PATCH 1401/1642] meson: add lcms2 dependency to cms-colord cms-colord uses cms-helper functions which require lcms2. Therefore, lcms2 must be added as a build dependency. Signed-off-by: Guillaume Champagne --- compositor/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compositor/meson.build b/compositor/meson.build index 621c3e076..6352a1108 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -137,7 +137,7 @@ if get_option('color-management-colord') error('cms-colord requires colord >= 0.1.27 which was not found. Or, you can use \'-Dcolor-management-colord=false\'.') endif - plugin_colord_deps = [ dep_libweston_public, dep_colord ] + plugin_colord_deps = [ dep_libweston_public, dep_colord, dep_lcms2 ] foreach depname : [ 'glib-2.0', 'gobject-2.0' ] dep = dependency(depname, required: false) From 478710c0037024086fa8523a55e99ab3722bfea3 Mon Sep 17 00:00:00 2001 From: Guillaume Champagne Date: Mon, 1 Jun 2020 22:07:20 -0400 Subject: [PATCH 1402/1642] meson: enable undefined functions errors for colord The missing build dependency was added. The override to disable this check can be removed. Signed-off-by: Guillaume Champagne --- compositor/meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/compositor/meson.build b/compositor/meson.build index 6352a1108..9dc95f3fc 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -153,7 +153,6 @@ if get_option('color-management-colord') include_directories: common_inc, dependencies: plugin_colord_deps, name_prefix: '', - override_options: [ 'b_lundef=false' ], install: true, install_dir: dir_module_weston ) From 15c603caa6f1fbd06bc866a4131dbbd26f941c1f Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Tue, 2 Jun 2020 17:39:43 +1200 Subject: [PATCH 1403/1642] drm: Fix leak of damage blob id This moves the creation of the blob to be earlier, to when the damage is calculated. It replaces the damage tracked inside of the plane state with the blob id itself. This should stop creating new blob ids for TEST_ONLY commits, and them being leaked in general, as the blob ids are now freed with the plane state. The FB_DAMAGE_CLIPS property is now always set if it's supported, and will be 0 in the case that we have no damage information, which signifies full damage to the kernel. Signed-off-by: Scott Anderson --- libweston/backend-drm/drm-internal.h | 2 +- libweston/backend-drm/drm.c | 43 ++++++++++++++++++++++----- libweston/backend-drm/kms.c | 41 ++----------------------- libweston/backend-drm/state-helpers.c | 18 ++++++++--- 4 files changed, 53 insertions(+), 51 deletions(-) diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 855baf151..49ce7ce00 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -418,7 +418,7 @@ struct drm_plane_state { /* We don't own the fd, so we shouldn't close it */ int in_fence_fd; - pixman_region32_t damage; /* damage to kernel */ + uint32_t damage_blob_id; /* damage to kernel */ struct wl_list link; /* drm_output_state::plane_list */ }; diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index ca0f3a866..4e5f39cf6 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -354,8 +354,13 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) struct weston_compositor *c = output->base.compositor; struct drm_plane_state *scanout_state; struct drm_plane *scanout_plane = output->scanout_plane; + struct drm_property_info *damage_info = + &scanout_plane->props[WDRM_PLANE_FB_DAMAGE_CLIPS]; struct drm_backend *b = to_drm_backend(c); struct drm_fb *fb; + pixman_region32_t scanout_damage; + pixman_box32_t *rects; + int n_rects; /* If we already have a client buffer promoted to scanout, then we don't * want to render. */ @@ -399,24 +404,46 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) scanout_state->dest_w = output->base.current_mode->width; scanout_state->dest_h = output->base.current_mode->height; - pixman_region32_copy(&scanout_state->damage, damage); + pixman_region32_subtract(&c->primary_plane.damage, + &c->primary_plane.damage, damage); + + /* Don't bother calculating plane damage if the plane doesn't support it */ + if (damage_info->prop_id == 0) + return; + + pixman_region32_init(&scanout_damage); + pixman_region32_copy(&scanout_damage, damage); + if (output->base.zoom.active) { - weston_matrix_transform_region(&scanout_state->damage, + weston_matrix_transform_region(&scanout_damage, &output->base.matrix, - &scanout_state->damage); + &scanout_damage); } else { - pixman_region32_translate(&scanout_state->damage, + pixman_region32_translate(&scanout_damage, -output->base.x, -output->base.y); weston_transformed_region(output->base.width, output->base.height, output->base.transform, output->base.current_scale, - &scanout_state->damage, - &scanout_state->damage); + &scanout_damage, + &scanout_damage); } - pixman_region32_subtract(&c->primary_plane.damage, - &c->primary_plane.damage, damage); + assert(scanout_state->damage_blob_id == 0); + + rects = pixman_region32_rectangles(&scanout_damage, &n_rects); + + /* + * If this function fails, the blob id should still be 0. + * This tells the kernel there is no damage information, which means + * that it will consider the whole plane damaged. While this may + * affect efficiency, it should still produce correct results. + */ + drmModeCreatePropertyBlob(b->drm.fd, rects, + sizeof(*rects) * n_rects, + &scanout_state->damage_blob_id); + + pixman_region32_fini(&scanout_damage); } static int diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index 7576c0060..b349ec13a 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -847,43 +847,6 @@ plane_add_prop(drmModeAtomicReq *req, struct drm_plane *plane, return (ret <= 0) ? -1 : 0; } - -static int -plane_add_damage(drmModeAtomicReq *req, struct drm_backend *backend, - struct drm_plane_state *plane_state) -{ - struct drm_plane *plane = plane_state->plane; - struct drm_property_info *info = - &plane->props[WDRM_PLANE_FB_DAMAGE_CLIPS]; - pixman_box32_t *rects; - uint32_t blob_id; - int n_rects; - int ret; - - if (!pixman_region32_not_empty(&plane_state->damage)) - return 0; - - /* - * If a plane doesn't support fb damage blob property, kernel will - * perform full plane update. - */ - if (info->prop_id == 0) - return 0; - - rects = pixman_region32_rectangles(&plane_state->damage, &n_rects); - - ret = drmModeCreatePropertyBlob(backend->drm.fd, rects, - sizeof(*rects) * n_rects, &blob_id); - if (ret != 0) - return ret; - - ret = plane_add_prop(req, plane, WDRM_PLANE_FB_DAMAGE_CLIPS, blob_id); - if (ret != 0) - return ret; - - return 0; -} - static bool drm_head_has_prop(struct drm_head *head, enum wdrm_connector_property prop) @@ -1043,7 +1006,9 @@ drm_output_apply_state_atomic(struct drm_output_state *state, plane_state->dest_w); ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_H, plane_state->dest_h); - ret |= plane_add_damage(req, b, plane_state); + if (plane->props[WDRM_PLANE_FB_DAMAGE_CLIPS].prop_id != 0) + ret |= plane_add_prop(req, plane, WDRM_PLANE_FB_DAMAGE_CLIPS, + plane_state->damage_blob_id); if (plane_state->fb && plane_state->fb->format) pinfo = plane_state->fb->format; diff --git a/libweston/backend-drm/state-helpers.c b/libweston/backend-drm/state-helpers.c index 7b1d9241d..d10549933 100644 --- a/libweston/backend-drm/state-helpers.c +++ b/libweston/backend-drm/state-helpers.c @@ -49,7 +49,6 @@ drm_plane_state_alloc(struct drm_output_state *state_output, state->plane = plane; state->in_fence_fd = -1; state->zpos = DRM_PLANE_ZPOS_INVALID_PLANE; - pixman_region32_init(&state->damage); /* Here we only add the plane state to the desired link, and not * set the member. Having an output pointer set means that the @@ -82,7 +81,15 @@ drm_plane_state_free(struct drm_plane_state *state, bool force) state->output_state = NULL; state->in_fence_fd = -1; state->zpos = DRM_PLANE_ZPOS_INVALID_PLANE; - pixman_region32_fini(&state->damage); + + /* Once the damage blob has been submitted, it is refcounted internally + * by the kernel, which means we can safely discard it. + */ + if (state->damage_blob_id != 0) { + drmModeDestroyPropertyBlob(state->plane->backend->drm.fd, + state->damage_blob_id); + state->damage_blob_id = 0; + } if (force || state != state->plane->state_cur) { drm_fb_unref(state->fb); @@ -99,12 +106,16 @@ struct drm_plane_state * drm_plane_state_duplicate(struct drm_output_state *state_output, struct drm_plane_state *src) { - struct drm_plane_state *dst = malloc(sizeof(*dst)); + struct drm_plane_state *dst = zalloc(sizeof(*dst)); struct drm_plane_state *old, *tmp; assert(src); assert(dst); *dst = *src; + /* We don't want to copy this, because damage is transient, and only + * lasts for the duration of a single repaint. + */ + dst->damage_blob_id = 0; wl_list_init(&dst->link); wl_list_for_each_safe(old, tmp, &state_output->plane_list, link) { @@ -120,7 +131,6 @@ drm_plane_state_duplicate(struct drm_output_state *state_output, if (src->fb) dst->fb = drm_fb_ref(src->fb); dst->output_state = state_output; - pixman_region32_init(&dst->damage); dst->complete = false; return dst; From ba54831100ab3a0488b3bb84381e9ca9668978a9 Mon Sep 17 00:00:00 2001 From: Tomek Bury Date: Thu, 4 Jun 2020 14:59:55 +0100 Subject: [PATCH 1404/1642] gl-renderer: fix pbuffer surface creation When there's neither configless nor surfaceless EGL extension (i.e. not a Mesa driver), Weston falls back to a dummy pbuffer surface. Weston attempts to find for that surface an EGL config but uses a NULL array of pixel formats. This fails with the following messages: EGL_KHR_surfaceless_context unavailable. Trying PbufferSurface Found an EGLConfig matching { pbf; } but it is not usable because neither EGL_KHR_no_config_context nor EGL_MESA_configless_context are supported by EGL. failed to choose EGL config for PbufferSurface EGL error state: EGL_SUCCESS (0x3000) Failed to initialise the GL renderer; Signed-off-by: Tomek Bury --- libweston/renderer-gl/gl-renderer.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 505d0559c..937ebe865 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -3448,8 +3448,12 @@ gl_renderer_create_pbuffer_surface(struct gl_renderer *gr) { EGL_NONE }; - pbuffer_config = gl_renderer_get_egl_config(gr, EGL_PBUFFER_BIT, - NULL, 0); + pbuffer_config = gr->egl_config; + if (pbuffer_config == EGL_NO_CONFIG_KHR) { + pbuffer_config = + gl_renderer_get_egl_config(gr, EGL_PBUFFER_BIT, + NULL, 0); + } if (pbuffer_config == EGL_NO_CONFIG_KHR) { weston_log("failed to choose EGL config for PbufferSurface\n"); return -1; From c8feaae7d2222de2b2967d0076e7a61edb7a0473 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Wed, 10 Jun 2020 14:40:04 -0600 Subject: [PATCH 1405/1642] libweston: don't clean up surface role Surface roles are permanent, so it should not be cleaned up. Fixes: #409 weston: ../libweston/compositor.c:4094: weston_surface_set_role: Assertion `role_name' failed. Signed-off-by: James Hilliard --- libweston/touch-calibration.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/libweston/touch-calibration.c b/libweston/touch-calibration.c index 3c76036d6..9dd99bba4 100644 --- a/libweston/touch-calibration.c +++ b/libweston/touch-calibration.c @@ -360,8 +360,6 @@ destroy_touch_calibrator(struct wl_resource *resource) if (calibrator->surface) { unmap_calibrator(calibrator); - weston_surface_set_role(calibrator->surface, NULL, - calibrator->surface->resource, 0); wl_list_remove(&calibrator->surface_destroy_listener.link); wl_list_remove(&calibrator->surface_commit_listener.link); } From 6ebda36a3f024207260e0ddcb772a58d84b77ee1 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Sat, 28 Mar 2020 00:23:14 +0200 Subject: [PATCH 1406/1642] desktop-shell: Avoid retrieving output's width/height if none present As in some circumstances there could be no output connected, avoid retrieving the width/height of the output if none was found/connected. Fixes: #384 Signed-off-by: Marius Vlad --- desktop-shell/shell.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 442a625f8..ef0696ed5 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2697,8 +2697,10 @@ set_fullscreen(struct shell_surface *shsurf, bool fullscreen, shell_surface_set_output(shsurf, output); shsurf->fullscreen_output = shsurf->output; - width = shsurf->output->width; - height = shsurf->output->height; + if (shsurf->output) { + width = shsurf->output->width; + height = shsurf->output->height; + } } else if (weston_desktop_surface_get_maximized(desktop_surface)) { get_maximized_size(shsurf, &width, &height); } From 77d06f7b8bc40f402468d4a5ab5ac8f935b0b971 Mon Sep 17 00:00:00 2001 From: Frank Binns Date: Wed, 24 Jun 2020 11:07:42 +0100 Subject: [PATCH 1407/1642] shared: fix unused variable warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the following build warning by moving the 'seals' declaration inside the HAVE_MEMFD_CREATE guard: ../shared/os-compatibility.c: In function ‘os_ro_anonymous_file_get_fd’: ../shared/os-compatibility.c:341:6: warning: unused variable ‘seals’ [-Wunused-variable] int seals, fd; ^ Signed-off-by: Frank Binns --- shared/os-compatibility.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/os-compatibility.c b/shared/os-compatibility.c index 2e12b7cc3..f76108070 100644 --- a/shared/os-compatibility.c +++ b/shared/os-compatibility.c @@ -338,10 +338,10 @@ os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file, enum ro_anonymous_file_mapmode mapmode) { void *src, *dst; - int seals, fd; + int fd; #ifdef HAVE_MEMFD_CREATE - seals = fcntl(file->fd, F_GET_SEALS); + int seals = fcntl(file->fd, F_GET_SEALS); /* file was sealed for read-only and we don't have to support MAP_SHARED * so we can simply pass the memfd fd From 887a7e5717275c0dec007e6128298d5956c70891 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Wed, 3 Jun 2020 10:01:06 -0300 Subject: [PATCH 1408/1642] launcher: do not touch VT/tty while using non-default seat Launcher-direct does not allow us to run using a different seat from the default seat0. This happens because VTs are only exposed to the default seat, and users that are on non-default seat should not touch VTs. Add check in launcher-direct to skip VT/tty management if user is running on a non-default seat. Signed-off-by: Leandro Ribeiro --- libweston/launcher-direct.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c index 9fa329b69..382ca49f7 100644 --- a/libweston/launcher-direct.c +++ b/libweston/launcher-direct.c @@ -301,9 +301,13 @@ launcher_direct_connect(struct weston_launcher **out, struct weston_compositor * launcher->base.iface = &launcher_direct_iface; launcher->compositor = compositor; - if (setup_tty(launcher, tty) == -1) { - free(launcher); - return -1; + if (strcmp("seat0", seat_id) == 0) { + if (setup_tty(launcher, tty) == -1) { + free(launcher); + return -1; + } + } else { + launcher->tty = -1; } * (struct launcher_direct **) out = launcher; @@ -315,11 +319,11 @@ launcher_direct_destroy(struct weston_launcher *launcher_base) { struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); - launcher_direct_restore(&launcher->base); - wl_event_source_remove(launcher->vt_source); - - if (launcher->tty >= 0) + if (launcher->tty >= 0) { + launcher_direct_restore(&launcher->base); + wl_event_source_remove(launcher->vt_source); close(launcher->tty); + } free(launcher); } From a12ba0b30a911d57d3d564298a149a03aa7f2592 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Wed, 3 Jun 2020 10:02:34 -0300 Subject: [PATCH 1409/1642] gitlab CI: add support for DRM-backend tests In order to run DRM-backend tests, a DRM-device is needed. As we do not necessarily have control of the hardware that is going to run our tests in GitLab CI, DRM-backend tests were being skipped. This patch add support to run the tests using VKMS (virtual KMS). To achieve this, virtualization is needed, as we need to run a custom kernel during the CI job. We've decided to go with virtme, as it is simpler to setup and works good for our use case. Signed-off-by: Leandro Ribeiro --- .gitlab-ci.yml | 13 +++++- .gitlab-ci/debian-install.sh | 46 +++++++++++++++++-- .gitlab-ci/virtme-scripts/run-weston-tests.sh | 30 ++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) create mode 100755 .gitlab-ci/virtme-scripts/run-weston-tests.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2831df42d..02c38b964 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,7 +20,7 @@ stages: variables: FDO_DISTRIBUTION_VERSION: buster FDO_DISTRIBUTION_EXEC: 'bash .gitlab-ci/debian-install.sh' - FDO_DISTRIBUTION_TAG: '2020-05-08.0' + FDO_DISTRIBUTION_TAG: '2020-06-24.0' container_prep: @@ -51,23 +51,32 @@ container_prep: - export BUILD_ID="weston-$CI_JOB_NAME" - export PREFIX="$(pwd)/prefix-$BUILD_ID" - export BUILDDIR="$(pwd)/build-$BUILD_ID" + - export TESTS_RES_PATH="$BUILDDIR/tests-res.txt" - mkdir "$BUILDDIR" "$PREFIX" .build-native-meson: extends: .build-native + tags: + - kvm script: - export PATH=~/.local/bin:$PATH - cd "$BUILDDIR" - meson --prefix="$PREFIX" ${MESON_OPTIONS} .. - ninja -k0 - ninja install - - ninja test + - virtme-run --rw --pwd --kimg /weston-virtme/bzImage --script-dir ../.gitlab-ci/virtme-scripts + - TEST_RES=$(cat $TESTS_RES_PATH) + - rm $TESTS_RES_PATH - ninja clean + - cp -R /weston-virtme ./ + - rm weston-virtme/bzImage + - exit $TEST_RES artifacts: name: weston-$CI_COMMIT_SHA when: always paths: - build-*/meson-logs + - build-*/weston-virtme - prefix-* build-native-meson-default-options: diff --git a/.gitlab-ci/debian-install.sh b/.gitlab-ci/debian-install.sh index aaa9df724..12fb4e3f1 100644 --- a/.gitlab-ci/debian-install.sh +++ b/.gitlab-ci/debian-install.sh @@ -2,6 +2,14 @@ set -o xtrace -o errexit +# These get temporary installed for building Linux and then force-removed. +LINUX_DEV_PKGS=" + bc + bison + flex + libelf-dev +" + # These get temporary installed for building Mesa and then force-removed. MESA_DEV_PKGS=" bison @@ -74,6 +82,8 @@ apt-get -y --no-install-recommends install \ pkg-config \ python3-pip \ python3-setuptools \ + qemu-system \ + sysvinit-core \ xwayland \ $MESA_RUNTIME_PKGS @@ -85,6 +95,37 @@ pip3 install sphinx==2.1.0 --user pip3 install breathe==4.13.0.post0 --user pip3 install sphinx_rtd_theme==0.4.3 --user +apt-get -y --no-install-recommends install $LINUX_DEV_PKGS +git clone --depth=1 --branch=drm-next-2020-06-11-1 https://anongit.freedesktop.org/git/drm/drm.git linux +cd linux +make x86_64_defconfig +make kvmconfig +./scripts/config --enable CONFIG_DRM_VKMS +make oldconfig +make -j8 +cd .. +mkdir /weston-virtme +mv linux/arch/x86/boot/bzImage /weston-virtme/bzImage +mv linux/.config /weston-virtme/.config +rm -rf linux + +# Link to upstream virtme: https://github.com/amluto/virtme +# +# The reason why we are using a fork here is that it adds a patch to have the +# --script-dir command line option. With that we can run scripts that are in a +# certain folder when virtme starts, which is necessary in our use case. +# +# The upstream also has some commands that could help us to reach the same +# results: --script-sh and --script-exec. Unfornutately they are not completely +# implemented yet, so we had some trouble to use them and it was becoming +# hackery. +# +git clone https://github.com/ezequielgarcia/virtme +cd virtme +git checkout -b snapshot 69e3cb83b3405edc99fcf9611f50012a4f210f78 +./setup.py install +cd .. + git clone --branch 1.17.0 --depth=1 https://gitlab.freedesktop.org/wayland/wayland export MAKEFLAGS="-j4" cd wayland @@ -104,7 +145,6 @@ meson build -Dauto_features=disabled \ ninja -C build install cd .. rm -rf mesa -apt-get -y --autoremove purge $MESA_DEV_PKGS -mkdir -p /tmp/.X11-unix -chmod 777 /tmp/.X11-unix +apt-get -y --autoremove purge $LINUX_DEV_PKGS +apt-get -y --autoremove purge $MESA_DEV_PKGS \ No newline at end of file diff --git a/.gitlab-ci/virtme-scripts/run-weston-tests.sh b/.gitlab-ci/virtme-scripts/run-weston-tests.sh new file mode 100755 index 000000000..bbcebfd7b --- /dev/null +++ b/.gitlab-ci/virtme-scripts/run-weston-tests.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# folders that are necessary to run Weston tests +mkdir -p /tmp/tests +mkdir -p /tmp/.X11-unix +chmod -R 0700 /tmp + +# set environment variables to run Weston tests +export XDG_RUNTIME_DIR=/tmp/tests +export WESTON_TEST_SUITE_DRM_DEVICE=card0 + +# ninja test depends on meson, and meson itself looks for its modules on folder +# $HOME/.local/lib/pythonX.Y/site-packages (the Python version may differ). +# virtme starts with HOME=/tmp/roothome, but as we installed meson on user root, +# meson can not find its modules. So we change the HOME env var to fix that. +export HOME=/root + +# run the tests and save the exit status +ninja test +TEST_RES=$? + +# create a file to keep the result of this script: +# - 0 means the script succeeded +# - 1 means the tests failed, so the job itself should fail +TESTS_RES_PATH=$(pwd)/tests-res.txt +echo $TEST_RES > $TESTS_RES_PATH + +# shutdown virtme +sync +poweroff -f From e55c86a16751f58577195cf76ec7f5c6a106f3bd Mon Sep 17 00:00:00 2001 From: Jimmy Ohn Date: Wed, 1 Jul 2020 18:54:12 +0900 Subject: [PATCH 1410/1642] screen-share: don't get weston_config object before zalloc we don't need to get the weston_config object before zalloc success. --- compositor/screen-share.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compositor/screen-share.c b/compositor/screen-share.c index 62b871bf3..8c37452da 100644 --- a/compositor/screen-share.c +++ b/compositor/screen-share.c @@ -1163,7 +1163,7 @@ wet_module_init(struct weston_compositor *compositor, int *argc, char *argv[]) { struct screen_share *ss; - struct weston_config *config = wet_get_config(compositor); + struct weston_config *config; struct weston_config_section *section; ss = zalloc(sizeof *ss); @@ -1171,6 +1171,8 @@ wet_module_init(struct weston_compositor *compositor, return -1; ss->compositor = compositor; + config = wet_get_config(compositor); + section = weston_config_get_section(config, "screen-share", NULL, NULL); weston_config_section_get_string(section, "command", &ss->command, ""); From f7f8f5f1a87dd697ad6de74a885493bcca920cde Mon Sep 17 00:00:00 2001 From: Jimmy Ohn Date: Wed, 1 Jul 2020 19:11:52 +0900 Subject: [PATCH 1411/1642] xwayland/window-manager: add a NULL check to fail when frame_create fails This adds a NULL check to fail when frame_create fails. This can happen crash in frame_resize_inside function if frame is NULL. --- xwayland/window-manager.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 747bf363d..de868218c 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -1082,6 +1082,10 @@ weston_wm_window_create_frame(struct weston_wm_window *window) window->frame = frame_create(window->wm->theme, window->width, window->height, buttons, window->name, NULL); + + if (!window->frame) + return; + frame_resize_inside(window->frame, window->width, window->height); weston_wm_window_get_frame_size(window, &width, &height); From c46c70dac84a4b3030cd05b380f9f410536690fc Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Wed, 17 Jun 2020 01:43:57 +0300 Subject: [PATCH 1412/1642] libweston: Send wl_keyboard.modifiers after wl_keyboard.enter The core Wayland protocol explicitly states that wl_keyboard.modifiers must be send after wl_keyboard.enter. This commit also changes the behavior of `seat_get_keyboard` to not send `wl_keyboard.modifiers` in case where seat had pointer focus, but not keyboard one. Signed-off-by: Kirill Chibisov --- libweston/input.c | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/libweston/input.c b/libweston/input.c index e348a56b9..b4e2c66fa 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -1518,10 +1518,10 @@ send_enter_to_resource_list(struct wl_list *list, struct wl_resource *resource; wl_resource_for_each(resource, list) { - send_modifiers_to_resource(keyboard, resource, serial); wl_keyboard_send_enter(resource, serial, surface->resource, &keyboard->keys); + send_modifiers_to_resource(keyboard, resource, serial); } } @@ -2848,28 +2848,6 @@ static const struct wl_keyboard_interface keyboard_interface = { keyboard_release }; -static bool -should_send_modifiers_to_client(struct weston_seat *seat, - struct wl_client *client) -{ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (keyboard && - keyboard->focus && - keyboard->focus->resource && - wl_resource_get_client(keyboard->focus->resource) == client) - return true; - - if (pointer && - pointer->focus && - pointer->focus->surface->resource && - wl_resource_get_client(pointer->focus->surface->resource) == client) - return true; - - return false; -} - static void seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, uint32_t id) @@ -2915,12 +2893,6 @@ seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, weston_keyboard_send_keymap(keyboard, cr); - if (should_send_modifiers_to_client(seat, client)) { - send_modifiers_to_resource(keyboard, - cr, - keyboard->focus_serial); - } - if (keyboard->focus && keyboard->focus->resource && wl_resource_get_client(keyboard->focus->resource) == client) { struct weston_surface *surface = @@ -2934,6 +2906,10 @@ seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, surface->resource, &keyboard->keys); + send_modifiers_to_resource(keyboard, + cr, + keyboard->focus_serial); + /* If this is the first keyboard resource for this * client... */ if (keyboard->focus_resource_list.prev == From ea4d13b3e30a599c4cbfec6a0c57f5e952ad25ac Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Tue, 28 Jul 2020 11:08:12 -0300 Subject: [PATCH 1413/1642] drm-backend: remove log that advertises universal planes support There's a log that advertises support for universal planes. That can make users think there's something wrong with Weston or their systems when universal planes are not supported, but that's not the case. Remove this log from the code. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/kms.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index b349ec13a..f5215f20d 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -1463,8 +1463,6 @@ init_kms_caps(struct drm_backend *b) ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); b->universal_planes = (ret == 0); } - weston_log("DRM: %s universal planes\n", - b->universal_planes ? "supports" : "does not support"); if (b->universal_planes && !getenv("WESTON_DISABLE_ATOMIC")) { ret = drmGetCap(b->drm.fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap); From 87c1679a0a4c6de48a9f87b6b17bf4df63de1e6b Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Tue, 21 Apr 2020 18:23:37 +0300 Subject: [PATCH 1414/1642] kiosk-shell: Introduce kiosk/fullscreen shell for desktop apps kiosk-shell is fullscreen shell for apps that use the xdg-shell protocol. The goal is to make life easier for people shipping embedded devices with simple fullscreen shell requirements, and reduce the proliferation of desktop-shell hacks. Top level surfaces are made fullscreen, whereas dialogs are placed on top in the center of the output and retain their natural sizes. Dialogs can be moved and (un)maximized, but resizing is currently not supported. An app can be directed to a particular output by populating the "app-ids" field with the app's XDG app id, in the relevant "[output]" section in the weston config file. Fixes: #277 Signed-off-by: Alexandros Frantzis --- doc/sphinx/index.rst | 1 + doc/sphinx/toc/kiosk-shell.rst | 17 + doc/sphinx/toc/meson.build | 1 + kiosk-shell/kiosk-shell-grab.c | 314 ++++++++++ kiosk-shell/kiosk-shell-grab.h | 43 ++ kiosk-shell/kiosk-shell.c | 1071 ++++++++++++++++++++++++++++++++ kiosk-shell/kiosk-shell.h | 91 +++ kiosk-shell/meson.build | 29 + kiosk-shell/util.c | 168 +++++ kiosk-shell/util.h | 48 ++ man/weston.ini.man | 6 + meson.build | 1 + meson_options.txt | 6 + 13 files changed, 1796 insertions(+) create mode 100644 doc/sphinx/toc/kiosk-shell.rst create mode 100644 kiosk-shell/kiosk-shell-grab.c create mode 100644 kiosk-shell/kiosk-shell-grab.h create mode 100644 kiosk-shell/kiosk-shell.c create mode 100644 kiosk-shell/kiosk-shell.h create mode 100644 kiosk-shell/meson.build create mode 100644 kiosk-shell/util.c create mode 100644 kiosk-shell/util.h diff --git a/doc/sphinx/index.rst b/doc/sphinx/index.rst index d29dc0f89..f952e645c 100644 --- a/doc/sphinx/index.rst +++ b/doc/sphinx/index.rst @@ -7,6 +7,7 @@ Welcome to Weston documentation! toc/libweston.rst toc/test-suite.rst + toc/kiosk-shell.rst Weston ------ diff --git a/doc/sphinx/toc/kiosk-shell.rst b/doc/sphinx/toc/kiosk-shell.rst new file mode 100644 index 000000000..dedf6a9c2 --- /dev/null +++ b/doc/sphinx/toc/kiosk-shell.rst @@ -0,0 +1,17 @@ +Weston kiosk-shell +================== + +Weston's kiosk-shell is a simple shell targeted at single-app/kiosk use cases. +It makes all top-level application windows fullscreen, and supports defining +which applications to place on particular outputs. This is achieved with the +``app-ids=`` field in the corresponding output section in weston.ini. For +example: + +.. code-block:: ini + + [output] + name=screen0 + app-ids=org.domain.app1,com.domain.app2 + +To run weston with kiosk-shell set ``shell=kiosk-shell.so`` in weston.ini, or +use the ``--shell=kiosk-shell.so`` command-line option. diff --git a/doc/sphinx/toc/meson.build b/doc/sphinx/toc/meson.build index 6ab3cda50..f91e460d8 100644 --- a/doc/sphinx/toc/meson.build +++ b/doc/sphinx/toc/meson.build @@ -1,5 +1,6 @@ # you need to add here any files you add to the toc directory as well files = [ + 'kiosk-shell.rst', 'libweston.rst', 'test-suite.rst', 'test-suite-api.rst', diff --git a/kiosk-shell/kiosk-shell-grab.c b/kiosk-shell/kiosk-shell-grab.c new file mode 100644 index 000000000..3ea0156c1 --- /dev/null +++ b/kiosk-shell/kiosk-shell-grab.c @@ -0,0 +1,314 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * 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 "kiosk-shell-grab.h" +#include "shared/helpers.h" + +struct kiosk_shell_grab { + struct kiosk_shell_surface *shsurf; + struct wl_listener shsurf_destroy_listener; + + struct weston_pointer_grab pointer_grab; + struct weston_touch_grab touch_grab; + wl_fixed_t dx, dy; + bool active; +}; + +static void +kiosk_shell_grab_destroy(struct kiosk_shell_grab *shgrab); + +/* + * pointer_move_grab_interface + */ + +static void +pointer_move_grab_focus(struct weston_pointer_grab *grab) +{ +} + +static void +pointer_move_grab_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ +} + +static void +pointer_move_grab_axis_source(struct weston_pointer_grab *grab, + uint32_t source) +{ +} + +static void +pointer_move_grab_frame(struct weston_pointer_grab *grab) +{ +} + +static void +pointer_move_grab_motion(struct weston_pointer_grab *pointer_grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct kiosk_shell_grab *shgrab = + container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); + struct weston_pointer *pointer = pointer_grab->pointer; + struct kiosk_shell_surface *shsurf = shgrab->shsurf; + struct weston_surface *surface; + int dx, dy; + + weston_pointer_move(pointer, event); + + if (!shsurf) + return; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + dx = wl_fixed_to_int(pointer->x + shgrab->dx); + dy = wl_fixed_to_int(pointer->y + shgrab->dy); + + weston_view_set_position(shsurf->view, dx, dy); + + weston_compositor_schedule_repaint(surface->compositor); +} + +static void +pointer_move_grab_button(struct weston_pointer_grab *pointer_grab, + const struct timespec *time, + uint32_t button, uint32_t state_w) +{ + struct kiosk_shell_grab *shgrab = + container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); + struct weston_pointer *pointer = pointer_grab->pointer; + enum wl_pointer_button_state state = state_w; + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) + kiosk_shell_grab_destroy(shgrab); +} + +static void +pointer_move_grab_cancel(struct weston_pointer_grab *pointer_grab) +{ + struct kiosk_shell_grab *shgrab = + container_of(pointer_grab, struct kiosk_shell_grab, pointer_grab); + + kiosk_shell_grab_destroy(shgrab); +} + +static const struct weston_pointer_grab_interface pointer_move_grab_interface = { + pointer_move_grab_focus, + pointer_move_grab_motion, + pointer_move_grab_button, + pointer_move_grab_axis, + pointer_move_grab_axis_source, + pointer_move_grab_frame, + pointer_move_grab_cancel, +}; + +/* + * touch_move_grab_interface + */ + +static void +touch_move_grab_down(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, wl_fixed_t x, wl_fixed_t y) +{ +} + +static void +touch_move_grab_up(struct weston_touch_grab *touch_grab, + const struct timespec *time, int touch_id) +{ + struct kiosk_shell_grab *shgrab = + container_of(touch_grab, struct kiosk_shell_grab, touch_grab); + + if (touch_id == 0) + shgrab->active = false; + + if (touch_grab->touch->num_tp == 0) + kiosk_shell_grab_destroy(shgrab); +} + +static void +touch_move_grab_motion(struct weston_touch_grab *touch_grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) +{ + struct kiosk_shell_grab *shgrab = + container_of(touch_grab, struct kiosk_shell_grab, touch_grab); + struct weston_touch *touch = touch_grab->touch; + struct kiosk_shell_surface *shsurf = shgrab->shsurf; + struct weston_surface *surface; + int dx, dy; + + if (!shsurf || !shgrab->active) + return; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + dx = wl_fixed_to_int(touch->grab_x + shgrab->dx); + dy = wl_fixed_to_int(touch->grab_y + shgrab->dy); + + weston_view_set_position(shsurf->view, dx, dy); + + weston_compositor_schedule_repaint(surface->compositor); +} + +static void +touch_move_grab_frame(struct weston_touch_grab *grab) +{ +} + +static void +touch_move_grab_cancel(struct weston_touch_grab *touch_grab) +{ + struct kiosk_shell_grab *shgrab = + container_of(touch_grab, struct kiosk_shell_grab, touch_grab); + + kiosk_shell_grab_destroy(shgrab); +} + +static const struct weston_touch_grab_interface touch_move_grab_interface = { + touch_move_grab_down, + touch_move_grab_up, + touch_move_grab_motion, + touch_move_grab_frame, + touch_move_grab_cancel, +}; + +/* + * kiosk_shell_grab + */ + +static void +kiosk_shell_grab_handle_shsurf_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_grab *shgrab = + container_of(listener, struct kiosk_shell_grab, + shsurf_destroy_listener); + + shgrab->shsurf = NULL; +} + +static struct kiosk_shell_grab * +kiosk_shell_grab_create(struct kiosk_shell_surface *shsurf) +{ + struct kiosk_shell_grab *shgrab; + + shgrab = zalloc(sizeof *shgrab); + if (!shgrab) + return NULL; + + shgrab->shsurf = shsurf; + shgrab->shsurf_destroy_listener.notify = + kiosk_shell_grab_handle_shsurf_destroy; + wl_signal_add(&shsurf->destroy_signal, + &shgrab->shsurf_destroy_listener); + + shsurf->grabbed = true; + + return shgrab; +} + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_pointer_move(struct kiosk_shell_surface *shsurf, + struct weston_pointer *pointer) +{ + struct kiosk_shell_grab *shgrab; + + if (!shsurf) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + if (shsurf->grabbed || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return KIOSK_SHELL_GRAB_RESULT_IGNORED; + + shgrab = kiosk_shell_grab_create(shsurf); + if (!shgrab) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + shgrab->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + pointer->grab_x; + shgrab->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + pointer->grab_y; + shgrab->active = true; + + weston_seat_break_desktop_grabs(pointer->seat); + + shgrab->pointer_grab.interface = &pointer_move_grab_interface; + weston_pointer_start_grab(pointer, &shgrab->pointer_grab); + + return KIOSK_SHELL_GRAB_RESULT_OK; +} + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_touch_move(struct kiosk_shell_surface *shsurf, + struct weston_touch *touch) +{ + struct kiosk_shell_grab *shgrab; + + if (!shsurf) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + if (shsurf->grabbed || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return KIOSK_SHELL_GRAB_RESULT_IGNORED; + + shgrab = kiosk_shell_grab_create(shsurf); + if (!shgrab) + return KIOSK_SHELL_GRAB_RESULT_ERROR; + + shgrab->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + touch->grab_x; + shgrab->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + touch->grab_y; + shgrab->active = true; + + weston_seat_break_desktop_grabs(touch->seat); + + shgrab->touch_grab.interface = &touch_move_grab_interface; + weston_touch_start_grab(touch, &shgrab->touch_grab); + + return KIOSK_SHELL_GRAB_RESULT_OK; +} + +static void +kiosk_shell_grab_destroy(struct kiosk_shell_grab *shgrab) +{ + if (shgrab->shsurf) { + wl_list_remove(&shgrab->shsurf_destroy_listener.link); + shgrab->shsurf->grabbed = false; + } + + if (shgrab->pointer_grab.pointer) + weston_pointer_end_grab(shgrab->pointer_grab.pointer); + else if (shgrab->touch_grab.touch) + weston_touch_end_grab(shgrab->touch_grab.touch); + + free(shgrab); +} diff --git a/kiosk-shell/kiosk-shell-grab.h b/kiosk-shell/kiosk-shell-grab.h new file mode 100644 index 000000000..bf78384e8 --- /dev/null +++ b/kiosk-shell/kiosk-shell-grab.h @@ -0,0 +1,43 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * + * 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. + */ + +#ifndef WESTON_KIOSK_SHELL_GRAB_H +#define WESTON_KIOSK_SHELL_GRAB_H + +#include "kiosk-shell.h" + +enum kiosk_shell_grab_result { + KIOSK_SHELL_GRAB_RESULT_OK, + KIOSK_SHELL_GRAB_RESULT_IGNORED, + KIOSK_SHELL_GRAB_RESULT_ERROR, +}; + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_pointer_move(struct kiosk_shell_surface *shsurf, + struct weston_pointer *pointer); + +enum kiosk_shell_grab_result +kiosk_shell_grab_start_for_touch_move(struct kiosk_shell_surface *shsurf, + struct weston_touch *touch); + +#endif /* WESTON_KIOSK_SHELL_GRAB_H */ diff --git a/kiosk-shell/kiosk-shell.c b/kiosk-shell/kiosk-shell.c new file mode 100644 index 000000000..ac9c86842 --- /dev/null +++ b/kiosk-shell/kiosk-shell.c @@ -0,0 +1,1071 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * 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 +#include +#include +#include +#include + +#include "kiosk-shell.h" +#include "kiosk-shell-grab.h" +#include "compositor/weston.h" +#include "shared/helpers.h" +#include "util.h" + +static struct kiosk_shell_surface * +get_kiosk_shell_surface(struct weston_surface *surface) +{ + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + + if (desktop_surface) + return weston_desktop_surface_get_user_data(desktop_surface); + + return NULL; +} + +static void +kiosk_shell_seat_handle_destroy(struct wl_listener *listener, void *data); + +static struct kiosk_shell_seat * +get_kiosk_shell_seat(struct weston_seat *seat) +{ + struct wl_listener *listener; + + listener = wl_signal_get(&seat->destroy_signal, + kiosk_shell_seat_handle_destroy); + assert(listener != NULL); + + return container_of(listener, + struct kiosk_shell_seat, seat_destroy_listener); +} + +/* + * kiosk_shell_surface + */ + +static void +kiosk_shell_surface_set_output(struct kiosk_shell_surface *shsurf, + struct weston_output *output); +static void +kiosk_shell_surface_set_parent(struct kiosk_shell_surface *shsurf, + struct kiosk_shell_surface *parent); + +static void +kiosk_shell_surface_notify_parent_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_surface *shsurf = + container_of(listener, + struct kiosk_shell_surface, parent_destroy_listener); + + kiosk_shell_surface_set_parent(shsurf, shsurf->parent->parent); +} + +static void +kiosk_shell_surface_notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_surface *shsurf = + container_of(listener, + struct kiosk_shell_surface, output_destroy_listener); + + kiosk_shell_surface_set_output(shsurf, NULL); +} + +static struct kiosk_shell_surface * +kiosk_shell_surface_get_parent_root(struct kiosk_shell_surface *shsurf) +{ + struct kiosk_shell_surface *root = shsurf; + while (root->parent) + root = root->parent; + return root; +} + +static bool +kiosk_shell_output_has_app_id(struct kiosk_shell_output *shoutput, + const char *app_id); + +static struct weston_output * +kiosk_shell_surface_find_best_output(struct kiosk_shell_surface *shsurf) +{ + struct weston_output *output; + struct kiosk_shell_output *shoutput; + struct kiosk_shell_surface *root; + const char *app_id; + + /* Always use current output if any. */ + if (shsurf->output) + return shsurf->output; + + /* Check if we have a designated output for this app. */ + app_id = weston_desktop_surface_get_app_id(shsurf->desktop_surface); + if (app_id) { + wl_list_for_each(shoutput, &shsurf->shell->output_list, link) { + if (kiosk_shell_output_has_app_id(shoutput, app_id)) + return shoutput->output; + } + } + + /* Group all related windows in the same output. */ + root = kiosk_shell_surface_get_parent_root(shsurf); + if (root->output) + return root->output; + + output = get_focused_output(shsurf->shell->compositor); + if (output) + return output; + + output = get_default_output(shsurf->shell->compositor); + if (output) + return output; + + return NULL; +} + +static void +kiosk_shell_surface_set_output(struct kiosk_shell_surface *shsurf, + struct weston_output *output) +{ + shsurf->output = output; + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + if (!shsurf->output) + return; + + shsurf->output_destroy_listener.notify = + kiosk_shell_surface_notify_output_destroy; + wl_signal_add(&shsurf->output->destroy_signal, + &shsurf->output_destroy_listener); +} + +static void +kiosk_shell_surface_set_fullscreen(struct kiosk_shell_surface *shsurf, + struct weston_output *output) +{ + if (!output) + output = kiosk_shell_surface_find_best_output(shsurf); + + kiosk_shell_surface_set_output(shsurf, output); + + weston_desktop_surface_set_fullscreen(shsurf->desktop_surface, true); + if (shsurf->output) + weston_desktop_surface_set_size(shsurf->desktop_surface, + shsurf->output->width, + shsurf->output->height); +} + +static void +kiosk_shell_surface_set_maximized(struct kiosk_shell_surface *shsurf) +{ + struct weston_output *output = + kiosk_shell_surface_find_best_output(shsurf); + + kiosk_shell_surface_set_output(shsurf, output); + + weston_desktop_surface_set_maximized(shsurf->desktop_surface, true); + if (shsurf->output) + weston_desktop_surface_set_size(shsurf->desktop_surface, + shsurf->output->width, + shsurf->output->height); +} + +static void +kiosk_shell_surface_set_normal(struct kiosk_shell_surface *shsurf) +{ + if (!shsurf->output) + kiosk_shell_surface_set_output(shsurf, + kiosk_shell_surface_find_best_output(shsurf)); + + weston_desktop_surface_set_fullscreen(shsurf->desktop_surface, false); + weston_desktop_surface_set_maximized(shsurf->desktop_surface, false); + weston_desktop_surface_set_size(shsurf->desktop_surface, 0, 0); +} + +static void +kiosk_shell_surface_set_parent(struct kiosk_shell_surface *shsurf, + struct kiosk_shell_surface *parent) +{ + if (shsurf->parent_destroy_listener.notify) { + wl_list_remove(&shsurf->parent_destroy_listener.link); + shsurf->parent_destroy_listener.notify = NULL; + } + + shsurf->parent = parent; + + if (shsurf->parent) { + shsurf->parent_destroy_listener.notify = + kiosk_shell_surface_notify_parent_destroy; + wl_signal_add(&shsurf->parent->destroy_signal, + &shsurf->parent_destroy_listener); + kiosk_shell_surface_set_output(shsurf, NULL); + kiosk_shell_surface_set_normal(shsurf); + } else { + kiosk_shell_surface_set_fullscreen(shsurf, shsurf->output); + } +} + +static void +kiosk_shell_surface_reconfigure_for_output(struct kiosk_shell_surface *shsurf) +{ + struct weston_desktop_surface *desktop_surface; + + if (!shsurf->output) + return; + + desktop_surface = shsurf->desktop_surface; + + if (weston_desktop_surface_get_maximized(desktop_surface) || + weston_desktop_surface_get_fullscreen(desktop_surface)) { + weston_desktop_surface_set_size(desktop_surface, + shsurf->output->width, + shsurf->output->height); + } + + center_on_output(shsurf->view, shsurf->output); + weston_view_update_transform(shsurf->view); +} + +static void +kiosk_shell_surface_destroy(struct kiosk_shell_surface *shsurf) +{ + wl_signal_emit(&shsurf->destroy_signal, shsurf); + + weston_desktop_surface_set_user_data(shsurf->desktop_surface, NULL); + shsurf->desktop_surface = NULL; + + weston_desktop_surface_unlink_view(shsurf->view); + + weston_view_destroy(shsurf->view); + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + if (shsurf->parent_destroy_listener.notify) { + wl_list_remove(&shsurf->parent_destroy_listener.link); + shsurf->parent_destroy_listener.notify = NULL; + shsurf->parent = NULL; + } + + free(shsurf); +} + +static struct kiosk_shell_surface * +kiosk_shell_surface_create(struct kiosk_shell *shell, + struct weston_desktop_surface *desktop_surface) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(desktop_surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + struct weston_view *view; + struct kiosk_shell_surface *shsurf; + + view = weston_desktop_surface_create_view(desktop_surface); + if (!view) + return NULL; + + shsurf = zalloc(sizeof *shsurf); + if (!shsurf) { + if (wl_client) + wl_client_post_no_memory(wl_client); + else + weston_log("no memory to allocate shell surface\n"); + return NULL; + } + + shsurf->desktop_surface = desktop_surface; + shsurf->view = view; + shsurf->shell = shell; + + weston_desktop_surface_set_user_data(desktop_surface, shsurf); + + wl_signal_init(&shsurf->destroy_signal); + + return shsurf; +} + +/* + * kiosk_shell_seat + */ + +static void +kiosk_shell_seat_handle_keyboard_focus(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard = data; + struct kiosk_shell_seat *shseat = get_kiosk_shell_seat(keyboard->seat); + + if (shseat->focused_surface) { + struct kiosk_shell_surface *shsurf = + get_kiosk_shell_surface(shseat->focused_surface); + if (shsurf && --shsurf->focus_count == 0) + weston_desktop_surface_set_activated(shsurf->desktop_surface, + false); + } + + shseat->focused_surface = weston_surface_get_main_surface(keyboard->focus); + + if (shseat->focused_surface) { + struct kiosk_shell_surface *shsurf = + get_kiosk_shell_surface(shseat->focused_surface); + if (shsurf && shsurf->focus_count++ == 0) + weston_desktop_surface_set_activated(shsurf->desktop_surface, + true); + } +} + +static void +kiosk_shell_seat_handle_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_seat *shseat = + container_of(listener, + struct kiosk_shell_seat, seat_destroy_listener); + + wl_list_remove(&shseat->keyboard_focus_listener.link); + wl_list_remove(&shseat->caps_changed_listener.link); + wl_list_remove(&shseat->seat_destroy_listener.link); + free(shseat); +} + +static void +kiosk_shell_seat_handle_caps_changed(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard; + struct kiosk_shell_seat *shseat; + + shseat = container_of(listener, struct kiosk_shell_seat, + caps_changed_listener); + keyboard = weston_seat_get_keyboard(shseat->seat); + + if (keyboard && + wl_list_empty(&shseat->keyboard_focus_listener.link)) { + wl_signal_add(&keyboard->focus_signal, + &shseat->keyboard_focus_listener); + } else if (!keyboard) { + wl_list_remove(&shseat->keyboard_focus_listener.link); + wl_list_init(&shseat->keyboard_focus_listener.link); + } +} + +static struct kiosk_shell_seat * +kiosk_shell_seat_create(struct weston_seat *seat) +{ + struct kiosk_shell_seat *shseat; + + shseat = zalloc(sizeof *shseat); + if (!shseat) { + weston_log("no memory to allocate shell seat\n"); + return NULL; + } + + shseat->seat = seat; + + shseat->seat_destroy_listener.notify = kiosk_shell_seat_handle_destroy; + wl_signal_add(&seat->destroy_signal, &shseat->seat_destroy_listener); + + shseat->keyboard_focus_listener.notify = kiosk_shell_seat_handle_keyboard_focus; + wl_list_init(&shseat->keyboard_focus_listener.link); + + shseat->caps_changed_listener.notify = kiosk_shell_seat_handle_caps_changed; + wl_signal_add(&seat->updated_caps_signal, + &shseat->caps_changed_listener); + kiosk_shell_seat_handle_caps_changed(&shseat->caps_changed_listener, NULL); + + return shseat; +} + +/* + * kiosk_shell_output + */ + +static int +kiosk_shell_background_surface_get_label(struct weston_surface *surface, + char *buf, size_t len) +{ + return snprintf(buf, len, "kiosk shell background surface"); +} + +static void +kiosk_shell_output_recreate_background(struct kiosk_shell_output *shoutput) +{ + struct kiosk_shell *shell = shoutput->shell; + struct weston_output *output = shoutput->output; + + if (shoutput->background_view) + weston_surface_destroy(shoutput->background_view->surface); + + if (!output) + return; + + shoutput->background_view = + create_colored_surface(shoutput->shell->compositor, + 0.5, 0.5, 0.5, + output->x, output->y, + output->width, + output->height); + + weston_surface_set_role(shoutput->background_view->surface, + "kiosk-shell-background", NULL, 0); + weston_surface_set_label_func(shoutput->background_view->surface, + kiosk_shell_background_surface_get_label); + + weston_layer_entry_insert(&shell->background_layer.view_list, + &shoutput->background_view->layer_link); + + shoutput->background_view->is_mapped = true; + shoutput->background_view->surface->is_mapped = true; + shoutput->background_view->surface->output = output; + weston_view_set_output(shoutput->background_view, output); +} + +static void +kiosk_shell_output_destroy(struct kiosk_shell_output *shoutput) +{ + shoutput->output = NULL; + shoutput->output_destroy_listener.notify = NULL; + + if (shoutput->background_view) + weston_surface_destroy(shoutput->background_view->surface); + + wl_list_remove(&shoutput->output_destroy_listener.link); + wl_list_remove(&shoutput->link); + + free(shoutput->app_ids); + + free(shoutput); +} + +static bool +kiosk_shell_output_has_app_id(struct kiosk_shell_output *shoutput, + const char *app_id) +{ + char *cur; + size_t app_id_len; + + if (!shoutput->app_ids) + return false; + + cur = shoutput->app_ids; + app_id_len = strlen(app_id); + + while ((cur = strstr(cur, app_id))) { + /* Check whether we have found a complete match of app_id. */ + if ((cur[app_id_len] == ',' || cur[app_id_len] == '\0') && + (cur == shoutput->app_ids || cur[-1] == ',')) + return true; + cur++; + } + + return false; +} + +static void +kiosk_shell_output_configure(struct kiosk_shell_output *shoutput) +{ + struct weston_config *wc = wet_get_config(shoutput->shell->compositor); + struct weston_config_section *section = + weston_config_get_section(wc, "output", "name", shoutput->output->name); + + assert(shoutput->app_ids == NULL); + + if (section) { + weston_config_section_get_string(section, "app-ids", + &shoutput->app_ids, NULL); + } +} + +static void +kiosk_shell_output_notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell_output *shoutput = + container_of(listener, + struct kiosk_shell_output, output_destroy_listener); + + kiosk_shell_output_destroy(shoutput); +} + +static struct kiosk_shell_output * +kiosk_shell_output_create(struct kiosk_shell *shell, struct weston_output *output) +{ + struct kiosk_shell_output *shoutput; + + shoutput = zalloc(sizeof *shoutput); + if (shoutput == NULL) + return NULL; + + shoutput->output = output; + shoutput->shell = shell; + + shoutput->output_destroy_listener.notify = + kiosk_shell_output_notify_output_destroy; + wl_signal_add(&shoutput->output->destroy_signal, + &shoutput->output_destroy_listener); + + wl_list_insert(shell->output_list.prev, &shoutput->link); + + kiosk_shell_output_recreate_background(shoutput); + kiosk_shell_output_configure(shoutput); + + return shoutput; +} + +/* + * libweston-desktop + */ + +static void +desktop_surface_added(struct weston_desktop_surface *desktop_surface, + void *data) +{ + struct kiosk_shell *shell = data; + struct kiosk_shell_surface *shsurf; + struct weston_seat *seat; + + shsurf = kiosk_shell_surface_create(shell, desktop_surface); + if (!shsurf) + return; + + kiosk_shell_surface_set_fullscreen(shsurf, NULL); + + wl_list_for_each(seat, &shell->compositor->seat_list, link) + weston_view_activate(shsurf->view, seat, 0); +} + +/* Return the view that should gain focus after the specified shsurf is + * destroyed. We prefer the top remaining view from the same parent surface, + * but if we can't find one we fall back to the top view regardless of + * parentage. */ +static struct weston_view * +find_focus_successor(struct weston_layer *layer, + struct kiosk_shell_surface *shsurf) +{ + struct kiosk_shell_surface *parent_root = + kiosk_shell_surface_get_parent_root(shsurf); + struct weston_view *top_view = NULL; + struct weston_view *view; + + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { + struct kiosk_shell_surface *view_shsurf; + struct kiosk_shell_surface *root; + + if (!view->is_mapped || view == shsurf->view) + continue; + + view_shsurf = get_kiosk_shell_surface(view->surface); + if (!view_shsurf) + continue; + + if (!top_view) + top_view = view; + + root = kiosk_shell_surface_get_parent_root(view_shsurf); + if (root == parent_root) + return view; + } + + return top_view; +} + +static void +desktop_surface_removed(struct weston_desktop_surface *desktop_surface, + void *data) +{ + struct kiosk_shell *shell = data; + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + struct weston_view *focus_view; + struct weston_seat *seat; + + if (!shsurf) + return; + + focus_view = find_focus_successor(&shell->normal_layer, shsurf); + + if (focus_view) { + wl_list_for_each(seat, &shell->compositor->seat_list, link) { + struct weston_keyboard *keyboard = seat->keyboard_state; + if (keyboard && keyboard->focus == surface) + weston_view_activate(focus_view, seat, 0); + } + } + + kiosk_shell_surface_destroy(shsurf); +} + +static void +desktop_surface_committed(struct weston_desktop_surface *desktop_surface, + int32_t sx, int32_t sy, void *data) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + bool is_resized; + bool is_fullscreen; + + if (surface->width == 0) + return; + + /* TODO: When the top-level surface is committed with a new size after an + * output resize, sometimes the view appears scaled. What state are we not + * updating? + */ + + is_resized = surface->width != shsurf->last_width || + surface->height != shsurf->last_height; + is_fullscreen = weston_desktop_surface_get_maximized(desktop_surface) || + weston_desktop_surface_get_fullscreen(desktop_surface); + + if (!weston_surface_is_mapped(surface) || (is_resized && is_fullscreen)) { + if (is_fullscreen || !shsurf->xwayland.is_set) { + center_on_output(shsurf->view, shsurf->output); + } else { + struct weston_geometry geometry = + weston_desktop_surface_get_geometry(desktop_surface); + float x = shsurf->xwayland.x - geometry.x; + float y = shsurf->xwayland.y - geometry.y; + + weston_view_set_position(shsurf->view, x, y); + } + + weston_view_update_transform(shsurf->view); + } + + if (!weston_surface_is_mapped(surface)) { + weston_layer_entry_insert(&shsurf->shell->normal_layer.view_list, + &shsurf->view->layer_link); + shsurf->view->is_mapped = true; + surface->is_mapped = true; + } + + if (!is_fullscreen && (sx != 0 || sy != 0)) { + float from_x, from_y; + float to_x, to_y; + float x, y; + + weston_view_to_global_float(shsurf->view, 0, 0, &from_x, &from_y); + weston_view_to_global_float(shsurf->view, sx, sy, &to_x, &to_y); + x = shsurf->view->geometry.x + to_x - from_x; + y = shsurf->view->geometry.y + to_y - from_y; + + weston_view_set_position(shsurf->view, x, y); + weston_view_update_transform(shsurf->view); + } + + shsurf->last_width = surface->width; + shsurf->last_height = surface->height; +} + +static void +desktop_surface_move(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, void *shell) +{ + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_touch *touch = weston_seat_get_touch(seat); + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_surface *focus; + + if (pointer && + pointer->focus && + pointer->button_count > 0 && + pointer->grab_serial == serial) { + focus = weston_surface_get_main_surface(pointer->focus->surface); + if ((focus == surface) && + (kiosk_shell_grab_start_for_pointer_move(shsurf, pointer) == + KIOSK_SHELL_GRAB_RESULT_ERROR)) + wl_resource_post_no_memory(surface->resource); + } + else if (touch && + touch->focus && + touch->grab_serial == serial) { + focus = weston_surface_get_main_surface(touch->focus->surface); + if ((focus == surface) && + (kiosk_shell_grab_start_for_touch_move(shsurf, touch) == + KIOSK_SHELL_GRAB_RESULT_ERROR)) + wl_resource_post_no_memory(surface->resource); + } +} + +static void +desktop_surface_resize(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges, void *shell) +{ +} + +static void +desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, + struct weston_desktop_surface *parent, + void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct kiosk_shell_surface *shsurf_parent = + parent ? weston_desktop_surface_get_user_data(parent) : NULL; + + kiosk_shell_surface_set_parent(shsurf, shsurf_parent); +} + +static void +desktop_surface_fullscreen_requested(struct weston_desktop_surface *desktop_surface, + bool fullscreen, + struct weston_output *output, void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + /* We should normally be able to ignore fullscreen requests for + * top-level surfaces, since we set them as fullscreen at creation + * time. However, xwayland surfaces set their internal WM state + * regardless of what the shell wants, so they may remove fullscreen + * state before informing weston-desktop of this request. Since we + * always want top-level surfaces to be fullscreen, we need to reapply + * the fullscreen state to force the correct xwayland WM state. + * + * TODO: Explore a model where the XWayland WM doesn't set the internal + * WM surface state itself, rather letting the shell make the decision. + */ + + if (!shsurf->parent || fullscreen) + kiosk_shell_surface_set_fullscreen(shsurf, output); + else + kiosk_shell_surface_set_normal(shsurf); +} + +static void +desktop_surface_maximized_requested(struct weston_desktop_surface *desktop_surface, + bool maximized, void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + /* Since xwayland surfaces may have already applied the max/min states + * internally, reapply fullscreen to force the correct xwayland WM state. + * Also see comment in desktop_surface_fullscreen_requested(). */ + if (!shsurf->parent) + kiosk_shell_surface_set_fullscreen(shsurf, NULL); + else if (maximized) + kiosk_shell_surface_set_maximized(shsurf); + else + kiosk_shell_surface_set_normal(shsurf); +} + +static void +desktop_surface_minimized_requested(struct weston_desktop_surface *desktop_surface, + void *shell) +{ +} + +static void +desktop_surface_ping_timeout(struct weston_desktop_client *desktop_client, + void *shell_) +{ +} + +static void +desktop_surface_pong(struct weston_desktop_client *desktop_client, + void *shell_) +{ +} + +static void +desktop_surface_set_xwayland_position(struct weston_desktop_surface *desktop_surface, + int32_t x, int32_t y, void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + shsurf->xwayland.x = x; + shsurf->xwayland.y = y; + shsurf->xwayland.is_set = true; +} + +static const struct weston_desktop_api kiosk_shell_desktop_api = { + .struct_size = sizeof(struct weston_desktop_api), + .surface_added = desktop_surface_added, + .surface_removed = desktop_surface_removed, + .committed = desktop_surface_committed, + .move = desktop_surface_move, + .resize = desktop_surface_resize, + .set_parent = desktop_surface_set_parent, + .fullscreen_requested = desktop_surface_fullscreen_requested, + .maximized_requested = desktop_surface_maximized_requested, + .minimized_requested = desktop_surface_minimized_requested, + .ping_timeout = desktop_surface_ping_timeout, + .pong = desktop_surface_pong, + .set_xwayland_position = desktop_surface_set_xwayland_position, +}; + +/* + * kiosk_shell + */ + +static struct kiosk_shell_output * +kiosk_shell_find_shell_output(struct kiosk_shell *shell, + struct weston_output *output) +{ + struct kiosk_shell_output *shoutput; + + wl_list_for_each(shoutput, &shell->output_list, link) { + if (shoutput->output == output) + return shoutput; + } + + return NULL; +} + +static void +kiosk_shell_activate_view(struct kiosk_shell *shell, + struct weston_view *view, + struct weston_seat *seat, + uint32_t flags) +{ + struct weston_surface *main_surface = + weston_surface_get_main_surface(view->surface); + struct kiosk_shell_surface *shsurf = + get_kiosk_shell_surface(main_surface); + + if (!shsurf) + return; + + /* If the view belongs to a child window bring it to the front. + * We don't do this for the parent top-level, since that would + * obscure all children. + */ + if (shsurf->parent) { + weston_layer_entry_remove(&view->layer_link); + weston_layer_entry_insert(&shell->normal_layer.view_list, + &view->layer_link); + weston_view_geometry_dirty(view); + weston_surface_damage(view->surface); + } + + weston_view_activate(view, seat, flags); +} + +static void +kiosk_shell_click_to_activate_binding(struct weston_pointer *pointer, + const struct timespec *time, + uint32_t button, void *data) +{ + struct kiosk_shell *shell = data; + + if (pointer->grab != &pointer->default_grab) + return; + if (pointer->focus == NULL) + return; + + kiosk_shell_activate_view(shell, pointer->focus, pointer->seat, + WESTON_ACTIVATE_FLAG_CLICKED); +} + +static void +kiosk_shell_touch_to_activate_binding(struct weston_touch *touch, + const struct timespec *time, + void *data) +{ + struct kiosk_shell *shell = data; + + if (touch->grab != &touch->default_grab) + return; + if (touch->focus == NULL) + return; + + kiosk_shell_activate_view(shell, touch->focus, touch->seat, + WESTON_ACTIVATE_FLAG_NONE); +} + +static void +kiosk_shell_add_bindings(struct kiosk_shell *shell) +{ + weston_compositor_add_button_binding(shell->compositor, BTN_LEFT, 0, + kiosk_shell_click_to_activate_binding, + shell); + weston_compositor_add_button_binding(shell->compositor, BTN_RIGHT, 0, + kiosk_shell_click_to_activate_binding, + shell); + weston_compositor_add_touch_binding(shell->compositor, 0, + kiosk_shell_touch_to_activate_binding, + shell); +} + +static void +kiosk_shell_handle_output_created(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, output_created_listener); + struct weston_output *output = data; + + kiosk_shell_output_create(shell, output); +} + +static void +kiosk_shell_handle_output_resized(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, output_resized_listener); + struct weston_output *output = data; + struct kiosk_shell_output *shoutput = + kiosk_shell_find_shell_output(shell, output); + struct weston_view *view; + + kiosk_shell_output_recreate_background(shoutput); + + wl_list_for_each(view, &shell->normal_layer.view_list.link, + layer_link.link) { + struct kiosk_shell_surface *shsurf; + if (view->output != output) + continue; + shsurf = get_kiosk_shell_surface(view->surface); + if (!shsurf) + continue; + kiosk_shell_surface_reconfigure_for_output(shsurf); + } +} + +static void +kiosk_shell_handle_output_moved(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, output_moved_listener); + struct weston_output *output = data; + struct weston_view *view; + + wl_list_for_each(view, &shell->background_layer.view_list.link, + layer_link.link) { + if (view->output != output) + continue; + weston_view_set_position(view, + view->geometry.x + output->move_x, + view->geometry.y + output->move_y); + } + + wl_list_for_each(view, &shell->normal_layer.view_list.link, + layer_link.link) { + if (view->output != output) + continue; + weston_view_set_position(view, + view->geometry.x + output->move_x, + view->geometry.y + output->move_y); + } +} + +static void +kiosk_shell_handle_seat_created(struct wl_listener *listener, void *data) +{ + struct weston_seat *seat = data; + kiosk_shell_seat_create(seat); +} + +static void +kiosk_shell_destroy(struct wl_listener *listener, void *data) +{ + struct kiosk_shell *shell = + container_of(listener, struct kiosk_shell, destroy_listener); + struct kiosk_shell_output *shoutput, *tmp; + + wl_list_remove(&shell->destroy_listener.link); + wl_list_remove(&shell->output_created_listener.link); + wl_list_remove(&shell->output_resized_listener.link); + wl_list_remove(&shell->output_moved_listener.link); + wl_list_remove(&shell->seat_created_listener.link); + + wl_list_for_each_safe(shoutput, tmp, &shell->output_list, link) { + kiosk_shell_output_destroy(shoutput); + } + + weston_desktop_destroy(shell->desktop); + + free(shell); +} + +WL_EXPORT int +wet_shell_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + struct kiosk_shell *shell; + struct weston_seat *seat; + struct weston_output *output; + + shell = zalloc(sizeof *shell); + if (shell == NULL) + return -1; + + shell->compositor = ec; + + if (!weston_compositor_add_destroy_listener_once(ec, + &shell->destroy_listener, + kiosk_shell_destroy)) { + free(shell); + return 0; + } + + weston_layer_init(&shell->background_layer, ec); + weston_layer_init(&shell->normal_layer, ec); + + weston_layer_set_position(&shell->background_layer, + WESTON_LAYER_POSITION_BACKGROUND); + /* We use the NORMAL layer position, so that xwayland surfaces, which + * are placed at NORMAL+1, are visible. */ + weston_layer_set_position(&shell->normal_layer, + WESTON_LAYER_POSITION_NORMAL); + + shell->desktop = weston_desktop_create(ec, &kiosk_shell_desktop_api, + shell); + if (!shell->desktop) + return -1; + + wl_list_for_each(seat, &ec->seat_list, link) + kiosk_shell_seat_create(seat); + shell->seat_created_listener.notify = kiosk_shell_handle_seat_created; + wl_signal_add(&ec->seat_created_signal, &shell->seat_created_listener); + + wl_list_init(&shell->output_list); + wl_list_for_each(output, &ec->output_list, link) + kiosk_shell_output_create(shell, output); + + shell->output_created_listener.notify = kiosk_shell_handle_output_created; + wl_signal_add(&ec->output_created_signal, &shell->output_created_listener); + + shell->output_resized_listener.notify = kiosk_shell_handle_output_resized; + wl_signal_add(&ec->output_resized_signal, &shell->output_resized_listener); + + shell->output_moved_listener.notify = kiosk_shell_handle_output_moved; + wl_signal_add(&ec->output_moved_signal, &shell->output_moved_listener); + + kiosk_shell_add_bindings(shell); + + return 0; +} diff --git a/kiosk-shell/kiosk-shell.h b/kiosk-shell/kiosk-shell.h new file mode 100644 index 000000000..09f5a777c --- /dev/null +++ b/kiosk-shell/kiosk-shell.h @@ -0,0 +1,91 @@ +/* + * Copyright 2020 Collabora, Ltd. + * + * 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. + */ + +#ifndef WESTON_KIOSK_SHELL_H +#define WESTON_KIOSK_SHELL_H + +#include +#include + +struct kiosk_shell { + struct weston_compositor *compositor; + struct weston_desktop *desktop; + + struct wl_listener destroy_listener; + struct wl_listener output_created_listener; + struct wl_listener output_resized_listener; + struct wl_listener output_moved_listener; + struct wl_listener seat_created_listener; + + struct weston_layer background_layer; + struct weston_layer normal_layer; + + struct wl_list output_list; +}; + +struct kiosk_shell_surface { + struct weston_desktop_surface *desktop_surface; + struct weston_view *view; + + struct kiosk_shell *shell; + + struct weston_output *output; + struct wl_listener output_destroy_listener; + + struct wl_signal destroy_signal; + struct wl_listener parent_destroy_listener; + struct kiosk_shell_surface *parent; + + int focus_count; + + int32_t last_width, last_height; + bool grabbed; + + struct { + bool is_set; + int32_t x; + int32_t y; + } xwayland; +}; + +struct kiosk_shell_seat { + struct weston_seat *seat; + struct wl_listener seat_destroy_listener; + struct weston_surface *focused_surface; + + struct wl_listener caps_changed_listener; + struct wl_listener keyboard_focus_listener; +}; + +struct kiosk_shell_output { + struct weston_output *output; + struct wl_listener output_destroy_listener; + struct weston_view *background_view; + + struct kiosk_shell *shell; + struct wl_list link; + + char *app_ids; +}; + +#endif /* WESTON_KIOSK_SHELL_H */ diff --git a/kiosk-shell/meson.build b/kiosk-shell/meson.build new file mode 100644 index 000000000..e838614ef --- /dev/null +++ b/kiosk-shell/meson.build @@ -0,0 +1,29 @@ +if get_option('shell-kiosk') + srcs_shell_kiosk = [ + 'kiosk-shell.c', + 'kiosk-shell-grab.c', + 'util.c', + weston_desktop_shell_server_protocol_h, + weston_desktop_shell_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + ] + deps_shell_kiosk = [ + dep_libm, + dep_libexec_weston, + dep_libshared, + dep_lib_desktop, + dep_libweston_public, + ] + plugin_shell_kiosk = shared_library( + 'kiosk-shell', + srcs_shell_kiosk, + include_directories: common_inc, + dependencies: deps_shell_kiosk, + name_prefix: '', + install: true, + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' + ) + env_modmap += 'kiosk-shell.so=@0@;'.format(plugin_shell_kiosk.full_path()) +endif diff --git a/kiosk-shell/util.c b/kiosk-shell/util.c new file mode 100644 index 000000000..ad3e0d9b3 --- /dev/null +++ b/kiosk-shell/util.c @@ -0,0 +1,168 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * 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. + */ + +/* Helper functions for kiosk-shell */ + +/* TODO: These functions are useful to many shells, and, in fact, + * much of content in this file was copied from desktop-shell. We should + * create a shared shell utility collection to deduplicate this code. */ + +#include "util.h" +#include "shared/helpers.h" +#include + +struct weston_output * +get_default_output(struct weston_compositor *compositor) +{ + if (wl_list_empty(&compositor->output_list)) + return NULL; + + return container_of(compositor->output_list.next, + struct weston_output, link); +} + +struct weston_output * +get_focused_output(struct weston_compositor *compositor) +{ + struct weston_seat *seat; + struct weston_output *output = NULL; + + wl_list_for_each(seat, &compositor->seat_list, link) { + struct weston_touch *touch = weston_seat_get_touch(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + /* Priority has touch focus, then pointer and + * then keyboard focus. We should probably have + * three for loops and check first for touch, + * then for pointer, etc. but unless somebody has some + * objections, I think this is sufficient. */ + if (touch && touch->focus) + output = touch->focus->output; + else if (pointer && pointer->focus) + output = pointer->focus->output; + else if (keyboard && keyboard->focus) + output = keyboard->focus->output; + + if (output) + break; + } + + return output; +} + +/* This is a copy of the same function from desktop-shell. + * TODO: Fix this function to take into account nested subsurfaces. */ +static void +surface_subsurfaces_boundingbox(struct weston_surface *surface, int32_t *x, + int32_t *y, int32_t *w, int32_t *h) { + pixman_region32_t region; + pixman_box32_t *box; + struct weston_subsurface *subsurface; + + pixman_region32_init_rect(®ion, 0, 0, + surface->width, + surface->height); + + wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) { + pixman_region32_union_rect(®ion, ®ion, + subsurface->position.x, + subsurface->position.y, + subsurface->surface->width, + subsurface->surface->height); + } + + box = pixman_region32_extents(®ion); + if (x) + *x = box->x1; + if (y) + *y = box->y1; + if (w) + *w = box->x2 - box->x1; + if (h) + *h = box->y2 - box->y1; + + pixman_region32_fini(®ion); +} + +void +center_on_output(struct weston_view *view, struct weston_output *output) +{ + int32_t surf_x, surf_y, width, height; + float x, y; + + if (!output) { + weston_view_set_position(view, 0, 0); + return; + } + + surface_subsurfaces_boundingbox(view->surface, &surf_x, &surf_y, &width, &height); + + x = output->x + (output->width - width) / 2 - surf_x / 2; + y = output->y + (output->height - height) / 2 - surf_y / 2; + + weston_view_set_position(view, x, y); +} + +static void +colored_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) +{ +} + +struct weston_view * +create_colored_surface(struct weston_compositor *compositor, + float r, float g, float b, + float x, float y, int w, int h) +{ + struct weston_surface *surface = NULL; + struct weston_view *view; + + surface = weston_surface_create(compositor); + if (surface == NULL) { + weston_log("no memory\n"); + return NULL; + } + view = weston_view_create(surface); + if (surface == NULL) { + weston_log("no memory\n"); + weston_surface_destroy(surface); + return NULL; + } + + surface->committed = colored_surface_committed; + surface->committed_private = NULL; + + weston_surface_set_color(surface, r, g, b, 1.0); + pixman_region32_fini(&surface->opaque); + pixman_region32_init_rect(&surface->opaque, 0, 0, w, h); + pixman_region32_fini(&surface->input); + pixman_region32_init_rect(&surface->input, 0, 0, w, h); + + weston_surface_set_size(surface, w, h); + weston_view_set_position(view, x, y); + + return view; +} diff --git a/kiosk-shell/util.h b/kiosk-shell/util.h new file mode 100644 index 000000000..e60aa3b5d --- /dev/null +++ b/kiosk-shell/util.h @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,2020 Collabora, Ltd. + * + * 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. + */ + +/* Helper functions adapted from desktop-shell */ + +#include + +struct weston_compositor; +struct weston_layer; +struct weston_output; +struct weston_surface; +struct weston_view; + +struct weston_output * +get_default_output(struct weston_compositor *compositor); + +struct weston_output * +get_focused_output(struct weston_compositor *compositor); + +void +center_on_output(struct weston_view *view, struct weston_output *output); + +struct weston_view * +create_colored_surface(struct weston_compositor *compositor, + float r, float g, float b, + float x, float y, int w, int h); diff --git a/man/weston.ini.man b/man/weston.ini.man index bce5628b6..e1364c635 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -554,6 +554,12 @@ content-protection can actually be realized, only if the hardware (source and sink) support HDCP, and the backend has the implementation of content-protection protocol. Currently, HDCP is supported by drm-backend. .RE +.TP 7 +.BI "app-ids=" app-id[,app_id]* +A comma separated list of the IDs of applications to place on this output. +These IDs should match the application IDs as set with the xdg_shell.set_app_id +request. Currently, this option is supported by kiosk-shell. +.RE .SH "INPUT-METHOD SECTION" .TP 7 .BI "path=" "@weston_libexecdir@/weston-keyboard" diff --git a/meson.build b/meson.build index 090fc7136..862f54cf7 100644 --- a/meson.build +++ b/meson.build @@ -158,6 +158,7 @@ subdir('compositor') subdir('desktop-shell') subdir('fullscreen-shell') subdir('ivi-shell') +subdir('kiosk-shell') subdir('remoting') subdir('pipewire') subdir('clients') diff --git a/meson_options.txt b/meson_options.txt index 9f6b08c31..239bd2da2 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -124,6 +124,12 @@ option( value: true, description: 'Weston shell UI: IVI (automotive)' ) +option( + 'shell-kiosk', + type: 'boolean', + value: true, + description: 'Weston shell UI: kiosk (desktop apps)' +) option( 'desktop-shell-client-default', From 905ddbb3b0708ec5d0277b698a357f84c840d9d9 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 30 Jul 2020 22:56:04 +0200 Subject: [PATCH 1415/1642] build: bump to version 9.0.91 for the alpha release --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 862f54cf7..adc62a4a6 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('weston', 'c', - version: '8.0.90', + version: '8.0.91', default_options: [ 'warning_level=3', 'c_std=gnu99', From 3ac911f69b9b65ad8d6bde9643826450b9eebd27 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Thu, 6 Aug 2020 10:07:41 +0200 Subject: [PATCH 1416/1642] drm: remove duplicate function declarations These functions are all declared twice in the same file. Remove on of the two declarations. Signed-off-by: Michael Olbrich --- libweston/backend-drm/drm-internal.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 49ce7ce00..03c6f40b7 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -668,16 +668,6 @@ drm_output_update_complete(struct drm_output *output, uint32_t flags, int on_drm_input(int fd, uint32_t mask, void *data); -struct drm_plane_state * -drm_output_state_get_existing_plane(struct drm_output_state *state_output, - struct drm_plane *plane); -void -drm_plane_state_free(struct drm_plane_state *state, bool force); -void -drm_output_state_free(struct drm_output_state *state); -void -drm_pending_state_free(struct drm_pending_state *pending_state); - struct drm_fb * drm_fb_ref(struct drm_fb *fb); void From b7e5f10bf47736d152360942dae7acfc4119519c Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 11 Aug 2020 16:33:35 +0200 Subject: [PATCH 1417/1642] compositor: use weston_view_is_opaque() to check for opacity in debug_scene_view_print() Currently the debug output for 'drm-backend' can be confusing. In the output of debug_scene_view_print() views may be listed as 'not opaque' but later, during plane assignment, other views underneath such a view is reported as 'occluded on our output'. This happens because weston_view_is_opaque() has some extra checks to determine if a view is fully opaque, such as 'is_opaque' provided by the renderer for formats that have no alpha channel. Use weston_view_is_opaque() in debug_scene_view_print() as well to get more accurate results. Signed-off-by: Michael Olbrich --- libweston/compositor.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 283f3f68c..a864d5a98 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -7145,8 +7145,7 @@ debug_scene_view_print(FILE *fp, struct weston_view *view, int view_idx) box->x1, box->y1, box->x2, box->y2); box = pixman_region32_extents(&view->transform.opaque); - if (pixman_region32_equal(&view->transform.opaque, - &view->transform.boundingbox)) { + if (weston_view_is_opaque(view, &view->transform.boundingbox)) { fprintf(fp, "\t\t[fully opaque]\n"); } else if (!pixman_region32_not_empty(&view->transform.opaque)) { fprintf(fp, "\t\t[not opaque]\n"); From c5ea495f7adec6137c23e094b498816569b0d20f Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 11 Aug 2020 12:42:35 +0200 Subject: [PATCH 1418/1642] compositor: ignore views on other outputs during compositor_accumulate_damage() compositor_accumulate_damage() is called for each output during repaint. The DRM backend will only set keep_buffer for the surfaces that are visible on the current output. So a buffer_ref is released that may still be needed. When the output that shows the surface is repainted, the buffer_ref is gone and the surface cannot be put on a plane. Ignore all surfaces that are not visible on the current output to avoid this. Signed-off-by: Michael Olbrich --- libweston/compositor.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index a864d5a98..57b220302 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2491,8 +2491,9 @@ view_accumulate_damage(struct weston_view *view, } static void -compositor_accumulate_damage(struct weston_compositor *ec) +output_accumulate_damage(struct weston_output *output) { + struct weston_compositor *ec = output->compositor; struct weston_plane *plane; struct weston_view *ev; pixman_region32_t opaque, clip; @@ -2521,6 +2522,9 @@ compositor_accumulate_damage(struct weston_compositor *ec) ev->surface->touched = false; wl_list_for_each(ev, &ec->view_list, link) { + /* Ignore views not visible on the current output */ + if (!(ev->output_mask & (1u << output->id))) + continue; if (ev->surface->touched) continue; ev->surface->touched = true; @@ -2763,7 +2767,7 @@ weston_output_repaint(struct weston_output *output, void *repaint_data) } } - compositor_accumulate_damage(ec); + output_accumulate_damage(output); pixman_region32_init(&output_damage); pixman_region32_intersect(&output_damage, From ad41ad968afbab4c56cb81becf79bb47d575d388 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 14 Jul 2020 16:14:50 +0200 Subject: [PATCH 1419/1642] gl-renderer: remove incorrect assertion The refcount is not zero if the corresponding buffer is attached to multiple surfaces. Signed-off-by: Michael Olbrich --- libweston/renderer-gl/gl-renderer.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 937ebe865..d5bfe8303 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -2534,7 +2534,6 @@ gl_renderer_attach_dmabuf(struct weston_surface *surface, struct gl_surface_state *gs = get_surface_state(surface); struct dmabuf_image *image; int i; - int ret; if (!gr->has_dmabuf_import) { linux_dmabuf_buffer_send_server_error(dmabuf, @@ -2580,10 +2579,8 @@ gl_renderer_attach_dmabuf(struct weston_surface *surface, /* The dmabuf_image should have been created during the import */ assert(image != NULL); - for (i = 0; i < image->num_images; ++i) { - ret = egl_image_unref(image->images[i]); - assert(ret == 0); - } + for (i = 0; i < image->num_images; ++i) + egl_image_unref(image->images[i]); if (!import_known_dmabuf(gr, image)) { linux_dmabuf_buffer_send_server_error(dmabuf, "EGL dmabuf import failed"); From 06d51cc4cbec4a9cb595d2560279feee9952f7c7 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 13 Aug 2020 21:09:40 +0200 Subject: [PATCH 1420/1642] build: bump to version 8.0.92 for the beta release --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index adc62a4a6..7d4bd23c5 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('weston', 'c', - version: '8.0.91', + version: '8.0.92', default_options: [ 'warning_level=3', 'c_std=gnu99', From c1e7151eb6c4cec1448f514be9d66b529c23e42a Mon Sep 17 00:00:00 2001 From: ganjing Date: Tue, 28 Jul 2020 15:28:45 +0800 Subject: [PATCH 1421/1642] desktop-shell: check memory allocation in switcher_binding after calling malloc() , be sure to determine whether the allocating for memory space is successful Signed-off-by: ganjing --- desktop-shell/shell.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index ef0696ed5..40cba183a 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -4630,6 +4630,9 @@ switcher_binding(struct weston_keyboard *keyboard, const struct timespec *time, struct switcher *switcher; switcher = malloc(sizeof *switcher); + if (!switcher) + return; + switcher->shell = shell; switcher->current = NULL; switcher->listener.notify = switcher_handle_view_destroy; From 85382d394af9a631ec5c5c4341eb1ebe554fbb8e Mon Sep 17 00:00:00 2001 From: Olivier Fourdan Date: Mon, 3 Aug 2020 13:53:50 +0200 Subject: [PATCH 1422/1642] clients: deprecate weston-info weston-info is now deprecated in favor of wayland-info which is part of wayland-utils. Add a note to weston-info to inform users that weston-info is deprecated and will be removed soon. Signed-off-by: Olivier Fourdan --- README.md | 2 +- clients/weston-info.c | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f1e58bf1..8947443d3 100644 --- a/README.md +++ b/README.md @@ -342,7 +342,7 @@ would be roughly like this: + desktop shell + ivi-shell + fullscreen shell - + weston-info, weston-terminal, etc. we install by default + + weston-info (deprecated), weston-terminal, etc. we install by default + screen-share - weston demos (not parallel-installable) diff --git a/clients/weston-info.c b/clients/weston-info.c index a26f0bf0a..977252732 100644 --- a/clients/weston-info.c +++ b/clients/weston-info.c @@ -1861,6 +1861,11 @@ main(int argc, char **argv) return -1; } + fprintf(stderr, "\n"); + fprintf(stderr, "*** Please use wayland-info instead\n"); + fprintf(stderr, "*** weston-info is deprecated and will be removed in a future version\n"); + fprintf(stderr, "\n"); + info.tablet_info = NULL; info.xdg_output_manager_v1_info = NULL; wl_list_init(&info.infos); From 9975134593e497ad587ca913e2d6496c6357a347 Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Mon, 18 May 2020 15:22:49 +0300 Subject: [PATCH 1423/1642] drm: Introduce drm_plane_reset_state() helper function Introduce a helper function to reset the current state of a drm_plane. Signed-off-by: Alexandros Frantzis --- libweston/backend-drm/drm-gbm.c | 5 +---- libweston/backend-drm/drm-internal.h | 2 ++ libweston/backend-drm/drm.c | 5 +---- libweston/backend-drm/state-helpers.c | 15 +++++++++++++++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/libweston/backend-drm/drm-gbm.c b/libweston/backend-drm/drm-gbm.c index 4b83e9949..30609e3ea 100644 --- a/libweston/backend-drm/drm-gbm.c +++ b/libweston/backend-drm/drm-gbm.c @@ -260,10 +260,7 @@ drm_output_fini_egl(struct drm_output *output) if (!b->shutting_down && output->scanout_plane->state_cur->fb && output->scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE) { - drm_plane_state_free(output->scanout_plane->state_cur, true); - output->scanout_plane->state_cur = - drm_plane_state_alloc(NULL, output->scanout_plane); - output->scanout_plane->state_cur->complete = true; + drm_plane_reset_state(output->scanout_plane); } gl_renderer->output_destroy(&output->base); diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 03c6f40b7..7ece756dc 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -748,6 +748,8 @@ drm_plane_state_put_back(struct drm_plane_state *state); bool drm_plane_state_coords_for_view(struct drm_plane_state *state, struct weston_view *ev, uint64_t zpos); +void +drm_plane_reset_state(struct drm_plane *plane); void drm_assign_planes(struct weston_output *output_base, void *repaint_data); diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 4e5f39cf6..7c1c0b402 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -1272,10 +1272,7 @@ drm_output_fini_pixman(struct drm_output *output) if (!b->shutting_down && output->scanout_plane->state_cur->fb && output->scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB) { - drm_plane_state_free(output->scanout_plane->state_cur, true); - output->scanout_plane->state_cur = - drm_plane_state_alloc(NULL, output->scanout_plane); - output->scanout_plane->state_cur->complete = true; + drm_plane_reset_state(output->scanout_plane); } pixman_renderer_output_destroy(&output->base); diff --git a/libweston/backend-drm/state-helpers.c b/libweston/backend-drm/state-helpers.c index d10549933..0ee663cb6 100644 --- a/libweston/backend-drm/state-helpers.c +++ b/libweston/backend-drm/state-helpers.c @@ -261,6 +261,21 @@ drm_plane_state_coords_for_view(struct drm_plane_state *state, return true; } +/** + * Reset the current state of a DRM plane + * + * The current state will be freed and replaced by a pristine state. + * + * @param plane The plane to reset the current state of + */ +void +drm_plane_reset_state(struct drm_plane *plane) +{ + drm_plane_state_free(plane->state_cur, true); + plane->state_cur = drm_plane_state_alloc(NULL, plane); + plane->state_cur->complete = true; +} + /** * Return a plane state from a drm_output_state. */ From 53a71cb186ff4f275d3c827abbfdbfda3670835f Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Mon, 18 May 2020 15:26:01 +0300 Subject: [PATCH 1424/1642] drm: Reset associated universal plane states when finalizing a crtc When dissociating a universal plane from a crtc, we currently don't reset the current state of the plane (plane->state_cur). When attempting to use this plane in the future, we can run into invalid memory accesses due to left over associations with potentially freed drm backend objects. This commit resets the state of the scanout and cursor universal planes associated with a crtc. The following scenario exhibits the problem: 1. Start a (fullscreen) client that is suitable for and assigned to the scanout plane. The plane's state_cur->output value is set. 2. Unplug the monitor: the scanout plane is "released" but still maintains the state_cur->output association. 3. Replug the monitor: the plane is deemed unavailable due to an existing, albeit invalid, state_cur->output value. Note the memory errors trying to access the drm_output which was freed at step (2). Signed-off-by: Alexandros Frantzis --- libweston/backend-drm/drm.c | 40 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 7c1c0b402..83993da5a 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -1679,23 +1679,29 @@ drm_output_fini_crtc(struct drm_output *output) struct drm_backend *b = to_drm_backend(output->base.compositor); uint32_t *unused; - if (!b->universal_planes && !b->shutting_down) { - /* With universal planes, the 'special' planes are allocated at - * startup, freed at shutdown, and live on the plane list in - * between. We want the planes to continue to exist and be freed - * up for other outputs. - * - * Without universal planes, our special planes are - * pseudo-planes allocated at output creation, freed at output - * destruction, and not usable by other outputs. - * - * On the other hand, if the compositor is already shutting down, - * the plane has already been destroyed. - */ - if (output->cursor_plane) - drm_plane_destroy(output->cursor_plane); - if (output->scanout_plane) - drm_plane_destroy(output->scanout_plane); + /* If the compositor is already shutting down, the planes have already + * been destroyed. */ + if (!b->shutting_down) { + if (!b->universal_planes) { + /* Without universal planes, our special planes are + * pseudo-planes allocated at output creation, freed at + * output destruction, and not usable by other outputs. + */ + if (output->cursor_plane) + drm_plane_destroy(output->cursor_plane); + if (output->scanout_plane) + drm_plane_destroy(output->scanout_plane); + } else { + /* With universal planes, the 'special' planes are + * allocated at startup, freed at shutdown, and live on + * the plane list in between. We want the planes to + * continue to exist and be freed up for other outputs. + */ + if (output->cursor_plane) + drm_plane_reset_state(output->cursor_plane); + if (output->scanout_plane) + drm_plane_reset_state(output->scanout_plane); + } } drm_property_info_free(output->props_crtc, WDRM_CRTC__COUNT); From 3097acc702465e54fea64dfd0eedfe522e38a212 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 14 Jul 2020 11:18:54 +0200 Subject: [PATCH 1425/1642] backend-drm: reorder plane checks to avoid unnecessary rendering If a surface is not visible, then is does not matter if the view is on multiple outputs. It will be skipped anyways when the output is rendered. So check first if the surface is acually visible on the output before doing any checks that might force rendering. This avoids unnecessary rendering. Signed-off-by: Michael Olbrich --- libweston/backend-drm/state-propose.c | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/libweston/backend-drm/state-propose.c b/libweston/backend-drm/state-propose.c index 767c34f46..b403e304c 100644 --- a/libweston/backend-drm/state-propose.c +++ b/libweston/backend-drm/state-propose.c @@ -862,20 +862,6 @@ drm_output_propose_state(struct weston_output *output_base, continue; } - /* We only assign planes to views which are exclusively present - * on our output. */ - if (ev->output_mask != (1u << output->base.id)) { - drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " - "(on multiple outputs)\n", ev); - force_renderer = true; - } - - if (!weston_view_has_valid_buffer(ev)) { - drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " - "(no buffer available)\n", ev); - force_renderer = true; - } - /* Ignore views we know to be totally occluded. */ pixman_region32_init(&clipped_view); pixman_region32_intersect(&clipped_view, @@ -897,6 +883,20 @@ drm_output_propose_state(struct weston_output *output_base, continue; } + /* We only assign planes to views which are exclusively present + * on our output. */ + if (ev->output_mask != (1u << output->base.id)) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(on multiple outputs)\n", ev); + force_renderer = true; + } + + if (!weston_view_has_valid_buffer(ev)) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(no buffer available)\n", ev); + force_renderer = true; + } + /* Since we process views from top to bottom, we know that if * the view intersects the calculated renderer region, it must * be part of, or occluded by, it, and cannot go on a plane. */ From 4a378afa3beac4f4341f5294927107f64123008a Mon Sep 17 00:00:00 2001 From: Rajendraprasad K J Date: Wed, 29 Jul 2020 16:50:59 +0200 Subject: [PATCH 1426/1642] ivi-shell: Avoid unnecessary scaling of the view transformation matrix The opaque region of a weston view is updated only if the alpha value is 1 and the transform matrix is of type WESTON_MATRIX_TRANSFORM_TRANSLATE. While using ivi-shell, opaque region is never updated, as we are performing scaling operations to the view transform matrix, even when the scaling factor is 1 and thereby changing the type to WESTON_MATRIX_TRANSFORM_SCALE. Perform scaling of the view transformation matrix only when the scaling factor is non-zero. Signed-off-by: Rajendraprasad K J --- ivi-shell/ivi-layout.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 7cec79d7d..b2f8323bd 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -345,9 +345,13 @@ calc_transformation_matrix(struct ivi_rectangle *source_rect, source_center_y = source_rect->y + source_rect->height * 0.5f; weston_matrix_translate(m, -source_center_x, -source_center_y, 0.0f); - scale_x = (float) dest_rect->width / (float) source_rect->width; - scale_y = (float) dest_rect->height / (float) source_rect->height; - weston_matrix_scale(m, scale_x, scale_y, 1.0f); + if ((dest_rect->width != source_rect->width) || + (dest_rect->height != source_rect->height)) + { + scale_x = (float) dest_rect->width / (float) source_rect->width; + scale_y = (float) dest_rect->height / (float) source_rect->height; + weston_matrix_scale(m, scale_x, scale_y, 1.0f); + } translate_x = dest_rect->width * 0.5f + dest_rect->x; translate_y = dest_rect->height * 0.5f + dest_rect->y; From 27fb564a1915fd594d037119955fc39456496820 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Wed, 15 Jul 2020 11:01:04 +0200 Subject: [PATCH 1427/1642] backend-drm: build DRM virtual support when the pipewire plugin is enabled The pipewire plugin uses this API as well, not just the remoting plugin. So enable it if either is enabled. And disable pipewire in the no-gl-renderer CI build. The virtual outputs don't work without it. Signed-off-by: Michael Olbrich --- .gitlab-ci.yml | 1 + libweston/backend-drm/meson.build | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 02c38b964..9c24dd80f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -92,6 +92,7 @@ build-native-meson-no-gl-renderer: -Dsimple-clients=damage,im,shm,touch,dmabuf-v4l -Drenderer-gl=false -Dremoting=false + -Dpipewire=false -Dwerror=true extends: .build-native-meson diff --git a/libweston/backend-drm/meson.build b/libweston/backend-drm/meson.build index a7f629652..8faedde97 100644 --- a/libweston/backend-drm/meson.build +++ b/libweston/backend-drm/meson.build @@ -71,7 +71,7 @@ if get_option('backend-drm-screencast-vaapi') config_h.set('BUILD_VAAPI_RECORDER', '1') endif -if get_option('remoting') +if get_option('remoting') or get_option('pipewire') if not get_option('renderer-gl') warning('DRM virtual requires renderer-gl.') endif From 43ebb7e25aa05955c2eb5c55db615c1839a26aeb Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Wed, 15 Jul 2020 11:54:47 +0200 Subject: [PATCH 1428/1642] backend-drm: the GL renderer is a hard requirement for DRM virtual outputs Building fails without it. So don't just warn about it but fail immediately. Signed-off-by: Michael Olbrich --- libweston/backend-drm/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/backend-drm/meson.build b/libweston/backend-drm/meson.build index 8faedde97..484c27028 100644 --- a/libweston/backend-drm/meson.build +++ b/libweston/backend-drm/meson.build @@ -73,7 +73,7 @@ endif if get_option('remoting') or get_option('pipewire') if not get_option('renderer-gl') - warning('DRM virtual requires renderer-gl.') + error('DRM virtual requires renderer-gl.') endif srcs_drm += 'drm-virtual.c' config_h.set('BUILD_DRM_VIRTUAL', '1') From 465ab2cd928eea47d8aae6d2e0acb21a87f62009 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 17 Jun 2020 23:36:44 +0200 Subject: [PATCH 1429/1642] backend-drm: allow to disable GBM modifiers Allow to disable GBM modifiers at runtime using the environment variable WESTON_DISABLE_GBM_MODIFIERS. This can be useful for debugging or when modifiers cause issues, e.g. in case modifiers use higher memory bandwidth and hence impose a lower resolution limit as it is the case with Intel Kaby Lake graphics. Related to: https://gitlab.freedesktop.org/wayland/weston/-/issues/404 Signed-off-by: Stefan Agner --- libweston/backend-drm/drm-internal.h | 3 ++- libweston/backend-drm/drm.c | 3 ++- libweston/backend-drm/kms.c | 18 ++++++++++++------ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 7ece756dc..06f952f22 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -634,7 +634,8 @@ drm_property_get_range_values(struct drm_property_info *info, const drmModeObjectProperties *props); int drm_plane_populate_formats(struct drm_plane *plane, const drmModePlane *kplane, - const drmModeObjectProperties *props); + const drmModeObjectProperties *props, + const bool use_modifiers); void drm_property_info_free(struct drm_property_info *info, int num_props); diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 83993da5a..980a12da6 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -808,7 +808,8 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane, plane->zpos_max = DRM_PLANE_ZPOS_INVALID_PLANE; } - if (drm_plane_populate_formats(plane, kplane, props) < 0) { + if (drm_plane_populate_formats(plane, kplane, props, + b->fb_modifiers) < 0) { drmModeFreeObjectProperties(props); goto err; } diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index f5215f20d..c91e38103 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -434,7 +434,8 @@ modifiers_ptr(struct drm_format_modifier_blob *blob) */ int drm_plane_populate_formats(struct drm_plane *plane, const drmModePlane *kplane, - const drmModeObjectProperties *props) + const drmModeObjectProperties *props, + const bool use_modifiers) { unsigned i; drmModePropertyBlobRes *blob; @@ -443,6 +444,9 @@ drm_plane_populate_formats(struct drm_plane *plane, const drmModePlane *kplane, uint32_t *blob_formats; uint32_t blob_id; + if (!use_modifiers) + goto fallback; + blob_id = drm_property_get_value(&plane->props[WDRM_PLANE_IN_FORMATS], props, 0); @@ -1474,11 +1478,13 @@ init_kms_caps(struct drm_backend *b) weston_log("DRM: %s atomic modesetting\n", b->atomic_modeset ? "supports" : "does not support"); - ret = drmGetCap(b->drm.fd, DRM_CAP_ADDFB2_MODIFIERS, &cap); - if (ret == 0) - b->fb_modifiers = cap; - else - b->fb_modifiers = 0; + if (!getenv("WESTON_DISABLE_GBM_MODIFIERS")) { + ret = drmGetCap(b->drm.fd, DRM_CAP_ADDFB2_MODIFIERS, &cap); + if (ret == 0) + b->fb_modifiers = cap; + } + weston_log("DRM: %s GBM modifiers\n", + b->fb_modifiers ? "supports" : "does not support"); /* * KMS support for hardware planes cannot properly synchronize From 2592d6591efba9fb5d8f76fdbf5add02568d1eb8 Mon Sep 17 00:00:00 2001 From: Andreas Heynig Date: Wed, 22 Jan 2020 19:36:57 +0100 Subject: [PATCH 1430/1642] libweston/launcher-direct.c: do not fail if already in graphics mode In case of a crash tty remains in graphic mode. This change allows to restart weston without taking care of the actual tty mode. Signed-off-by: ahe --- libweston/launcher-direct.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c index 382ca49f7..c20d70f29 100644 --- a/libweston/launcher-direct.c +++ b/libweston/launcher-direct.c @@ -160,7 +160,6 @@ setup_tty(struct launcher_direct *launcher, int tty) if (kd_mode != KD_TEXT) { weston_log("%s is already in graphics mode, " "is another display server running?\n", tty_device); - goto err_close; } ioctl(launcher->tty, VT_ACTIVATE, minor(buf.st_rdev)); From 5130a8c21a9deea54e8f7c96a3a5049e2d60a210 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Thu, 30 Jul 2020 14:47:32 +0300 Subject: [PATCH 1431/1642] backend-drm: Correctly tear down the DRM backend It seem that we skipped to put back in TEXT mode the tty, in case a DRM device node wasn't present at that time, or it isn't present at all. This orders the destroy part correctly as to handle that case as well. As a side effect, as the tty will still be set to GRAPHICS mode we will require a manual change of the tty number, which might be not possible on all systems. Properly putting back the tty to TEXT mode should avoid that, and allows to re-use the same tty no in case the DRM device has been created at a later point in time. Signed-off-by: Marius Vlad --- libweston/backend-drm/drm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 980a12da6..1cf61a33c 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -3031,10 +3031,10 @@ drm_backend_create(struct weston_compositor *compositor, destroy_sprites(b); err_udev_dev: udev_device_unref(drm_device); -err_launcher: - weston_launcher_destroy(compositor->launcher); err_udev: udev_unref(b->udev); +err_launcher: + weston_launcher_destroy(compositor->launcher); err_compositor: weston_compositor_shutdown(compositor); free(b); From d70e712c2f63c5af7483ea5053e57bfa83d7d7e7 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Thu, 6 Aug 2020 09:57:54 +0200 Subject: [PATCH 1432/1642] drm: always check the repaint_status in update_complete Initially finish_frame() was never called in drm_output_update_complete() for 'dpms_off_pending = true'. This is wrong for repaint_status == REPAINT_AWAITING_COMPLETION and that was fixed in 68d49d772cfba6c53033cb009b0f490fd38f24ad ("compositor-drm: run finish_frame when dpms is turned off in update_complete"). However finish_frame() may now be called for repaint_status != REPAINT_AWAITING_COMPLETION, which is not allowed and results in a failed assertion. Fix this by checking dpms and repaint_status unconditionally. Signed-off-by: Michael Olbrich --- libweston/backend-drm/drm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 1cf61a33c..2780f3bd7 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -306,8 +306,9 @@ drm_output_update_complete(struct drm_output *output, uint32_t flags, output->dpms_off_pending = false; drm_output_get_disable_state(pending, output); drm_pending_state_apply_sync(pending); - } else if (output->state_cur->dpms == WESTON_DPMS_OFF && - output->base.repaint_status != REPAINT_AWAITING_COMPLETION) { + } + if (output->state_cur->dpms == WESTON_DPMS_OFF && + output->base.repaint_status != REPAINT_AWAITING_COMPLETION) { /* DPMS can happen to us either in the middle of a repaint * cycle (when we have painted fresh content, only to throw it * away for DPMS off), or at any other random point. If the From a24a326bb19ec53d7405fc2ed474de99a111e289 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Wed, 19 Aug 2020 08:43:15 +0200 Subject: [PATCH 1433/1642] pipewire: implement DPMS Pipewire doesn't need to wait for any hardware. The finish_frame() callback is just artifically delayed to generate the desired framerate. So when the DPMS level changes, we can just call finish_frame() immediately if necessary and cancel the timer. Signed-off-by: Michael Olbrich --- pipewire/pipewire-plugin.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pipewire/pipewire-plugin.c b/pipewire/pipewire-plugin.c index 9c75a7833..6f892574f 100644 --- a/pipewire/pipewire-plugin.c +++ b/pipewire/pipewire-plugin.c @@ -88,6 +88,7 @@ struct pipewire_output { struct wl_event_source *finish_frame_timer; struct wl_list link; bool submitted_frame; + enum dpms_enum dpms; }; struct pipewire_frame_data { @@ -317,7 +318,11 @@ pipewire_output_finish_frame_handler(void *data) api->finish_frame(output->output, &now, 0); } - pipewire_output_timer_update(output); + if (output->dpms == WESTON_DPMS_ON) + pipewire_output_timer_update(output); + else + wl_event_source_timer_update(output->finish_frame_timer, 0); + return 0; } @@ -355,6 +360,18 @@ pipewire_output_start_repaint_loop(struct weston_output *base_output) return 0; } +static void +pipewire_set_dpms(struct weston_output *base_output, enum dpms_enum level) +{ + struct pipewire_output *output = lookup_pipewire_output(base_output); + + if (output->dpms == level) + return; + + output->dpms = level; + pipewire_output_finish_frame_handler(output); +} + static int pipewire_output_connect(struct pipewire_output *output) { @@ -420,12 +437,14 @@ pipewire_output_enable(struct weston_output *base_output) output->saved_start_repaint_loop = base_output->start_repaint_loop; base_output->start_repaint_loop = pipewire_output_start_repaint_loop; + base_output->set_dpms = pipewire_set_dpms; loop = wl_display_get_event_loop(c->wl_display); output->finish_frame_timer = wl_event_loop_add_timer(loop, pipewire_output_finish_frame_handler, output); + output->dpms = WESTON_DPMS_ON; return 0; } From 67b382ccdc42ceb2e52e4de59b4de76e7a68aa09 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 21 Aug 2020 13:57:54 +0300 Subject: [PATCH 1434/1642] compositor: Avoid using weston_log() in weston_view_is_opaque() As from commit b7e5f10bf47, weston_view_is_opaque() is called from debug_scene_graph_cb(), which on its own represents a (different) scope. By default, we already have a subscriber for the 'log' scope, which will cause a harmless, yet spurious, message. Signed-off-by: Marius Vlad --- libweston/compositor.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libweston/compositor.c b/libweston/compositor.c index 57b220302..7fd4cc1ef 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -1888,10 +1888,8 @@ weston_view_is_opaque(struct weston_view *ev, pixman_region32_t *region) if (ev->surface->is_opaque) return true; - if (ev->transform.dirty) { - weston_log("%s: transform dirty", __func__); + if (ev->transform.dirty) return false; - } pixman_region32_init(&r); pixman_region32_subtract(&r, region, &ev->transform.opaque); From fd874ff53379ccdc6584fb4412e69889f94e186f Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Mon, 30 Dec 2019 19:11:51 -0300 Subject: [PATCH 1435/1642] exposay: move constant value calculation out of loop In exposay_layout(), int pad is being calculated within the loop. This is unnecessary, as its value is constant. Move it out of the loop. Signed-off-by: Leandro Ribeiro --- desktop-shell/exposay.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c index 9fd438342..b1a8d3473 100644 --- a/desktop-shell/exposay.c +++ b/desktop-shell/exposay.c @@ -219,6 +219,7 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) struct weston_view *view; struct exposay_surface *esurface, *highlight = NULL; int w, h; + int pad; int i; int last_row_removed = 0; @@ -275,11 +276,10 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) if (eoutput->surface_size > (output->height / 2)) eoutput->surface_size = output->height / 2; + pad = eoutput->surface_size + eoutput->padding_inner; + i = 0; wl_list_for_each(view, &workspace->layer.view_list.link, layer_link.link) { - int pad; - - pad = eoutput->surface_size + eoutput->padding_inner; if (!get_shell_surface(view->surface)) continue; From 0c59e92f0c848214100c12f1268a1287d6bf22b2 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 24 Jan 2020 17:53:44 -0300 Subject: [PATCH 1436/1642] desktop-shell: make get_output_work_area() global get_output_work_area() can be used by exposay to know the free space where it can render its surfaces, what avoids overlapping the panel. Currently this function is declared as static in desktop-shell/shell.c, so it cannot be used by exposay. Remove static from get_output_work_area() and add it to shell.h so it can be used by exposay as well. Signed-off-by: Leandro Ribeiro --- desktop-shell/shell.c | 2 +- desktop-shell/shell.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 40cba183a..c1c126e81 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -334,7 +334,7 @@ get_output_panel_size(struct desktop_shell *shell, /* the correct view wasn't found */ } -static void +void get_output_work_area(struct desktop_shell *shell, struct weston_output *output, pixman_rectangle32_t *area) diff --git a/desktop-shell/shell.h b/desktop-shell/shell.h index c82fd28c7..6f72b3576 100644 --- a/desktop-shell/shell.h +++ b/desktop-shell/shell.h @@ -235,6 +235,11 @@ get_shell_surface(struct weston_surface *surface); struct workspace * get_current_workspace(struct desktop_shell *shell); +void +get_output_work_area(struct desktop_shell *shell, + struct weston_output *output, + pixman_rectangle32_t *area); + void lower_fullscreen_layer(struct desktop_shell *shell, struct weston_output *lowering_output); From b68454e893d4ffbe7a0789983e787a4f31eeaeef Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 24 Jan 2020 18:02:27 -0300 Subject: [PATCH 1437/1642] exposay: do not account panel size to compute exposay area Commit "desktop-shell: make get_output_work_area() global" allowed the usage of get_output_work_area() in exposay. This is necessary to avoid overlapping the panel when rendering exposay's surfaces. Use get_output_work_area() to not take into account the panel size, instead of considering that the whole screen is available to render exposay's surfaces. Signed-off-by: Leandro Ribeiro --- desktop-shell/exposay.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c index b1a8d3473..7594301a8 100644 --- a/desktop-shell/exposay.c +++ b/desktop-shell/exposay.c @@ -218,6 +218,7 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) struct exposay_output *eoutput = &shell_output->eoutput; struct weston_view *view; struct exposay_surface *esurface, *highlight = NULL; + pixman_rectangle32_t exposay_area; int w, h; int pad; int i; @@ -241,6 +242,10 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) return EXPOSAY_LAYOUT_OVERVIEW; } + /* Get exposay area and position, taking into account + * the shell panel position and size */ + get_output_work_area(shell, output, &exposay_area); + /* Lay the grid out as square as possible, losing surfaces from the * bottom row if required. Start with fixed padding of a 10% margin * around the outside and 80px internal padding between surfaces, and @@ -258,23 +263,23 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) eoutput->grid_size++; last_row_removed = pow(eoutput->grid_size, 2) - eoutput->num_surfaces; - eoutput->hpadding_outer = (output->width / 10); - eoutput->vpadding_outer = (output->height / 10); + eoutput->hpadding_outer = (exposay_area.width / 10); + eoutput->vpadding_outer = (exposay_area.height / 10); eoutput->padding_inner = 80; - w = output->width - (eoutput->hpadding_outer * 2); + w = exposay_area.width - (eoutput->hpadding_outer * 2); w -= eoutput->padding_inner * (eoutput->grid_size - 1); w /= eoutput->grid_size; - h = output->height - (eoutput->vpadding_outer * 2); + h = exposay_area.height - (eoutput->vpadding_outer * 2); h -= eoutput->padding_inner * (eoutput->grid_size - 1); h /= eoutput->grid_size; eoutput->surface_size = (w < h) ? w : h; - if (eoutput->surface_size > (output->width / 2)) - eoutput->surface_size = output->width / 2; - if (eoutput->surface_size > (output->height / 2)) - eoutput->surface_size = output->height / 2; + if ((uint32_t)eoutput->surface_size > (exposay_area.width / 2)) + eoutput->surface_size = exposay_area.width / 2; + if ((uint32_t)eoutput->surface_size > (exposay_area.height / 2)) + eoutput->surface_size = exposay_area.height / 2; pad = eoutput->surface_size + eoutput->padding_inner; @@ -301,9 +306,9 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) esurface->row = i / eoutput->grid_size; esurface->column = i % eoutput->grid_size; - esurface->x = output->x + eoutput->hpadding_outer; + esurface->x = exposay_area.x + eoutput->hpadding_outer; esurface->x += pad * esurface->column; - esurface->y = output->y + eoutput->vpadding_outer; + esurface->y = exposay_area.y + eoutput->vpadding_outer; esurface->y += pad * esurface->row; if (esurface->row == eoutput->grid_size - 1) From 33e29d88cc109555ee8286ae06b1580ef0f42c92 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 24 Jan 2020 18:10:59 -0300 Subject: [PATCH 1438/1642] exposay: make inner border dependent of exposay's surfaces size We've been using an inner border of fixed size (80px), but this is dangerous. If you have too many open applications or a small window, the surface size computed will be negative, crashing the exposay: "error: weston_view transformation not invertible". Also, it creates a lot of unnecessary space, making the exposay unusable when we have a small window or many applications open. Make inner border to be 10% of surface size and surface size to be 90% of its original size, avoiding the crashes and making it more visually pleasant. Signed-off-by: Leandro Ribeiro --- desktop-shell/exposay.c | 68 +++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c index 7594301a8..e03a3114d 100644 --- a/desktop-shell/exposay.c +++ b/desktop-shell/exposay.c @@ -207,9 +207,42 @@ handle_view_destroy(struct wl_listener *listener, void *data) exposay_surface_destroy(esurface); } -/* Pretty lame layout for now; just tries to make a square. Should take +/* Compute each surface size and then inner pad (10% of surface size). + * After that, it's necessary to recompute surface size (90% of its + * original size). Also, each surface can't be bigger than half the + * exposay area width and height. + */ +static void +exposay_surface_and_inner_pad_size(pixman_rectangle32_t exposay_area, struct exposay_output *eoutput) +{ + if (exposay_area.height < exposay_area.width) + eoutput->surface_size = + (exposay_area.height - (eoutput->vpadding_outer * 2)) / eoutput->grid_size; + else + eoutput->surface_size = + (exposay_area.width - (eoutput->hpadding_outer * 2)) / eoutput->grid_size; + eoutput->padding_inner = eoutput->surface_size / 10; + eoutput->surface_size -= eoutput->padding_inner; + + if ((uint32_t)eoutput->surface_size > (exposay_area.width / 2)) + eoutput->surface_size = exposay_area.width / 2; + if ((uint32_t)eoutput->surface_size > (exposay_area.height / 2)) + eoutput->surface_size = exposay_area.height / 2; +} + +/* Pretty lame layout for now; just tries to make a square. Should take * aspect ratio into account really. Also needs to be notified of surface - * addition and removal and adjust layout/animate accordingly. */ + * addition and removal and adjust layout/animate accordingly. + * + * Lay the grid out as square as possible, losing surfaces from the + * bottom row if required. Start with fixed padding of a 10% margin + * around the outside, and maximise the area made available to surfaces + * after this. Also, add an inner padding between surfaces that varies + * with the surface size (10% of its size). + * + * If we can't make a square grid, add one extra row at the bottom which + * will have a smaller number of columns. + */ static enum exposay_layout_state exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) { @@ -219,7 +252,6 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) struct weston_view *view; struct exposay_surface *esurface, *highlight = NULL; pixman_rectangle32_t exposay_area; - int w, h; int pad; int i; int last_row_removed = 0; @@ -246,40 +278,18 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) * the shell panel position and size */ get_output_work_area(shell, output, &exposay_area); - /* Lay the grid out as square as possible, losing surfaces from the - * bottom row if required. Start with fixed padding of a 10% margin - * around the outside and 80px internal padding between surfaces, and - * maximise the area made available to surfaces after this, but only - * to a maximum of 1/3rd the total output size. - * - * If we can't make a square grid, add one extra row at the bottom - * which will have a smaller number of columns. - * - * XXX: Surely there has to be a better way to express this maths, - * right?! - */ + /* Compute grid size */ eoutput->grid_size = floor(sqrtf(eoutput->num_surfaces)); if (pow(eoutput->grid_size, 2) != eoutput->num_surfaces) eoutput->grid_size++; last_row_removed = pow(eoutput->grid_size, 2) - eoutput->num_surfaces; + /* Fixed outer padding of 10% the size of the screen */ eoutput->hpadding_outer = (exposay_area.width / 10); eoutput->vpadding_outer = (exposay_area.height / 10); - eoutput->padding_inner = 80; - - w = exposay_area.width - (eoutput->hpadding_outer * 2); - w -= eoutput->padding_inner * (eoutput->grid_size - 1); - w /= eoutput->grid_size; - h = exposay_area.height - (eoutput->vpadding_outer * 2); - h -= eoutput->padding_inner * (eoutput->grid_size - 1); - h /= eoutput->grid_size; - - eoutput->surface_size = (w < h) ? w : h; - if ((uint32_t)eoutput->surface_size > (exposay_area.width / 2)) - eoutput->surface_size = exposay_area.width / 2; - if ((uint32_t)eoutput->surface_size > (exposay_area.height / 2)) - eoutput->surface_size = exposay_area.height / 2; + /* Compute each surface size and the inner padding between them */ + exposay_surface_and_inner_pad_size(exposay_area, eoutput); pad = eoutput->surface_size + eoutput->padding_inner; From 048c628139d3716aa7ace3cf2d65fd47d84682d7 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 24 Jan 2020 19:50:34 -0300 Subject: [PATCH 1439/1642] exposay: add margins to centralize exposay The exposay is being rendered in the top-left corner of the screen. Add margins to render it in the center, making it more visually pleasant. Signed-off-by: Leandro Ribeiro --- desktop-shell/exposay.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c index e03a3114d..7836dc032 100644 --- a/desktop-shell/exposay.c +++ b/desktop-shell/exposay.c @@ -230,6 +230,15 @@ exposay_surface_and_inner_pad_size(pixman_rectangle32_t exposay_area, struct exp eoutput->surface_size = exposay_area.height / 2; } +/* Compute the exposay top/left margin in order to centralize it */ +static void +exposay_margin_size(struct desktop_shell *shell, pixman_rectangle32_t exposay_area, + int row_size, int column_size, int *left_margin, int *top_margin) +{ + (*left_margin) = exposay_area.x + (exposay_area.width - row_size) / 2; + (*top_margin) = exposay_area.y + (exposay_area.height - column_size) / 2; +} + /* Pretty lame layout for now; just tries to make a square. Should take * aspect ratio into account really. Also needs to be notified of surface * addition and removal and adjust layout/animate accordingly. @@ -252,7 +261,8 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) struct weston_view *view; struct exposay_surface *esurface, *highlight = NULL; pixman_rectangle32_t exposay_area; - int pad; + int pad, row_size, column_size, left_margin, top_margin; + int populated_rows; int i; int last_row_removed = 0; @@ -291,7 +301,16 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) /* Compute each surface size and the inner padding between them */ exposay_surface_and_inner_pad_size(exposay_area, eoutput); + /* Compute each row/column size */ pad = eoutput->surface_size + eoutput->padding_inner; + row_size = (pad * eoutput->grid_size) - eoutput->padding_inner; + /* We may have empty rows that should be desconsidered to compute + * column size */ + populated_rows = ceil(eoutput->num_surfaces / (float) eoutput->grid_size); + column_size = (pad * populated_rows) - eoutput->padding_inner; + + /* Compute a top/left margin to centralize the exposay */ + exposay_margin_size(shell, exposay_area, row_size, column_size, &left_margin, &top_margin); i = 0; wl_list_for_each(view, &workspace->layer.view_list.link, layer_link.link) { @@ -316,10 +335,8 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) esurface->row = i / eoutput->grid_size; esurface->column = i % eoutput->grid_size; - esurface->x = exposay_area.x + eoutput->hpadding_outer; - esurface->x += pad * esurface->column; - esurface->y = exposay_area.y + eoutput->vpadding_outer; - esurface->y += pad * esurface->row; + esurface->x = left_margin + (pad * esurface->column); + esurface->y = top_margin + (pad * esurface->row); if (esurface->row == eoutput->grid_size - 1) esurface->x += (eoutput->surface_size + eoutput->padding_inner) * last_row_removed / 2; From 55b4b47ec1824c7a4b51434e01c8a8bd5dbf8d4b Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Sat, 25 Jan 2020 14:40:49 -0300 Subject: [PATCH 1440/1642] exposay: centralize exposay's surfaces in their own square Commit "exposay: add margins to centralize exposay" has centralized the whole exposay, but that's not enough. The internal surfaces of exposay are not centralized. Each internal surface of exposay is a square, but most of the windows are rectangular. Add margin to centralize exposay's surfaces in their own square. Signed-off-by: Leandro Ribeiro --- desktop-shell/exposay.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c index 7836dc032..09bc3f26b 100644 --- a/desktop-shell/exposay.c +++ b/desktop-shell/exposay.c @@ -348,6 +348,13 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) esurface->width = view->surface->width * esurface->scale; esurface->height = view->surface->height * esurface->scale; + /* Surfaces are usually rectangular, but their exposay surfaces + * are square. centralize them in their own square */ + if (esurface->width > esurface->height) + esurface->y += (esurface->width - esurface->height) / 2; + else + esurface->x += (esurface->height - esurface->width) / 2; + if (shell->exposay.focus_current == esurface->view) highlight = esurface; From 82b4d4294319075938ad5cdfedaa19ccc57aa9ec Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 24 Jan 2020 19:57:39 -0300 Subject: [PATCH 1441/1642] exposay: centralize surfaces of the last row when we don't have enough surfaces The exposay grid is square, but we don't always have enough surfaces to fill all the columns of the last row. The code to centralize the surfaces of the last row is not working. Fix the code that centralizes the surfaces in the last row, making it more visually pleasant. Signed-off-by: Leandro Ribeiro --- desktop-shell/exposay.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c index 09bc3f26b..06b07ef19 100644 --- a/desktop-shell/exposay.c +++ b/desktop-shell/exposay.c @@ -262,9 +262,9 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) struct exposay_surface *esurface, *highlight = NULL; pixman_rectangle32_t exposay_area; int pad, row_size, column_size, left_margin, top_margin; + int last_row_size, last_row_margin_increase; int populated_rows; int i; - int last_row_removed = 0; eoutput->num_surfaces = 0; wl_list_for_each(view, &workspace->layer.view_list.link, layer_link.link) { @@ -292,7 +292,6 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) eoutput->grid_size = floor(sqrtf(eoutput->num_surfaces)); if (pow(eoutput->grid_size, 2) != eoutput->num_surfaces) eoutput->grid_size++; - last_row_removed = pow(eoutput->grid_size, 2) - eoutput->num_surfaces; /* Fixed outer padding of 10% the size of the screen */ eoutput->hpadding_outer = (exposay_area.width / 10); @@ -309,6 +308,15 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) populated_rows = ceil(eoutput->num_surfaces / (float) eoutput->grid_size); column_size = (pad * populated_rows) - eoutput->padding_inner; + /* The last row size can be different, since it may have less surfaces + * than the grid size. Also, its margin may be increased to centralize + * its surfaces, in the case where we don't have a perfect grid. */ + last_row_size = ((eoutput->num_surfaces % eoutput->grid_size) * pad) - eoutput->padding_inner; + if (eoutput->num_surfaces % eoutput->grid_size) + last_row_margin_increase = (row_size - last_row_size) / 2; + else + last_row_margin_increase = 0; + /* Compute a top/left margin to centralize the exposay */ exposay_margin_size(shell, exposay_area, row_size, column_size, &left_margin, &top_margin); @@ -338,8 +346,10 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) esurface->x = left_margin + (pad * esurface->column); esurface->y = top_margin + (pad * esurface->row); - if (esurface->row == eoutput->grid_size - 1) - esurface->x += (eoutput->surface_size + eoutput->padding_inner) * last_row_removed / 2; + /* If this is the last row, increase left margin (it sums 0 if + * we have a perfect square) to centralize the surfaces */ + if (eoutput->num_surfaces / eoutput->grid_size == esurface->row) + esurface->x += last_row_margin_increase; if (view->surface->width > view->surface->height) esurface->scale = eoutput->surface_size / (float) view->surface->width; From 2ac73a8b535f1a5d5c024935d37ad8eb06db6d2e Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Sat, 25 Jan 2020 17:49:07 -0300 Subject: [PATCH 1442/1642] exposay: delete outer padding from struct exposay_output After some changes in the exposay layout, the outer padding makes no sense anymore. It was used to avoid the panel to get overlapped by the exposay surfaces and to keep distance from the borders of the window. Currently, the exposay is centralized and the panel cannot get overlapped. The outer padding just creates unnecessary unused space, what makes exposay's surfaces smaller. Delete outer padding from struct exposay_output. Signed-off-by: Leandro Ribeiro --- desktop-shell/exposay.c | 13 +++---------- desktop-shell/shell.h | 3 --- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c index 06b07ef19..7e9a324c4 100644 --- a/desktop-shell/exposay.c +++ b/desktop-shell/exposay.c @@ -216,11 +216,10 @@ static void exposay_surface_and_inner_pad_size(pixman_rectangle32_t exposay_area, struct exposay_output *eoutput) { if (exposay_area.height < exposay_area.width) - eoutput->surface_size = - (exposay_area.height - (eoutput->vpadding_outer * 2)) / eoutput->grid_size; + eoutput->surface_size = exposay_area.height / eoutput->grid_size; else - eoutput->surface_size = - (exposay_area.width - (eoutput->hpadding_outer * 2)) / eoutput->grid_size; + eoutput->surface_size = exposay_area.width / eoutput->grid_size; + eoutput->padding_inner = eoutput->surface_size / 10; eoutput->surface_size -= eoutput->padding_inner; @@ -277,8 +276,6 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) if (eoutput->num_surfaces == 0) { eoutput->grid_size = 0; - eoutput->hpadding_outer = 0; - eoutput->vpadding_outer = 0; eoutput->padding_inner = 0; eoutput->surface_size = 0; return EXPOSAY_LAYOUT_OVERVIEW; @@ -293,10 +290,6 @@ exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) if (pow(eoutput->grid_size, 2) != eoutput->num_surfaces) eoutput->grid_size++; - /* Fixed outer padding of 10% the size of the screen */ - eoutput->hpadding_outer = (exposay_area.width / 10); - eoutput->vpadding_outer = (exposay_area.height / 10); - /* Compute each surface size and the inner padding between them */ exposay_surface_and_inner_pad_size(exposay_area, eoutput); diff --git a/desktop-shell/shell.h b/desktop-shell/shell.h index 6f72b3576..9f0b6abe9 100644 --- a/desktop-shell/shell.h +++ b/desktop-shell/shell.h @@ -62,9 +62,6 @@ struct exposay_output { int num_surfaces; int grid_size; int surface_size; - - int hpadding_outer; - int vpadding_outer; int padding_inner; }; From e0b937af2893f13de9d17cc4385f759962bb613e Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Thu, 27 Aug 2020 14:14:27 +0300 Subject: [PATCH 1443/1642] remoting: Add DPMS support in remoting pluging Just like pipewire, add DPMS support in remoting plug-in. Mechanical change mimicking a24a326bb19ec5. Signed-off-by: Marius Vlad --- remoting/remoting-plugin.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/remoting/remoting-plugin.c b/remoting/remoting-plugin.c index 0928a6b10..85b6bcfd5 100644 --- a/remoting/remoting-plugin.c +++ b/remoting/remoting-plugin.c @@ -119,6 +119,7 @@ struct remoted_output { struct remoted_gstpipe gstpipe; GstClockTime start_time; int retry_count; + enum dpms_enum dpms; }; struct mem_free_cb_data { @@ -477,8 +478,12 @@ remoting_output_finish_frame_handler(void *data) api->finish_frame(output->output, &now, 0); } - msec = millihz_to_nsec(output->output->current_mode->refresh) / 1000000; - wl_event_source_timer_update(output->finish_frame_timer, msec); + if (output->dpms == WESTON_DPMS_ON) { + msec = millihz_to_nsec(output->output->current_mode->refresh) / 1000000; + wl_event_source_timer_update(output->finish_frame_timer, msec); + } else { + wl_event_source_timer_update(output->finish_frame_timer, 0); + } return 0; } @@ -666,6 +671,18 @@ remoting_output_start_repaint_loop(struct weston_output *output) return 0; } +static void +remoting_output_set_dpms(struct weston_output *base_output, enum dpms_enum level) +{ + struct remoted_output *output = lookup_remoted_output(base_output); + + if (output->dpms == level) + return; + + output->dpms = level; + remoting_output_finish_frame_handler(output); +} + static int remoting_output_enable(struct weston_output *output) { @@ -684,6 +701,7 @@ remoting_output_enable(struct weston_output *output) remoted_output->saved_start_repaint_loop = output->start_repaint_loop; output->start_repaint_loop = remoting_output_start_repaint_loop; + output->set_dpms = remoting_output_set_dpms; ret = remoting_gst_pipeline_init(remoted_output); if (ret < 0) { @@ -697,6 +715,7 @@ remoting_output_enable(struct weston_output *output) remoting_output_finish_frame_handler, remoted_output); + remoted_output->dpms = WESTON_DPMS_ON; return 0; } From 380ceee6a1d930be5d6852744064aa162c7da248 Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Thu, 27 Aug 2020 14:32:26 +0300 Subject: [PATCH 1444/1642] remoting, pipewire: Install pipewire and remoting plug-ins headers Required for other users of libweston. Signed-off-by: Marius Vlad --- pipewire/meson.build | 1 + remoting/meson.build | 1 + 2 files changed, 2 insertions(+) diff --git a/pipewire/meson.build b/pipewire/meson.build index 67db61f07..3d3374b81 100644 --- a/pipewire/meson.build +++ b/pipewire/meson.build @@ -27,4 +27,5 @@ if get_option('pipewire') install_dir: dir_module_libweston ) env_modmap += 'pipewire-plugin.so=@0@;'.format(plugin_pipewire.full_path()) + install_headers('pipewire-plugin.h', subdir: dir_include_libweston_install) endif diff --git a/remoting/meson.build b/remoting/meson.build index ed7ff479b..ac9fa5119 100644 --- a/remoting/meson.build +++ b/remoting/meson.build @@ -29,4 +29,5 @@ if get_option('remoting') install_dir: dir_module_libweston ) env_modmap += 'remoting-plugin.so=@0@;'.format(plugin_remoting.full_path()) + install_headers('remoting-plugin.h', subdir: dir_include_libweston_install) endif From 13f501c51b536256e935eefc9bde7922c5bdd7e3 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 27 Aug 2020 22:58:53 +0200 Subject: [PATCH 1445/1642] build: bump to version 8.0.93 for the RC1 release --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 7d4bd23c5..6b501a273 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('weston', 'c', - version: '8.0.92', + version: '8.0.93', default_options: [ 'warning_level=3', 'c_std=gnu99', From 04d3ae265d8d8f84352c8dac21ec40b2fe07e7d2 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 4 Sep 2020 12:20:00 +0200 Subject: [PATCH 1446/1642] build: bump to version 9.0.0 for the official release --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 6b501a273..e7d053b98 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('weston', 'c', - version: '8.0.93', + version: '9.0.0', default_options: [ 'warning_level=3', 'c_std=gnu99', From f590a956c378eb2ab765d05cb2331386f19f8704 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Sat, 30 May 2020 06:02:08 +0000 Subject: [PATCH 1447/1642] Merged PR 4742168: Add preliminary RDP-RAIL support for weston Add RDP-RAIL support for weston - this also copied some OpenSSL support so backend-rdp can start without manually specify certificate or key. --- .gitignore | 1 + azure-pipelines.yml | 137 + compositor/main.c | 56 +- compositor/xwayland.c | 68 +- include/libweston-desktop/libweston-desktop.h | 9 + include/libweston/backend-rdp.h | 171 + include/libweston/libweston.h | 15 +- include/libweston/xwayland-api.h | 27 + ivi-shell/ivi-layout.c | 4 +- libweston-desktop/internal.h | 5 + libweston-desktop/libweston-desktop.c | 12 +- libweston-desktop/xwayland.c | 32 +- libweston/backend-rdp/hash.c | 316 ++ libweston/backend-rdp/hash.h | 52 + libweston/backend-rdp/meson.build | 44 +- libweston/backend-rdp/rdp.c | 1322 ++++-- libweston/backend-rdp/rdp.h | 625 +++ libweston/backend-rdp/rdpaudio.c | 809 ++++ libweston/backend-rdp/rdpaudioin.c | 592 +++ libweston/backend-rdp/rdpclip.c | 1417 ++++++ libweston/backend-rdp/rdpdisp.c | 749 +++ libweston/backend-rdp/rdprail.c | 3929 +++++++++++++++ libweston/backend-rdp/rdputil.c | 235 + libweston/compositor.c | 42 +- libweston/input.c | 22 +- libweston/pixman-renderer.c | 41 +- libweston/renderer-gl/gl-renderer.c | 32 +- meson.build | 3 +- meson_options.txt | 8 +- rdprail-shell/app-list.c | 1147 +++++ rdprail-shell/input-panel.c | 267 ++ rdprail-shell/meson.build | 35 + rdprail-shell/shell.c | 4201 +++++++++++++++++ rdprail-shell/shell.h | 187 + shared/config-parser.c | 2 +- tests/surface-screenshot-test.c | 4 +- xwayland/launcher.c | 52 +- xwayland/selection.c | 3 +- xwayland/window-manager.c | 338 +- xwayland/xwayland-internal-interface.h | 3 + xwayland/xwayland.h | 1 + 41 files changed, 16553 insertions(+), 462 deletions(-) create mode 100644 azure-pipelines.yml create mode 100644 libweston/backend-rdp/hash.c create mode 100644 libweston/backend-rdp/hash.h create mode 100644 libweston/backend-rdp/rdp.h create mode 100644 libweston/backend-rdp/rdpaudio.c create mode 100644 libweston/backend-rdp/rdpaudioin.c create mode 100644 libweston/backend-rdp/rdpclip.c create mode 100644 libweston/backend-rdp/rdpdisp.c create mode 100644 libweston/backend-rdp/rdprail.c create mode 100644 libweston/backend-rdp/rdputil.c create mode 100644 rdprail-shell/app-list.c create mode 100644 rdprail-shell/input-panel.c create mode 100644 rdprail-shell/meson.build create mode 100644 rdprail-shell/shell.c create mode 100644 rdprail-shell/shell.h diff --git a/.gitignore b/.gitignore index b81eee661..26d9ae9ed 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ cscope.out TAGS 00*.patch +build/* diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..59bf9014b --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,137 @@ +# This pipeline will just build Weston to be sure we dont break + +resources: + repositories: + - repository: FreeRDP + type: github + endpoint: GitHub connection 1 + name: microsoft/FreeRDP-mirror + ref: working + +trigger: + - working + +pool: + vmImage: 'ubuntu-latest' + +variables: + prefix: '/usr/local' + +steps: + - checkout: FreeRDP + - checkout: self + + - script: sudo apt-get update && sudo apt-get install --no-install-recommends -y + build-essential + cmake + git + libcairo2-dev + libcolord-dev + libdbus-glib-1-dev + libdrm-dev + libffi-dev + libgbm-dev + libgles2-mesa-dev + libgstreamer-plugins-base1.0-dev + libgstreamer1.0-dev + libinput-dev + libjpeg-dev + liblcms2-dev + libpam-dev + libpango1.0-dev + libpixman-1-dev + libssl-dev + libsystemd-dev + libtool + libudev-dev + libudev-dev + libusb-1.0-0-dev + libva-dev + libwebp-dev + libx11-dev + libx11-xcb-dev + libxcb-composite0-dev + libxcb-xkb-dev + libxcursor-dev + libxdamage-dev + libxext-dev + libxfixes-dev + libxi-dev + libxinerama-dev + libxkbcommon-dev + libxkbfile-dev + libxml2-dev + libxrandr-dev + libxrender-dev + libxtst-dev + libxv-dev + lsb-release + python3-setuptools + python3-pip + ninja-build + pkg-config + software-properties-common + squashfs-tools + uuid-dev + libwayland-dev + wayland-protocols + wget + displayName: 'Install Dependencies' + + + + - script: cmake -G Ninja + -B build + -DCMAKE_INSTALL_PREFIX=$(prefix) + -DCMAKE_INSTALL_LIBDIR=$(prefix)/lib + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DWITH_SERVER=ON + -DWITH_CHANNEL_GFXREDIR=ON + -DWITH_CHANNEL_RDPAPPLIST=ON + -DWITH_CLIENT=OFF + -DWITH_CLIENT_COMMON=OFF + -DWITH_CLIENT_CHANNELS=OFF + -DWITH_CLIENT_INTERFACE=OFF + -DWITH_PROXY=OFF + -DWITH_SHADOW=OFF + -DWITH_SAMPLE=OFF + workingDirectory: ./FreeRDP-mirror + displayName: 'CMake (Ninja)' + + - script: ninja -C build -j8 + workingDirectory: ./FreeRDP-mirror + displayName: 'Ninja build' + + - script: sudo ninja -C build install + workingDirectory: ./FreeRDP-mirror + displayName: 'Ninja Install (FreeRDP)' + + - script: sudo pip3 install meson + displayName: 'Install Meson (from PIP)' + + - script: meson --prefix=$(prefix) build + -Dbackend-default=rdp + -Dbackend-drm=false + -Dbackend-drm-screencast-vaapi=false + -Dbackend-headless=false + -Dbackend-wayland=false + -Dbackend-x11=false + -Dbackend-fbdev=false + -Dscreenshare=false + -Dremoting=false + -Dpipewire=false + -Dshell-fullscreen=false + -Dshell-ivi=false + -Dshell-kiosk=false + -Ddemo-clients=false + -Dsimple-clients=[] + -Dtools=[] + -Dresize-pool=false + -Dwcap-decode=false + -Dtest-junit-xml=false + workingDirectory: ./weston-mirror + displayName: 'Meson (weston)' + + - script: ninja -C build -j8 + workingDirectory: ./weston-mirror + displayName: 'Ninja (weston)' diff --git a/compositor/main.c b/compositor/main.c index 65da9dbc4..503dd1f8a 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -708,6 +708,7 @@ usage(int error_code) "Options for rdp-backend.so:\n\n" " --width=WIDTH\t\tWidth of desktop\n" " --height=HEIGHT\tHeight of desktop\n" + " --scale=SCALE\t\tScale factor of desktop\n" " --env-socket\t\tUse socket defined in RDP_FD env variable as peer connection\n" " --address=ADDR\tThe address to bind\n" " --port=PORT\t\tThe port to listen on\n" @@ -2648,8 +2649,10 @@ 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); + struct weston_config_section *section = NULL; int width = 640; int height = 480; + int scale = 1; assert(parsed_options); @@ -2658,14 +2661,25 @@ rdp_backend_output_configure(struct weston_output *output) return -1; } + /* obtain output configuration from backend */ + if (api->output_get_config(output, &width, &height, &scale) < 0) { + weston_log("Cannot get output configuration from backend \"%s\" using weston_rdp_output_api.\n", + output->name); + 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); + wet_output_set_scale(output, section, scale, parsed_options->scale); + if (wet_output_set_transform(output, section, + WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX) < 0) { + return -1; + } if (api->output_set_size(output, width, height) < 0) { weston_log("Cannot configure output \"%s\" using weston_rdp_output_api.\n", @@ -2709,6 +2723,7 @@ load_rdp_backend(struct weston_compositor *c, { WESTON_OPTION_BOOLEAN, "env-socket", 0, &config.env_socket }, { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, + { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, { WESTON_OPTION_STRING, "address", 0, &config.bind_address }, { WESTON_OPTION_INTEGER, "port", 0, &config.port }, { WESTON_OPTION_BOOLEAN, "no-clients-resize", 0, &config.no_clients_resize }, @@ -3130,6 +3145,7 @@ wet_main(int argc, char *argv[]) char *option_modules = NULL; char *log = NULL; char *log_scopes = NULL; + char *log_scopes_env = NULL; char *flight_rec_scopes = NULL; char *server_socket = NULL; int32_t idle_time = -1; @@ -3152,6 +3168,7 @@ wet_main(int argc, char *argv[]) sigset_t mask; bool wait_for_debugger = false; + bool disable_terminate_on_sigint = false; struct wl_protocol_logger *protologger = NULL; const struct weston_option core_options[] = { @@ -3169,6 +3186,7 @@ wet_main(int argc, char *argv[]) { WESTON_OPTION_BOOLEAN, "no-config", 0, &noconfig }, { WESTON_OPTION_STRING, "config", 'c', &config_file }, { WESTON_OPTION_BOOLEAN, "wait-for-debugger", 0, &wait_for_debugger }, + { WESTON_OPTION_BOOLEAN, "disable-terminate-on-sigint", 0, &disable_terminate_on_sigint }, { WESTON_OPTION_BOOLEAN, "debug", 0, &debug_protocol }, { WESTON_OPTION_STRING, "logger-scopes", 'l', &log_scopes }, { WESTON_OPTION_STRING, "flight-rec-scopes", 'f', &flight_rec_scopes }, @@ -3213,6 +3231,10 @@ wet_main(int argc, char *argv[]) weston_log_subscribe_to_scopes(log_ctx, logger, flight_rec, log_scopes, flight_rec_scopes); + log_scopes_env = getenv("WESTON_LOG_SCOPES"); + if (log_scopes_env) + weston_log_setup_scopes(log_ctx, logger, log_scopes_env); + weston_log("%s\n" STAMP_SPACE "%s\n" STAMP_SPACE "Bug reports to: %s\n" @@ -3231,11 +3253,27 @@ wet_main(int argc, char *argv[]) goto out_display; } + if (load_configuration(&config, noconfig, config_file) < 0) + goto out_config; + wet.config = config; + wet.parsed_options = NULL; + + section = weston_config_get_section(config, "core", NULL, NULL); + loop = wl_display_get_event_loop(display); signals[0] = wl_event_loop_add_signal(loop, SIGTERM, on_term_signal, display); - signals[1] = wl_event_loop_add_signal(loop, SIGINT, on_term_signal, - display); + + /* vs-code 'pause' button raise SIGINT, disable to terminate if requested */ + if (!disable_terminate_on_sigint) + weston_config_section_get_bool(section, "disable-terminate-on-sigint", + &disable_terminate_on_sigint, false); + if (!disable_terminate_on_sigint) + signals[1] = wl_event_loop_add_signal(loop, SIGINT, on_term_signal, + display); + else + signals[1] = NULL; + signals[2] = wl_event_loop_add_signal(loop, SIGQUIT, on_term_signal, display); @@ -3243,7 +3281,7 @@ wet_main(int argc, char *argv[]) signals[3] = wl_event_loop_add_signal(loop, SIGCHLD, sigchld_handler, NULL); - if (!signals[0] || !signals[1] || !signals[2] || !signals[3]) + if (!signals[0] || (!signals[1] && !disable_terminate_on_sigint) || !signals[2] || !signals[3]) goto out_signals; /* Xwayland uses SIGUSR1 for communicating with weston. Since some @@ -3254,13 +3292,6 @@ wet_main(int argc, char *argv[]) sigaddset(&mask, SIGUSR1); pthread_sigmask(SIG_BLOCK, &mask, NULL); - if (load_configuration(&config, noconfig, config_file) < 0) - goto out_signals; - wet.config = config; - wet.parsed_options = NULL; - - section = weston_config_get_section(config, "core", NULL, NULL); - if (!wait_for_debugger) { weston_config_section_get_bool(section, "wait-for-debugger", &wait_for_debugger, false); @@ -3428,6 +3459,7 @@ wet_main(int argc, char *argv[]) if (signals[i]) wl_event_source_remove(signals[i]); +out_config: wl_display_destroy(display); out_display: diff --git a/compositor/xwayland.c b/compositor/xwayland.c index 8eadbe252..d9f0eec90 100644 --- a/compositor/xwayland.c +++ b/compositor/xwayland.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "compositor/weston.h" @@ -68,6 +69,7 @@ spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd char s[12], abstract_fd_str[12], unix_fd_str[12], wm_fd_str[12]; int sv[2], wm[2], fd; char *xserver = NULL; + bool disable_ac = false; struct weston_config *config = wet_get_config(wxw->compositor); struct weston_config_section *section; @@ -92,10 +94,13 @@ spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd snprintf(s, sizeof s, "%d", fd); setenv("WAYLAND_SOCKET", s, 1); - fd = dup(abstract_fd); - if (fd < 0) - goto fail; - snprintf(abstract_fd_str, sizeof abstract_fd_str, "%d", fd); + if (abstract_fd) { + fd = dup(abstract_fd); + if (fd < 0) + goto fail; + snprintf(abstract_fd_str, sizeof abstract_fd_str, "%d", fd); + } + fd = dup(unix_fd); if (fd < 0) goto fail; @@ -110,6 +115,9 @@ spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd weston_config_section_get_string(section, "path", &xserver, XSERVER_PATH); + weston_config_section_get_bool(section, "disable_access_control", + &disable_ac, false); + /* Ignore SIGUSR1 in the child, which will make the X * server send SIGUSR1 to the parent (weston) when * it's done with initialization. During @@ -119,21 +127,43 @@ spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd * it's done with that. */ signal(SIGUSR1, SIG_IGN); - if (execl(xserver, - xserver, - display, - "-rootless", - "-listen", abstract_fd_str, - "-listen", unix_fd_str, - "-wm", wm_fd_str, - "-terminate", - NULL) < 0) - weston_log("exec of '%s %s -rootless " - "-listen %s -listen %s -wm %s " - "-terminate' failed: %s\n", - xserver, display, - abstract_fd_str, unix_fd_str, wm_fd_str, - strerror(errno)); + // Build our parameters + #define ARGS_COUNT 13 + const char *argv[ARGS_COUNT] = { + xserver, // 0 + display, // 1 + "-rootless", // 2 + "-core", // 3 + "-listen", unix_fd_str, // 4, 5 + "-wm", wm_fd_str, // 6, 7 + "-terminate", // 8 + NULL, NULL, // 9, 10 (-listen, abstract_fd_str) + NULL, // 11 (-ac) + NULL // 12 + }; + + int argc = 9; + if (abstract_fd) { + argv[argc++] = "-listen"; + argv[argc++] = abstract_fd_str; + } else { + argv[argc++] = "-nolisten"; + argv[argc++] = "local"; + } + + if (disable_ac) { + argv[argc++] = "-ac"; + } + assert(argc <= ARGS_COUNT); + + if(execv(xserver, (char* const*)argv) < 0) { + weston_log("%s ", argv[0]); + for (int i=0; iuser_data); } -void +WL_EXPORT void weston_desktop_api_maximized_requested(struct weston_desktop *desktop, struct weston_desktop_surface *surface, bool maximized) @@ -243,6 +243,16 @@ weston_desktop_api_minimized_requested(struct weston_desktop *desktop, desktop->api.minimized_requested(surface, desktop->user_data); } +void +weston_desktop_api_set_window_icon(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t width, int32_t height, int32_t bpp, void *bits) +{ + if (desktop->api.set_window_icon != NULL) + desktop->api.set_window_icon(surface, width, height, bpp, bits, + desktop->user_data); +} + void weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, struct weston_desktop_surface *surface, diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index 711c8a30c..d0b6896dd 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -363,11 +363,16 @@ static void set_window_geometry(struct weston_desktop_xwayland_surface *surface, int32_t x, int32_t y, int32_t width, int32_t height) { - surface->has_next_geometry = true; - surface->next_geometry.x = x; - surface->next_geometry.y = y; - surface->next_geometry.width = width; - surface->next_geometry.height = height; + if (surface->next_geometry.x != x || + surface->next_geometry.y != y || + surface->next_geometry.width != width || + surface->next_geometry.height != height) { + surface->has_next_geometry = true; + surface->next_geometry.x = x; + surface->next_geometry.y = y; + surface->next_geometry.width = width; + surface->next_geometry.height = height; + } } static void @@ -379,6 +384,21 @@ set_maximized(struct weston_desktop_xwayland_surface *surface) surface->surface, true); } +static void +set_minimized(struct weston_desktop_xwayland_surface *surface) +{ + weston_desktop_api_minimized_requested(surface->desktop, + surface->surface); +} + +static void +set_window_icon(struct weston_desktop_xwayland_surface *surface, + int32_t width, int32_t height, int32_t bpp, void *bits) +{ + weston_desktop_api_set_window_icon(surface->desktop, + surface->surface, width, height, bpp, bits); +} + static void set_pid(struct weston_desktop_xwayland_surface *surface, pid_t pid) { @@ -398,7 +418,9 @@ static const struct weston_desktop_xwayland_interface weston_desktop_xwayland_in .set_title = set_title, .set_window_geometry = set_window_geometry, .set_maximized = set_maximized, + .set_minimized = set_minimized, .set_pid = set_pid, + .set_window_icon = set_window_icon, }; void diff --git a/libweston/backend-rdp/hash.c b/libweston/backend-rdp/hash.c new file mode 100644 index 000000000..fcdeaee65 --- /dev/null +++ b/libweston/backend-rdp/hash.c @@ -0,0 +1,316 @@ +/* + * Copyright © 2009 Intel Corporation + * Copyright © 1988-2004 Keith Packard and Bart Massey. + * + * 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. + * + * Except as contained in this notice, the names of the authors + * or their institutions shall not be used in advertising or + * otherwise to promote the sale, use or other dealings in this + * Software without prior written authorization from the + * authors. + * + * Authors: + * Eric Anholt + * Keith Packard + */ + +#include "config.h" + +#include +#include + +#include "hash.h" + +struct hash_entry { + uint32_t hash; + void *data; +}; + +struct hash_table { + struct hash_entry *table; + uint32_t size; + uint32_t rehash; + uint32_t max_entries; + uint32_t size_index; + uint32_t entries; + uint32_t deleted_entries; +}; + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) + +/* + * From Knuth -- a good choice for hash/rehash values is p, p-2 where + * p and p-2 are both prime. These tables are sized to have an extra 10% + * free to avoid exponential performance degradation as the hash table fills + */ + +static const uint32_t deleted_data; + +static const struct { + uint32_t max_entries, size, rehash; +} hash_sizes[] = { + { 2, 5, 3 }, + { 4, 7, 5 }, + { 8, 13, 11 }, + { 16, 19, 17 }, + { 32, 43, 41 }, + { 64, 73, 71 }, + { 128, 151, 149 }, + { 256, 283, 281 }, + { 512, 571, 569 }, + { 1024, 1153, 1151 }, + { 2048, 2269, 2267 }, + { 4096, 4519, 4517 }, + { 8192, 9013, 9011 }, + { 16384, 18043, 18041 }, + { 32768, 36109, 36107 }, + { 65536, 72091, 72089 }, + { 131072, 144409, 144407 }, + { 262144, 288361, 288359 }, + { 524288, 576883, 576881 }, + { 1048576, 1153459, 1153457 }, + { 2097152, 2307163, 2307161 }, + { 4194304, 4613893, 4613891 }, + { 8388608, 9227641, 9227639 }, + { 16777216, 18455029, 18455027 }, + { 33554432, 36911011, 36911009 }, + { 67108864, 73819861, 73819859 }, + { 134217728, 147639589, 147639587 }, + { 268435456, 295279081, 295279079 }, + { 536870912, 590559793, 590559791 }, + { 1073741824, 1181116273, 1181116271}, + { 2147483648ul, 2362232233ul, 2362232231ul} +}; + +static int +entry_is_free(struct hash_entry *entry) +{ + return entry->data == NULL; +} + +static int +entry_is_deleted(struct hash_entry *entry) +{ + return entry->data == &deleted_data; +} + +static int +entry_is_present(struct hash_entry *entry) +{ + return entry->data != NULL && entry->data != &deleted_data; +} + +struct hash_table * +hash_table_create(void) +{ + struct hash_table *ht; + + ht = malloc(sizeof(*ht)); + if (ht == NULL) + return NULL; + + ht->size_index = 0; + ht->size = hash_sizes[ht->size_index].size; + ht->rehash = hash_sizes[ht->size_index].rehash; + ht->max_entries = hash_sizes[ht->size_index].max_entries; + ht->table = calloc(ht->size, sizeof(*ht->table)); + ht->entries = 0; + ht->deleted_entries = 0; + + if (ht->table == NULL) { + free(ht); + return NULL; + } + + return ht; +} + +/** + * Frees the given hash table. + */ +void +hash_table_destroy(struct hash_table *ht) +{ + if (!ht) + return; + + free(ht->table); + free(ht); +} + +/** + * Finds a hash table entry with the given key and hash of that key. + * + * Returns NULL if no entry is found. Note that the data pointer may be + * modified by the user. + */ +static void * +hash_table_search(struct hash_table *ht, uint32_t hash) +{ + uint32_t hash_address; + + hash_address = hash % ht->size; + do { + uint32_t double_hash; + + struct hash_entry *entry = ht->table + hash_address; + + if (entry_is_free(entry)) { + return NULL; + } else if (entry_is_present(entry) && entry->hash == hash) { + return entry; + } + + double_hash = 1 + hash % ht->rehash; + + hash_address = (hash_address + double_hash) % ht->size; + } while (hash_address != hash % ht->size); + + return NULL; +} + +void +hash_table_for_each(struct hash_table *ht, + hash_table_iterator_func_t func, void *data) +{ + struct hash_entry *entry; + uint32_t i; + + for (i = 0; i < ht->size; i++) { + entry = ht->table + i; + if (entry_is_present(entry)) + func(entry->data, data); + } +} + +void * +hash_table_lookup(struct hash_table *ht, uint32_t hash) +{ + struct hash_entry *entry; + + entry = hash_table_search(ht, hash); + if (entry != NULL) + return entry->data; + + return NULL; +} + +static void +hash_table_rehash(struct hash_table *ht, unsigned int new_size_index) +{ + struct hash_table old_ht; + struct hash_entry *table, *entry; + + if (new_size_index >= ARRAY_SIZE(hash_sizes)) + return; + + table = calloc(hash_sizes[new_size_index].size, sizeof(*ht->table)); + if (table == NULL) + return; + + old_ht = *ht; + + ht->table = table; + ht->size_index = new_size_index; + ht->size = hash_sizes[ht->size_index].size; + ht->rehash = hash_sizes[ht->size_index].rehash; + ht->max_entries = hash_sizes[ht->size_index].max_entries; + ht->entries = 0; + ht->deleted_entries = 0; + + for (entry = old_ht.table; + entry != old_ht.table + old_ht.size; + entry++) { + if (entry_is_present(entry)) { + hash_table_insert(ht, entry->hash, entry->data); + } + } + + free(old_ht.table); +} + +/** + * Inserts the data with the given hash into the table. + * + * Note that insertion may rearrange the table on a resize or rehash, + * so previously found hash_entries are no longer valid after this function. + */ +int +hash_table_insert(struct hash_table *ht, uint32_t hash, void *data) +{ + uint32_t hash_address; + + if (ht->entries >= ht->max_entries) { + hash_table_rehash(ht, ht->size_index + 1); + } else if (ht->deleted_entries + ht->entries >= ht->max_entries) { + hash_table_rehash(ht, ht->size_index); + } + + hash_address = hash % ht->size; + do { + struct hash_entry *entry = ht->table + hash_address; + uint32_t double_hash; + + if (!entry_is_present(entry)) { + if (entry_is_deleted(entry)) + ht->deleted_entries--; + entry->hash = hash; + entry->data = data; + ht->entries++; + return 0; + } + + double_hash = 1 + hash % ht->rehash; + + hash_address = (hash_address + double_hash) % ht->size; + } while (hash_address != hash % ht->size); + + /* We could hit here if a required resize failed. An unchecked-malloc + * application could ignore this result. + */ + return -1; +} + +/** + * This function deletes the given hash table entry. + * + * Note that deletion doesn't otherwise modify the table, so an iteration over + * the table deleting entries is safe. + */ +void +hash_table_remove(struct hash_table *ht, uint32_t hash) +{ + struct hash_entry *entry; + + entry = hash_table_search(ht, hash); + if (entry != NULL) { + entry->data = (void *) &deleted_data; + ht->entries--; + ht->deleted_entries++; + } +} + +uint32_t +hash_table_num_entries(struct hash_table *ht) +{ + return ht->entries; +} + diff --git a/libweston/backend-rdp/hash.h b/libweston/backend-rdp/hash.h new file mode 100644 index 000000000..1318337b0 --- /dev/null +++ b/libweston/backend-rdp/hash.h @@ -0,0 +1,52 @@ +/* + * Copyright © 2009 Intel Corporation + * Copyright © 1988-2004 Keith Packard and Bart Massey. + * + * 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. + * + * Except as contained in this notice, the names of the authors + * or their institutions shall not be used in advertising or + * otherwise to promote the sale, use or other dealings in this + * Software without prior written authorization from the + * authors. + * + * Authors: + * Eric Anholt + * Keith Packard + */ + +#ifndef HASH_H +#define HASH_H + +#include + +struct hash_table; +struct hash_table *hash_table_create(void); +typedef void (*hash_table_iterator_func_t)(void *element, void *data); + +void hash_table_destroy(struct hash_table *ht); +void *hash_table_lookup(struct hash_table *ht, uint32_t hash); +int hash_table_insert(struct hash_table *ht, uint32_t hash, void *data); +void hash_table_remove(struct hash_table *ht, uint32_t hash); +void hash_table_for_each(struct hash_table *ht, + hash_table_iterator_func_t func, void *data); +uint32_t hash_table_num_entries(struct hash_table *ht); + +#endif diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index e3b602507..43ef3bf10 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -9,6 +9,11 @@ if not dep_frdp.found() error('RDP-backend requires freerdp2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') endif +dep_frdp_server = dependency('freerdp-server2', version: '>= 2.0.0', required: false) +if not dep_frdp_server.found() + error('RDP-backend requires freerdp2-server2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') +endif + dep_wpr = dependency('winpr2', version: '>= 2.0.0', required: false) if not dep_wpr.found() error('RDP-backend requires winpr2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') @@ -18,6 +23,14 @@ if cc.has_header('freerdp/version.h', dependencies: dep_frdp) config_h.set('HAVE_FREERDP_VERSION_H', '1') endif +if cc.has_header('freerdp/channels/gfxredir.h', dependencies: dep_frdp) + config_h.set('HAVE_FREERDP_GFXREDIR_H', '1') +endif + +if cc.has_header('freerdp/channels/rdpapplist.h', dependencies: dep_frdp) + config_h.set('HAVE_FREERDP_RDPAPPLIST_H', '1') +endif + if cc.has_member( 'SURFACE_BITS_COMMAND', 'bmp', dependencies : dep_frdp, @@ -42,14 +55,43 @@ if cc.has_function( config_h.set('HAVE_NSC_CONTEXT_SET_PARAMETERS', '1') endif +if cc.has_member( + 'rdpsnd_server_context', 'use_dynamic_virtual_channel', + dependencies : dep_frdp, + prefix : '#include ' +) + config_h.set('HAVE_RDPSND_DYNAMIC_VIRTUAL_CHANNEL', '1') +endif + deps_rdp = [ + dep_threads, + dep_libdl, dep_libweston_private, dep_frdp, + dep_frdp_server, dep_wpr, ] + +dep_openssl = dependency('openssl', version: '>= 1.1.1', required: false) +if dep_openssl.found() + config_h.set('HAVE_OPENSSL', '1') + deps_rdp += dep_openssl +endif + +srcs_rdp = [ + 'hash.c', + 'rdp.c', + 'rdpaudio.c', + 'rdpaudioin.c', + 'rdpdisp.c', + 'rdpclip.c', + 'rdprail.c', + 'rdputil.c', +] + plugin_rdp = shared_library( 'rdp-backend', - 'rdp.c', + srcs_rdp, include_directories: common_inc, dependencies: deps_rdp, name_prefix: '', diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 9e414aa24..5a4abcbb7 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -32,163 +32,45 @@ #include #include -#if HAVE_FREERDP_VERSION_H -#include -#else -/* assume it's a early 1.1 version */ -#define FREERDP_VERSION_MAJOR 1 -#define FREERDP_VERSION_MINOR 1 -#define FREERDP_VERSION_REVISION 0 -#endif - -#define FREERDP_VERSION_NUMBER ((FREERDP_VERSION_MAJOR * 0x10000) + \ - (FREERDP_VERSION_MINOR * 0x100) + FREERDP_VERSION_REVISION) - - -#if FREERDP_VERSION_NUMBER >= 0x10201 -#define HAVE_SKIP_COMPRESSION -#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rdp.h" -#if FREERDP_VERSION_NUMBER < 0x10202 -# define FREERDP_CB_RET_TYPE void -# define FREERDP_CB_RETURN(V) return -# define NSC_RESET(C, W, H) -# define RFX_RESET(C, W, H) do { rfx_context_reset(C); C->width = W; C->height = H; } while(0) -#else -#if FREERDP_VERSION_MAJOR >= 2 -# define NSC_RESET(C, W, H) nsc_context_reset(C, W, H) -# define RFX_RESET(C, W, H) rfx_context_reset(C, W, H) -#else -# define NSC_RESET(C, W, H) do { nsc_context_reset(C); C->width = W; C->height = H; } while(0) -# define RFX_RESET(C, W, H) do { rfx_context_reset(C); C->width = W; C->height = H; } while(0) -#endif -#define FREERDP_CB_RET_TYPE BOOL -#define FREERDP_CB_RETURN(V) return TRUE -#endif - -#ifdef HAVE_SURFACE_BITS_BMP -#define SURFACE_BPP(cmd) cmd.bmp.bpp -#define SURFACE_CODECID(cmd) cmd.bmp.codecID -#define SURFACE_WIDTH(cmd) cmd.bmp.width -#define SURFACE_HEIGHT(cmd) cmd.bmp.height -#define SURFACE_BITMAP_DATA(cmd) cmd.bmp.bitmapData -#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bmp.bitmapDataLength -#else -#define SURFACE_BPP(cmd) cmd.bpp -#define SURFACE_CODECID(cmd) cmd.codecID -#define SURFACE_WIDTH(cmd) cmd.width -#define SURFACE_HEIGHT(cmd) cmd.height -#define SURFACE_BITMAP_DATA(cmd) cmd.bitmapData -#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bitmapDataLength -#endif - -#include -#include -#include -#include -#include -#include -#include -#include #include #if FREERDP_VERSION_MAJOR >= 2 #include #endif -#include "shared/helpers.h" #include "shared/timespec-util.h" #include #include #include "pixman-renderer.h" -#define MAX_FREERDP_FDS 32 -#define DEFAULT_AXIS_STEP_DISTANCE 10 -#define RDP_MODE_FREQ 60 * 1000 - -#if FREERDP_VERSION_MAJOR >= 2 && defined(PIXEL_FORMAT_BGRA32) && !defined(PIXEL_FORMAT_B8G8R8A8) - /* The RDP API is truly wonderful: the pixel format definition changed - * from BGRA32 to B8G8R8A8, but some versions ship with a definition of - * PIXEL_FORMAT_BGRA32 which doesn't actually build. Try really, really, - * hard to find one which does. */ -# define DEFAULT_PIXEL_FORMAT PIXEL_FORMAT_BGRA32 -#else -# define DEFAULT_PIXEL_FORMAT RDP_PIXEL_FORMAT_B8G8R8A8 +#if HAVE_OPENSSL +/* for session certificate generation */ +#include +#include +#include +#include +#include +#include #endif -struct rdp_output; - -struct rdp_backend { - struct weston_backend base; - struct weston_compositor *compositor; - - freerdp_listener *listener; - struct wl_event_source *listener_events[MAX_FREERDP_FDS]; - struct rdp_output *output; - - char *server_cert; - char *server_key; - char *rdp_key; - int tls_enabled; - int no_clients_resize; - int force_no_compression; -}; - -enum peer_item_flags { - RDP_PEER_ACTIVATED = (1 << 0), - RDP_PEER_OUTPUT_ENABLED = (1 << 1), -}; - -struct rdp_peers_item { - int flags; - freerdp_peer *peer; - struct weston_seat *seat; - - struct wl_list link; -}; - -struct rdp_head { - struct weston_head base; -}; - -struct rdp_output { - struct weston_output base; - struct wl_event_source *finish_frame_timer; - pixman_image_t *shadow_surface; - - struct wl_list peers; -}; - -struct rdp_peer_context { - rdpContext _p; - - struct rdp_backend *rdpBackend; - struct wl_event_source *events[MAX_FREERDP_FDS]; - RFX_CONTEXT *rfx_context; - wStream *encode_stream; - RFX_RECT *rfx_rects; - NSC_CONTEXT *nsc_context; - - struct rdp_peers_item item; -}; -typedef struct rdp_peer_context RdpPeerContext; - -static inline struct rdp_head * -to_rdp_head(struct weston_head *base) -{ - return container_of(base, struct rdp_head, base); -} - -static inline struct rdp_output * -to_rdp_output(struct weston_output *base) -{ - return container_of(base, struct rdp_output, base); -} +extern PWtsApiFunctionTable FreeRDP_InitWtsApi(void); -static inline struct rdp_backend * -to_rdp_backend(struct weston_compositor *base) +static void +rdp_peer_seat_led_update(struct weston_seat *seat_base, enum weston_led leds) { - return container_of(base->backend, struct rdp_backend, base); + /*TODO: if Caps/Num lock change is triggered by server side, here can forward to client */ } static void @@ -379,7 +261,7 @@ static void rdp_peer_refresh_region(pixman_region32_t *region, freerdp_peer *peer) { RdpPeerContext *context = (RdpPeerContext *)peer->context; - struct rdp_output *output = context->rdpBackend->output; + struct rdp_output *output = context->rdpBackend->output_default; rdpSettings *settings = peer->settings; if (settings->RemoteFxCodec) @@ -408,22 +290,39 @@ rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, struct rdp_output *output = container_of(output_base, struct rdp_output, base); struct weston_compositor *ec = output->base.compositor; struct rdp_peers_item *outputPeer; + struct rdp_backend *b = to_rdp_backend(ec); - pixman_renderer_output_set_buffer(output_base, output->shadow_surface); - ec->renderer->repaint_output(&output->base, damage); - - if (pixman_region32_not_empty(damage)) { - wl_list_for_each(outputPeer, &output->peers, link) { - if ((outputPeer->flags & RDP_PEER_ACTIVATED) && + if (b->rdp_peer && + b->rdp_peer->settings->HiDefRemoteApp) { + /* RAIL mode, repaint RAIL window */ + rdp_rail_output_repaint(output_base, damage); + } else if (output_base->renderer_state) { + /* Add above 'output_base->renderer_state' check since this turns NULL when RDP + connection is disconnected and hit fault at pixman_renderer_output_set_buffer() */ + pixman_renderer_output_set_buffer(output_base, output->shadow_surface); + ec->renderer->repaint_output(&output->base, damage); + if (pixman_region32_not_empty(damage)) { + pixman_region32_t transformed_damage; + pixman_region32_init(&transformed_damage); + weston_transformed_region(output_base->width, + output_base->height, + output_base->transform, + output_base->current_scale, + damage, &transformed_damage); + /* note: if this code really need to walk peers in HiDef mode, */ + /* it must walk from output_default in backend, in non-HiDef */ + /* there must be only one default output, so doesn't matter. */ + wl_list_for_each(outputPeer, &output->peers, link) { + if ((outputPeer->flags & RDP_PEER_ACTIVATED) && (outputPeer->flags & RDP_PEER_OUTPUT_ENABLED)) - { - rdp_peer_refresh_region(damage, outputPeer->peer); + rdp_peer_refresh_region(&transformed_damage, outputPeer->peer); } + pixman_region32_fini(&transformed_damage); } - } - pixman_region32_subtract(&ec->primary_plane.damage, - &ec->primary_plane.damage, damage); + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); + } wl_event_source_timer_update(output->finish_frame_timer, 16); return 0; @@ -472,11 +371,16 @@ static int rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) { struct rdp_output *rdpOutput = container_of(output, struct rdp_output, base); + struct rdp_backend *rdpBackend = to_rdp_backend(output->compositor); struct rdp_peers_item *rdpPeer; rdpSettings *settings; pixman_image_t *new_shadow_buffer; - struct weston_mode *local_mode; + struct weston_mode *local_mode, *previous_mode; const struct pixman_renderer_output_options options = { }; + bool HiDefRemoteApp = false; + + if (rdpBackend->rdp_peer && rdpBackend->rdp_peer->settings->HiDefRemoteApp) + HiDefRemoteApp = true; local_mode = ensure_matching_mode(output, target_mode); if (!local_mode) { @@ -487,34 +391,82 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) if (local_mode == output->current_mode) return 0; - output->current_mode->flags &= ~WL_OUTPUT_MODE_CURRENT; + if (HiDefRemoteApp) + previous_mode = output->current_mode; + else + output->current_mode->flags &= ~WL_OUTPUT_MODE_CURRENT; output->current_mode = local_mode; output->current_mode->flags |= WL_OUTPUT_MODE_CURRENT; - pixman_renderer_output_destroy(output); - pixman_renderer_output_create(output, &options); - - 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, &rdpOutput->peers, link) { - settings = rdpPeer->peer->settings; - if (settings->DesktopWidth == (UINT32)target_mode->width && - settings->DesktopHeight == (UINT32)target_mode->height) - continue; - - if (!settings->DesktopResize) { - /* too bad this peer does not support desktop resize */ - rdpPeer->peer->Close(rdpPeer->peer); - } else { - settings->DesktopWidth = target_mode->width; - settings->DesktopHeight = target_mode->height; - rdpPeer->peer->update->DesktopResize(rdpPeer->peer->context); + if (HiDefRemoteApp) { + /* Mark current mode as preferred mode */ + output->current_mode->flags |= WL_OUTPUT_MODE_PREFERRED; + + /* In HiDefRemoteApp mode, free previous current_mode, + since it only want to expose current mode to app */ + wl_list_remove(&previous_mode->link); + free(previous_mode); + } + + if (!HiDefRemoteApp) { + pixman_renderer_output_destroy(output); + pixman_renderer_output_create(output, &options); + + 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->output_default->peers, link) { + settings = rdpPeer->peer->settings; + if (settings->DesktopWidth == (UINT32)target_mode->width && + settings->DesktopHeight == (UINT32)target_mode->height) + continue; + + if (!settings->DesktopResize) { + /* too bad this peer does not support desktop resize */ + weston_log("%s: desktop resize is not allowed\n", __func__); + rdpPeer->peer->Close(rdpPeer->peer); + } else { + settings->DesktopWidth = target_mode->width; + settings->DesktopHeight = target_mode->height; + rdpPeer->peer->update->DesktopResize(rdpPeer->peer->context); + } + } + } + return 0; +} + +static int +rdp_output_get_config(struct weston_output *base, + int *width, int *height, int *scale) +{ + struct rdp_output *output = to_rdp_output(base); + struct rdp_backend *rdpBackend = to_rdp_backend(base->compositor); + freerdp_peer *client = rdpBackend->rdp_peer; + struct weston_head *head; + + wl_list_for_each(head, &output->base.head_list, output_link) { + struct rdp_head *h = to_rdp_head(head); + + rdp_debug(rdpBackend, "get_config: attached head [%d]: make:%s, mode:%s, name:%s, (%p)\n", + h->index, head->make, head->model, head->name, head); + rdp_debug(rdpBackend, "get_config: attached head [%d]: x:%d, y:%d, width:%d, height:%d\n", + h->index, h->monitorMode.monitorDef.x, h->monitorMode.monitorDef.y, + h->monitorMode.monitorDef.width, h->monitorMode.monitorDef.height); + + /* In HiDef RAIL mode, get monitor resolution from RDP client if provided. */ + if (client && client->settings->HiDefRemoteApp) { + if (h->monitorMode.monitorDef.width && h->monitorMode.monitorDef.height) { + /* Return true client resolution (not adjusted by DPI) */ + *width = h->monitorMode.monitorDef.width; + *height = h->monitorMode.monitorDef.height; + *scale = h->monitorMode.scale; + } + break; // only one head per output in HiDef. } } return 0; @@ -522,22 +474,47 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) static int rdp_output_set_size(struct weston_output *base, - int width, int height) + int width, int height) { struct rdp_output *output = to_rdp_output(base); + struct rdp_backend *rdpBackend = to_rdp_backend(base->compositor); + freerdp_peer *client = rdpBackend->rdp_peer; struct weston_head *head; struct weston_mode *currentMode; struct weston_mode initMode; + BOOL is_preferred_mode = false; /* We can only be called once. */ assert(!output->base.current_mode); wl_list_for_each(head, &output->base.head_list, output_link) { + struct rdp_head *h = to_rdp_head(head); + weston_head_set_monitor_strings(head, "weston", "rdp", NULL); + rdp_debug(rdpBackend, "set_size: attached head [%d]: make:%s, mode:%s, name:%s, (%p)\n", + h->index, head->make, head->model, head->name, head); + rdp_debug(rdpBackend, "set_size: attached head [%d]: x:%d, y:%d, width:%d, height:%d\n", + h->index, h->monitorMode.monitorDef.x, h->monitorMode.monitorDef.y, + h->monitorMode.monitorDef.width, h->monitorMode.monitorDef.height); + /* This is a virtual output, so report a zero physical size. * It's better to let frontends/clients use their defaults. */ - weston_head_set_physical_size(head, 0, 0); + /* If MonitorDef has it, use it from MonitorDef */ + weston_head_set_physical_size(head, + h->monitorMode.monitorDef.attributes.physicalWidth, + h->monitorMode.monitorDef.attributes.physicalHeight); + + /* In HiDef RAIL mode, set this mode as preferred mode */ + if (client && client->settings->HiDefRemoteApp) { + if (h->monitorMode.monitorDef.width && h->monitorMode.monitorDef.height) { + /* given width/height must match with monitor's if provided */ + assert(width == h->monitorMode.monitorDef.width); + assert(height == h->monitorMode.monitorDef.height); + is_preferred_mode = true; + } + break; // only one head per output in HiDef. + } } wl_list_init(&output->peers); @@ -546,12 +523,17 @@ rdp_output_set_size(struct weston_output *base, initMode.width = width; initMode.height = height; initMode.refresh = RDP_MODE_FREQ; - currentMode = ensure_matching_mode(&output->base, &initMode); if (!currentMode) return -1; - output->base.current_mode = output->base.native_mode = currentMode; + currentMode->flags |= WL_OUTPUT_MODE_CURRENT; + if (is_preferred_mode) + currentMode->flags |= WL_OUTPUT_MODE_PREFERRED; + + output->base.current_mode = currentMode; + output->base.native_mode = currentMode; + output->base.native_scale = base->scale; output->base.start_repaint_loop = rdp_output_start_repaint_loop; output->base.repaint = rdp_output_repaint; @@ -572,27 +554,45 @@ rdp_output_enable(struct weston_output *base) const struct pixman_renderer_output_options options = { .use_shadow = true, }; + bool HiDefRemoteApp = false; + + if (b->rdp_peer && b->rdp_peer->settings->HiDefRemoteApp) + HiDefRemoteApp = true; + + if (HiDefRemoteApp) { + struct weston_head *eh; + wl_list_for_each(eh, &output->base.head_list, output_link) { + struct rdp_head *h = to_rdp_head(eh); + rdp_debug(b, "move head/output %s (%d,%d) -> (%d,%d)\n", + output->base.name, output->base.x, output->base.y, + h->monitorMode.rectWeston.x, + h->monitorMode.rectWeston.y); + weston_output_move(&output->base, + h->monitorMode.rectWeston.x, + h->monitorMode.rectWeston.y); + break; // must be only 1 head per output. + } + } else { + output->shadow_surface = pixman_image_create_bits(PIXMAN_x8r8g8b8, + output->base.current_mode->width, + output->base.current_mode->height, + NULL, + output->base.current_mode->width * 4); + if (output->shadow_surface == NULL) { + weston_log("Failed to create surface for frame buffer.\n"); + return -1; + } - output->shadow_surface = pixman_image_create_bits(PIXMAN_x8r8g8b8, - output->base.current_mode->width, - output->base.current_mode->height, - NULL, - output->base.current_mode->width * 4); - if (output->shadow_surface == NULL) { - weston_log("Failed to create surface for frame buffer.\n"); - return -1; - } - - if (pixman_renderer_output_create(&output->base, &options) < 0) { - pixman_image_unref(output->shadow_surface); - return -1; + if (pixman_renderer_output_create(&output->base, &options) < 0) { + pixman_image_unref(output->shadow_surface); + output->shadow_surface = NULL; + return -1; + } } loop = wl_display_get_event_loop(b->compositor->wl_display); output->finish_frame_timer = wl_event_loop_add_timer(loop, finish_frame_handler, output); - b->output = output; - return 0; } @@ -600,16 +600,17 @@ static int rdp_output_disable(struct weston_output *base) { struct rdp_output *output = to_rdp_output(base); - struct rdp_backend *b = to_rdp_backend(base->compositor); if (!output->base.enabled) return 0; - pixman_image_unref(output->shadow_surface); - pixman_renderer_output_destroy(&output->base); + if (output->shadow_surface) { + pixman_image_unref(output->shadow_surface); + pixman_renderer_output_destroy(&output->base); + output->shadow_surface = NULL; + } wl_event_source_remove(output->finish_frame_timer); - b->output = NULL; return 0; } @@ -621,51 +622,124 @@ rdp_output_destroy(struct weston_output *base) rdp_output_disable(&output->base); weston_output_release(&output->base); + wl_list_remove(&output->link); free(output); } +static int +rdp_output_attach_head(struct weston_output *output_base, + struct weston_head *head_base) +{ + struct rdp_backend *b = to_rdp_backend(output_base->compositor); + struct rdp_output *o = to_rdp_output(output_base); + struct rdp_head *h = to_rdp_head(head_base); + rdp_debug(b, "Head attaching: %s, index:%d, is_primary: %d\n", + head_base->name, h->index, h->monitorMode.monitorDef.is_primary); + if (!wl_list_empty(&output_base->head_list)) { + weston_log("attaching more than 1 head to single output (= clone) is not supported\n"); + return -1; + } + o->index = h->index; + if (h->monitorMode.monitorDef.is_primary) { + assert(b->output_default == NULL); + b->output_default = o; + } + return 0; +} + +static void +rdp_output_detach_head(struct weston_output *output_base, + struct weston_head *head_base) +{ + struct rdp_backend *b = to_rdp_backend(output_base->compositor); + struct rdp_head *h = to_rdp_head(head_base); + rdp_debug(b, "Head detaching: %s, index:%d, is_primary: %d\n", + head_base->name, h->index, h->monitorMode.monitorDef.is_primary); + if (h->monitorMode.monitorDef.is_primary) { + assert(b->output_default == to_rdp_output(output_base)); + b->output_default = NULL; + } +} + static struct weston_output * rdp_output_create(struct weston_compositor *compositor, const char *name) { + struct rdp_backend *backend = to_rdp_backend(compositor); struct rdp_output *output; output = zalloc(sizeof *output); if (output == NULL) return NULL; + wl_list_insert(&backend->output_list, &output->link); + weston_output_init(&output->base, compositor, name); output->base.destroy = rdp_output_destroy; output->base.disable = rdp_output_disable; output->base.enable = rdp_output_enable; - output->base.attach_head = NULL; + output->base.attach_head = rdp_output_attach_head; + output->base.detach_head = rdp_output_detach_head; weston_compositor_add_pending_output(&output->base, compositor); return &output->base; } -static int -rdp_head_create(struct weston_compositor *compositor, const char *name) +struct rdp_head * +rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp_monitor_mode *monitorMode) { + struct rdp_backend *b = to_rdp_backend(compositor); struct rdp_head *head; + char name[13] = {}; // 'rdp-' + 8 chars for hex uint32_t + NULL. head = zalloc(sizeof *head); if (!head) - return -1; + return NULL; + + head->index = b->head_index++; + if (monitorMode) { + head->monitorMode = *monitorMode; + pixman_region32_init_rect(&head->regionClient, + monitorMode->monitorDef.x, monitorMode->monitorDef.y, + monitorMode->monitorDef.width, monitorMode->monitorDef.height); + pixman_region32_init_rect(&head->regionWeston, + monitorMode->rectWeston.x, monitorMode->rectWeston.y, + monitorMode->rectWeston.width, monitorMode->rectWeston.height); + } else { + head->monitorMode.scale = 1.0f; + head->monitorMode.clientScale = 1; + pixman_region32_init(&head->regionClient); + pixman_region32_init(&head->regionWeston); + } + if (isPrimary) { + rdp_debug(b, "Default head is being added\n"); + b->head_default = head; + } + head->monitorMode.monitorDef.is_primary = isPrimary; + wl_list_insert(&b->head_list, &head->link); + sprintf(name, "rdp-%x", head->index); weston_head_init(&head->base, name); weston_head_set_connection_status(&head->base, true); weston_compositor_add_head(compositor, &head->base); - return 0; + return head; } -static void -rdp_head_destroy(struct rdp_head *head) +void +rdp_head_destroy(struct weston_compositor *compositor, struct rdp_head *head) { + struct rdp_backend *b = to_rdp_backend(compositor); weston_head_release(&head->base); + wl_list_remove(&head->link); + pixman_region32_fini(&head->regionWeston); + pixman_region32_fini(&head->regionClient); + if (b->head_default == head) { + rdp_debug(b, "Default head is being removed\n"); + b->head_default = NULL; + } free(head); } @@ -677,8 +751,17 @@ rdp_destroy(struct weston_compositor *ec) struct rdp_peers_item *rdp_peer, *tmp; int i; - wl_list_for_each_safe(rdp_peer, tmp, &b->output->peers, link) { - freerdp_peer* client = rdp_peer->peer; + if (b->output_default) { + wl_list_for_each_safe(rdp_peer, tmp, &b->output_default->peers, link) { + freerdp_peer* client = rdp_peer->peer; + + client->Disconnect(client); + freerdp_peer_context_free(client); + freerdp_peer_free(client); + } + } else if (b->rdp_peer) { + freerdp_peer* client = b->rdp_peer; + assert(client->settings->HiDefRemoteApp); client->Disconnect(client); freerdp_peer_context_free(client); @@ -689,10 +772,19 @@ rdp_destroy(struct weston_compositor *ec) if (b->listener_events[i]) wl_event_source_remove(b->listener_events[i]); + rdp_rail_destroy(b); + + if (b->debug) { + weston_log_scope_destroy(b->debug); + b->debug = NULL; + } + weston_compositor_shutdown(ec); wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - rdp_head_destroy(to_rdp_head(base)); + rdp_head_destroy(ec, to_rdp_head(base)); + + assert(wl_list_empty(&b->head_list)); freerdp_listener_free(b->listener); @@ -787,21 +879,50 @@ rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) static void rdp_peer_context_free(freerdp_peer* client, RdpPeerContext* context) { - int i; + unsigned i; if (!context) return; wl_list_remove(&context->item.link); - for (i = 0; i < MAX_FREERDP_FDS; i++) { + + for (i = 0; i < ARRAY_LENGTH(context->events); i++) { if (context->events[i]) wl_event_source_remove(context->events[i]); } + rdp_audioin_destroy(context); + + rdp_audio_destroy(context); + + rdp_clipboard_destroy(context); + + rdp_rail_peer_context_free(client, context); + + rdp_drdynvc_destroy(context); + + if (context->vcm) + WTSCloseServer(context->vcm); + + /* clear the peer, in RAIL mode, this allows new peer to connect */ + if (context->rdpBackend->rdp_peer == client) + context->rdpBackend->rdp_peer = NULL; + if (context->item.flags & RDP_PEER_ACTIVATED) { weston_seat_release_keyboard(context->item.seat); weston_seat_release_pointer(context->item.seat); - /* XXX we should weston_seat_release(context->item.seat); here - * but it would crash on reconnect */ + if (!client->settings->RemoteApplicationMode) { + /* XXX we should weston_seat_release(context->item.seat); here + * but it would crash on reconnect */ + } else { + /* Without understanding full details of above comments, but + in RAIL mode, only one peer per backend can be activated, + and no "deactivate_all" PDU to be used (since no client + side desktop resize is allowed), so safe to free seat here + to prevent possible memory leak. */ + weston_seat_release(context->item.seat); + context->item.seat = NULL; + context->item.flags &= ~RDP_PEER_ACTIVATED; + } } Stream_Free(context->encode_stream, TRUE); @@ -810,16 +931,25 @@ rdp_peer_context_free(freerdp_peer* client, RdpPeerContext* context) free(context->rfx_rects); } - static int rdp_client_activity(int fd, uint32_t mask, void *data) { freerdp_peer* client = (freerdp_peer *)data; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; if (!client->CheckFileDescriptor(client)) { weston_log("unable to checkDescriptor for %p\n", client); goto out_clean; } + + if (peerCtx && peerCtx->vcm) + { + if (!WTSVirtualChannelManagerCheckFileDescriptor(peerCtx->vcm)) { + weston_log("failed to check FreeRDP WTS VC file descriptor for %p\n", client); + goto out_clean; + } + } + return 0; out_clean: @@ -871,7 +1001,7 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_ITALIAN, "it", 0}, {KBD_ITALIAN_142, "it", "nodeadkeys"}, {KBD_JAPANESE, "jp", 0}, - {KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, "jp", "kana"}, + {KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, "jp", 0}, // default to alphabet input instead of "kana". {KBD_KOREAN, "kr", 0}, {KBD_KOREAN_INPUT_SYSTEM_IME_2000, "kr", "kr104"}, {KBD_DUTCH, "nl", 0}, @@ -965,9 +1095,30 @@ static const char *rdp_keyboard_types[] = { "pc102",/* 4: IBM enhanced (101- or 102-key) keyboard */ "", /* 5: Nokia 1050 and similar keyboards */ "", /* 6: Nokia 9140 and similar keyboards */ - "" /* 7: Japanese keyboard */ + "jp106" /* 7: Japanese keyboard */ /* alternative ja106 */ }; +void +convert_rdp_keyboard_to_xkb_rule_names( + UINT32 KeyboardType, + UINT32 KeyboardLayout, + struct xkb_rule_names *xkbRuleNames) +{ + int i; + memset(xkbRuleNames, 0, sizeof(*xkbRuleNames)); + if (KeyboardType <= 7) + xkbRuleNames->model = rdp_keyboard_types[KeyboardType]; + for (i = 0; rdp_keyboards[i].rdpLayoutCode; i++) { + if (rdp_keyboards[i].rdpLayoutCode == KeyboardLayout) { + xkbRuleNames->layout = rdp_keyboards[i].xkbLayout; + xkbRuleNames->variant = rdp_keyboards[i].xkbVariant; + weston_log("%s: matching layout=%s variant=%s\n", __FUNCTION__, + xkbRuleNames->layout, xkbRuleNames->variant); + break; + } + } +} + static BOOL xf_peer_activate(freerdp_peer* client) { @@ -980,16 +1131,15 @@ xf_peer_activate(freerdp_peer* client) struct xkb_rule_names xkbRuleNames; struct xkb_keymap *keymap; struct weston_output *weston_output; - int i; pixman_box32_t box; pixman_region32_t damage; char seat_name[50]; POINTER_SYSTEM_UPDATE pointer_system; + char *s; peerCtx = (RdpPeerContext *)client->context; b = peerCtx->rdpBackend; peersItem = &peerCtx->item; - output = b->output; settings = client->settings; if (!settings->SurfaceCommandsEnabled) { @@ -1002,61 +1152,153 @@ xf_peer_activate(freerdp_peer* client) settings->CompressionEnabled = FALSE; } - 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 (!settings->DesktopResize) { - /* peer does not support desktop resize */ - weston_log("%s: client doesn't support resizing, closing connection\n", __FUNCTION__); - return FALSE; + /* in RAIL mode, only one peer per backend can be activated */ + if (settings->RemoteApplicationMode) { + if (b->rdp_peer != client) { + weston_log("Another RAIL connection active, only one connection is allowed.\n"); + return FALSE; + } + + if (!settings->HiDefRemoteApp) { + /* HiDef is required for RAIL mode. Cookie-cutter window remoting is not supported. */ + weston_log("HiDef-RAIL is required for RAIL.\n"); + return FALSE; + } + + /* in HiDef RAIL mode, RAIL-shell must be used */ + if (b->rdprail_shell_api == NULL) { + weston_log("HiDef-RAIL is requested from client, but RAIL-shell is not used\n"); + return FALSE; + } + } + + /* override settings by env variables */ + s = getenv("WESTON_RDP_DISABLE_CLIPBOARD"); + if (s) { + if (strcmp(s, "true") == 0) + settings->RedirectClipboard = FALSE; + } + + s = getenv("WESTON_RDP_DISABLE_AUDIO_PLAYBACK"); + if (s) { + if (strcmp(s, "true") == 0) + settings->AudioPlayback = FALSE; + } + + s = getenv("WESTON_RDP_DISABLE_AUDIO_CAPTURE"); + if (s) { + if (strcmp(s, "true") == 0) + settings->AudioCapture = FALSE; + } + + if (settings->RemoteApplicationMode || + settings->RedirectClipboard || + settings->AudioPlayback || + settings->AudioCapture) { + + if (!peerCtx->vcm) { + weston_log("Virtual channel is required for RAIL, clipboard, audio playback/capture\n"); + goto error_exit; + } + + /* RAIL, clipboard, Audio playback/capture requires dynamic virtual channel */ + if (!rdp_drdynvc_init(client)) + goto error_exit; + + if (settings->RemoteApplicationMode) + if (!rdp_rail_peer_activate(client)) + goto error_exit; + + if (settings->AudioPlayback) + if (rdp_audio_init(peerCtx) != 0) + goto error_exit; + + if (settings->AudioCapture) + if (rdp_audioin_init(peerCtx) != 0) + goto error_exit; + } + + if (settings->HiDefRemoteApp) { + /* single monitor case, FreeRDP doesn't call AdjustMonitorsLayout callback, so call now */ + xf_peer_adjust_monitor_layout(client); + output = NULL; + weston_output = NULL; + } else { + /* multiple monitor is not supported in non-HiDef */ + assert(b->output_default); + output = b->output_default; + weston_log("%s: DesktopWidth:%d, DesktopHeigh:%d, DesktopScaleFactor:%d\n", __FUNCTION__, + settings->DesktopWidth, settings->DesktopHeight, settings->DesktopScaleFactor); + 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 (!settings->DesktopResize) { + /* peer does not support desktop resize */ + weston_log("%s: client doesn't support resizing, closing connection\n", __FUNCTION__); + goto error_exit; + } else { + settings->DesktopWidth = output->base.width; + settings->DesktopHeight = output->base.height; + client->update->DesktopResize(client->context); + } } else { - settings->DesktopWidth = output->base.width; - settings->DesktopHeight = output->base.height; - client->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_matching_mode(&output->base, &new_mode); - if (!target_mode) { - weston_log("client mode not found\n"); - return FALSE; + /* 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_matching_mode(&output->base, &new_mode); + if (!target_mode) { + weston_log("client mode not found\n"); + goto error_exit; + } + weston_output_mode_set_native(&output->base, target_mode, + output->base.scale ? output->base.scale : 1); + weston_head_set_physical_size(&b->head_default->base, + settings->DesktopPhysicalWidth, + settings->DesktopPhysicalHeight); } - weston_output_mode_set_native(&output->base, target_mode, 1); - output->base.width = new_mode.width; - output->base.height = new_mode.height; } + pixman_region32_clear(&peerCtx->regionClientHeads); + pixman_region32_init_rect(&peerCtx->regionClientHeads, + 0, 0, settings->DesktopWidth, settings->DesktopHeight); + + pixman_region32_clear(&b->head_default->regionClient); + pixman_region32_init_rect(&b->head_default->regionClient, + 0, 0, settings->DesktopWidth, settings->DesktopHeight); + + weston_output = &output->base; + + weston_log("%s: OutputWidth:%d, OutputHeight:%d, OutputScaleFactor:%d\n", __FUNCTION__, + weston_output->width, weston_output->height, weston_output->scale); + + pixman_region32_clear(&peerCtx->regionWestonHeads); + pixman_region32_init_rect(&peerCtx->regionWestonHeads, + 0, 0, weston_output->width, weston_output->height); + + pixman_region32_clear(&b->head_default->regionWeston); + pixman_region32_init_rect(&b->head_default->regionWeston, + 0, 0, weston_output->width, weston_output->height); + + RFX_RESET(peerCtx->rfx_context, weston_output->width, weston_output->height); + NSC_RESET(peerCtx->nsc_context, weston_output->width, weston_output->height); } - weston_output = &output->base; - RFX_RESET(peerCtx->rfx_context, weston_output->width, weston_output->height); - NSC_RESET(peerCtx->nsc_context, weston_output->width, weston_output->height); + if (settings->RemoteApplicationMode) + rdp_rail_sync_window_status(client); if (peersItem->flags & RDP_PEER_ACTIVATED) return TRUE; /* when here it's the first reactivation, we need to setup a little more */ - weston_log("kbd_layout:0x%x kbd_type:0x%x kbd_subType:0x%x kbd_functionKeys:0x%x\n", + rdp_debug(b, "kbd_layout:0x%x kbd_type:0x%x kbd_subType:0x%x kbd_functionKeys:0x%x\n", settings->KeyboardLayout, settings->KeyboardType, settings->KeyboardSubType, settings->KeyboardFunctionKey); - memset(&xkbRuleNames, 0, sizeof(xkbRuleNames)); - if (settings->KeyboardType <= 7) - xkbRuleNames.model = rdp_keyboard_types[settings->KeyboardType]; - for (i = 0; rdp_keyboards[i].rdpLayoutCode; i++) { - if (rdp_keyboards[i].rdpLayoutCode == settings->KeyboardLayout) { - xkbRuleNames.layout = rdp_keyboards[i].xkbLayout; - xkbRuleNames.variant = rdp_keyboards[i].xkbVariant; - weston_log("%s: matching layout=%s variant=%s\n", __FUNCTION__, - xkbRuleNames.layout, xkbRuleNames.variant); - break; - } - } + convert_rdp_keyboard_to_xkb_rule_names(settings->KeyboardType, + settings->KeyboardLayout, + &xkbRuleNames); keymap = NULL; if (xkbRuleNames.layout) { @@ -1073,33 +1315,51 @@ xf_peer_activate(freerdp_peer* client) if (!peersItem->seat) { xkb_keymap_unref(keymap); weston_log("unable to create a weston_seat\n"); - return FALSE; + goto error_exit; } weston_seat_init(peersItem->seat, b->compositor, seat_name); weston_seat_init_keyboard(peersItem->seat, keymap); xkb_keymap_unref(keymap); weston_seat_init_pointer(peersItem->seat); + peersItem->seat->led_update = rdp_peer_seat_led_update; + + /* Initialize RDP clipboard after seat is initialized */ + if (settings->RedirectClipboard) + if (rdp_clipboard_init(client) != 0) + goto error_exit; peersItem->flags |= RDP_PEER_ACTIVATED; - /* disable pointer on the client side */ - pointer = client->update->pointer; - pointer_system.type = SYSPTR_NULL; - pointer->PointerSystem(client->context, &pointer_system); + if (!settings->HiDefRemoteApp && output) { + /* disable pointer on the client side */ + pointer = client->update->pointer; + pointer_system.type = SYSPTR_NULL; + pointer->PointerSystem(client->context, &pointer_system); - /* sends a full refresh */ - box.x1 = 0; - box.y1 = 0; - box.x2 = output->base.width; - box.y2 = output->base.height; - pixman_region32_init_with_extents(&damage, &box); + /* sends a full refresh */ + box.x1 = 0; + box.y1 = 0; + box.x2 = output->base.width; + box.y2 = output->base.height; + pixman_region32_init_with_extents(&damage, &box); - rdp_peer_refresh_region(&damage, client); + rdp_peer_refresh_region(&damage, client); - pixman_region32_fini(&damage); + pixman_region32_fini(&damage); + } return TRUE; + +error_exit: + + rdp_clipboard_destroy(peerCtx); + rdp_audioin_destroy(peerCtx); + rdp_audio_destroy(peerCtx); + rdp_rail_peer_context_free(client, peerCtx); + rdp_drdynvc_destroy(peerCtx); + + return FALSE; } static BOOL @@ -1108,23 +1368,104 @@ 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 to the coordinate based on primary monitor's upper-left as (0,0) */ + sx = x + peerContext->regionClientHeads.extents.x1; + sy = y + peerContext->regionClientHeads.extents.y1; + + /* 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, NULL, NULL)) { + 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) +{ + struct rdp_backend *b = peerContext->rdpBackend; + + rdp_debug_verbose(b, "RDP mouse input%s: (%d, %d): flags:%x: ", is_ex ? "_ex" : "", x, y, flags); + if (is_ex) { + if (flags & PTR_XFLAGS_DOWN) + rdp_debug_verbose_continue(b, "DOWN "); + if (flags & PTR_XFLAGS_BUTTON1) + rdp_debug_verbose_continue(b, "XBUTTON1 "); + if (flags & PTR_XFLAGS_BUTTON2) + rdp_debug_verbose_continue(b, "XBUTTON2 "); + } else { + if (flags & PTR_FLAGS_WHEEL) + rdp_debug_verbose_continue(b, "WHEEL "); + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + rdp_debug_verbose_continue(b, "WHEEL_NEGATIVE "); + if (flags & PTR_FLAGS_MOVE) + rdp_debug_verbose_continue(b, "MOVE "); + if (flags & PTR_FLAGS_DOWN) + rdp_debug_verbose_continue(b, "DOWN "); + if (flags & PTR_FLAGS_BUTTON1) + rdp_debug_verbose_continue(b, "BUTTON1 "); + if (flags & PTR_FLAGS_BUTTON2) + rdp_debug_verbose_continue(b, "BUTTON2 "); + if (flags & PTR_FLAGS_BUTTON3) + rdp_debug_verbose_continue(b, "BUTTON3 "); + } + rdp_debug_verbose_continue(b, "\n"); +} + +static void +rdp_validate_button_state(RdpPeerContext *peerContext, bool pressed, uint32_t* button) +{ + struct rdp_backend *b = peerContext->rdpBackend; + assert(*button >= BTN_LEFT && *button <= BTN_EXTRA); + uint32_t index = *button - BTN_LEFT; + assert(index < ARRAY_LENGTH(peerContext->button_state)); + if (pressed == peerContext->button_state[index]) { + rdp_debug(b, "%s: inconsistent button state button:%d (index:%d) pressed:%d\n", + __func__, *button, index, pressed); + /* ignore button input */ + *button = 0; + } else { + peerContext->button_state[index] = pressed; + } + return; +} + static FREERDP_CB_RET_TYPE 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; - if (flags & PTR_FLAGS_MOVE) { - output = peerContext->rdpBackend->output; - if (x < output->base.width && y < output->base.height) { - weston_compositor_get_time(&time); - notify_motion_absolute(peerContext->item.seat, &time, - x, y); + dump_mouseinput(peerContext, flags, x, y, false); + + /* Per RDP spec, the x,y position is valid on all input mouse messages, + * except for PTR_FLAGS_WHEEL and PTR_FLAGS_HWHEEL event. Take the opportunity + * to resample our x,y position even when PTR_FLAGS_MOVE isn't explicitly set, + * for example a button down/up only notification, to ensure proper sync with + * the RDP client. + */ + if (!(flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))) { + if (rdp_translate_and_notify_mouse_position(peerContext, x, y)) need_frame = true; - } } if (flags & PTR_FLAGS_BUTTON1) @@ -1134,6 +1475,12 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) else if (flags & PTR_FLAGS_BUTTON3) button = BTN_MIDDLE; + if (button) + rdp_validate_button_state( + peerContext, + flags & PTR_FLAGS_DOWN ? true : false, + &button); + if (button) { weston_compositor_get_time(&time); notify_button(peerContext->item.seat, &time, button, @@ -1177,42 +1524,84 @@ static FREERDP_CB_RET_TYPE xf_extendedMouseEvent(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; - output = peerContext->rdpBackend->output; - if (x < output->base.width && y < output->base.height) { + dump_mouseinput(peerContext, flags, x, y, true); + + if (rdp_translate_and_notify_mouse_position(peerContext, x, y)) + need_frame = true; + + if (flags & PTR_XFLAGS_BUTTON1) + button = BTN_SIDE; + else if (flags & PTR_XFLAGS_BUTTON2) + button = BTN_EXTRA; + + if (button) + rdp_validate_button_state( + peerContext, + flags & PTR_XFLAGS_DOWN ? true : false, + &button); + + if (button) { weston_compositor_get_time(&time); - notify_motion_absolute(peerContext->item.seat, &time, x, y); + notify_button(peerContext->item.seat, &time, button, + (flags & PTR_XFLAGS_DOWN) ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED + ); + need_frame = true; } + if (need_frame) + notify_pointer_frame(peerContext->item.seat); + FREERDP_CB_RETURN(TRUE); } - static FREERDP_CB_RET_TYPE xf_input_synchronize_event(rdpInput *input, UINT32 flags) { freerdp_peer *client = input->context->peer; RdpPeerContext *peerCtx = (RdpPeerContext *)input->context; - struct rdp_output *output = peerCtx->rdpBackend->output; + struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_output *output = b->output_default; pixman_box32_t box; pixman_region32_t damage; - /* sends a full refresh */ - box.x1 = 0; - box.y1 = 0; - box.x2 = output->base.width; - box.y2 = output->base.height; - pixman_region32_init_with_extents(&damage, &box); + rdp_debug_verbose(b, "input_synchronize_event: ScrLk:%d, NumLk:%d, CapsLk:%d, KanaLk:%d\n", + flags & KBD_SYNC_SCROLL_LOCK ? 1 : 0, + flags & KBD_SYNC_NUM_LOCK ? 1 : 0, + flags & KBD_SYNC_CAPS_LOCK ? 1 : 0, + flags & KBD_SYNC_KANA_LOCK ? 1 : 0); + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(peerCtx->item.seat); + if (keyboard) { + uint32_t value = 0; + if (flags & KBD_SYNC_NUM_LOCK) + value |= WESTON_NUM_LOCK; + if (flags & KBD_SYNC_CAPS_LOCK) + value |= WESTON_CAPS_LOCK; + weston_keyboard_set_locks(keyboard, + WESTON_NUM_LOCK|WESTON_CAPS_LOCK, + value); + } + + if (!client->settings->HiDefRemoteApp && output) { + /* sends a full refresh */ + box.x1 = 0; + box.y1 = 0; + box.x2 = output->base.width; + box.y2 = output->base.height; + pixman_region32_init_with_extents(&damage, &box); + + rdp_peer_refresh_region(&damage, client); - rdp_peer_refresh_region(&damage, client); + pixman_region32_fini(&damage); + } - pixman_region32_fini(&damage); FREERDP_CB_RETURN(TRUE); } - static FREERDP_CB_RET_TYPE xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) { @@ -1275,12 +1664,24 @@ xf_suppress_output(rdpContext *context, BYTE allow, const RECTANGLE_16 *area) FREERDP_CB_RETURN(TRUE); } +static BOOL +using_session_tls(struct rdp_backend *b) +{ + return b->server_cert_content && b->server_key_content; +} + +static BOOL +is_tls_enabled(struct rdp_backend *b) +{ + return (b->server_cert && b->server_key) || using_session_tls(b); +} + static int rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) { - int rcount = 0; - void *rfds[MAX_FREERDP_FDS]; - int i, fd; + unsigned i, rcount = 0; + void *rfds[MAX_FREERDP_FDS+1]; // +1 for WTSVirtualChannelManagerGetFileDescriptor. + int fd; struct wl_event_loop *loop; rdpSettings *settings; rdpInput *input; @@ -1298,9 +1699,14 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) /* configure security settings */ if (b->rdp_key) settings->RdpKeyFile = strdup(b->rdp_key); - if (b->tls_enabled) { - settings->CertificateFile = strdup(b->server_cert); - settings->PrivateKeyFile = strdup(b->server_key); + if (is_tls_enabled(b)) { + if (using_session_tls(b)) { + settings->CertificateContent = strdup(b->server_cert_content); + settings->PrivateKeyContent = strdup(b->server_key_content); + } else { + settings->CertificateFile = strdup(b->server_cert); + settings->PrivateKeyFile = strdup(b->server_key); + } } else { settings->TlsSecurity = FALSE; } @@ -1315,14 +1721,26 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) settings->OsMinorType = OSMINORTYPE_PSEUDO_XSERVER; settings->ColorDepth = 32; settings->RefreshRect = TRUE; - settings->RemoteFxCodec = TRUE; + settings->RemoteFxCodec = FALSE; // TODO: settings->NSCodec = TRUE; settings->FrameMarkerCommandEnabled = TRUE; settings->SurfaceFrameMarkerEnabled = TRUE; + settings->RemoteApplicationMode = TRUE; + settings->RemoteApplicationSupportLevel = + RAIL_LEVEL_SUPPORTED | + RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED | + RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED | + RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED | + RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED; + settings->SupportGraphicsPipeline = TRUE; + settings->SupportMonitorLayoutPdu = TRUE; + settings->RedirectClipboard = TRUE; + settings->HasExtendedMouseEvent = TRUE; client->Capabilities = xf_peer_capabilities; client->PostConnect = xf_peer_post_connect; client->Activate = xf_peer_activate; + client->AdjustMonitorsLayout = xf_peer_adjust_monitor_layout; client->update->SuppressOutput = (pSuppressOutput)xf_suppress_output; @@ -1338,6 +1756,18 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) goto error_initialize; } + PWtsApiFunctionTable fn = FreeRDP_InitWtsApi(); + WTSRegisterWtsApiFunctionTable(fn); + peerCtx->vcm = WTSOpenServerA((LPSTR)peerCtx); + if (peerCtx->vcm) { + WTSVirtualChannelManagerGetFileDescriptor(peerCtx->vcm, rfds, &rcount); + peerCtx->eventVcm = WTSVirtualChannelManagerGetEventHandle(peerCtx->vcm); + /* event must be valid when server is successfully opened */ + assert(peerCtx->eventVcm); + } else { + weston_log("WTSOpenServer is failed! continue without virtual channel.\n"); + } + loop = wl_display_get_event_loop(b->compositor->wl_display); for (i = 0; i < rcount; i++) { fd = (int)(long)(rfds[i]); @@ -1345,12 +1775,37 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) peerCtx->events[i] = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, rdp_client_activity, client); } - for ( ; i < MAX_FREERDP_FDS; i++) + for ( ; i < ARRAY_LENGTH(peerCtx->events); i++) peerCtx->events[i] = 0; - wl_list_insert(&b->output->peers, &peerCtx->item.link); + if (!rdp_rail_peer_init(client, peerCtx)) + goto error_peer_initialize; + + /* This tracks the single peer connected. This field only used for RAIL mode + and, with RAIL mode, there can be only one peer per backend, and that + will be validated at xf_peer_activate once connection mode is reflected + in settings, and this will be reset to NULL when the peer disconnects */ + if (!b->rdp_peer) + b->rdp_peer = client; + + /* chain peers at default_output */ + if (b->output_default) + wl_list_insert(&b->output_default->peers, &peerCtx->item.link); return 0; +error_peer_initialize: + for (i = 0; i < ARRAY_LENGTH(peerCtx->events); i++) { + if (peerCtx->events[i]) { + wl_event_source_remove(peerCtx->events[i]); + peerCtx->events[i] = NULL; + } + } + if (peerCtx->vcm) { + WTSCloseServer(peerCtx->vcm); + peerCtx->eventVcm = NULL; + peerCtx->vcm = NULL; + } + error_initialize: client->Close(client); return -1; @@ -1369,10 +1824,159 @@ rdp_incoming_peer(freerdp_listener *instance, freerdp_peer *client) FREERDP_CB_RETURN(TRUE); } +#if HAVE_OPENSSL +static void +rdp_generate_session_tls(struct rdp_backend *b) +{ + EVP_PKEY *pkey; + BIGNUM *rsa_bn; + RSA *rsa; + BIO *bio, *bio_x509; + BUF_MEM *mem, *mem_x509; + X509 *x509; + long serial = 0; + ASN1_TIME *before, *after; + X509_NAME *name; + X509V3_CTX ctx; + X509_EXTENSION *ext; + const EVP_MD *md; + const char session_name[] = "weston"; + + pkey = EVP_PKEY_new(); + assert(pkey != NULL); + rsa_bn = BN_new(); + assert(rsa_bn != NULL); + rsa = RSA_new(); + assert(rsa != NULL); + BN_set_word(rsa_bn, RSA_F4); + assert(RSA_generate_key_ex(rsa, 2048, rsa_bn, NULL) == 1); + BN_clear_free(rsa_bn); + EVP_PKEY_assign_RSA(pkey, rsa); + + bio = BIO_new(BIO_s_mem()); + assert(bio != NULL); + assert(PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, NULL, NULL) == 1); + BIO_get_mem_ptr(bio, &mem); + b->server_key_content = (char *)calloc(mem->length+1, 1); + memcpy(b->server_key_content, mem->data, mem->length); + BIO_free_all(bio); + + x509 = X509_new(); + X509_set_version(x509, 2); + RAND_bytes((unsigned char *)&serial, sizeof(serial)); + ASN1_INTEGER_set(X509_get_serialNumber(x509), serial); + before = X509_getm_notBefore(x509); + X509_gmtime_adj(before, 0); + after = X509_getm_notAfter(x509); + X509_gmtime_adj(after, 60); /* good for a minute */ + X509_set_pubkey(x509, pkey); + name = X509_get_subject_name(x509); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_UTF8, session_name, sizeof(session_name)-1, -1, 0); + X509_set_issuer_name(x509, name); + X509V3_set_ctx_nodb(&ctx); + X509V3_set_ctx(&ctx, x509, x509, NULL, NULL, 0); + ext = X509V3_EXT_conf_nid(NULL, &ctx, NID_ext_key_usage, "serverAuth"); + assert(ext); + X509_add_ext(x509, ext, -1); + X509_EXTENSION_free(ext); + md = EVP_sha256(); + assert(X509_sign(x509, pkey, md) != 0); + + bio_x509 = BIO_new(BIO_s_mem()); + assert(bio_x509 != NULL); + PEM_write_bio_X509(bio, x509); + BIO_get_mem_ptr(bio_x509, &mem_x509); + b->server_cert_content = (char *)calloc(mem_x509->length+1, 1); + memcpy(b->server_cert_content, mem_x509->data, mem_x509->length); + //weston_log("%s", b->server_cert_content); + BIO_free_all(bio_x509); + + X509_free(x509); + EVP_PKEY_free(pkey); +} +#endif + static const struct weston_rdp_output_api api = { rdp_output_set_size, + rdp_output_get_config, }; +static int create_vsock_fd(int port) +{ + struct sockaddr_vm socket_address; + + int socket_fd = socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC, 0); + + if (socket_fd < 0) + { + weston_log("Fail to create vsocket"); + return -1; + } + + const int bufferSize = 65536; + + if (setsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)) < 0) + { + weston_log("Fail to setsockopt SO_SNDBUF"); + return -1; + } + + if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)) < 0) + { + weston_log("Fail to setsockopt SO_RCVBUF"); + return -1; + } + + memset(&socket_address, 0, sizeof(socket_address)); + + socket_address.svm_family = AF_VSOCK; + socket_address.svm_cid = VMADDR_CID_ANY; + socket_address.svm_port = port; + + socklen_t socket_addr_size = sizeof(socket_address); + + if (bind(socket_fd, (const struct sockaddr *)&socket_address, socket_addr_size) < 0) + { + weston_log("Fail to bind socket to address socket"); + close(socket_fd); + return -2; + } + + int status = listen(socket_fd, 1); + + if (status != 0) + { + weston_log("Fail to listen on socket"); + close(socket_fd); + return -4; + } + return socket_fd; +} + +static int use_vsock_fd(int port) +{ + char *fd_str = getenv("USE_VSOCK"); + if (!fd_str) { + return -1; + } + + int fd; + if (strlen(fd_str) != 0) { + fd = atoi(fd_str); + weston_log("Using external fd for incoming connections: %d\n", fd); + if (fd == 0) { + fd = -1; + } + } else { + fd = create_vsock_fd(port); + weston_log("Created vsock for external connections: %d\n", fd); + } + + return fd; +} + +extern char **environ; /* defined by libc */ + static struct rdp_backend * rdp_backend_create(struct weston_compositor *compositor, struct weston_rdp_backend_config *config) @@ -1381,28 +1985,74 @@ rdp_backend_create(struct weston_compositor *compositor, char *fd_str; char *fd_tail; int fd, ret; + struct rdp_output *output; + char *rdp_debug_level; + int i; b = zalloc(sizeof *b); if (b == NULL) return NULL; + b->compositor_tid = rdp_get_tid(); b->compositor = compositor; b->base.destroy = rdp_destroy; b->base.create_output = rdp_output_create; b->rdp_key = config->rdp_key ? strdup(config->rdp_key) : NULL; + b->server_cert = config->server_cert ? strdup(config->server_cert) : NULL; + b->server_key = config->server_key ? strdup(config->server_key) : NULL; b->no_clients_resize = config->no_clients_resize; b->force_no_compression = config->force_no_compression; + wl_list_init(&b->output_list); + wl_list_init(&b->head_list); + b->head_index = 0; + + b->debug = weston_log_ctx_add_log_scope(compositor->weston_log_ctx, + "rdp-backend", + "Debug messages from RDP backend\n", + NULL, NULL, NULL); + rdp_debug_level = getenv("WESTON_RDP_DEBUG_LEVEL"); + if (rdp_debug_level) { + b->debugLevel = atoi(rdp_debug_level); + if (b->debugLevel > RDP_DEBUG_LEVEL_VERBOSE) + b->debugLevel = RDP_DEBUG_LEVEL_VERBOSE; + } else { + b->debugLevel = RDP_DEBUG_LEVEL_DEFAULT; + } + weston_log("RDP backend: WESTON_RDP_DEBUG_LEVEL: %d.\n", b->debugLevel); + /* After here, rdp_debug() is ready to be used */ + + /* For diagnostics purpose, dump all enviroment to log file */ + /* TODO: privacy review */ + rdp_debug(b, "RDP backend: Environment dump - start\n"); + for (i = 0; environ[i]; i++) { + rdp_debug(b, " %s\n", environ[i]); + } + rdp_debug(b, "RDP backend: Environment dump - end\n"); + compositor->backend = &b->base; - /* activate TLS only if certificate/key are available */ - if (config->server_cert && config->server_key) { - weston_log("TLS support activated\n"); - b->server_cert = strdup(config->server_cert); - b->server_key = strdup(config->server_key); - if (!b->server_cert || !b->server_key) + fd = use_vsock_fd(config->port); + /* if we are using VSOCK to connect to the rdp backend, we don't need to enforce the TLS + encryption, since FreeRDP will consider AF_UNIX and AF_VSOCK as a local connection */ + if (fd <= 0 || config->env_socket) + { + if (!b->rdp_key && (!b->server_cert || !b->server_key)) { + #if HAVE_OPENSSL + rdp_generate_session_tls(b); + #else + weston_log("the RDP compositor requires keys and an optional certificate for RDP or TLS security (" + "--rdp4-key or --rdp-tls-cert/--rdp-tls-key)\n"); + goto err_free_strings; + #endif + } + + /* activate TLS only if certificate/key are available */ + if (is_tls_enabled(b)) { + weston_log("TLS support activated\n"); + } else if (!b->rdp_key) { goto err_free_strings; - b->tls_enabled = 1; + } } if (weston_compositor_set_presentation_clock_software(compositor) < 0) @@ -1411,18 +2061,30 @@ rdp_backend_create(struct weston_compositor *compositor, if (pixman_renderer_init(compositor) < 0) goto err_compositor; - if (rdp_head_create(compositor, "rdp") < 0) + if (rdp_head_create(compositor, TRUE, NULL) == NULL) goto err_compositor; + if (rdp_rail_backend_create(b) < 0) + goto err_output; + compositor->capabilities |= WESTON_CAP_ARBITRARY_MODES; if (!config->env_socket) { b->listener = freerdp_listener_new(); b->listener->PeerAccepted = rdp_incoming_peer; b->listener->param4 = b; - if (!b->listener->Open(b->listener, config->bind_address, config->port)) { - weston_log("unable to bind rdp socket\n"); - goto err_listener; + if (fd > 0) { + weston_log("Using VSOCK for incoming connections: %d\n", fd); + + if (!b->listener->OpenFromSocket(b->listener, fd)) { + weston_log("unable opem from socket fd: %d\n", fd); + goto err_listener; + } + } else { + if (!b->listener->Open(b->listener, config->bind_address, config->port)) { + weston_log("unable to bind rdp socket\n"); + goto err_listener; + } } if (rdp_implant_listener(b, b->listener) < 0) @@ -1437,7 +2099,7 @@ rdp_backend_create(struct weston_compositor *compositor, fd = strtoul(fd_str, &fd_tail, 10); if (errno != 0 || fd_tail == fd_str || *fd_tail != '\0' - || rdp_peer_init(freerdp_peer_new(fd), b)) + || rdp_peer_init(freerdp_peer_new(fd), b)) goto err_output; } @@ -1454,13 +2116,21 @@ rdp_backend_create(struct weston_compositor *compositor, err_listener: freerdp_listener_free(b->listener); err_output: - weston_output_release(&b->output->base); + wl_list_for_each(output, &b->output_list, link) { + weston_output_release(&output->base); + } + if (b->head_default) { + rdp_head_destroy(compositor, b->head_default); + assert(b->head_default == NULL); + } err_compositor: weston_compositor_shutdown(compositor); err_free_strings: free(b->rdp_key); free(b->server_cert); free(b->server_key); + free(b->server_cert_content); + free(b->server_key_content); free(b); return NULL; } @@ -1480,7 +2150,7 @@ config_init_to_defaults(struct weston_rdp_backend_config *config) WL_EXPORT int weston_backend_init(struct weston_compositor *compositor, - struct weston_backend_config *config_base) + struct weston_backend_config *config_base) { struct rdp_backend *b; struct weston_rdp_backend_config config = {{ 0, }}; @@ -1493,8 +2163,8 @@ weston_backend_init(struct weston_compositor *compositor, weston_log("using FreeRDP version %d.%d.%d\n", major, minor, revision); if (config_base == NULL || - config_base->struct_version != WESTON_RDP_BACKEND_CONFIG_VERSION || - config_base->struct_size > sizeof(struct weston_rdp_backend_config)) { + config_base->struct_version != WESTON_RDP_BACKEND_CONFIG_VERSION || + config_base->struct_size > sizeof(struct weston_rdp_backend_config)) { weston_log("RDP backend config structure is invalid\n"); return -1; } @@ -1502,12 +2172,6 @@ weston_backend_init(struct weston_compositor *compositor, config_init_to_defaults(&config); memcpy(&config, config_base, config_base->struct_size); - if (!config.rdp_key && (!config.server_cert || !config.server_key)) { - weston_log("the RDP compositor requires keys and an optional certificate for RDP or TLS security (" - "--rdp4-key or --rdp-tls-cert/--rdp-tls-key)\n"); - return -1; - } - b = rdp_backend_create(compositor, &config); if (b == NULL) return -1; diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h new file mode 100644 index 000000000..ab9ed1e9f --- /dev/null +++ b/libweston/backend-rdp/rdp.h @@ -0,0 +1,625 @@ +/* + * 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. + */ + +#ifndef RDP_H +#define RDP_H + +#if HAVE_FREERDP_VERSION_H +#include +#else +/* assume it's a early 1.1 version */ +#define FREERDP_VERSION_MAJOR 1 +#define FREERDP_VERSION_MINOR 1 +#define FREERDP_VERSION_REVISION 0 +#endif + +#define FREERDP_VERSION_NUMBER ((FREERDP_VERSION_MAJOR * 0x10000) + \ + (FREERDP_VERSION_MINOR * 0x100) + FREERDP_VERSION_REVISION) + + +#if FREERDP_VERSION_NUMBER >= 0x10201 +#define HAVE_SKIP_COMPRESSION +#endif + +#if FREERDP_VERSION_NUMBER < 0x10202 +# define FREERDP_CB_RET_TYPE void +# define FREERDP_CB_RETURN(V) return +# define NSC_RESET(C, W, H) +# define RFX_RESET(C, W, H) do { rfx_context_reset(C); C->width = W; C->height = H; } while(0) +#else +#if FREERDP_VERSION_MAJOR >= 2 +# define NSC_RESET(C, W, H) nsc_context_reset(C, W, H) +# define RFX_RESET(C, W, H) rfx_context_reset(C, W, H) +#else +# define NSC_RESET(C, W, H) do { nsc_context_reset(C); C->width = W; C->height = H; } while(0) +# define RFX_RESET(C, W, H) do { rfx_context_reset(C); C->width = W; C->height = H; } while(0) +#endif +#define FREERDP_CB_RET_TYPE BOOL +#define FREERDP_CB_RETURN(V) return TRUE +#endif + +#ifdef HAVE_SURFACE_BITS_BMP +#define SURFACE_BPP(cmd) cmd.bmp.bpp +#define SURFACE_CODECID(cmd) cmd.bmp.codecID +#define SURFACE_WIDTH(cmd) cmd.bmp.width +#define SURFACE_HEIGHT(cmd) cmd.bmp.height +#define SURFACE_BITMAP_DATA(cmd) cmd.bmp.bitmapData +#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bmp.bitmapDataLength +#else +#define SURFACE_BPP(cmd) cmd.bpp +#define SURFACE_CODECID(cmd) cmd.codecID +#define SURFACE_WIDTH(cmd) cmd.width +#define SURFACE_HEIGHT(cmd) cmd.height +#define SURFACE_BITMAP_DATA(cmd) cmd.bitmapData +#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bitmapDataLength +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_FREERDP_GFXREDIR_H +#include +#endif // HAVE_FREERDP_GFXREDIR_H +#ifdef HAVE_FREERDP_RDPAPPLIST_H +#include +#endif // HAVE_FREERDP_RDPAPPLIST_H + +#include +#include +#include + +#include "hash.h" +#include "backend.h" + +#include "shared/helpers.h" +#include "shared/string-helpers.h" +#include "shared/timespec-util.h" + +#define MAX_FREERDP_FDS 32 +#define DEFAULT_AXIS_STEP_DISTANCE 10 +#define RDP_MODE_FREQ 60 * 1000 +#define RDP_MAX_MONITOR 16 // RDP max monitors. + +#if FREERDP_VERSION_MAJOR >= 2 && defined(PIXEL_FORMAT_BGRA32) && !defined(PIXEL_FORMAT_B8G8R8A8) + /* The RDP API is truly wonderful: the pixel format definition changed + * from BGRA32 to B8G8R8A8, but some versions ship with a definition of + * PIXEL_FORMAT_BGRA32 which doesn't actually build. Try really, really, + * hard to find one which does. */ +# define DEFAULT_PIXEL_FORMAT PIXEL_FORMAT_BGRA32 +#else +# define DEFAULT_PIXEL_FORMAT RDP_PIXEL_FORMAT_B8G8R8A8 +#endif + +struct rdp_output; +struct rdp_clipboard_data_source; + +struct rdp_id_manager { + UINT32 id; + UINT32 id_low_limit; + UINT32 id_high_limit; + UINT32 id_total; + UINT32 id_used; + struct hash_table *hash_table; +}; + +struct rdp_loop_event_source { + struct wl_list link; + struct wl_event_source *event_source; +}; + +struct rdp_backend { + struct weston_backend base; + struct weston_compositor *compositor; + + freerdp_listener *listener; + struct wl_event_source *listener_events[MAX_FREERDP_FDS]; + struct rdp_output *output_default; // default output created at backend initialize + struct rdp_head *head_default; // default head created at backend initialize + struct wl_list output_list; // rdp_output::link + struct wl_list head_list; // rdp_head::link + struct wl_list head_pending_list; // used during monitor layout change. + struct wl_list head_move_pending_list; // used during monitor layout change. + uint32_t head_index; + struct weston_log_scope *debug; + uint32_t debugLevel; + + char *server_cert; + char *server_key; + char *server_cert_content; + char *server_key_content; + char *rdp_key; + int no_clients_resize; + int force_no_compression; + + const struct weston_rdprail_shell_api *rdprail_shell_api; + void *rdprail_shell_context; + char *rdprail_shell_name; + bool enable_copy_warning_title; + bool enable_distro_name_title; + + freerdp_peer *rdp_peer; // this points a single instance of RAIL RDP peer. + pid_t compositor_tid; + + struct weston_binding *debug_binding_M; + struct weston_binding *debug_binding_W; + + struct wl_listener create_window_listener; + + bool enable_hi_dpi_support; + bool enable_fractional_hi_dpi_support; + uint32_t debug_desktop_scaling_factor; /* must be between 100 to 500 */ + +#if defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) + void *libFreeRDPServer; +#endif // defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) + +#ifdef HAVE_FREERDP_RDPAPPLIST_H + /* import from libfreerdp-server2.so */ + RdpAppListServerContext* (*rdpapplist_server_context_new)(HANDLE vcm); + void (*rdpapplist_server_context_free)(RdpAppListServerContext* context); +#endif // HAVE_FREERDP_RDPAPPLIST_H + +#ifdef HAVE_FREERDP_GFXREDIR_H + /* import from libfreerdp-server2.so */ + GfxRedirServerContext* (*gfxredir_server_context_new)(HANDLE vcm); + void (*gfxredir_server_context_free)(GfxRedirServerContext* context); + + bool use_gfxredir; + char *shared_memory_mount_path; + size_t shared_memory_mount_path_size; +#endif // HAVE_FREERDP_GFXREDIR_H +}; + +enum peer_item_flags { + RDP_PEER_ACTIVATED = (1 << 0), + RDP_PEER_OUTPUT_ENABLED = (1 << 1), +}; + +struct rdp_peers_item { + int flags; + freerdp_peer *peer; + struct weston_seat *seat; + + struct wl_list link; // rdp_output::peers +}; + +struct rdp_monitor_mode { + rdpMonitor monitorDef; // in client coordinate. + int scale; // per monitor DPI scaling. + float clientScale; + pixman_rectangle32_t rectWeston; // in weston coordinate. +}; + +struct rdp_head { + struct weston_head base; + uint32_t index; + struct rdp_monitor_mode monitorMode; + /*TODO: these region/rectangles can be moved to rdp_output */ + pixman_region32_t regionClient; // in client coordnate. + pixman_region32_t regionWeston; // in weston coordnate. + pixman_rectangle32_t workareaClient; // in client coordinate. + pixman_rectangle32_t workarea; // in weston coordinate. + + struct wl_list link; // rdp_backend::head_list +}; + +struct rdp_output { + struct weston_output base; + struct wl_event_source *finish_frame_timer; + pixman_image_t *shadow_surface; + uint32_t index; + + struct wl_list peers; + struct wl_list link; // rdp_backend::output_list +}; + +typedef struct _rdp_audio_block_info { + UINT64 submissionTime; + UINT64 ackReceivedTime; + UINT64 ackPlayedTime; +} rdp_audio_block_info; + +struct rdp_peer_context { + rdpContext _p; + + struct rdp_backend *rdpBackend; + struct wl_event_source *events[MAX_FREERDP_FDS+1]; // +1 for WTSVirtualChannelManagerGetFileDescriptor + RFX_CONTEXT *rfx_context; + wStream *encode_stream; + RFX_RECT *rfx_rects; + NSC_CONTEXT *nsc_context; + + struct rdp_peers_item item; + + bool button_state[5]; + + // RAIL support + HANDLE vcm; + HANDLE eventVcm; + RailServerContext* rail_server_context; + DrdynvcServerContext* drdynvc_server_context; + DispServerContext* disp_server_context; + RdpgfxServerContext* rail_grfx_server_context; +#ifdef HAVE_FREERDP_GFXREDIR_H + GfxRedirServerContext* gfxredir_server_context; +#endif // HAVE_FREERDP_GFXREDIR_H +#ifdef HAVE_FREERDP_RDPAPPLIST_H + RdpAppListServerContext* applist_server_context; +#endif // HAVE_FREERDP_RDPAPPLIST_H + BOOL handshakeCompleted; + BOOL activationRailCompleted; + BOOL activationGraphicsCompleted; + BOOL activationGraphicsRedirectionCompleted; + UINT32 clientStatusFlags; + struct rdp_id_manager windowId; + struct rdp_id_manager surfaceId; +#ifdef HAVE_FREERDP_GFXREDIR_H + struct rdp_id_manager poolId; + struct rdp_id_manager bufferId; +#endif // HAVE_FREERDP_GFXREDIR_H + UINT32 currentFrameId; + UINT32 acknowledgedFrameId; + BOOL isAcknowledgedSuspended; + struct wl_client *clientExec; + struct wl_listener clientExec_destroy_listener; + struct weston_surface *cursorSurface; + // list of outstanding event_source sent from FreeRDP thread to display loop. + pthread_mutex_t loop_event_source_list_mutex; + struct wl_list loop_event_source_list; + struct wl_listener idle_listener; + struct wl_listener wake_listener; + + // Multiple monitor support (monitor topology) + pixman_region32_t regionClientHeads; + pixman_region32_t regionWestonHeads; + + // Audio support + RdpsndServerContext* rdpsnd_server_context; + BOOL audioExitSignal; + int pulseAudioSinkListenerFd; + int pulseAudioSinkFd; + pthread_t pulseAudioSinkThread; + int bytesPerFrame; + UINT audioBufferSize; + BYTE* audioBuffer; + BYTE lastBlockSent; + UINT64 lastNetworkLatency; + UINT64 accumulatedNetworkLatency; + UINT accumulatedNetworkLatencyCount; + UINT64 lastRenderedLatency; + UINT64 accumulatedRenderedLatency; + UINT accumulatedRenderedLatencyCount; + rdp_audio_block_info blockInfo[256]; + int nextValidBlock; + UINT PAVersion; + + // AudioIn support + audin_server_context* audin_server_context; + BOOL audioInExitSignal; + int pulseAudioSourceListenerFd; + int pulseAudioSourceFd; + int closeAudioSourceFd; + int audioInSem; + pthread_t pulseAudioSourceThread; + BOOL isAudioInStreamOpened; + + // Clipboard support + CliprdrServerContext* clipboard_server_context; + + struct rdp_clipboard_data_source* clipboard_client_data_source; + struct rdp_clipboard_data_source* clipboard_inflight_client_data_source; + + struct wl_listener clipboard_selection_listener; + struct wl_event_source *clipboard_data_request_event_source; + UINT32 clipboard_last_requested_format_index; + + // Application List support + BOOL isAppListEnabled; +}; + +typedef struct rdp_peer_context RdpPeerContext; + +#define RDP_RAIL_MARKER_WINDOW_ID 0xFFFFFFFE +#define RDP_RAIL_DESKTOP_WINDOW_ID 0xFFFFFFFF + +#define ENABLE_RDP_THREAD_CHECK + +#ifdef ENABLE_RDP_THREAD_CHECK +#define ASSERT_COMPOSITOR_THREAD(b) assert_compositor_thread(b) +#define ASSERT_NOT_COMPOSITOR_THREAD(b) assert_not_compositor_thread(b) +#else +#define ASSERT_COMPOSITOR_THREAD(b) +#define ASSERT_NOT_COMPOSITOR_THREAD(b) +#endif // ENABLE_RDP_THREAD_CHECK + +#define RDP_DEBUG_LEVEL_NONE 0 +#define RDP_DEBUG_LEVEL_ERR 1 +#define RDP_DEBUG_LEVEL_WARN 2 +#define RDP_DEBUG_LEVEL_INFO 3 +#define RDP_DEBUG_LEVEL_DEBUG 4 +#define RDP_DEBUG_LEVEL_VERBOSE 5 + +#define RDP_DEBUG_LEVEL_DEFAULT RDP_DEBUG_LEVEL_INFO + +/* Ideally here should use weston_log_scope_printf() instead of weston_log() + since weston_log() requires "log" scope to be enabled, but weston_log() + added timestamp which is often helpful, thus use weston_log() here. + + To enable rdp_debug message, add "--logger-scopes=rdp-backend,log" to + weston's command line, this added rdp-backend and log scopes */ +/* TODO: support debug level */ +#define rdp_debug_level(b, cont, lvl, ...) \ + if ((b) && (b)->debug && ((b)->debugLevel >= (lvl)) && weston_log_scope_is_enabled((b)->debug)) { \ + if (cont) \ + weston_log_continue(__VA_ARGS__); \ + else \ + weston_log(__VA_ARGS__); \ + } + +#define rdp_debug_verbose(b, ...) rdp_debug_level(b, FALSE, RDP_DEBUG_LEVEL_VERBOSE, __VA_ARGS__) +#define rdp_debug(b, ...) rdp_debug_level(b, FALSE, RDP_DEBUG_LEVEL_INFO, __VA_ARGS__) + +#define rdp_debug_verbose_continue(b, ...) rdp_debug_level(b, TRUE, RDP_DEBUG_LEVEL_VERBOSE, __VA_ARGS__) +#define rdp_debug_continue(b, ...) rdp_debug_level(b, TRUE, RDP_DEBUG_LEVEL_INFO, __VA_ARGS__) + +// rdp.c +void convert_rdp_keyboard_to_xkb_rule_names(UINT32 KeyboardType, UINT32 KeyboardLayout, struct xkb_rule_names *xkbRuleNames); +struct rdp_head * rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp_monitor_mode *monitorMode); +void rdp_head_destroy(struct weston_compositor *compositor, struct rdp_head *head); + +// rdputil.c +pid_t rdp_get_tid(void); +#ifdef ENABLE_RDP_THREAD_CHECK +void assert_compositor_thread(struct rdp_backend *b); +void assert_not_compositor_thread(struct rdp_backend *b); +#endif // ENABLE_RDP_THREAD_CHECK +BOOL rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory); +void rdp_free_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory); +BOOL rdp_id_manager_init(struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 high_limit); +void rdp_id_manager_free(struct rdp_id_manager *id_manager); +BOOL rdp_id_manager_allocate_id(struct rdp_id_manager *id_manager, void *object, UINT32 *new_id); +void rdp_id_manager_free_id(struct rdp_id_manager *id_manager, UINT32 id); +void dump_id_manager_state(FILE *fp, struct rdp_id_manager *id_manager, char* title); + +// rdprail.c +int rdp_rail_backend_create(struct rdp_backend *b); +void rdp_rail_destroy(struct rdp_backend *b); +BOOL rdp_rail_peer_activate(freerdp_peer* client); +void rdp_rail_sync_window_status(freerdp_peer* client); +BOOL rdp_rail_peer_init(freerdp_peer *client, RdpPeerContext *peerCtx); +void rdp_rail_peer_context_free(freerdp_peer* client, RdpPeerContext* context); +void rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage); +BOOL rdp_drdynvc_init(freerdp_peer *client); +void rdp_drdynvc_destroy(RdpPeerContext* context); +void rdp_rail_start_window_move(struct weston_surface* surface, int pointerGrabX, int pointerGrabY, struct weston_size minSize, struct weston_size maxSize); +void rdp_rail_end_window_move(struct weston_surface* surface); + +// rdpdisp.c +UINT disp_client_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MONITOR_LAYOUT_PDU* displayControl); +BOOL xf_peer_adjust_monitor_layout(freerdp_peer* client); + +// rdpaudio.c +int rdp_audio_init(RdpPeerContext *peerCtx); +void rdp_audio_destroy(RdpPeerContext *peerCtx); + +// rdpaudioin.c +int rdp_audioin_init(RdpPeerContext *peerCtx); +void rdp_audioin_destroy(RdpPeerContext *peerCtx); + +// rdpclip.c +int rdp_clipboard_init(freerdp_peer* client); +void rdp_clipboard_destroy(RdpPeerContext *peerCtx); + +/* this function is ONLY used to defer the task from RDP thread, + to be performed at wayland display loop thread */ +static inline struct wl_event_source * +rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_idle_func_t func, void *data) +{ + if (peerCtx->vcm) { + struct rdp_backend *b = peerCtx->rdpBackend; + ASSERT_NOT_COMPOSITOR_THREAD(b); + struct wl_event_loop *loop = wl_display_get_event_loop(b->compositor->wl_display); + struct wl_event_source *event_source = wl_event_loop_add_idle(loop, func, data); + if (event_source) + SetEvent(peerCtx->eventVcm); + else + weston_log("%s: wl_event_loop_add_idle failed\n", __func__); + return event_source; + } else { + /* RDP server is not opened, this must not be used */ + assert(false); + return NULL; + } +} + +static inline struct rdp_head * +to_rdp_head(struct weston_head *base) +{ + return container_of(base, struct rdp_head, base); +} + +static inline struct rdp_output * +to_rdp_output(struct weston_output *base) +{ + return container_of(base, struct rdp_output, base); +} + +static inline struct rdp_backend * +to_rdp_backend(struct weston_compositor *base) +{ + return container_of(base->backend, struct rdp_backend, base); +} + +static inline void +rdp_matrix_transform_position(struct weston_matrix *matrix, int *x, int *y) +{ + struct weston_vector v; + if (matrix->type != 0) { + v.f[0] = *x; + v.f[1] = *y; + v.f[2] = 0.0f; + v.f[3] = 1.0f; + weston_matrix_transform(matrix, &v); + *x = v.f[0] / v.f[3]; + *y = v.f[1] / v.f[3]; + } +} + +static inline void +rdp_matrix_transform_scale(struct weston_matrix *matrix, int *sx, int *sy) +{ + struct weston_vector v; + if (matrix->type != 0) { + v.f[0] = *sx; + v.f[1] = *sy; + v.f[2] = 0.0f; + v.f[3] = 0.0f; + weston_matrix_transform(matrix, &v); + *sx = v.f[0]; // / v.f[3]; + *sy = v.f[1]; // / v.f[3]; + } +} + +/* TO BE REMOVED */ +static inline int32_t +to_weston_x(RdpPeerContext *peer, int32_t x) +{ + return x - peer->regionClientHeads.extents.x1; +} + +/* TO BE REMOVED */ +static inline int32_t +to_weston_y(RdpPeerContext *peer, int32_t y) +{ + return y - peer->regionClientHeads.extents.y1; +} + +static inline void +to_weston_scale_only(RdpPeerContext *peer, struct weston_output *output, float scale, int *x, int *y) +{ + //rdp_matrix_transform_scale(&output->inverse_matrix, x, y); + /* TODO: built-in to matrix */ + *x = (float)(*x) * scale; + *y = (float)(*y) * scale; +} + +/* Input x/y in client space, output x/y in weston space */ +static inline struct weston_output * +to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32_t *width, uint32_t *height) +{ + struct rdp_backend *b = peerContext->rdpBackend; + int sx = *x, sy = *y; + /* First, find which monitor contains this x/y. */ + struct rdp_head *head_iter; + wl_list_for_each(head_iter, &b->head_list, link) { + if (pixman_region32_contains_point(&head_iter->regionClient, sx, sy, NULL)) { + struct weston_output *output = head_iter->base.output; + float scale = 1.0f / head_iter->monitorMode.clientScale; + /* translate x/y to offset from this output on client space. */ + sx -= head_iter->monitorMode.monitorDef.x; + sy -= head_iter->monitorMode.monitorDef.y; + /* scale x/y to client output space. */ + to_weston_scale_only(peerContext, output, scale, &sx, &sy); + if (width && height) + to_weston_scale_only(peerContext, output, scale, width, height); + /* translate x/y to offset from this output on weston space. */ + sx += head_iter->monitorMode.rectWeston.x; + sy += head_iter->monitorMode.rectWeston.y; + rdp_debug_verbose(b, "%s: (x:%d, y:%d) -> (sx:%d, sy:%d) at head:%s\n", + __func__, *x, *y, sx, sy, head_iter->base.name); + *x = sx; + *y = sy; + return output; // must be only 1 head per output. + } + } + /* x/y is outside of any monitors. */ + return NULL; +} + +/* TO BE REMOVED */ +static inline int32_t +to_client_x(RdpPeerContext *peer, int32_t x) +{ + return x + peer->regionClientHeads.extents.x1; +} + +/* TO BE REMOVED */ +static inline int32_t +to_client_y(RdpPeerContext *peer, int32_t y) +{ + return y + peer->regionClientHeads.extents.y1; +} + +static inline void +to_client_scale_only(RdpPeerContext *peer, struct weston_output *output, float scale, int *x, int *y) +{ + //rdp_matrix_transform_scale(&output->matrix, x, y); + /* TODO: built-in to matrix */ + *x = (float)(*x) * scale; + *y = (float)(*y) * scale; +} + +/* Input x/y in weston space, output x/y in client space */ +static inline void +to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, int32_t *x, int32_t *y, uint32_t *width, uint32_t *height) +{ + struct rdp_backend *b = peerContext->rdpBackend; + int sx = *x, sy = *y; + /* Pick first head from output. */ + struct weston_head *head_iter; + wl_list_for_each(head_iter, &output->head_list, output_link) { + struct rdp_head *head = to_rdp_head(head_iter); + float scale = head->monitorMode.clientScale; + /* translate x/y to offset from this output on weston space. */ + sx -= head->monitorMode.rectWeston.x; + sy -= head->monitorMode.rectWeston.y; + /* scale x/y to client output space. */ + to_client_scale_only(peerContext, output, scale, &sx, &sy); + if (width && height) + to_client_scale_only(peerContext, output, scale, width, height); + /* translate x/y to offset from this output on client space. */ + sx += head->monitorMode.monitorDef.x; + sy += head->monitorMode.monitorDef.y; + rdp_debug_verbose(b, "%s: (x:%d, y:%d) -> (sx:%d, sy:%d) at head:%s\n", + __func__, *x, *y, sx, sy, head_iter->name); + *x = sx; + *y = sy; + return; // must be only 1 head per output. + } +} + +#endif diff --git a/libweston/backend-rdp/rdpaudio.c b/libweston/backend-rdp/rdpaudio.c new file mode 100644 index 000000000..96bf37c2f --- /dev/null +++ b/libweston/backend-rdp/rdpaudio.c @@ -0,0 +1,809 @@ +/* + * 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 +#include +#include +#include "rdp.h" + +static AUDIO_FORMAT rdp_audio_supported_audio_formats[] = { + { WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0, NULL }, + }; + +#define AUDIO_LATENCY 5 +#define AUDIO_FRAMES_PER_RDP_PACKET (44100 * AUDIO_LATENCY / 1000) + +#define RDP_SINK_INTERFACE_VERSION 1 + +#define RDP_AUDIO_CMD_VERSION 0 +#define RDP_AUDIO_CMD_TRANSFER 1 +#define RDP_AUDIO_CMD_GET_LATENCY 2 +#define RDP_AUDIO_CMD_RESET_LATENCY 3 + +typedef struct _rdp_audio_cmd_header { + uint32_t cmd; + union { + uint32_t version; + struct { + uint32_t bytes; + uint64_t timestamp; + } transfer; + uint64_t reserved[8]; + }; +} rdp_audio_cmd_header; + +static char* +AUDIO_FORMAT_to_String(UINT16 format) +{ + switch (format) { + case WAVE_FORMAT_UNKNOWN: + return "WAVE_FORMAT_UNKNOWN"; + case WAVE_FORMAT_PCM: + return "WAVE_FORMAT_PCM"; + case WAVE_FORMAT_ADPCM: + return "WAVE_FORMAT_ADPCM"; + case WAVE_FORMAT_IEEE_FLOAT: + return "WAVE_FORMAT_IEEE_FLOAT"; + case WAVE_FORMAT_VSELP: + return "WAVE_FORMAT_VSELP"; + case WAVE_FORMAT_IBM_CVSD: + return "WAVE_FORMAT_IBM_CVSD"; + case WAVE_FORMAT_ALAW: + return "WAVE_FORMAT_ALAW"; + case WAVE_FORMAT_MULAW: + return "WAVE_FORMAT_MULAW"; + case WAVE_FORMAT_OKI_ADPCM: + return "WAVE_FORMAT_OKI_ADPCM"; + case WAVE_FORMAT_DVI_ADPCM: + return "WAVE_FORMAT_DVI_ADPCM"; + case WAVE_FORMAT_MEDIASPACE_ADPCM: + return "WAVE_FORMAT_MEDIASPACE_ADPCM"; + case WAVE_FORMAT_SIERRA_ADPCM: + return "WAVE_FORMAT_SIERRA_ADPCM"; + case WAVE_FORMAT_G723_ADPCM: + return "WAVE_FORMAT_G723_ADPCM"; + case WAVE_FORMAT_DIGISTD: + return "WAVE_FORMAT_DIGISTD"; + case WAVE_FORMAT_DIGIFIX: + return "WAVE_FORMAT_DIGIFIX"; + case WAVE_FORMAT_DIALOGIC_OKI_ADPCM: + return "WAVE_FORMAT_DIALOGIC_OKI_ADPCM"; + case WAVE_FORMAT_MEDIAVISION_ADPCM: + return "WAVE_FORMAT_MEDIAVISION_ADPCM"; + case WAVE_FORMAT_CU_CODEC: + return "WAVE_FORMAT_CU_CODEC"; + case WAVE_FORMAT_YAMAHA_ADPCM: + return "WAVE_FORMAT_YAMAHA_ADPCM"; + case WAVE_FORMAT_SONARC: + return "WAVE_FORMAT_SONARC"; + case WAVE_FORMAT_DSPGROUP_TRUESPEECH: + return "WAVE_FORMAT_DSPGROUP_TRUESPEECH"; + case WAVE_FORMAT_ECHOSC1: + return "WAVE_FORMAT_ECHOSC1"; + case WAVE_FORMAT_AUDIOFILE_AF36: + return "WAVE_FORMAT_AUDIOFILE_AF36"; + case WAVE_FORMAT_APTX: + return "WAVE_FORMAT_APTX"; + case WAVE_FORMAT_AUDIOFILE_AF10: + return "WAVE_FORMAT_AUDIOFILE_AF10"; + case WAVE_FORMAT_PROSODY_1612: + return "WAVE_FORMAT_PROSODY_1612"; + case WAVE_FORMAT_DOLBY_AC2: + return "WAVE_FORMAT_DOLBY_AC2"; + case WAVE_FORMAT_GSM610: + return "WAVE_FORMAT_GSM610"; + case WAVE_FORMAT_MSNAUDIO: + return "WAVE_FORMAT_MSNAUDIO"; + case WAVE_FORMAT_ANTEX_ADPCME: + return "WAVE_FORMAT_ANTEX_ADPCME"; + case WAVE_FORMAT_CONTROL_RES_VQLPC: + return "WAVE_FORMAT_CONTROL_RES_VQLPC"; + case WAVE_FORMAT_DIGIREAL: + return "WAVE_FORMAT_DIGIREAL"; + case WAVE_FORMAT_DIGIADPCM: + return "WAVE_FORMAT_DIGIADPCM"; + case WAVE_FORMAT_CONTROL_RES_CR10: + return "WAVE_FORMAT_CONTROL_RES_CR10"; + case WAVE_FORMAT_NMS_VBXADPCM: + return "WAVE_FORMAT_NMS_VBXADPCM"; + case WAVE_FORMAT_ROLAND_RDAC: + return "WAVE_FORMAT_ROLAND_RDAC"; + case WAVE_FORMAT_ECHOSC3: + return "WAVE_FORMAT_ECHOSC3"; + case WAVE_FORMAT_ROCKWELL_ADPCM: + return "WAVE_FORMAT_ROCKWELL_ADPCM"; + case WAVE_FORMAT_ROCKWELL_DIGITALK: + return "WAVE_FORMAT_ROCKWELL_DIGITALK"; + case WAVE_FORMAT_XEBEC: + return "WAVE_FORMAT_XEBEC"; + case WAVE_FORMAT_G721_ADPCM: + return "WAVE_FORMAT_G721_ADPCM"; + case WAVE_FORMAT_G728_CELP: + return "WAVE_FORMAT_G728_CELP"; + case WAVE_FORMAT_MSG723: + return "WAVE_FORMAT_MSG723"; + case WAVE_FORMAT_MPEG: + return "WAVE_FORMAT_MPEG"; + case WAVE_FORMAT_RT24: + return "WAVE_FORMAT_RT24"; + case WAVE_FORMAT_PAC: + return "WAVE_FORMAT_PAC"; + case WAVE_FORMAT_MPEGLAYER3: + return "WAVE_FORMAT_MPEGLAYER3"; + case WAVE_FORMAT_LUCENT_G723: + return "WAVE_FORMAT_LUCENT_G723"; + case WAVE_FORMAT_CIRRUS: + return "WAVE_FORMAT_CIRRUS"; + case WAVE_FORMAT_ESPCM: + return "WAVE_FORMAT_ESPCM"; + case WAVE_FORMAT_VOXWARE: + return "WAVE_FORMAT_VOXWARE"; + case WAVE_FORMAT_CANOPUS_ATRAC: + return "WAVE_FORMAT_CANOPUS_ATRAC"; + case WAVE_FORMAT_G726_ADPCM: + return "WAVE_FORMAT_G726_ADPCM"; + case WAVE_FORMAT_G722_ADPCM: + return "WAVE_FORMAT_G722_ADPCM"; + case WAVE_FORMAT_DSAT: + return "WAVE_FORMAT_DSAT"; + case WAVE_FORMAT_DSAT_DISPLAY: + return "WAVE_FORMAT_DSAT_DISPLAY"; + case WAVE_FORMAT_VOXWARE_BYTE_ALIGNED: + return "WAVE_FORMAT_VOXWARE_BYTE_ALIGNED"; + case WAVE_FORMAT_VOXWARE_AC8: + return "WAVE_FORMAT_VOXWARE_AC8"; + case WAVE_FORMAT_VOXWARE_AC10: + return "WAVE_FORMAT_VOXWARE_AC10"; + case WAVE_FORMAT_VOXWARE_AC16: + return "WAVE_FORMAT_VOXWARE_AC16"; + case WAVE_FORMAT_VOXWARE_AC20: + return "WAVE_FORMAT_VOXWARE_AC20"; + case WAVE_FORMAT_VOXWARE_RT24: + return "WAVE_FORMAT_VOXWARE_RT24"; + case WAVE_FORMAT_VOXWARE_RT29: + return "WAVE_FORMAT_VOXWARE_RT29"; + case WAVE_FORMAT_VOXWARE_RT29HW: + return "WAVE_FORMAT_VOXWARE_RT29HW"; + case WAVE_FORMAT_VOXWARE_VR12: + return "WAVE_FORMAT_VOXWARE_VR12"; + case WAVE_FORMAT_VOXWARE_VR18: + return "WAVE_FORMAT_VOXWARE_VR18"; + case WAVE_FORMAT_VOXWARE_TQ40: + return "WAVE_FORMAT_VOXWARE_TQ40"; + case WAVE_FORMAT_SOFTSOUND: + return "WAVE_FORMAT_SOFTSOUND"; + case WAVE_FORMAT_VOXWARE_TQ60: + return "WAVE_FORMAT_VOXWARE_TQ60"; + case WAVE_FORMAT_MSRT24: + return "WAVE_FORMAT_MSRT24"; + case WAVE_FORMAT_G729A: + return "WAVE_FORMAT_G729A"; + case WAVE_FORMAT_MVI_MV12: + return "WAVE_FORMAT_MVI_MV12"; + case WAVE_FORMAT_DF_G726: + return "WAVE_FORMAT_DF_G726"; + case WAVE_FORMAT_DF_GSM610: + return "WAVE_FORMAT_DF_GSM610"; + case WAVE_FORMAT_ISIAUDIO: + return "WAVE_FORMAT_ISIAUDIO"; + case WAVE_FORMAT_ONLIVE: + return "WAVE_FORMAT_ONLIVE"; + case WAVE_FORMAT_SBC24: + return "WAVE_FORMAT_SBC24"; + case WAVE_FORMAT_DOLBY_AC3_SPDIF: + return "WAVE_FORMAT_DOLBY_AC3_SPDIF"; + case WAVE_FORMAT_ZYXEL_ADPCM: + return "WAVE_FORMAT_ZYXEL_ADPCM"; + case WAVE_FORMAT_PHILIPS_LPCBB: + return "WAVE_FORMAT_PHILIPS_LPCBB"; + case WAVE_FORMAT_PACKED: + return "WAVE_FORMAT_PACKED"; + case WAVE_FORMAT_RHETOREX_ADPCM: + return "WAVE_FORMAT_RHETOREX_ADPCM"; + case WAVE_FORMAT_IRAT: + return "WAVE_FORMAT_IRAT"; + case WAVE_FORMAT_VIVO_G723: + return "WAVE_FORMAT_VIVO_G723"; + case WAVE_FORMAT_VIVO_SIREN: + return "WAVE_FORMAT_VIVO_SIREN"; + case WAVE_FORMAT_DIGITAL_G723: + return "WAVE_FORMAT_DIGITAL_G723"; + case WAVE_FORMAT_WMAUDIO2: + return "WAVE_FORMAT_WMAUDIO2"; + case WAVE_FORMAT_WMAUDIO3: + return "WAVE_FORMAT_WMAUDIO3"; + case WAVE_FORMAT_WMAUDIO_LOSSLESS: + return "WAVE_FORMAT_WMAUDIO_LOSSLESS"; + case WAVE_FORMAT_CREATIVE_ADPCM: + return "WAVE_FORMAT_CREATIVE_ADPCM"; + case WAVE_FORMAT_CREATIVE_FASTSPEECH8: + return "WAVE_FORMAT_CREATIVE_FASTSPEECH8"; + case WAVE_FORMAT_CREATIVE_FASTSPEECH10: + return "WAVE_FORMAT_CREATIVE_FASTSPEECH10"; + case WAVE_FORMAT_QUARTERDECK: + return "WAVE_FORMAT_QUARTERDECK"; + case WAVE_FORMAT_FM_TOWNS_SND: + return "WAVE_FORMAT_FM_TOWNS_SND"; + case WAVE_FORMAT_BTV_DIGITAL: + return "WAVE_FORMAT_BTV_DIGITAL"; + case WAVE_FORMAT_VME_VMPCM: + return "WAVE_FORMAT_VME_VMPCM"; + case WAVE_FORMAT_OLIGSM: + return "WAVE_FORMAT_OLIGSM"; + case WAVE_FORMAT_OLIADPCM: + return "WAVE_FORMAT_OLIADPCM"; + case WAVE_FORMAT_OLICELP: + return "WAVE_FORMAT_OLICELP"; + case WAVE_FORMAT_OLISBC: + return "WAVE_FORMAT_OLISBC"; + case WAVE_FORMAT_OLIOPR: + return "WAVE_FORMAT_OLIOPR"; + case WAVE_FORMAT_LH_CODEC: + return "WAVE_FORMAT_LH_CODEC"; + case WAVE_FORMAT_NORRIS: + return "WAVE_FORMAT_NORRIS"; + case WAVE_FORMAT_SOUNDSPACE_MUSICOMPRESS: + return "WAVE_FORMAT_SOUNDSPACE_MUSICOMPRESS"; + case WAVE_FORMAT_DVM: + return "WAVE_FORMAT_DVM"; + case WAVE_FORMAT_AAC_MS: + return "WAVE_FORMAT_AAC_MS"; + } + + return "WAVE_FORMAT_UNKNOWN"; +} + +static int +rdp_audio_setup_listener(RdpPeerContext *peerCtx) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + char *sink_socket_path; + int fd; + struct sockaddr_un s; + int bytes; + int error; + + fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + weston_log("Couldn't create listener socket.\n"); + return -1; + } + + sink_socket_path = getenv("PULSE_AUDIO_RDP_SINK"); + if (sink_socket_path == NULL || sink_socket_path[0] == '\0') { + close(fd); + weston_log("Environment variable PULSE_AUDIO_RDP_SINK not set.\n"); + return -1; + } + + memset(&s, 0, sizeof(s)); + s.sun_family = AF_UNIX; + bytes = sizeof(s.sun_path) - 1; + snprintf(s.sun_path, bytes, "%s", sink_socket_path); + + remove(s.sun_path); + + rdp_debug(b, "Pulse Audio Sink listener socket on %s\n", s.sun_path); + error = bind(fd, (struct sockaddr *)&s, sizeof(struct sockaddr_un)); + if (error != 0) { + close(fd); + weston_log("Failed to bind to listener socket (%d).\n", error); + return -1; + } + + listen(fd, 100); + return fd; +} + +static UINT64 +rdp_audio_timestamp() +{ + struct timespec time; + clock_gettime(CLOCK_REALTIME, &time); + return time.tv_sec * 1000000 + time.tv_nsec / 1000; +} + +static UINT +rdp_audio_client_confirm_block( + RdpsndServerContext* context, + BYTE confirmBlockNum, + UINT16 wtimestamp) +{ + RdpPeerContext *peerCtx = (RdpPeerContext*)context->data; + + if (peerCtx->blockInfo[confirmBlockNum].ackReceivedTime != 0) { + assert(peerCtx->blockInfo[confirmBlockNum].ackPlayedTime == 0); + peerCtx->blockInfo[confirmBlockNum].ackPlayedTime = rdp_audio_timestamp(); + + /* + * Sum up all of the latency, we'll compute an average for the last period + * requested by the sink. + */ + if (peerCtx->nextValidBlock == -1 || peerCtx->nextValidBlock == confirmBlockNum) { + peerCtx->nextValidBlock = -1; + + peerCtx->accumulatedRenderedLatency += + peerCtx->blockInfo[confirmBlockNum].ackPlayedTime - + peerCtx->blockInfo[confirmBlockNum].submissionTime; + peerCtx->accumulatedRenderedLatencyCount++; + } + + uint64_t one = 1; + if (write(peerCtx->audioInSem, &one, sizeof(one)) != sizeof(uint64_t)) { + weston_log("RDP Audio error at confirm_block while writing to audioInSem (%s)\n", strerror(errno)); + return ERROR_INTERNAL_ERROR; + } + + } else { + peerCtx->blockInfo[confirmBlockNum].ackReceivedTime = rdp_audio_timestamp(); + + peerCtx->accumulatedNetworkLatency += + peerCtx->blockInfo[confirmBlockNum].ackReceivedTime - + peerCtx->blockInfo[confirmBlockNum].submissionTime; + peerCtx->accumulatedNetworkLatencyCount++; + } + + return 0; +} + +static int +rdp_audio_handle_version( + RdpPeerContext *peerCtx, + UINT PAVersion) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + uint32_t version = RDP_SINK_INTERFACE_VERSION; + ssize_t sizeSent; + + peerCtx->PAVersion = PAVersion; + + rdp_debug(b, "RDP Sink version (%d - %d)\n", PAVersion, version); + + sizeSent = send(peerCtx->pulseAudioSinkFd, &version, + sizeof(version), MSG_DONTWAIT); + if (sizeSent != sizeof(version)) { + weston_log("RDP audio error responding to version request sent:%ld. %s\n", + sizeSent, strerror(errno)); + return -1; + } + + return 0; +} + +static int +rdp_audio_handle_transfer( + RdpPeerContext *peerCtx, + UINT bytesLeft, + UINT64 timestamp) +{ + int nbFrames = bytesLeft / peerCtx->bytesPerFrame; + UINT bytesRead = 0; + ssize_t sizeRead = 0; + + if (bytesLeft > peerCtx->audioBufferSize) { + if(peerCtx->audioBuffer) + free(peerCtx->audioBuffer); + + peerCtx->audioBuffer = zalloc(bytesLeft); + if (!peerCtx->audioBuffer) { + weston_log("RDP Audio error zalloc(%d) failed.\n", bytesLeft); + return -1; + } + peerCtx->audioBufferSize = bytesLeft; + } + + assert((bytesLeft % peerCtx->bytesPerFrame) == 0); + + /* + * Read the expected amount of data over the sink before sending it to RDP + */ + while (bytesLeft > 0) { + sizeRead = read(peerCtx->pulseAudioSinkFd, + peerCtx->audioBuffer + bytesRead, bytesLeft); + if (sizeRead <= 0) { + weston_log("RDP Audio error while reading data from sink socket sizeRead:%ld. %s\n", sizeRead, strerror(errno)); + return -1; + } + bytesRead += sizeRead; + bytesLeft -= sizeRead; + } + + BYTE* audioBuffer = peerCtx->audioBuffer; + while (nbFrames > 0) { + /* + * Ensure we don't overrun our audio buffers. + * + * SendSamples may not submit audio every time, it may accumulate audio and + * submit on subsequent call. We've sent latency such that it should never + * submit more than one packet of audio over the RDP channel for one + * of our incoming audio packet from pulse. + */ + uint64_t dummy; + if (read(peerCtx->audioInSem, &dummy, sizeof(dummy)) != sizeof(uint64_t)) { + weston_log("RDP Audio error at handle_transfer while reading from audioInSem (%s)\n", strerror(errno)); + return -1; + } + + /* + * Setup tracking of all block sent by RDP so we can compute latency later + * when those block gets acknowledge by the client. + * + * Set 0 to timestamp to disable A/V sync at client side. + */ + BYTE block_no = peerCtx->rdpsnd_server_context->block_no; + peerCtx->blockInfo[block_no].submissionTime = timestamp; + peerCtx->blockInfo[block_no].ackReceivedTime = 0; + peerCtx->blockInfo[block_no].ackPlayedTime = 0; + if (peerCtx->rdpsnd_server_context->SendSamples(peerCtx->rdpsnd_server_context, + audioBuffer, + MIN(nbFrames, AUDIO_FRAMES_PER_RDP_PACKET), + 0) != 0) { + weston_log("RDP Audio error while SendSamples\n"); + return -1; + } + + if (block_no == peerCtx->rdpsnd_server_context->block_no) { + /* + * Didn't submit any audio this time around, adjust our semaphore. + */ + uint64_t one = 1; + if (write(peerCtx->audioInSem, &one, sizeof(one)) != sizeof(uint64_t)) { + weston_log("RDP Audio error at handle_transfer while writing to audioInSem (%s)\n", strerror(errno)); + return -1; + } + } else { + /* + * There shouldn't be more than one packet of audio sent by RDP. + */ + assert((block_no > peerCtx->rdpsnd_server_context->block_no) || (block_no+1) == peerCtx->rdpsnd_server_context->block_no); + assert((block_no < peerCtx->rdpsnd_server_context->block_no) || (block_no==255 && peerCtx->rdpsnd_server_context->block_no==0)); + } + + audioBuffer += AUDIO_FRAMES_PER_RDP_PACKET * peerCtx->bytesPerFrame; + nbFrames -= AUDIO_FRAMES_PER_RDP_PACKET; + } + + return 0; +} + +static int +rdp_audio_handle_get_latency( + RdpPeerContext *peerCtx) +{ + UINT networkLatency; + UINT renderedLatency; + ssize_t sizeSent; + + if (peerCtx->accumulatedNetworkLatencyCount > 0) { + networkLatency = peerCtx->accumulatedNetworkLatency / peerCtx->accumulatedNetworkLatencyCount; + peerCtx->lastNetworkLatency = networkLatency; + peerCtx->accumulatedNetworkLatency = 0; + peerCtx->accumulatedNetworkLatencyCount = 0; + } else { + networkLatency = peerCtx->lastNetworkLatency; + } + + if (peerCtx->accumulatedRenderedLatencyCount > 0) { + renderedLatency = peerCtx->accumulatedRenderedLatency / peerCtx->accumulatedRenderedLatencyCount; + peerCtx->lastRenderedLatency = renderedLatency; + peerCtx->accumulatedRenderedLatency = 0; + peerCtx->accumulatedRenderedLatencyCount = 0; + } else { + renderedLatency = peerCtx->lastRenderedLatency; + } + + if (renderedLatency > networkLatency) + renderedLatency -= networkLatency; + + sizeSent = send(peerCtx->pulseAudioSinkFd, &renderedLatency, + sizeof(renderedLatency), MSG_DONTWAIT); + if (sizeSent != sizeof(renderedLatency)) { + weston_log("RDP audio error responding to latency request sent:%ld. %s\n", + sizeSent, strerror(errno)); + return -1; + } + + return 0; +} + +static void signalhandler(int sig) { + weston_log("RDP Audio: %s(%d)\n", __func__, sig); + return; +} + +static void* +rdp_audio_pulse_audio_sink_thread(void *context) +{ + RdpPeerContext *peerCtx = (RdpPeerContext*)context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct sigaction act; + sigset_t set; + + sigemptyset(&set); + if (sigaddset(&set, SIGUSR2) == -1) { + weston_log("Audio sink thread: sigaddset(SIGUSR2) failed.\n"); + return NULL; + } + if (pthread_sigmask(SIG_UNBLOCK, &set, NULL) != 0) { + weston_log("Audio sink thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); + return NULL; + } + act.sa_flags = 0; + act.sa_mask = set; + act.sa_handler = &signalhandler; + if (sigaction(SIGUSR2, &act, NULL) == -1) { + weston_log("Audio sink thread: sigaction(SIGUSR2) failed.\n"); + return NULL; + } + + assert(peerCtx->pulseAudioSinkListenerFd != 0); + + for (;;) { + rdp_debug(b, "Audio sink thread: Listening for audio connection.\n"); + + if (peerCtx->audioExitSignal) { + rdp_debug(b, "Audio sink thread is asked to exit (accept loop)\n"); + break; + } + + /* + * Wait for a connection on our listening socket + */ + assert(peerCtx->pulseAudioSinkFd < 0); + peerCtx->pulseAudioSinkFd = accept(peerCtx->pulseAudioSinkListenerFd, + NULL, NULL); + if (peerCtx->pulseAudioSinkFd < 0) { + weston_log("Audio sink thread: Listener connection error (%s)\n", strerror(errno)); + continue; + } else { + rdp_debug(b, "Audio sink thread: connection successful on socket (%d).\n", + peerCtx->pulseAudioSinkFd); + } + + /* + * Read audio from the socket and stream to the RDP Client. + */ + for (;;) { + rdp_audio_cmd_header header; + ssize_t sizeRead; + + sizeRead = read(peerCtx->pulseAudioSinkFd, &header, sizeof(header)); + /* pulseaudio RDP sink always send sizeof(header) regardless command type. */ + if (sizeRead != sizeof(header)) { + weston_log("Audio sink thread: error while reading from sink socket sizeRead:%ld. %s\n", + sizeRead, strerror(errno)); + break; + } else if (header.cmd == RDP_AUDIO_CMD_VERSION) { + rdp_debug_verbose(b, "Audio sink command RDP_AUDIO_CMD_VERSION: %d\n", header.version); + if (rdp_audio_handle_version(peerCtx, header.version) < 0) + break; + } else if (header.cmd == RDP_AUDIO_CMD_TRANSFER) { + rdp_debug_verbose(b, "Audio sink command RDP_AUDIO_CMD_TRANSFER: %d\n", header.transfer.bytes); + if (rdp_audio_handle_transfer(peerCtx, header.transfer.bytes, header.transfer.timestamp) < 0) + break; + } else if (header.cmd == RDP_AUDIO_CMD_GET_LATENCY) { + rdp_debug_verbose(b, "Audio sink command RDP_AUDIO_CMD_GET_LATENCY\n"); + if (rdp_audio_handle_get_latency(peerCtx) < 0) + break; + } else if (header.cmd == RDP_AUDIO_CMD_RESET_LATENCY) { + rdp_debug_verbose(b, "Audio sink command RDP_AUDIO_CMD_RESET_LATENCY\n"); + peerCtx->nextValidBlock = peerCtx->rdpsnd_server_context->block_no; + peerCtx->lastNetworkLatency = 0; + peerCtx->accumulatedNetworkLatency = 0; + peerCtx->accumulatedNetworkLatencyCount = 0; + peerCtx->lastRenderedLatency = 0; + peerCtx->accumulatedRenderedLatency = 0; + peerCtx->accumulatedRenderedLatencyCount = 0; + } else { + weston_log("Audio sink thread: unknown command from sink.\n"); + break; + } + } + + close(peerCtx->pulseAudioSinkFd); + peerCtx->pulseAudioSinkFd = -1; + } + + assert(peerCtx->pulseAudioSinkFd < 0); + + return NULL; +} + +static void +rdp_audio_client_activated(RdpsndServerContext* context) +{ + RdpPeerContext *peerCtx = (RdpPeerContext*)context->data; + struct rdp_backend *b = peerCtx->rdpBackend; + int format = -1; + int i, j; + + rdp_debug(b, "rdp_audio_server_activated: %d audio formats supported.\n", + context->num_client_formats); + + for (i = 0; i < context->num_client_formats; i++) { + rdp_debug(b, "\t[%d] - Format(%s) - Bits(%d), Channels(%d), Frequency(%d)\n", + i, + AUDIO_FORMAT_to_String(context->client_formats[i].wFormatTag), + context->client_formats[i].wBitsPerSample, + context->client_formats[i].nChannels, + context->client_formats[i].nSamplesPerSec); + + for (j = 0; j < (int)context->num_server_formats; j++) { + if ((context->client_formats[i].wFormatTag == context->server_formats[j].wFormatTag) && + (context->client_formats[i].nChannels == context->server_formats[j].nChannels) && + (context->client_formats[i].nSamplesPerSec == context->server_formats[j].nSamplesPerSec)) { + rdp_debug(b, "RDPAudio - Agreed on format %d.\n", i); + format = i; + break; + } + } + } + + if (format != -1) { + peerCtx->nextValidBlock = -1; + peerCtx->bytesPerFrame = (context->client_formats[format].wBitsPerSample / 8) * context->client_formats[format].nChannels; + context->latency = AUDIO_LATENCY; + + rdp_debug(b, "rdp_audio_server_activated: bytesPerFrame:%d, latency:%d\n", + peerCtx->bytesPerFrame, context->latency); + + context->SelectFormat(context, format); + context->SetVolume(context, 0x7FFF, 0x7FFF); + + peerCtx->pulseAudioSinkListenerFd = rdp_audio_setup_listener(peerCtx); + if (peerCtx->pulseAudioSinkListenerFd < 0) + weston_log("RDPAudio - Failed to create listener socket\n"); + else if (pthread_create(&peerCtx->pulseAudioSinkThread, NULL, rdp_audio_pulse_audio_sink_thread, (void*)peerCtx) < 0) + weston_log("RDPAudio - Failed to start Pulse Audio Sink Thread. No audio will be available.\n"); + } else { + weston_log("RDPAudio - No agreeded format.\n"); + } +} + +int +rdp_audio_init(RdpPeerContext *peerCtx) +{ + char *s; + + peerCtx->rdpsnd_server_context = rdpsnd_server_context_new(peerCtx->vcm); + if (!peerCtx->rdpsnd_server_context) { + weston_log("RDPAudio - Couldn't initialize audio virtual channel.\n"); + return 0; // Continue without audio + } + + peerCtx->audioExitSignal = FALSE; + peerCtx->pulseAudioSinkThread = 0; + peerCtx->pulseAudioSinkListenerFd = -1; + peerCtx->pulseAudioSinkFd = -1; + peerCtx->audioBuffer = NULL; + + peerCtx->audioInSem = eventfd(256, EFD_SEMAPHORE | EFD_CLOEXEC); + if (!peerCtx->audioInSem) { + weston_log("RDPAudio - Couldn't initialize event semaphore.\n"); + goto Error_Exit; + } + + /* this will be freed by FreeRDP at rdpsnd_server_context_free. */ + AUDIO_FORMAT *audio_formats = malloc(sizeof rdp_audio_supported_audio_formats); + if (!audio_formats) { + weston_log("RDPAudio - Couldn't allocate memory for audio formats.\n"); + goto Error_Exit; + } + memcpy(audio_formats, rdp_audio_supported_audio_formats, sizeof rdp_audio_supported_audio_formats); + + peerCtx->rdpsnd_server_context->data = (void*)peerCtx; + peerCtx->rdpsnd_server_context->Activated = rdp_audio_client_activated; + peerCtx->rdpsnd_server_context->ConfirmBlock = rdp_audio_client_confirm_block; + peerCtx->rdpsnd_server_context->num_server_formats = ARRAYSIZE(rdp_audio_supported_audio_formats); + peerCtx->rdpsnd_server_context->server_formats = audio_formats; + peerCtx->rdpsnd_server_context->src_format = &rdp_audio_supported_audio_formats[0]; +#if HAVE_RDPSND_DYNAMIC_VIRTUAL_CHANNEL + peerCtx->rdpsnd_server_context->use_dynamic_virtual_channel = TRUE; + s = getenv("WESTON_RDP_DISABLE_AUDIO_PLAYBACK_DYNAMIC_VIRTUAL_CHANNEL"); + if (s) { + if (strcmp(s, "true") == 0) { + peerCtx->rdpsnd_server_context->use_dynamic_virtual_channel = FALSE; + weston_log("RDPAudio - force static channel.\n"); + } + } +#endif // HAVE_RDPAUDIO_DYNAMIC_VIRTUAL_CHANNEL + + /* Calling Initialize does Start as well */ + if (peerCtx->rdpsnd_server_context->Initialize(peerCtx->rdpsnd_server_context, TRUE) != 0) + goto Error_Exit; + + return 0; + +Error_Exit: + if (peerCtx->audioInSem != -1) { + close(peerCtx->audioInSem); + peerCtx->audioInSem = -1; + } + + if (peerCtx->rdpsnd_server_context) { + rdpsnd_server_context_free(peerCtx->rdpsnd_server_context); + peerCtx->rdpsnd_server_context = NULL; + } + + return 0; // Continue without audio +} + +void +rdp_audio_destroy(RdpPeerContext *peerCtx) +{ + if (peerCtx->rdpsnd_server_context) { + + if (peerCtx->pulseAudioSinkThread) { + peerCtx->audioExitSignal = TRUE; + shutdown(peerCtx->pulseAudioSinkListenerFd, SHUT_RDWR); + shutdown(peerCtx->pulseAudioSinkFd, SHUT_RDWR); + pthread_kill(peerCtx->pulseAudioSinkThread, SIGUSR2); + pthread_join(peerCtx->pulseAudioSinkThread, NULL); + + if (peerCtx->pulseAudioSinkListenerFd != -1) { + close(peerCtx->pulseAudioSinkListenerFd); + peerCtx->pulseAudioSinkListenerFd = -1; + } + + if (peerCtx->pulseAudioSinkFd != -1) { + close(peerCtx->pulseAudioSinkFd); + peerCtx->pulseAudioSinkFd = -1; + } + + if (peerCtx->audioBuffer) { + free(peerCtx->audioBuffer); + peerCtx->audioBuffer = NULL; + } + + peerCtx->pulseAudioSinkThread = 0; + } + + assert(peerCtx->pulseAudioSinkListenerFd < 0); + assert(peerCtx->pulseAudioSinkFd < 0); + assert(peerCtx->audioBuffer == NULL); + + peerCtx->rdpsnd_server_context->Close(peerCtx->rdpsnd_server_context); + peerCtx->rdpsnd_server_context->Stop(peerCtx->rdpsnd_server_context); + + if (peerCtx->audioInSem != -1) { + close(peerCtx->audioInSem); + peerCtx->audioInSem = -1; + } + + rdpsnd_server_context_free(peerCtx->rdpsnd_server_context); + peerCtx->rdpsnd_server_context = NULL; + } +} diff --git a/libweston/backend-rdp/rdpaudioin.c b/libweston/backend-rdp/rdpaudioin.c new file mode 100644 index 000000000..f49a2c112 --- /dev/null +++ b/libweston/backend-rdp/rdpaudioin.c @@ -0,0 +1,592 @@ +/* + * 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 +#include +#include +#include +#include "rdp.h" + +static AUDIO_FORMAT rdp_audioin_supported_audio_formats[] = { + { WAVE_FORMAT_PCM, 1, 44100, 88200, 2, 16, 0, NULL }, + }; + +static char* +AUDIO_FORMAT_to_String(UINT16 format) +{ + switch (format) { + case WAVE_FORMAT_UNKNOWN: + return "WAVE_FORMAT_UNKNOWN"; + case WAVE_FORMAT_PCM: + return "WAVE_FORMAT_PCM"; + case WAVE_FORMAT_ADPCM: + return "WAVE_FORMAT_ADPCM"; + case WAVE_FORMAT_IEEE_FLOAT: + return "WAVE_FORMAT_IEEE_FLOAT"; + case WAVE_FORMAT_VSELP: + return "WAVE_FORMAT_VSELP"; + case WAVE_FORMAT_IBM_CVSD: + return "WAVE_FORMAT_IBM_CVSD"; + case WAVE_FORMAT_ALAW: + return "WAVE_FORMAT_ALAW"; + case WAVE_FORMAT_MULAW: + return "WAVE_FORMAT_MULAW"; + case WAVE_FORMAT_OKI_ADPCM: + return "WAVE_FORMAT_OKI_ADPCM"; + case WAVE_FORMAT_DVI_ADPCM: + return "WAVE_FORMAT_DVI_ADPCM"; + case WAVE_FORMAT_MEDIASPACE_ADPCM: + return "WAVE_FORMAT_MEDIASPACE_ADPCM"; + case WAVE_FORMAT_SIERRA_ADPCM: + return "WAVE_FORMAT_SIERRA_ADPCM"; + case WAVE_FORMAT_G723_ADPCM: + return "WAVE_FORMAT_G723_ADPCM"; + case WAVE_FORMAT_DIGISTD: + return "WAVE_FORMAT_DIGISTD"; + case WAVE_FORMAT_DIGIFIX: + return "WAVE_FORMAT_DIGIFIX"; + case WAVE_FORMAT_DIALOGIC_OKI_ADPCM: + return "WAVE_FORMAT_DIALOGIC_OKI_ADPCM"; + case WAVE_FORMAT_MEDIAVISION_ADPCM: + return "WAVE_FORMAT_MEDIAVISION_ADPCM"; + case WAVE_FORMAT_CU_CODEC: + return "WAVE_FORMAT_CU_CODEC"; + case WAVE_FORMAT_YAMAHA_ADPCM: + return "WAVE_FORMAT_YAMAHA_ADPCM"; + case WAVE_FORMAT_SONARC: + return "WAVE_FORMAT_SONARC"; + case WAVE_FORMAT_DSPGROUP_TRUESPEECH: + return "WAVE_FORMAT_DSPGROUP_TRUESPEECH"; + case WAVE_FORMAT_ECHOSC1: + return "WAVE_FORMAT_ECHOSC1"; + case WAVE_FORMAT_AUDIOFILE_AF36: + return "WAVE_FORMAT_AUDIOFILE_AF36"; + case WAVE_FORMAT_APTX: + return "WAVE_FORMAT_APTX"; + case WAVE_FORMAT_AUDIOFILE_AF10: + return "WAVE_FORMAT_AUDIOFILE_AF10"; + case WAVE_FORMAT_PROSODY_1612: + return "WAVE_FORMAT_PROSODY_1612"; + case WAVE_FORMAT_DOLBY_AC2: + return "WAVE_FORMAT_DOLBY_AC2"; + case WAVE_FORMAT_GSM610: + return "WAVE_FORMAT_GSM610"; + case WAVE_FORMAT_MSNAUDIO: + return "WAVE_FORMAT_MSNAUDIO"; + case WAVE_FORMAT_ANTEX_ADPCME: + return "WAVE_FORMAT_ANTEX_ADPCME"; + case WAVE_FORMAT_CONTROL_RES_VQLPC: + return "WAVE_FORMAT_CONTROL_RES_VQLPC"; + case WAVE_FORMAT_DIGIREAL: + return "WAVE_FORMAT_DIGIREAL"; + case WAVE_FORMAT_DIGIADPCM: + return "WAVE_FORMAT_DIGIADPCM"; + case WAVE_FORMAT_CONTROL_RES_CR10: + return "WAVE_FORMAT_CONTROL_RES_CR10"; + case WAVE_FORMAT_NMS_VBXADPCM: + return "WAVE_FORMAT_NMS_VBXADPCM"; + case WAVE_FORMAT_ROLAND_RDAC: + return "WAVE_FORMAT_ROLAND_RDAC"; + case WAVE_FORMAT_ECHOSC3: + return "WAVE_FORMAT_ECHOSC3"; + case WAVE_FORMAT_ROCKWELL_ADPCM: + return "WAVE_FORMAT_ROCKWELL_ADPCM"; + case WAVE_FORMAT_ROCKWELL_DIGITALK: + return "WAVE_FORMAT_ROCKWELL_DIGITALK"; + case WAVE_FORMAT_XEBEC: + return "WAVE_FORMAT_XEBEC"; + case WAVE_FORMAT_G721_ADPCM: + return "WAVE_FORMAT_G721_ADPCM"; + case WAVE_FORMAT_G728_CELP: + return "WAVE_FORMAT_G728_CELP"; + case WAVE_FORMAT_MSG723: + return "WAVE_FORMAT_MSG723"; + case WAVE_FORMAT_MPEG: + return "WAVE_FORMAT_MPEG"; + case WAVE_FORMAT_RT24: + return "WAVE_FORMAT_RT24"; + case WAVE_FORMAT_PAC: + return "WAVE_FORMAT_PAC"; + case WAVE_FORMAT_MPEGLAYER3: + return "WAVE_FORMAT_MPEGLAYER3"; + case WAVE_FORMAT_LUCENT_G723: + return "WAVE_FORMAT_LUCENT_G723"; + case WAVE_FORMAT_CIRRUS: + return "WAVE_FORMAT_CIRRUS"; + case WAVE_FORMAT_ESPCM: + return "WAVE_FORMAT_ESPCM"; + case WAVE_FORMAT_VOXWARE: + return "WAVE_FORMAT_VOXWARE"; + case WAVE_FORMAT_CANOPUS_ATRAC: + return "WAVE_FORMAT_CANOPUS_ATRAC"; + case WAVE_FORMAT_G726_ADPCM: + return "WAVE_FORMAT_G726_ADPCM"; + case WAVE_FORMAT_G722_ADPCM: + return "WAVE_FORMAT_G722_ADPCM"; + case WAVE_FORMAT_DSAT: + return "WAVE_FORMAT_DSAT"; + case WAVE_FORMAT_DSAT_DISPLAY: + return "WAVE_FORMAT_DSAT_DISPLAY"; + case WAVE_FORMAT_VOXWARE_BYTE_ALIGNED: + return "WAVE_FORMAT_VOXWARE_BYTE_ALIGNED"; + case WAVE_FORMAT_VOXWARE_AC8: + return "WAVE_FORMAT_VOXWARE_AC8"; + case WAVE_FORMAT_VOXWARE_AC10: + return "WAVE_FORMAT_VOXWARE_AC10"; + case WAVE_FORMAT_VOXWARE_AC16: + return "WAVE_FORMAT_VOXWARE_AC16"; + case WAVE_FORMAT_VOXWARE_AC20: + return "WAVE_FORMAT_VOXWARE_AC20"; + case WAVE_FORMAT_VOXWARE_RT24: + return "WAVE_FORMAT_VOXWARE_RT24"; + case WAVE_FORMAT_VOXWARE_RT29: + return "WAVE_FORMAT_VOXWARE_RT29"; + case WAVE_FORMAT_VOXWARE_RT29HW: + return "WAVE_FORMAT_VOXWARE_RT29HW"; + case WAVE_FORMAT_VOXWARE_VR12: + return "WAVE_FORMAT_VOXWARE_VR12"; + case WAVE_FORMAT_VOXWARE_VR18: + return "WAVE_FORMAT_VOXWARE_VR18"; + case WAVE_FORMAT_VOXWARE_TQ40: + return "WAVE_FORMAT_VOXWARE_TQ40"; + case WAVE_FORMAT_SOFTSOUND: + return "WAVE_FORMAT_SOFTSOUND"; + case WAVE_FORMAT_VOXWARE_TQ60: + return "WAVE_FORMAT_VOXWARE_TQ60"; + case WAVE_FORMAT_MSRT24: + return "WAVE_FORMAT_MSRT24"; + case WAVE_FORMAT_G729A: + return "WAVE_FORMAT_G729A"; + case WAVE_FORMAT_MVI_MV12: + return "WAVE_FORMAT_MVI_MV12"; + case WAVE_FORMAT_DF_G726: + return "WAVE_FORMAT_DF_G726"; + case WAVE_FORMAT_DF_GSM610: + return "WAVE_FORMAT_DF_GSM610"; + case WAVE_FORMAT_ISIAUDIO: + return "WAVE_FORMAT_ISIAUDIO"; + case WAVE_FORMAT_ONLIVE: + return "WAVE_FORMAT_ONLIVE"; + case WAVE_FORMAT_SBC24: + return "WAVE_FORMAT_SBC24"; + case WAVE_FORMAT_DOLBY_AC3_SPDIF: + return "WAVE_FORMAT_DOLBY_AC3_SPDIF"; + case WAVE_FORMAT_ZYXEL_ADPCM: + return "WAVE_FORMAT_ZYXEL_ADPCM"; + case WAVE_FORMAT_PHILIPS_LPCBB: + return "WAVE_FORMAT_PHILIPS_LPCBB"; + case WAVE_FORMAT_PACKED: + return "WAVE_FORMAT_PACKED"; + case WAVE_FORMAT_RHETOREX_ADPCM: + return "WAVE_FORMAT_RHETOREX_ADPCM"; + case WAVE_FORMAT_IRAT: + return "WAVE_FORMAT_IRAT"; + case WAVE_FORMAT_VIVO_G723: + return "WAVE_FORMAT_VIVO_G723"; + case WAVE_FORMAT_VIVO_SIREN: + return "WAVE_FORMAT_VIVO_SIREN"; + case WAVE_FORMAT_DIGITAL_G723: + return "WAVE_FORMAT_DIGITAL_G723"; + case WAVE_FORMAT_WMAUDIO2: + return "WAVE_FORMAT_WMAUDIO2"; + case WAVE_FORMAT_WMAUDIO3: + return "WAVE_FORMAT_WMAUDIO3"; + case WAVE_FORMAT_WMAUDIO_LOSSLESS: + return "WAVE_FORMAT_WMAUDIO_LOSSLESS"; + case WAVE_FORMAT_CREATIVE_ADPCM: + return "WAVE_FORMAT_CREATIVE_ADPCM"; + case WAVE_FORMAT_CREATIVE_FASTSPEECH8: + return "WAVE_FORMAT_CREATIVE_FASTSPEECH8"; + case WAVE_FORMAT_CREATIVE_FASTSPEECH10: + return "WAVE_FORMAT_CREATIVE_FASTSPEECH10"; + case WAVE_FORMAT_QUARTERDECK: + return "WAVE_FORMAT_QUARTERDECK"; + case WAVE_FORMAT_FM_TOWNS_SND: + return "WAVE_FORMAT_FM_TOWNS_SND"; + case WAVE_FORMAT_BTV_DIGITAL: + return "WAVE_FORMAT_BTV_DIGITAL"; + case WAVE_FORMAT_VME_VMPCM: + return "WAVE_FORMAT_VME_VMPCM"; + case WAVE_FORMAT_OLIGSM: + return "WAVE_FORMAT_OLIGSM"; + case WAVE_FORMAT_OLIADPCM: + return "WAVE_FORMAT_OLIADPCM"; + case WAVE_FORMAT_OLICELP: + return "WAVE_FORMAT_OLICELP"; + case WAVE_FORMAT_OLISBC: + return "WAVE_FORMAT_OLISBC"; + case WAVE_FORMAT_OLIOPR: + return "WAVE_FORMAT_OLIOPR"; + case WAVE_FORMAT_LH_CODEC: + return "WAVE_FORMAT_LH_CODEC"; + case WAVE_FORMAT_NORRIS: + return "WAVE_FORMAT_NORRIS"; + case WAVE_FORMAT_SOUNDSPACE_MUSICOMPRESS: + return "WAVE_FORMAT_SOUNDSPACE_MUSICOMPRESS"; + case WAVE_FORMAT_DVM: + return "WAVE_FORMAT_DVM"; + case WAVE_FORMAT_AAC_MS: + return "WAVE_FORMAT_AAC_MS"; + } + + return "WAVE_FORMAT_UNKNOWN"; +} + +static int +rdp_audioin_setup_listener(RdpPeerContext *peerCtx) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + char *source_socket_path; + int fd; + struct sockaddr_un s; + int bytes; + int error; + + fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + weston_log("Couldn't create audioin listener socket.\n"); + return -1; + } + + source_socket_path = getenv("PULSE_AUDIO_RDP_SOURCE"); + if (source_socket_path == NULL || source_socket_path[0] == '\0') { + close(fd); + weston_log("Environment variable PULSE_AUDIO_RDP_SOURCE not set.\n"); + return -1; + } + + memset(&s, 0, sizeof(s)); + s.sun_family = AF_UNIX; + bytes = sizeof(s.sun_path) - 1; + snprintf(s.sun_path, bytes, "%s", source_socket_path); + + remove(s.sun_path); + + rdp_debug(b, "Pulse Audio source listener socket on %s\n", s.sun_path); + error = bind(fd, (struct sockaddr *)&s, sizeof(struct sockaddr_un)); + if (error != 0) { + close(fd); + weston_log("Failed to bind to listener socket for audioin (%d).\n", error); + return -1; + } + + listen(fd, 100); + return fd; +} + +static UINT +rdp_audioin_client_opening(audin_server_context* context) +{ + RdpPeerContext *peerCtx = (RdpPeerContext*)context->data; + struct rdp_backend *b = peerCtx->rdpBackend; + int format = -1; + int i, j; + + rdp_debug(b, "RDP Audio Open: %d audio formats supported.\n", + (int)context->num_client_formats); + + for (i = 0; i < (int)context->num_client_formats; i++) { + rdp_debug(b, "\t[%d] - Format(%s) - Bits(%d), Channels(%d), Frequency(%d)\n", + i, + AUDIO_FORMAT_to_String(context->client_formats[i].wFormatTag), + context->client_formats[i].wBitsPerSample, + context->client_formats[i].nChannels, + context->client_formats[i].nSamplesPerSec); + + for (j = 0; j < (int)context->num_server_formats; j++) { + if ((context->client_formats[i].wFormatTag == context->server_formats[j].wFormatTag) && + (context->client_formats[i].nChannels == context->server_formats[j].nChannels) && + (context->client_formats[i].nSamplesPerSec == context->server_formats[j].nSamplesPerSec)) { + rdp_debug(b, "RDPAudioIn - Agreed on format %d.\n", i); + format = i; + break; + } + } + } + + if (format == -1) { + weston_log("RDPAudioIn - No agreeded format.\n"); + return ERROR_INVALID_DATA; + } + + context->SelectFormat(context, format); + peerCtx->isAudioInStreamOpened = TRUE; + + return 0; +} + +static UINT +rdp_audioin_client_open_result( + audin_server_context* context, + UINT32 result) +{ + RdpPeerContext *peerCtx = (RdpPeerContext*)context->data; + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug(b, "RDP AudioIn Open Result (%d)\n", result); + return 0; +} + +static UINT +rdp_audioin_client_receive_samples( + audin_server_context* context, + const AUDIO_FORMAT* format, + wStream* buf, + size_t nframes) +{ + RdpPeerContext *peerCtx = (RdpPeerContext*)context->data; + struct rdp_backend *b = peerCtx->rdpBackend; + + if (!peerCtx->isAudioInStreamOpened || peerCtx->pulseAudioSourceFd == -1) { + weston_log("RDPAudioIn - audio stream is not opened.\n"); + return 0; + } + + if (nframes) { + assert(format->wFormatTag == WAVE_FORMAT_PCM); + assert(format->nChannels == 1); + assert(format->nSamplesPerSec == 44100); + assert(format->wBitsPerSample == 16); + assert(buf != NULL); + + int bytes = nframes * format->wBitsPerSample / 8; + int sent = send(peerCtx->pulseAudioSourceFd, buf->buffer, bytes, 0); + if (sent != bytes) { + rdp_debug(b, "RDP AudioIn source send failed (sent:%d, bytes:%d) %s\n", + sent, bytes, strerror(errno)); + + /* Unblock worker thread to close pipe to pulseaudio */ + uint64_t one=1; + if (write(peerCtx->closeAudioSourceFd, &one, sizeof(one)) != sizeof(uint64_t)) { + weston_log("RDP AudioIn error at receive_samples while writing to closeAudioSourceFd (%s)\n", strerror(errno)); + return ERROR_INTERNAL_ERROR; + } + + if (sent <= 0) { + /* return error to FreeRDP as failed to send samples to pulseaudio. */ + return ERROR_INTERNAL_ERROR; + } + } + } + + return 0; +} + +static void signalhandler(int sig) { + weston_log("RDP AudioIn: %s(%d)\n", __func__, sig); + return; +} + +static void* +rdp_audioin_source_thread(void *context) +{ + RdpPeerContext *peerCtx = (RdpPeerContext*)context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct sigaction act; + sigset_t set; + + sigemptyset(&set); + if (sigaddset(&set, SIGUSR2) == -1) { + weston_log("AudioIn source thread: sigaddset(SIGUSR2) failed.\n"); + return NULL; + } + if (pthread_sigmask(SIG_UNBLOCK, &set, NULL) != 0) { + weston_log("AudioIn source thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); + return NULL; + } + act.sa_flags = 0; + act.sa_mask = set; + act.sa_handler = &signalhandler; + if (sigaction(SIGUSR2, &act, NULL) == -1) { + weston_log("AudioIn source thread: sigaction(SIGUSR2) failed.\n"); + return NULL; + } + + assert(peerCtx->closeAudioSourceFd != -1); + assert(peerCtx->pulseAudioSourceListenerFd != -1); + + for (;;) { + rdp_debug(b, "AudioIn source_thread: Listening for audio in connection.\n"); + + if (peerCtx->audioInExitSignal) { + rdp_debug(b, "AudioIn source_thread is asked to exit (accept loop)\n"); + break; + } + + /* + * Wait for a connection on our listening socket + */ + peerCtx->pulseAudioSourceFd = accept(peerCtx->pulseAudioSourceListenerFd, NULL, NULL); + if (peerCtx->pulseAudioSourceFd < 0) { + weston_log("AudioIn source thread: Listener connection error (%s)\n", strerror(errno)); + continue; + } else { + rdp_debug(b, "AudioIn connection successful on socket (%d).\n", peerCtx->pulseAudioSourceFd); + if (peerCtx->audin_server_context->Open(peerCtx->audin_server_context)) { + rdp_debug(b, "RDP AudioIn opened.\n"); + /* + * Wait for the connection to be closed + */ + uint64_t dummy; + if (read(peerCtx->closeAudioSourceFd, &dummy, sizeof(dummy)) != sizeof(uint64_t)) { + weston_log("RDP AudioIn wait on eventfd failed. thread exiting. %s\n", strerror(errno)); + break; + } + peerCtx->audin_server_context->Close(peerCtx->audin_server_context); + rdp_debug(b, "RDP AudioIn closed.\n"); + } else { + weston_log("Failed to open audio in connection with RDP client.\n"); + } + + close(peerCtx->pulseAudioSourceFd); + peerCtx->pulseAudioSourceFd = -1; + } + } + + if (peerCtx->audin_server_context->IsOpen(peerCtx->audin_server_context)) + peerCtx->audin_server_context->Close(peerCtx->audin_server_context); + + if (peerCtx->pulseAudioSourceFd != -1) { + close(peerCtx->pulseAudioSourceFd); + peerCtx->pulseAudioSourceFd = -1; + } + + return NULL; +} + +int +rdp_audioin_init(RdpPeerContext *peerCtx) +{ + peerCtx->audin_server_context = audin_server_context_new(peerCtx->vcm); + if (!peerCtx->audin_server_context) { + weston_log("RDPAudioIn - Couldn't initialize audio virtual channel.\n"); + return 0; // Continue without audio + } + + peerCtx->audioInExitSignal = FALSE; + peerCtx->pulseAudioSourceThread = 0; + peerCtx->pulseAudioSourceListenerFd = -1; + peerCtx->pulseAudioSourceFd = -1; + peerCtx->closeAudioSourceFd = -1; + + // this will be freed by FreeRDP at audin_server_context_free. + AUDIO_FORMAT *audio_formats = malloc(sizeof rdp_audioin_supported_audio_formats); + if (!audio_formats) { + weston_log("RDPAudioIn - Couldn't allocate memory for audio formats.\n"); + goto Error_Exit; + } + memcpy(audio_formats, rdp_audioin_supported_audio_formats, sizeof rdp_audioin_supported_audio_formats); + + peerCtx->audin_server_context->data = (void*)peerCtx; + peerCtx->audin_server_context->Opening = rdp_audioin_client_opening; + peerCtx->audin_server_context->OpenResult = rdp_audioin_client_open_result; + peerCtx->audin_server_context->ReceiveSamples = rdp_audioin_client_receive_samples; + peerCtx->audin_server_context->num_server_formats = ARRAYSIZE(rdp_audioin_supported_audio_formats); + peerCtx->audin_server_context->server_formats = audio_formats; + peerCtx->audin_server_context->dst_format = &rdp_audioin_supported_audio_formats[0]; + peerCtx->audin_server_context->frames_per_packet = rdp_audioin_supported_audio_formats[0].nSamplesPerSec / 100; // 10ms per packet + + peerCtx->closeAudioSourceFd = eventfd(0, EFD_CLOEXEC); + if (peerCtx->closeAudioSourceFd < 0) { + weston_log("RDPAudioIn - Couldn't initialize eventfd.\n"); + goto Error_Exit; + } + + peerCtx->pulseAudioSourceListenerFd = rdp_audioin_setup_listener(peerCtx); + if (peerCtx->pulseAudioSourceListenerFd < 0) { + weston_log("RDPAudioIn - rdp_audioin_setup_listener failed.\n"); + goto Error_Exit; + } + + if (pthread_create(&peerCtx->pulseAudioSourceThread, NULL, rdp_audioin_source_thread, (void*)peerCtx) < 0) { + weston_log("RDPAudioIn - Failed to start Pulse Audio Source Thread. No audio in will be available.\n"); + goto Error_Exit; + } + + return 0; + +Error_Exit: + if (peerCtx->pulseAudioSourceListenerFd != -1) { + close(peerCtx->pulseAudioSourceListenerFd); + peerCtx->pulseAudioSourceListenerFd = -1; + } + + if (peerCtx->closeAudioSourceFd != -1) { + close(peerCtx->closeAudioSourceFd); + peerCtx->closeAudioSourceFd = -1; + } + + if (peerCtx->audin_server_context) { + audin_server_context_free(peerCtx->audin_server_context); + peerCtx->audin_server_context = NULL; + } + + return 0; // Continue without audio +} + +void +rdp_audioin_destroy(RdpPeerContext *peerCtx) +{ + if (peerCtx->audin_server_context) { + + if (peerCtx->pulseAudioSourceThread) { + peerCtx->audioInExitSignal = TRUE; + shutdown(peerCtx->pulseAudioSourceListenerFd, SHUT_RDWR); + shutdown(peerCtx->closeAudioSourceFd, SHUT_RDWR); + pthread_kill(peerCtx->pulseAudioSourceThread, SIGUSR2); + pthread_join(peerCtx->pulseAudioSourceThread, NULL); + + if (peerCtx->pulseAudioSourceListenerFd != -1) { + close(peerCtx->pulseAudioSourceListenerFd); + peerCtx->pulseAudioSourceListenerFd = -1; + } + + if (peerCtx->closeAudioSourceFd != -1) { + close(peerCtx->closeAudioSourceFd); + peerCtx->closeAudioSourceFd = -1; + } + + peerCtx->pulseAudioSourceThread = 0; + } + + assert(peerCtx->pulseAudioSourceListenerFd < 0); + assert(peerCtx->closeAudioSourceFd < 0); + + assert(!peerCtx->audin_server_context->IsOpen(peerCtx->audin_server_context)); + audin_server_context_free(peerCtx->audin_server_context); + peerCtx->audin_server_context = NULL; + } +} diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c new file mode 100644 index 000000000..9b6fd065c --- /dev/null +++ b/libweston/backend-rdp/rdpclip.c @@ -0,0 +1,1417 @@ +/* + * 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 "rdp.h" + +#include "libweston-internal.h" + +#define RDP_INVALID_EVENT_SOURCE ((void*)(-1)) + +/* From MSDN, RegisterClipboardFormat API. + Registered clipboard formats are identified by values in the range 0xC000 through 0xFFFF. */ +#define CF_PRIVATE_RTF 49309 // fake format ID for "Rich Text Format". +#define CF_PRIVATE_HTML 49405 // fake format ID for "HTML Format". + + /* 1 2 3 4 5 6 7 8 */ + /*01234567890 1 2345678901234 5 67890123456 7 89012345678901234567890 1 234567890123456789012 3 4*/ +char rdp_clipboard_html_header[] = "Version:0.9\r\nStartHTML:-1\r\nEndHTML:-1\r\nStartFragment:00000000\r\nEndFragment:00000000\r\n"; +#define RDP_CLIPBOARD_FRAGMENT_START_OFFSET (53) //--------------------------------------------+ | +#define RDP_CLIPBOARD_FRAGMENT_END_OFFSET (75) //----------------------------------------------------------------------+ + +/* + * https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format + * + * The fragment should be preceded and followed by the HTML comments and + * (no space allowed between the !-- and the text) to conveniently + * indicate where the fragment starts and ends. + */ +char rdp_clipboard_html_fragment_start[] = "\r\n"; +char rdp_clipboard_html_fragment_end[] = "\r\n"; + +struct rdp_clipboard_data_source; + +typedef void *(*pfn_process_data)(struct rdp_clipboard_data_source *source, BOOL is_send); + +struct rdp_clipboard_supported_format { + UINT32 index; + UINT32 format_id; + char *format_name; + char *mime_type; + pfn_process_data pfn; +}; + +static void *clipboard_process_text(struct rdp_clipboard_data_source *, BOOL); +static void *clipboard_process_bmp(struct rdp_clipboard_data_source *, BOOL); +static void *clipboard_process_html(struct rdp_clipboard_data_source *, BOOL); + +struct rdp_clipboard_supported_format clipboard_supported_formats[] = { + { 0, CF_TEXT, NULL, "text/plain;charset=utf-8", clipboard_process_text }, + { 1, CF_DIB, NULL, "image/bmp", clipboard_process_bmp }, + { 2, CF_PRIVATE_RTF, "Rich Text Format", "text/rtf", clipboard_process_text }, // same as text + { 3, CF_PRIVATE_HTML, "HTML Format", "text/html", clipboard_process_html }, +}; +#define RDP_NUM_CLIPBOARD_FORMATS ARRAY_LENGTH(clipboard_supported_formats) + +struct rdp_clipboard_data_source { + struct weston_data_source base; + struct wl_event_source *event_source; + struct wl_array data_contents; + void *context; + int refcount; + int data_source_fd; + int format_index; + UINT32 inflight_write_count; + void *inflight_data_to_write; + size_t inflight_data_size; + BOOL is_data_processed; + BOOL is_canceled; + UINT32 client_format_id_table[RDP_NUM_CLIPBOARD_FORMATS]; +}; + +static void * +clipboard_process_text(struct rdp_clipboard_data_source *source, BOOL is_send) +{ + freerdp_peer *client = (freerdp_peer*)source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + if (!source->is_data_processed) { + if (is_send) { + /* Linux to Windows */ + assert(source->data_contents.size <= source->data_contents.alloc); + assert(((char*)source->data_contents.data)[source->data_contents.size] == '\0'); + /* Include terminating NULL in size */ + source->data_contents.size++; + } else { + /* Windows to Linux */ + char *data = (char*)source->data_contents.data; + size_t data_size = source->data_contents.size; + + /* Windows's data has trailing chars, which Linux doesn't expect. */ + while(data_size && ((data[data_size-1] == '\0') || (data[data_size-1] == '\n'))) + data_size -= 1; + source->data_contents.size = data_size; + } + source->is_data_processed = TRUE; + } + + rdp_debug_verbose(b, "RDP %s (%p): %s (%d bytes)\n", + __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); + + return source->data_contents.data; +} + +/* based off sample code at https://docs.microsoft.com/en-us/troubleshoot/cpp/add-html-code-clipboard + But this missing a lot of corner cases, it must be rewritten with use of proper HTML parser */ +/* TODO: This doesn't work for converting HTML from Firefox in Wayland mode to Windows in certain case, + because Firefox sends "...", thus + here needs to property strip meta header and convert to the Windows clipboard style HTML. */ +static void * +clipboard_process_html(struct rdp_clipboard_data_source *source, BOOL is_send) +{ + freerdp_peer *client = (freerdp_peer*)source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct wl_array data_contents; + + wl_array_init(&data_contents); + + if (!source->is_data_processed) { + char *cur = (char *)source->data_contents.data; + cur = strstr(cur, "data_contents.size - + (cur - (char *)source->data_contents.data); + + /* Windows's data has trailing chars, which Linux doesn't expect. */ + while(data_size && ((cur[data_size-1] == '\0') || (cur[data_size-1] == '\n'))) + data_size -= 1; + + if (!data_size) + goto error_return; + + if (!wl_array_add(&data_contents, data_size+1)) // +1 for null + goto error_return; + + memcpy(data_contents.data, cur, data_size); + ((char *)(data_contents.data))[data_size] = '\0'; + data_contents.size = data_size; + } else { + /* Linux to Windows */ + char *last, *buf; + UINT32 fragment_start, fragment_end; + + if (!wl_array_add(&data_contents, source->data_contents.size+200)) + goto error_return; + + buf = (char *)data_contents.data; + strcpy(buf, rdp_clipboard_html_header); + last = cur; + cur = strstr(cur, "' + strncat(buf, last, cur-last); + last = cur; + fragment_start = strlen(buf); + strcat(buf, rdp_clipboard_html_fragment_start); + cur = strstr(cur, "data_contents); + source->data_contents = data_contents; + source->is_data_processed = TRUE; + } + + rdp_debug_verbose(b, "RDP %s (%p): %s (%d bytes)\n", + __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); + //rdp_debug_verbose(b, "RDP clipboard_process_html (%p): %s \n\"%s\"\n (%d bytes)\n", + // source, is_send ? "send" : "receive", + // (char *)source->data_contents.data, + // (UINT32)source->data_contents.size); + + return source->data_contents.data; + +error_return: + + weston_log("RDP %s FAILED (%p): %s (%d bytes)\n", + __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); + //rdp_debug_verbose(b, "RDP clipboard_process_html FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", + // source, is_send ? "send" : "receive", + // (char *)source->data_contents.data, + // (UINT32)source->data_contents.size); + + wl_array_release(&data_contents); + + return NULL; +} + +#define DIB_HEADER_MARKER ((WORD) ('M' << 8) | 'B') +#define DIB_WIDTH_BYTES(bits) ((((bits) + 31) & ~31) >> 3) + +static void * +clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) +{ + freerdp_peer *client = (freerdp_peer*)source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + void *ret = NULL; + BITMAPFILEHEADER *bmfh = NULL; + BITMAPINFOHEADER *bmih = NULL; + UINT32 color_table_size = 0; + size_t original_data_size = source->data_contents.size; + BOOL was_data_processed = source->is_data_processed; + struct wl_array data_contents; + + wl_array_init(&data_contents); + + if (is_send) { + /* Linux to Windows */ + if (source->data_contents.size <= sizeof(BITMAPFILEHEADER)) + goto error_return; + + bmfh = (BITMAPFILEHEADER *)source->data_contents.data; + bmih = (BITMAPINFOHEADER *)(bmfh + 1); + if (bmih->biCompression == BI_BITFIELDS) + color_table_size = sizeof(RGBQUAD) * 3; + else + color_table_size = sizeof(RGBQUAD) * bmih->biClrUsed; + + /* size must be adjusted only once */ + if (!source->is_data_processed) { + source->data_contents.size -= sizeof(*bmfh); + source->is_data_processed = TRUE; + } + + ret = (void *)bmih; // Skip BITMAPFILEHEADER. + } else { + /* Windows to Linux */ + if (!source->is_data_processed) { + BITMAPFILEHEADER _bmfh = {}; + bmih = (BITMAPINFOHEADER *)source->data_contents.data; + bmfh = &_bmfh; + if (bmih->biCompression == BI_BITFIELDS) + color_table_size = sizeof(RGBQUAD) * 3; + else + color_table_size = sizeof(RGBQUAD) * bmih->biClrUsed; + + bmfh->bfType = DIB_HEADER_MARKER; + bmfh->bfOffBits = sizeof(*bmfh) + bmih->biSize + color_table_size; + if (bmih->biSizeImage) + bmfh->bfSize = bmfh->bfOffBits + bmih->biSizeImage; + else if (bmih->biCompression == BI_BITFIELDS || bmih->biCompression == BI_RGB) + bmfh->bfSize = bmfh->bfOffBits + + (DIB_WIDTH_BYTES(bmih->biWidth * bmih->biBitCount) * abs(bmih->biHeight)); + else + goto error_return; + + if (!wl_array_add(&data_contents, bmfh->bfSize)) + goto error_return; + assert(data_contents.size == bmfh->bfSize); + + memcpy(data_contents.data, bmfh, sizeof(*bmfh)); + memcpy((char *)data_contents.data + sizeof(*bmfh), source->data_contents.data, bmih->biSizeImage - sizeof(*bmfh)); + + /* swap the data_contents with new one */ + wl_array_release(&source->data_contents); + source->data_contents = data_contents; + source->is_data_processed = TRUE; + + bmfh = (BITMAPFILEHEADER *)source->data_contents.data; + bmih = (BITMAPINFOHEADER *)(bmfh + 1); + } else { + bmfh = (BITMAPFILEHEADER *)source->data_contents.data; + bmih = (BITMAPINFOHEADER *)(bmfh + 1); + if (bmih->biCompression == BI_BITFIELDS) + color_table_size = sizeof(RGBQUAD) * 3; + else + color_table_size = sizeof(RGBQUAD) * bmih->biClrUsed; + } + + ret = source->data_contents.data; + } + + assert(ret); + assert(bmfh); + assert(bmih); + + rdp_debug_verbose(b, "RDP %s (%p): %s (%d bytes)\n", + __func__, source, is_send ? "send" : "receive", + (UINT32)source->data_contents.size); + rdp_debug_verbose_continue(b, " BITMAPFILEHEADER.bfType:0x%x\n", bmfh->bfType); + rdp_debug_verbose_continue(b, " BITMAPFILEHEADER.bfSize:%d\n", bmfh->bfSize); + rdp_debug_verbose_continue(b, " BITMAPFILEHEADER.bfOffBits:%d\n", bmfh->bfOffBits); + rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biSize:%d\n", bmih->biSize); + rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biWidth:%d\n", bmih->biWidth); + rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biHeight:%d, y-Up:%s\n", abs(bmih->biHeight), bmih->biHeight < 0 ? "yes" : "no"); + rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biPlanes:%d\n", bmih->biPlanes); + rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biBitCount:%d\n", bmih->biBitCount); + rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biCompression:%d\n", bmih->biCompression); + rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biSizeImage:%d\n", bmih->biSizeImage); + rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biXPelsPerMeter:%d\n", bmih->biXPelsPerMeter); + rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biYPelsPerMeter:%d\n", bmih->biYPelsPerMeter); + rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biClrUsed:%d\n", bmih->biClrUsed); + rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biClrImportant:%d\n", bmih->biClrImportant); + BITMAPINFO *bmi = (BITMAPINFO *)bmih; + for (UINT32 i = 0; i < color_table_size / sizeof(RGBQUAD); i++) { + rdp_debug_verbose_continue(b, " BITMAPINFO.bmiColors[%d]:%02x:%02x:%02x:%02x\n", + i, + (UINT32)bmi->bmiColors[i].rgbReserved, + (UINT32)bmi->bmiColors[i].rgbRed, + (UINT32)bmi->bmiColors[i].rgbGreen, + (UINT32)bmi->bmiColors[i].rgbBlue); + } + if (bmih->biBitCount == 32) { + DWORD *bits = (DWORD*)((char*)bmfh + bmfh->bfOffBits); + assert(bits == (DWORD*)(&bmi->bmiColors[color_table_size/sizeof(RGBQUAD)])); + //for (UINT32 i = 0; i < 4; i++) { + // rdp_debug_verbose_continue(b, " %08x %08x %08x %08x %08x %08x %08x %08x\n", + // bits[0],bits[1],bits[2],bits[3],bits[4],bits[5],bits[6],bits[7]); + // bits += 8; + //} + } else if (bmih->biBitCount == 24) { + BYTE *bits = (BYTE*)bmfh + bmfh->bfOffBits; + assert(bits == (BYTE*)(&bmi->bmiColors[color_table_size/sizeof(RGBQUAD)])); + //for (UINT32 i = 0; i < 4; i++) { + // rdp_debug_verbose_continue(b, " %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x\n", + // bits[ 0],bits[ 1],bits[ 2], bits[ 3],bits[ 4],bits[ 5], bits[ 6],bits[ 7],bits[ 8], bits[ 9],bits[10],bits[11], + // bits[12],bits[13],bits[14], bits[15],bits[16],bits[17], bits[18],bits[19],bits[20], bits[21],bits[22],bits[23]); + // bits += 24; + //} + } + rdp_debug_verbose_continue(b, " sizeof(BITMAPFILEHEADER):%d\n", (UINT32) sizeof(BITMAPFILEHEADER)); + rdp_debug_verbose_continue(b, " sizeof(BITMAPINFOHEADER):%d\n", (UINT32) sizeof(BITMAPINFOHEADER)); + rdp_debug_verbose_continue(b, " original_data_size:%d\n", (UINT32) original_data_size); + rdp_debug_verbose_continue(b, " new_data_size:%d\n", (UINT32) source->data_contents.size); + rdp_debug_verbose_continue(b, " data_processed:%d -> %d\n", was_data_processed, source->is_data_processed); + + return ret; + +error_return: + + weston_log("RDP %s FAILED (%p): %s (%d bytes)\n", + __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); + + wl_array_release(&data_contents); + + return NULL; +} + +static char * +clipboard_format_id_to_string(UINT32 formatId, bool is_server_format_id) +{ + switch (formatId) + { + case CF_RAW: + return "CF_RAW"; + case CF_TEXT: + return "CF_TEXT"; + case CF_BITMAP: + return "CF_BITMAP"; + case CF_METAFILEPICT: + return "CF_METAFILEPICT"; + case CF_SYLK: + return "CF_SYLK"; + case CF_DIF: + return "CF_DIF"; + case CF_TIFF: + return "CF_TIFF"; + case CF_OEMTEXT: + return "CF_OEMTEX"; + case CF_DIB: + return "CF_DIB"; + case CF_PALETTE: + return "CF_PALETTE"; + case CF_PENDATA: + return "CF_PENDATA"; + case CF_RIFF: + return "CF_RIFF"; + case CF_WAVE: + return "CF_WAVE"; + case CF_UNICODETEXT: + return "CF_UNICODETEXT"; + case CF_ENHMETAFILE: + return "CF_ENHMETAFILE"; + case CF_HDROP: + return "CF_HDROP"; + case CF_LOCALE: + return "CF_LOCALE"; + case CF_DIBV5: + return "CF_DIBV5"; + + case CF_OWNERDISPLAY: + return "CF_OWNERDISPLAY"; + case CF_DSPTEXT: + return "CF_DSPTEXT"; + case CF_DSPBITMAP: + return "CF_DSPBITMAP"; + case CF_DSPMETAFILEPICT: + return "CF_DSPMETAFILEPICT"; + case CF_DSPENHMETAFILE: + return "CF_DSPENHMETAFILE"; + } + + if (formatId >= CF_PRIVATEFIRST && formatId <= CF_PRIVATELAST) + return "CF_PRIVATE"; + + if (formatId >= CF_GDIOBJFIRST && formatId <= CF_GDIOBJLAST) + return "CF_GDIOBJ"; + + if (is_server_format_id) { + if (formatId == CF_PRIVATE_HTML) + return "CF_PRIVATE_HTML"; + + if (formatId == CF_PRIVATE_RTF) + return "CF_PRIVATE_RTF"; + } else { + /* From MSDN, RegisterClipboardFormat API. + Registered clipboard formats are identified by values in the range 0xC000 through 0xFFFF. */ + if (formatId >= 0xC000 && formatId <= 0xFFFF) + return "Client side Registered Clipboard Format"; + } + + return "Unknown format"; +} + +/* find supported index in supported format table by format id from client */ +static int +clipboard_find_supported_format_by_format_id(UINT32 format_id) +{ + for (UINT i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { + struct rdp_clipboard_supported_format *format = &clipboard_supported_formats[i]; + if (format_id == format->format_id) { + assert(i == format->index); + return format->index; + } + } + return -1; +} + +/* find supported index in supported format table by format id and name from client */ +static int +clipboard_find_supported_format_by_format_id_and_name(UINT32 format_id, const char *format_name) +{ + for (UINT i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { + struct rdp_clipboard_supported_format *format = &clipboard_supported_formats[i]; + /* when our supported format table has format name, only format name must match, + format id provided from client is ignored (but it may be saved by caller for future use. + When our supported format table doesn't have format name, only format id must match, + format name (if provided from client) is ignored */ + if ((format->format_name == NULL && format_id == format->format_id) || + (format->format_name && format_name && strcmp(format_name, format->format_name) == 0)) { + assert(i == format->index); + return format->index; + } + } + return -1; +} + +/* find supported index in supported format table by mime */ +static int +clipboard_find_supported_format_by_mime_type(const char *mime_type) +{ + for (UINT i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { + struct rdp_clipboard_supported_format *format = &clipboard_supported_formats[i]; + if (strcmp(mime_type, format->mime_type) == 0) { + assert(i == format->index); + return format->index; + } + } + return -1; +} + +static void +clipboard_data_source_unref(struct rdp_clipboard_data_source *source) +{ + freerdp_peer *client = (freerdp_peer*)source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + char **p; + + assert(source->refcount); + source->refcount--; + + rdp_debug(b, "RDP %s (%p): refcount:%d\n", __func__, source, source->refcount); + + if (source->refcount > 0) + return; + + if (source->event_source) + wl_event_source_remove(source->event_source); + + if (source->data_source_fd != -1) + close(source->data_source_fd); + + wl_array_release(&source->data_contents); + + wl_signal_emit(&source->base.destroy_signal, + &source->base); + + wl_array_for_each(p, &source->base.mime_types) + free(*p); + + wl_array_release(&source->base.mime_types); + + free(source); +} + +/*****************************************\ + * FreeRDP format data response functions * +\******************************************/ + +/* Inform client data request is succeeded with data */ +static UINT +clipboard_client_send_format_data_response(RdpPeerContext *peerCtx, struct rdp_clipboard_data_source *source, void *data, UINT32 size) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; + + rdp_debug(b, "Client: %s (%p) format_index:%d %s (%d bytes)\n", + __func__, source, source->format_index, + clipboard_supported_formats[source->format_index].mime_type, size); + + formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; + formatDataResponse.msgFlags = CB_RESPONSE_OK; + formatDataResponse.dataLen = size; + formatDataResponse.requestedFormatData = data; + peerCtx->clipboard_server_context->ServerFormatDataResponse(peerCtx->clipboard_server_context, &formatDataResponse); + /* if here failed to send response, what can we do ? */ + + /* now client can send new data request */ + peerCtx->clipboard_data_request_event_source = NULL; + + return 0; +} + +/* Inform client data request is failed */ +static UINT +clipboard_client_send_format_data_response_fail(RdpPeerContext *peerCtx, struct rdp_clipboard_data_source *source) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; + + rdp_debug(b, "Client: %s (%p)\n", __func__, source); + + formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; + formatDataResponse.msgFlags = CB_RESPONSE_FAIL; + formatDataResponse.dataLen = 0; + formatDataResponse.requestedFormatData = NULL; + peerCtx->clipboard_server_context->ServerFormatDataResponse(peerCtx->clipboard_server_context, &formatDataResponse); + /* if here failed to send response, what can we do ? */ + + /* now client can send new data request */ + peerCtx->clipboard_data_request_event_source = NULL; + + return 0; +} + +/***************************************\ + * Compositor file descritor callbacks * +\***************************************/ + +/* Send server clipboard data to client when server side application sent them via pipe. */ +static int +clipboard_data_source_read(int fd, uint32_t mask, void *arg) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; + freerdp_peer *client = (freerdp_peer*)source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + char *data; + int len, size; + void *data_to_send; + + rdp_debug_verbose(b, "RDP %s (%p) fd:%d\n", __func__, source, fd); + + ASSERT_COMPOSITOR_THREAD(b); + + assert(source->data_source_fd == fd); + + /* event source is not removed here, but it will be removed when read is completed, + until it's completed this function will be called whenever next chunk of data is + available for read in pipe. */ + assert(source->event_source); + + /* if buffer is less than 1024 bytes remaining, request another 1024 bytes minimum */ + /* but actual reallocated buffer size will be increased by ^2 */ + if (source->data_contents.alloc - source->data_contents.size < 1024) { + if (!wl_array_add(&source->data_contents, 1024)) { + goto error_exit; + } + source->data_contents.size -= 1024; + } + + data = (char*)source->data_contents.data + source->data_contents.size; + size = source->data_contents.alloc - source->data_contents.size - 1; // -1 leave space for NULL-terminate. + len = read(fd, data, size); + if (len == 0) { + /* all data from source is read, so completed. */ + rdp_debug(b, "RDP %s (%p): read completed (%ld bytes)\n", + __func__, source, source->data_contents.size); + if (!source->data_contents.size) + goto error_exit; + /* process data before sending to client */ + if (clipboard_supported_formats[source->format_index].pfn) + data_to_send = clipboard_supported_formats[source->format_index].pfn(source, TRUE); + else + data_to_send = source->data_contents.data; + /* send clipboard data to client */ + if (data_to_send) + clipboard_client_send_format_data_response(peerCtx, source, data_to_send, source->data_contents.size); + else + goto error_exit; + /* make sure this is the last reference, so event source is removed at unref */ + assert(source->refcount == 1); + clipboard_data_source_unref(source); + } else if (len < 0) { + weston_log("RDP %s (%p) read failed (%s)\n", __func__, source, strerror(errno)); + goto error_exit; + } else { + source->data_contents.size += len; + ((char*)source->data_contents.data)[source->data_contents.size] = '\0'; + rdp_debug_verbose(b, "RDP %s (%p): read (%zu bytes)\n", + __func__, source, source->data_contents.size); + } + return 0; + +error_exit: + clipboard_client_send_format_data_response_fail(peerCtx, source); + /* make sure this is the last reference, so event source is removed at unref */ + assert(source->refcount == 1); + clipboard_data_source_unref(source); + return 0; +} + +/* Send client's clipboard data to the requesting application at server side */ +static int +clipboard_data_source_write(int fd, uint32_t mask, void *arg) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; + freerdp_peer *client = (freerdp_peer *) source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct weston_seat *seat = peerCtx->item.seat; + struct wl_event_loop *loop = wl_display_get_event_loop(seat->compositor->wl_display); + void *data_to_write; + size_t data_size; + ssize_t size; + + rdp_debug_verbose(b, "RDP %s (%p) fd:%d\n", __func__, source, fd); + + ASSERT_COMPOSITOR_THREAD(b); + + assert(source->data_source_fd == fd); + assert(source == peerCtx->clipboard_inflight_client_data_source); + + /* remove event source now, and if write is failed with EAGAIN, queue back to display loop. */ + wl_event_source_remove(source->event_source); + source->event_source = NULL; + + if (source->is_canceled == FALSE && source->data_contents.data && source->data_contents.size) { + if (source->inflight_data_to_write) { + assert(source->inflight_data_size); + rdp_debug_verbose(b, "RDP %s: retry write retry count:%d (%p)\n", + __func__, source->inflight_write_count, source); + data_to_write = source->inflight_data_to_write; + data_size = source->inflight_data_size; + } else { + fcntl(source->data_source_fd, F_SETFL, O_WRONLY | O_NONBLOCK); + if (clipboard_supported_formats[source->format_index].pfn) + data_to_write = clipboard_supported_formats[source->format_index].pfn(source, FALSE); + else + data_to_write = source->data_contents.data; + data_size = source->data_contents.size; + } + while (data_to_write && data_size) { + size = write(fd, data_to_write, data_size); + if (size <= 0) { + if (errno != EAGAIN) { + weston_log("RDP %s (%p) write failed %s\n", + __func__, source, strerror(errno)); + break; + } + source->inflight_data_to_write = data_to_write; + source->inflight_data_size = data_size; + source->inflight_write_count++; + source->event_source = + wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + clipboard_data_source_write, source); + if (!source->event_source) { + weston_log("RDP %s (%p) wl_event_loop_add_fd failed\n", + __func__, source); + break; + } + return 0; + } else { + assert(data_size >= (size_t)size); + data_size -= size; + data_to_write = (char *)data_to_write + size; + rdp_debug_verbose(b, "RDP %s (%p) wrote %ld bytes, remaining %ld bytes\n", + __func__, source, size, data_size); + if (!data_size) + rdp_debug(b, "RDP %s (%p) write completed (%ld bytes)\n", + __func__, source, source->data_contents.size); + } + } + } + + close(source->data_source_fd); + source->data_source_fd = -1; + source->inflight_write_count = 0; + source->inflight_data_to_write = NULL; + source->inflight_data_size = 0; + clipboard_data_source_unref(source); + peerCtx->clipboard_inflight_client_data_source = NULL; + + return 0; +} + +/***********************************\ + * Clipboard data-device callbacks * +\***********************************/ + +/* data-device informs the given data format is accepted */ +static void +clipboard_data_source_accept(struct weston_data_source *base, + uint32_t time, const char *mime_type) +{ + weston_log("RDP %s (base:%p) mime-type:\"%s\"\n", __func__, base, mime_type); +} + +/* data-device informs the application requested the specified format data in given data_source (= client's clipboard) */ +static void +clipboard_data_source_send(struct weston_data_source *base, + const char *mime_type, int32_t fd) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)base; + freerdp_peer *client = (freerdp_peer*)source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct weston_seat *seat = peerCtx->item.seat; + struct wl_event_loop *loop = wl_display_get_event_loop(seat->compositor->wl_display); + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = {}; + int index; + + rdp_debug(b, "RDP %s (%p) fd:%d, mime-type:\"%s\"\n", __func__, source, fd, mime_type); + + ASSERT_COMPOSITOR_THREAD(b); + + if (peerCtx->clipboard_inflight_client_data_source) { + /* Here means server side (Linux application) request clipboard data, + but server hasn't completed with previous request yet. + If this happens, punt to idle loop and reattempt. */ + weston_log("\n\n\nRDP %s (%p) vs (%p): outstanding RDP data request (client to server)\n\n\n", + __func__, source, peerCtx->clipboard_inflight_client_data_source); + goto error_return_close_fd; + } + + index = clipboard_find_supported_format_by_mime_type(mime_type); + if (index >= 0 && /* check supported by this RDP bridge */ + source->client_format_id_table[index]) { /* check supported by current data source from client */ + peerCtx->clipboard_inflight_client_data_source = source; + source->refcount++; // reference while request inflight. + source->data_source_fd = fd; + assert(source->inflight_write_count == 0); + assert(source->inflight_data_to_write == NULL); + assert(source->inflight_data_size == 0); + if (index == source->format_index) { + /* data is already in data_contents, no need to pull from client */ + assert(source->event_source == NULL); + source->event_source = + wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + clipboard_data_source_write, source); + if (!source->event_source) { + weston_log("RDP %s (%p) wl_event_loop_add_fd failed\n", __func__, source); + goto error_return_unref_source; + } + } else { + /* purge cached data */ + wl_array_release(&source->data_contents); + wl_array_init(&source->data_contents); + source->is_data_processed = FALSE; + /* update requesting format property */ + source->format_index = index; + /* request clipboard data from client */ + formatDataRequest.msgType = CB_FORMAT_DATA_REQUEST; + formatDataRequest.dataLen = 4; + formatDataRequest.requestedFormatId = source->client_format_id_table[index]; + rdp_debug(b, "RDP %s (%p) request index:%d formatId:%d %s\n", + __func__, source, index, + formatDataRequest.requestedFormatId, + clipboard_format_id_to_string(formatDataRequest.requestedFormatId, false)); + if (peerCtx->clipboard_server_context->ServerFormatDataRequest(peerCtx->clipboard_server_context, &formatDataRequest) != 0) + goto error_return_unref_source; + } + } else { + weston_log("RDP %s (%p) specified format (%s.%d.%d) is not supported by client\n", + __func__, source, mime_type, index, source->client_format_id_table[index]); + goto error_return_close_fd; + } + + return; + +error_return_unref_source: + source->data_source_fd = -1; + assert(source->inflight_write_count == 0); + assert(source->inflight_data_to_write == NULL); + assert(source->inflight_data_size == 0); + clipboard_data_source_unref(source); + assert(peerCtx->clipboard_inflight_client_data_source == source); + peerCtx->clipboard_inflight_client_data_source = NULL; + +error_return_close_fd: + close(fd); + + return; +} + +/* data-device informs the given data source is not longer referenced by compositor */ +static void +clipboard_data_source_cancel(struct weston_data_source *base) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *) base; + freerdp_peer *client = (freerdp_peer*)source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug(b, "RDP %s (%p)\n", __func__, source); + + ASSERT_COMPOSITOR_THREAD(b); + + if (source == peerCtx->clipboard_inflight_client_data_source) { + weston_log("RDP %s (%p): still inflight\n", __func__, source); + assert(source->refcount > 1); + source->is_canceled = TRUE; + } else { + /* everything outside of the base has to be cleaned up */ + assert(source->event_source == NULL); + wl_array_release(&source->data_contents); + wl_array_init(&source->data_contents); + source->is_data_processed = FALSE; + source->format_index = -1; + memset(source->client_format_id_table, 0, sizeof(source->client_format_id_table)); + source->inflight_write_count = 0; + source->inflight_data_to_write = NULL; + source->inflight_data_size = 0; + if (source->data_source_fd != -1) { + close(source->data_source_fd); + source->data_source_fd = -1; + } + } +} + +/**********************************\ + * Compositor idle loop callbacks * +\**********************************/ + +/* Publish client's available clipboard formats to compositor (make them visible to applications in server) */ +static void +clipboard_data_source_publish(void *arg) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; + freerdp_peer *client = (freerdp_peer*)source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_clipboard_data_source *source_prev; + + rdp_debug(b, "RDP %s (%p)\n", __func__, source); + + ASSERT_COMPOSITOR_THREAD(b); + + /* here is going to publish new data, if previous data from us is still referenced, + unref it after selection */ + source_prev = peerCtx->clipboard_client_data_source; + peerCtx->clipboard_client_data_source = source; + + source->event_source = NULL; + source->base.accept = clipboard_data_source_accept; + source->base.send = clipboard_data_source_send; + source->base.cancel = clipboard_data_source_cancel; + weston_seat_set_selection(peerCtx->item.seat, &source->base, + wl_display_next_serial(b->compositor->wl_display)); + + if (source_prev) + clipboard_data_source_unref(source_prev); + + return; +} + +/* Request the specified clipboard data from data-device at server side */ +static void +clipboard_data_source_request(void *arg) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)arg; + struct rdp_backend *b = peerCtx->rdpBackend; + struct weston_seat *seat = peerCtx->item.seat; + struct weston_data_source *selection_data_source = seat->selection_data_source; + struct wl_event_loop *loop = wl_display_get_event_loop(seat->compositor->wl_display); + struct rdp_clipboard_data_source *source = NULL; + int p[2] = {}; + const char *requested_mime_type, **mime_type; + int index; + BOOL found_requested_format; + + ASSERT_COMPOSITOR_THREAD(b); + + /* set to invalid, so it still validate incoming request, but won't free event source at error. */ + peerCtx->clipboard_data_request_event_source = RDP_INVALID_EVENT_SOURCE; + + index = peerCtx->clipboard_last_requested_format_index; + assert(index >= 0 && index < (int)RDP_NUM_CLIPBOARD_FORMATS); + requested_mime_type = clipboard_supported_formats[index].mime_type; + rdp_debug(b, "RDP %s (base:%p) requested mime type:\"%s\"\n", + __func__, selection_data_source, requested_mime_type); + + found_requested_format = FALSE; + wl_array_for_each(mime_type, &selection_data_source->mime_types) { + rdp_debug(b, "RDP %s (base:%p) available formats: %s\n", + __func__, selection_data_source, *mime_type); + if (strcmp(requested_mime_type, *mime_type) == 0) { + found_requested_format = TRUE; + break; + } + } + if (!found_requested_format) { + rdp_debug(b, "RDP %s (base:%p) requested format not found format:\"%s\"\n", + __func__, selection_data_source, requested_mime_type); + goto error_exit_response_fail; + } + + source = zalloc(sizeof *source); + if (!source) + goto error_exit_response_fail; + + rdp_debug(b, "RDP %s (%p) allocated\n", __func__, source); + wl_signal_init(&source->base.destroy_signal); + wl_array_init(&source->base.mime_types); + wl_array_init(&source->data_contents); + source->is_data_processed = FALSE; + source->context = (void*)peerCtx->item.peer; + source->refcount = 1; // decremented when data sent to client. + source->data_source_fd = -1; + source->format_index = index; + + if (pipe2(p, O_CLOEXEC) == -1) + goto error_exit_free_source; + + source->data_source_fd = p[0]; + + /* Request data from data source */ + selection_data_source->send(selection_data_source, requested_mime_type, p[1]); + /* p[1] should be closed by data source */ + + /* wait until data is ready on pipe */ + source->event_source = + wl_event_loop_add_fd(loop, p[0], WL_EVENT_READABLE, + clipboard_data_source_read, source); + if (!source->event_source) { + weston_log("RDP %s: wl_event_loop_add_fd failed (%p)\n", __func__, source); + goto error_exit_free_source; + } + + return; + +error_exit_free_source: + clipboard_data_source_unref(source); + +error_exit_response_fail: + clipboard_client_send_format_data_response_fail(peerCtx, NULL); + + return; +} + +/*************************************\ + * Compositor notification callbacks * +\*************************************/ + +/* Compositor notify new clipboard data is going to be copied to clipboard, and its supported formats */ +static void +clipboard_set_selection(struct wl_listener *listener, void *data) +{ + RdpPeerContext *peerCtx = + container_of(listener, RdpPeerContext, clipboard_selection_listener); + struct rdp_backend *b = peerCtx->rdpBackend; + struct weston_seat *seat = data; + struct weston_data_source *selection_data_source = seat->selection_data_source; + CLIPRDR_FORMAT_LIST formatList = {}; + CLIPRDR_FORMAT format[RDP_NUM_CLIPBOARD_FORMATS] = {}; + const char **mime_type; + int index, num_supported_format = 0, num_avail_format = 0; + + rdp_debug(b, "RDP %s (base:%p}\n", __func__, selection_data_source); + + ASSERT_COMPOSITOR_THREAD(b); + + if (selection_data_source == NULL) { + return; + } else if (selection_data_source->accept == clipboard_data_source_accept) { + /* Callback for our data source. */ + return; + } + + /* another data source (from server side) gets selected, + no longer need previous data from us */ + if (peerCtx->clipboard_client_data_source) { + clipboard_data_source_unref(peerCtx->clipboard_client_data_source); + peerCtx->clipboard_client_data_source = NULL; + } + + wl_array_for_each(mime_type, &selection_data_source->mime_types) { + rdp_debug(b, "RDP %s (base:%p) available formats[%d]: %s\n", + __func__, selection_data_source, num_avail_format, *mime_type); + num_avail_format++; + } + + /* check supported clipboard formats */ + wl_array_for_each(mime_type, &selection_data_source->mime_types) { + index = clipboard_find_supported_format_by_mime_type(*mime_type); + if (index >= 0) { + format[num_supported_format].formatId = clipboard_supported_formats[index].format_id; + format[num_supported_format].formatName = clipboard_supported_formats[index].format_name; + rdp_debug(b, "RDP %s (base:%p) supported formats[%d]: %d: %s\n", + __func__, + selection_data_source, + num_supported_format, + format[num_supported_format].formatId, + format[num_supported_format].formatName ? \ + format[num_supported_format].formatName : \ + clipboard_format_id_to_string(format[num_supported_format].formatId, true)); + num_supported_format++; + } + } + + if (num_supported_format) { + /* let client knows formats are available in server clipboard */ + formatList.msgType = CB_FORMAT_LIST; + formatList.numFormats = num_supported_format; + formatList.formats = &format[0]; + peerCtx->clipboard_server_context->ServerFormatList(peerCtx->clipboard_server_context, &formatList); + } else { + rdp_debug(b, "RDP %s (base:%p) no supported formats\n", __func__, selection_data_source); + } + + return; +} + +/*********************\ + * FreeRDP callbacks * +\*********************/ + +/* client reports the path of temp folder */ +static UINT +clipboard_client_temp_directory(CliprdrServerContext* context, const CLIPRDR_TEMP_DIRECTORY* tempDirectory) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + rdp_debug(b, "Client: %s %s\n", __func__, tempDirectory->szTempDir); + return 0; +} + +/* client reports thier clipboard capabilities */ +static UINT +clipboard_client_capabilities(CliprdrServerContext* context, const CLIPRDR_CAPABILITIES* capabilities) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + rdp_debug(b, "Client: clipboard capabilities: cCapabilitiesSet:%d\n", capabilities->cCapabilitiesSets); + for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++) { + CLIPRDR_CAPABILITY_SET* capabilitySets = &capabilities->capabilitySets[i]; + switch (capabilitySets->capabilitySetType) { + case CB_CAPSTYPE_GENERAL: + { + CLIPRDR_GENERAL_CAPABILITY_SET *generalCapabilitySet = (CLIPRDR_GENERAL_CAPABILITY_SET *)capabilitySets; + rdp_debug(b, "Client: clipboard capabilities[%d]: General\n", i); + rdp_debug(b, " Version:%d\n", generalCapabilitySet->version); + rdp_debug(b, " GeneralFlags:0x%x\n", generalCapabilitySet->generalFlags); + if (generalCapabilitySet->generalFlags & CB_USE_LONG_FORMAT_NAMES) + rdp_debug(b, " CB_USE_LONG_FORMAT_NAMES\n"); + if (generalCapabilitySet->generalFlags & CB_STREAM_FILECLIP_ENABLED) + rdp_debug(b, " CB_STREAM_FILECLIP_ENABLED\n"); + if (generalCapabilitySet->generalFlags & CB_FILECLIP_NO_FILE_PATHS) + rdp_debug(b, " CB_FILECLIP_NO_FILE_PATHS\n"); + if (generalCapabilitySet->generalFlags & CB_CAN_LOCK_CLIPDATA) + rdp_debug(b, " CB_CAN_LOCK_CLIPDATA\n"); + break; + } + default: + return -1; + } + } + return 0; +} + +/* client reports the supported format list in client's clipboard */ +static UINT +clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT_LIST* formatList) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_clipboard_data_source *source = NULL; + BOOL isPublished = FALSE; + char **p, *s; + + ASSERT_NOT_COMPOSITOR_THREAD(b); + + rdp_debug(b, "Client: %s clipboard format list: numFormats:%d\n", __func__, formatList->numFormats); + for (UINT32 i = 0; i < formatList->numFormats; i++) { + CLIPRDR_FORMAT* format = &formatList->formats[i]; + rdp_debug(b, "Client: %s clipboard formats[%d]: formatId:%d, formatName:%s\n", + __func__, i, format->formatId, + format->formatName ? format->formatName : clipboard_format_id_to_string(format->formatId, false)); + } + + source = zalloc(sizeof *source); + if (source) { + rdp_debug(b, "Client: %s (%p) allocated\n", __func__, source); + wl_signal_init(&source->base.destroy_signal); + wl_array_init(&source->base.mime_types); + wl_array_init(&source->data_contents); + source->context = (void*) client; + source->refcount = 1; // decremented when another source is selected. + source->data_source_fd = -1; + source->format_index = -1; + + for (UINT32 i = 0; i < formatList->numFormats; i++) { + CLIPRDR_FORMAT* format = &formatList->formats[i]; + int index = clipboard_find_supported_format_by_format_id_and_name(format->formatId, format->formatName); + if (index >= 0) { + /* save format id given from client, client can handle its own format id for private format. */ + source->client_format_id_table[index] = format->formatId; + s = strdup(clipboard_supported_formats[index].mime_type); + if (s) { + p = wl_array_add(&source->base.mime_types, sizeof *p); + if (p) { + rdp_debug(b, "Client: %s (%p) mine_type:\"%s\"\n", __func__, source, s); + *p = s; + } else { + rdp_debug(b, "Client: %s (%p) wl_array_add failed\n", __func__, source); + free(s); + } + } else { + rdp_debug(b, "Client: %s (%p) strdup failed\n", __func__, source); + } + } + } + + if (source->base.mime_types.size) { + source->event_source = + rdp_defer_rdp_task_to_display_loop(peerCtx, + clipboard_data_source_publish, + source); + if (source->event_source) + isPublished = TRUE; + else + weston_log("Client: %s (%p) rdp_defer_rdp_task_to_display_loop failed\n", __func__, source); + } else { + rdp_debug(b, "Client: %s (%p) no formats are supported\n", __func__, source); + } + } + + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = {}; + formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.msgFlags = source ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + formatListResponse.dataLen = 0; + if (peerCtx->clipboard_server_context->ServerFormatListResponse(peerCtx->clipboard_server_context, &formatListResponse) != 0) { + weston_log("Client: %s (%p) ServerFormatListResponse failed\n", __func__, source); + return -1; + } + + if (!isPublished && source) + clipboard_data_source_unref(source); + + return 0; +} + +/* client responded with clipboard data asked by server */ +static UINT +clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct wl_event_loop *loop = wl_display_get_event_loop(b->compositor->wl_display); + struct rdp_clipboard_data_source *source = peerCtx->clipboard_inflight_client_data_source; + BOOL Success = FALSE; + + rdp_debug(b, "Client: %s (%p) flags:%d, dataLen:%d\n", + __func__, source, formatDataResponse->msgFlags, formatDataResponse->dataLen); + + ASSERT_NOT_COMPOSITOR_THREAD(b); + + if (source) { + if (formatDataResponse->msgFlags == CB_RESPONSE_OK) { + /* Recieved data from client, cache to data source */ + if (wl_array_add(&source->data_contents, formatDataResponse->dataLen+1)) { + memcpy(source->data_contents.data, + formatDataResponse->requestedFormatData, + formatDataResponse->dataLen); + source->data_contents.size = formatDataResponse->dataLen; + /* regardless data type, make sure it ends with NULL */ + ((char*)source->data_contents.data)[source->data_contents.size] = '\0'; + Success = TRUE; + } + } + + if (Success) { + assert(source->event_source == NULL); + source->event_source = + wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + clipboard_data_source_write, source); + if (!source->event_source) + weston_log("Client: %s: wl_event_loop_add_fd failed (%p)\n", __func__, source); + } + + if (!source->event_source) { + wl_array_release(&source->data_contents); + wl_array_init(&source->data_contents); + source->is_data_processed = FALSE; + source->format_index = -1; + memset(source->client_format_id_table, 0, sizeof(source->client_format_id_table)); + assert(source->inflight_write_count == 0); + assert(source->inflight_data_to_write == NULL); + assert(source->inflight_data_size == 0); + close(source->data_source_fd); + source->data_source_fd = -1; + clipboard_data_source_unref(source); + peerCtx->clipboard_inflight_client_data_source = NULL; + } + } else { + rdp_debug(b, "Client: %s client send data without server asking. protocol error.\n", __func__); + return -1; + } + + return 0; +} + +/* client responded on the format list sent by server */ +static UINT +clipboard_client_format_list_response(CliprdrServerContext* context, const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + rdp_debug(b, "Client: %s msgFlags:0x%x\n", __func__, formatListResponse->msgFlags); + return 0; +} + +/* client requested the data of specificed format in server clipboard */ +static UINT +clipboard_client_format_data_request(CliprdrServerContext* context, const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + int index; + + rdp_debug(b, "Client: %s requestedFormatId:%d - %s\n", + __func__, formatDataRequest->requestedFormatId, + clipboard_format_id_to_string(formatDataRequest->requestedFormatId, true)); + + ASSERT_NOT_COMPOSITOR_THREAD(b); + + if (peerCtx->clipboard_data_request_event_source) { + weston_log("Client: %s (%p) client requests data while server hasn't responded previous request yet. protocol error.\n", + __func__, peerCtx->clipboard_data_request_event_source); + return -1; + } + + /* Make sure clients requested the format we knew */ + index = clipboard_find_supported_format_by_format_id(formatDataRequest->requestedFormatId); + if (index >= 0) { + peerCtx->clipboard_last_requested_format_index = index; + peerCtx->clipboard_data_request_event_source = + rdp_defer_rdp_task_to_display_loop(peerCtx, clipboard_data_source_request, peerCtx); + if (!peerCtx->clipboard_data_request_event_source) { + weston_log("Client: %s rdp_defer_rdp_task_to_display_loop failed\n", __func__); + goto error_return; + } + } else { + weston_log("Client: %s client requests data format the server never reported in format list response. protocol error.\n", __func__); + return -1; + } + + return 0; + +error_return: + /* send FAIL response to client */ + if (clipboard_client_send_format_data_response_fail(peerCtx, NULL) != 0) + return -1; + return 0; +} + +/********************\ + * Public functions * +\********************/ + +int +rdp_clipboard_init(freerdp_peer* client) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct weston_seat *seat = peerCtx->item.seat; + + assert(seat); + + ASSERT_COMPOSITOR_THREAD(b); + + peerCtx->clipboard_server_context = cliprdr_server_context_new(peerCtx->vcm); + if (!peerCtx->clipboard_server_context) + return -1; + + peerCtx->clipboard_server_context->custom = (void *)client; + peerCtx->clipboard_server_context->TempDirectory = clipboard_client_temp_directory; + peerCtx->clipboard_server_context->ClientCapabilities = clipboard_client_capabilities; + peerCtx->clipboard_server_context->ClientFormatList = clipboard_client_format_list; + peerCtx->clipboard_server_context->ClientFormatListResponse = clipboard_client_format_list_response; + //peerCtx->clipboard_server_context->ClientLockClipboardData + //peerCtx->clipboard_server_context->ClientUnlockClipboardData + peerCtx->clipboard_server_context->ClientFormatDataRequest = clipboard_client_format_data_request; + peerCtx->clipboard_server_context->ClientFormatDataResponse = clipboard_client_format_data_response; + //peerCtx->clipboard_server_context->ClientFileContentsRequest + //peerCtx->clipboard_server_context->ClientFileContentsResponse + peerCtx->clipboard_server_context->useLongFormatNames = FALSE; // ASCII8 format name only (No Windows-style 2 bytes Unicode). + peerCtx->clipboard_server_context->streamFileClipEnabled = FALSE; + peerCtx->clipboard_server_context->fileClipNoFilePaths = FALSE; + peerCtx->clipboard_server_context->canLockClipData = TRUE; + if (peerCtx->clipboard_server_context->Start(peerCtx->clipboard_server_context) != 0) { + cliprdr_server_context_free(peerCtx->clipboard_server_context); + return -1; + } + + peerCtx->clipboard_selection_listener.notify = clipboard_set_selection; + wl_signal_add(&seat->selection_signal, + &peerCtx->clipboard_selection_listener); + + return 0; +} + +void +rdp_clipboard_destroy(RdpPeerContext *peerCtx) +{ + if (peerCtx->clipboard_selection_listener.notify) { + wl_list_remove(&peerCtx->clipboard_selection_listener.link); + peerCtx->clipboard_selection_listener.notify = NULL; + } + if (peerCtx->clipboard_data_request_event_source && + peerCtx->clipboard_data_request_event_source != RDP_INVALID_EVENT_SOURCE) { + wl_event_source_remove(peerCtx->clipboard_data_request_event_source); + peerCtx->clipboard_data_request_event_source = NULL; + } + + if (peerCtx->clipboard_inflight_client_data_source) { + clipboard_data_source_unref(peerCtx->clipboard_inflight_client_data_source); + peerCtx->clipboard_inflight_client_data_source = NULL; + } + if (peerCtx->clipboard_client_data_source) { + clipboard_data_source_unref(peerCtx->clipboard_client_data_source); + peerCtx->clipboard_client_data_source = NULL; + } + + if (peerCtx->clipboard_server_context) { + peerCtx->clipboard_server_context->Stop(peerCtx->clipboard_server_context); + cliprdr_server_context_free(peerCtx->clipboard_server_context); + peerCtx->clipboard_server_context = NULL; + } +} diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c new file mode 100644 index 000000000..2b5c8c3fe --- /dev/null +++ b/libweston/backend-rdp/rdpdisp.c @@ -0,0 +1,749 @@ +/* + * 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" + +static BOOL +is_line_intersected(int l1, int l2, int r1, int r2) +{ + int l = l1 > r1 ? l1 : r1; + int r = l2 < r2 ? l2 : r2; + return (l < r); +} + +static int +compare_monitors_x(const void *p1, const void *p2) +{ + const struct rdp_monitor_mode *l = p1; + const struct rdp_monitor_mode *r = p2; + return l->monitorDef.x > r->monitorDef.x; +} + +static int +compare_monitors_y(const void *p1, const void *p2) +{ + const struct rdp_monitor_mode *l = p1; + const struct rdp_monitor_mode *r = p2; + return l->monitorDef.y > r->monitorDef.y; +} + +static float +disp_get_client_scale_from_monitor(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + if (b->enable_hi_dpi_support) { + if (b->debug_desktop_scaling_factor) + return (float)b->debug_desktop_scaling_factor / 100.f; + else if (b->enable_fractional_hi_dpi_support) + return (float)monitorMode->monitorDef.attributes.desktopScaleFactor / 100.0f; + else + return (float)(int)(monitorMode->monitorDef.attributes.desktopScaleFactor / 100); + } else { + return 1.0f; + } +} + +static int +disp_get_output_scale_from_monitor(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode) +{ + return (int) disp_get_client_scale_from_monitor(peerCtx, monitorMode); +} + +static void +disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *monitorMode, UINT32 monitorCount, int *doneIndex) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + ASSERT_COMPOSITOR_THREAD(b); + + pixman_region32_clear(&peerCtx->regionClientHeads); + pixman_region32_clear(&peerCtx->regionWestonHeads); + /* move all heads to pending list */ + b->head_pending_list = b->head_list; + b->head_pending_list.next->prev = &b->head_pending_list; + b->head_pending_list.prev->next = &b->head_pending_list; + /* init move pending list */ + wl_list_init(&b->head_move_pending_list); + /* clear head list */ + wl_list_init(&b->head_list); + for (UINT32 i = 0; i < monitorCount; i++, monitorMode++) { + struct rdp_head *current; + wl_list_for_each(current, &b->head_pending_list, link) { + if (memcmp(¤t->monitorMode, monitorMode, sizeof(*monitorMode)) == 0) { + rdp_debug_verbose(b, "Head mode exact match:%s, x:%d, y:%d, width:%d, height:%d, is_primary: %d\n", + current->base.name, + current->monitorMode.monitorDef.x, current->monitorMode.monitorDef.y, + current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height, + current->monitorMode.monitorDef.is_primary); + /* move from pending list to move pending list */ + wl_list_remove(¤t->link); + wl_list_insert(&b->head_move_pending_list, ¤t->link); + /* accumulate monitor layout */ + pixman_region32_union_rect(&peerCtx->regionClientHeads, &peerCtx->regionClientHeads, + current->monitorMode.monitorDef.x, current->monitorMode.monitorDef.y, + current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height); + pixman_region32_union_rect(&peerCtx->regionWestonHeads, &peerCtx->regionWestonHeads, + current->monitorMode.rectWeston.x, current->monitorMode.rectWeston.y, + current->monitorMode.rectWeston.width, current->monitorMode.rectWeston.height); + *doneIndex |= (1 << i); + break; + } + } + } +} + +static void +disp_end_monitor_layout_change(freerdp_peer *client) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_head *current, *next; + + ASSERT_COMPOSITOR_THREAD(b); + + /* move output to final location */ + wl_list_for_each_safe(current, next, &b->head_move_pending_list, link) { + /* move from move pending list to current list */ + wl_list_remove(¤t->link); + wl_list_insert(&b->head_list, ¤t->link); + if (current->base.output) { + rdp_debug(b, "move head/output %s (%d,%d) -> (%d,%d)\n", + current->base.name, + current->base.output->x, + current->base.output->y, + current->monitorMode.rectWeston.x, + current->monitorMode.rectWeston.y); + /* Notify clients for updated output position. */ + weston_output_move(current->base.output, + current->monitorMode.rectWeston.x, + current->monitorMode.rectWeston.y); + } else { + /* newly created head doesn't have output yet */ + /* position will be set at rdp_output_enable */ + } + } + assert(wl_list_empty(&b->head_move_pending_list)); + wl_list_init(&b->head_move_pending_list); + /* remove all unsed head from pending list */ + if (!wl_list_empty(&b->head_pending_list)) { + wl_list_for_each_safe(current, next, &b->head_pending_list, link) + rdp_head_destroy(b->compositor, current); + /* make sure nothing left in pending list */ + assert(wl_list_empty(&b->head_pending_list)); + wl_list_init(&b->head_pending_list); + } + /* make sure head list is not empty */ + assert(!wl_list_empty(&b->head_list)); + + BOOL is_primary_found = FALSE; + wl_list_for_each(current, &b->head_list, link) { + if (current->monitorMode.monitorDef.is_primary) { + rdp_debug(b, "client origin (0,0) is (%d,%d) in Weston space\n", + current->monitorMode.rectWeston.x, + current->monitorMode.rectWeston.y); + /* primary must be at (0,0) in client space */ + assert(current->monitorMode.monitorDef.x == 0); + assert(current->monitorMode.monitorDef.y == 0); + /* there must be only one primary */ + assert(is_primary_found == FALSE); + is_primary_found = TRUE; + } + } + rdp_debug(b, "client virtual desktop is (%d,%d) - (%d,%d)\n", + peerCtx->regionClientHeads.extents.x1, peerCtx->regionClientHeads.extents.y1, + peerCtx->regionClientHeads.extents.x2, peerCtx->regionClientHeads.extents.y2); + rdp_debug(b, "weston virtual desktop is (%d,%d) - (%d,%d)\n", + peerCtx->regionWestonHeads.extents.x1, peerCtx->regionWestonHeads.extents.y1, + peerCtx->regionWestonHeads.extents.x2, peerCtx->regionWestonHeads.extents.y2); +} + +static UINT +disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *monitorMode) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + rdpSettings *settings = client->settings; + struct weston_output *output = NULL; + struct weston_head *head = NULL; + struct rdp_head *current; + BOOL updateMode = FALSE; + + ASSERT_COMPOSITOR_THREAD(b); + + if (monitorMode->monitorDef.is_primary) { + assert(b->head_default); + assert(b->output_default); + + /* use default output and head for primary */ + output = &b->output_default->base; + head = &b->head_default->base; + current = to_rdp_head(head); + + if (current->monitorMode.monitorDef.width != monitorMode->monitorDef.width || + current->monitorMode.monitorDef.height != monitorMode->monitorDef.height || + current->monitorMode.scale != monitorMode->scale) + updateMode = TRUE; + } else { + /* search head match configuration from pending list */ + wl_list_for_each(current, &b->head_pending_list, link) { + if (current->monitorMode.monitorDef.is_primary) { + /* primary is only re-used for primary */ + } else if (current->monitorMode.monitorDef.width == monitorMode->monitorDef.width && + current->monitorMode.monitorDef.height == monitorMode->monitorDef.height && + current->monitorMode.scale == monitorMode->scale) { + /* size mode (width/height/scale) */ + head = ¤t->base; + output = head->output; + break; + } else if (current->monitorMode.monitorDef.x == monitorMode->monitorDef.x && + current->monitorMode.monitorDef.y == monitorMode->monitorDef.y) { + /* position match in client space */ + head = ¤t->base; + output = head->output; + updateMode = TRUE; + break; + } + } + if (!head) { + /* just pick first one to change mode */ + wl_list_for_each(current, &b->head_pending_list, link) { + /* primary is only re-used for primary */ + if (!current->monitorMode.monitorDef.is_primary) { + head = ¤t->base; + output = head->output; + updateMode = TRUE; + break; + } + } + } + } + + if (head) { + assert(output); + assert(to_rdp_head(head) == current); + rdp_debug(b, "Head mode change:%s OLD width:%d, height:%d, scale:%d, clientScale:%f\n", + output->name, current->monitorMode.monitorDef.width, + current->monitorMode.monitorDef.height, + current->monitorMode.scale, + current->monitorMode.clientScale); + /* reusing exising head */ + current->monitorMode = *monitorMode; + /* update monitor region in client */ + pixman_region32_clear(¤t->regionClient); + pixman_region32_init_rect(¤t->regionClient, + monitorMode->monitorDef.x, monitorMode->monitorDef.y, + monitorMode->monitorDef.width, monitorMode->monitorDef.height); + pixman_region32_clear(¤t->regionWeston); + pixman_region32_init_rect(¤t->regionWeston, + monitorMode->rectWeston.x, monitorMode->rectWeston.y, + monitorMode->rectWeston.width, monitorMode->rectWeston.height); + /* move from pending list to move pending list */ + wl_list_remove(¤t->link); + wl_list_insert(&b->head_move_pending_list, &to_rdp_head(head)->link); + } else { + /* no head found, create one */ + if (rdp_head_create(b->compositor, monitorMode->monitorDef.is_primary, monitorMode) == NULL) + return ERROR_INTERNAL_ERROR; + } + + if (updateMode) { + if (output) { + assert(head); + /* ask weston to adjust size */ + struct weston_mode new_mode = {}; + new_mode.width = monitorMode->monitorDef.width; + new_mode.height = monitorMode->monitorDef.height; + if (monitorMode->monitorDef.is_primary) { + /* it looks settings's desktopWidth/Height only represents primary */ + settings->DesktopWidth = new_mode.width; + settings->DesktopHeight = new_mode.height; + } + rdp_debug(b, "Head mode change:%s NEW width:%d, height:%d, scale:%d, clientScale:%f\n", + output->name, monitorMode->monitorDef.width, + monitorMode->monitorDef.height, + monitorMode->scale, + monitorMode->clientScale); + if (output->scale != monitorMode->scale) { + weston_output_disable(output); + output->scale = 0; /* reset scale first, otherwise assert */ + weston_output_set_scale(output, monitorMode->scale); + weston_output_enable(output); + } + weston_output_mode_set_native(output, &new_mode, monitorMode->scale); + weston_head_set_physical_size(head, + monitorMode->monitorDef.attributes.physicalWidth, + monitorMode->monitorDef.attributes.physicalHeight); + /* Notify clients for updated resolution/scale. */ + weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); + /* output size must match with monitor's rect in weston space */ + assert(output->width == (int32_t) monitorMode->rectWeston.width); + assert(output->height == (int32_t) monitorMode->rectWeston.height); + } else { + /* if head doesn't have output yet, mode is set at rdp_output_set_size */ + rdp_debug(b, "output doesn't exist for head %s\n", head->name); + } + } + + /* accumulate monitor layout */ + pixman_region32_union_rect(&peerCtx->regionClientHeads, &peerCtx->regionClientHeads, + monitorMode->monitorDef.x, monitorMode->monitorDef.y, + monitorMode->monitorDef.width, monitorMode->monitorDef.height); + pixman_region32_union_rect(&peerCtx->regionWestonHeads, &peerCtx->regionWestonHeads, + monitorMode->rectWeston.x, monitorMode->rectWeston.y, + monitorMode->rectWeston.width, monitorMode->rectWeston.height); + + return 0; +} + +static void +disp_force_recreate_iter(void *element, void *data) +{ + struct weston_surface *surface = (struct weston_surface *)element; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + rail_state->forceRecreateSurface = TRUE; + rail_state->forceUpdateWindowState = TRUE; +} + +static BOOL +disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode, UINT32 monitorCount) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + bool isConnected_H = false; + bool isConnected_V = false; + bool isScalingUsed = false; + bool isScalingSupported = true; + uint32_t primaryCount = 0; + int upperLeftX = 0; + int upperLeftY = 0; + uint32_t i; + + /* dump client monitor topology */ + rdp_debug(b, "%s:---INPUT---\n", __func__); + for (i = 0; i < monitorCount; i++) { + rdp_debug(b, " rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", + i, monitorMode[i].monitorDef.x, monitorMode[i].monitorDef.y, + monitorMode[i].monitorDef.width, monitorMode[i].monitorDef.height, + monitorMode[i].monitorDef.is_primary); + rdp_debug(b, " rdpMonitor[%d]: physicalWidth:%d, physicalHeight:%d, orientation:%d\n", + i, monitorMode[i].monitorDef.attributes.physicalWidth, + monitorMode[i].monitorDef.attributes.physicalHeight, + monitorMode[i].monitorDef.attributes.orientation); + rdp_debug(b, " rdpMonitor[%d]: desktopScaleFactor:%d, deviceScaleFactor:%d\n", + i, monitorMode[i].monitorDef.attributes.desktopScaleFactor, + monitorMode[i].monitorDef.attributes.deviceScaleFactor); + rdp_debug(b, " rdpMonitor[%d]: scale:%d, client scale :%3.2f\n", + i, monitorMode[i].scale, monitorMode[i].clientScale); + } + + for (i = 0; i < monitorCount; i++) { + /* make sure there is only one primary and its position at client */ + if (monitorMode[i].monitorDef.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 (monitorMode[i].monitorDef.x != 0 || monitorMode[i].monitorDef.y != 0) { + weston_log("%s: RDP client reported primary is not at (0,0) but (%d,%d).\n", + __func__, monitorMode[i].monitorDef.x, monitorMode[i].monitorDef.y); + return FALSE; + } + } + + /* check if any monitor has scaling enabled */ + if (monitorMode[i].clientScale != 1.0f) + isScalingUsed = true; + + /* find upper-left corner of combined monitors in client space */ + if (upperLeftX > monitorMode[i].monitorDef.x) + upperLeftX = monitorMode[i].monitorDef.x; + if (upperLeftY > monitorMode[i].monitorDef.y) + upperLeftY = monitorMode[i].monitorDef.y; + } + assert(upperLeftX <= 0); + assert(upperLeftY <= 0); + rdp_debug(b, "Client desktop upper left coordinate (%d,%d)\n", upperLeftX, upperLeftY); + + if (monitorCount > 1) { + int32_t offsetFromOriginClient; + + /* first, sort monitors horizontally */ + qsort(monitorMode, monitorCount, sizeof(*monitorMode), compare_monitors_x); + assert(upperLeftX == monitorMode[0].monitorDef.x); + + /* check if monitors are horizontally connected each other */ + offsetFromOriginClient = monitorMode[0].monitorDef.x + monitorMode[0].monitorDef.width; + for (i = 1; i < monitorCount; i++) { + if (offsetFromOriginClient != monitorMode[i].monitorDef.x) { + rdp_debug(b, "\tRDP client reported monitors not horizontally connected each other at %d (x check)\n", i); + break; + } + offsetFromOriginClient += monitorMode[i].monitorDef.width; + + if (!is_line_intersected(monitorMode[i-1].monitorDef.y, + monitorMode[i-1].monitorDef.y + monitorMode[i-1].monitorDef.height, + monitorMode[i].monitorDef.y, + monitorMode[i].monitorDef.y + monitorMode[i].monitorDef.height)) { + rdp_debug(b, "\tRDP client reported monitors not horizontally connected each other at %d (y check)\n\n", i); + break; + } + } + if (i == monitorCount) { + rdp_debug(b, "\tAll monitors are horizontally placed\n"); + isConnected_H = true; + } else { + /* next, trying sort monitors vertically */ + qsort(monitorMode, monitorCount, sizeof(*monitorMode), compare_monitors_y); + assert(upperLeftY == monitorMode[0].monitorDef.y); + + /* make sure monitors are horizontally connected each other */ + offsetFromOriginClient = monitorMode[0].monitorDef.y + monitorMode[0].monitorDef.height; + for (i = 1; i < monitorCount; i++) { + if (offsetFromOriginClient != monitorMode[i].monitorDef.y) { + rdp_debug(b, "\tRDP client reported monitors not vertically connected each other at %d (y check)\n", i); + break; + } + offsetFromOriginClient += monitorMode[i].monitorDef.height; + + if (!is_line_intersected(monitorMode[i-1].monitorDef.x, + monitorMode[i-1].monitorDef.x + monitorMode[i-1].monitorDef.width, + monitorMode[i].monitorDef.x, + monitorMode[i].monitorDef.x + monitorMode[i].monitorDef.width)) { + rdp_debug(b, "\tRDP client reported monitors not horizontally connected each other at %d (x check)\n\n", i); + break; + } + } + + if (i == monitorCount) { + rdp_debug(b, "\tAll monitors are vertically placed\n"); + isConnected_V = true; + } + } + } else { + isConnected_H = true; + } + + if (isScalingUsed && (!isConnected_H && !isConnected_V)) { + /* scaling can't be supported in complex monitor placement */ + weston_log("\nWARNING\nWARNING\nWARNING: Scaling is used, but can't be supported in complex monitor placement\nWARNING\nWARNING\n"); + isScalingSupported = false; + } + + if (isScalingSupported) { + uint32_t offsetFromOriginWeston = 0; + for (i = 0; i < monitorCount; i++) { + monitorMode[i].rectWeston.width = monitorMode[i].monitorDef.width / monitorMode[i].scale; + monitorMode[i].rectWeston.height = monitorMode[i].monitorDef.height / monitorMode[i].scale; + if (isConnected_H) { + assert(isConnected_V == false); + monitorMode[i].rectWeston.x = offsetFromOriginWeston; + monitorMode[i].rectWeston.y = abs((upperLeftY - monitorMode[i].monitorDef.y) / monitorMode[i].scale); + offsetFromOriginWeston += monitorMode[i].rectWeston.width; + } else { + assert(isConnected_V == true); + monitorMode[i].rectWeston.x = abs((upperLeftX - monitorMode[i].monitorDef.x) / monitorMode[i].scale); + monitorMode[i].rectWeston.y = offsetFromOriginWeston; + offsetFromOriginWeston += monitorMode[i].rectWeston.height; + } + } + } else { + /* monitor placement is too complex to scale in weston space, fallback to 1.0f */ + for (i = 0; i < monitorCount; i++) { + monitorMode[i].rectWeston.width = monitorMode[i].monitorDef.width; + monitorMode[i].rectWeston.height = monitorMode[i].monitorDef.height; + monitorMode[i].rectWeston.x = monitorMode[i].monitorDef.x + abs(monitorMode[0].monitorDef.x); + monitorMode[i].rectWeston.y = monitorMode[i].monitorDef.y + abs(monitorMode[0].monitorDef.y); + monitorMode[i].scale = 1; + monitorMode[i].clientScale = 1.0f; + } + } + + rdp_debug(b, "%s:---OUTPUT---\n", __func__); + for (UINT32 i = 0; i < monitorCount; i++) { + rdp_debug(b, " rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", + i, monitorMode[i].monitorDef.x, monitorMode[i].monitorDef.y, + monitorMode[i].monitorDef.width, monitorMode[i].monitorDef.height, + monitorMode[i].monitorDef.is_primary); + rdp_debug(b, " rdpMonitor[%d]: weston x:%d, y:%d, width:%d, height:%d\n", + i, monitorMode[i].rectWeston.x, monitorMode[i].rectWeston.y, + monitorMode[i].rectWeston.width, monitorMode[i].rectWeston.height); + rdp_debug(b, " rdpMonitor[%d]: physicalWidth:%d, physicalHeight:%d, orientation:%d\n", + i, monitorMode[i].monitorDef.attributes.physicalWidth, + monitorMode[i].monitorDef.attributes.physicalHeight, + monitorMode[i].monitorDef.attributes.orientation); + rdp_debug(b, " rdpMonitor[%d]: desktopScaleFactor:%d, deviceScaleFactor:%d\n", + i, monitorMode[i].monitorDef.attributes.desktopScaleFactor, + monitorMode[i].monitorDef.attributes.deviceScaleFactor); + rdp_debug(b, " rdpMonitor[%d]: scale:%d, clientScale:%3.2f\n", + i, monitorMode[i].scale, monitorMode[i].clientScale); + } + + return TRUE; +} + +static void +disp_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MONITOR_LAYOUT_PDU* displayControl) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + rdpSettings *settings = client->settings; + struct rdp_backend *b = peerCtx->rdpBackend; + DISPLAY_CONTROL_MONITOR_LAYOUT *monitorLayout = displayControl->Monitors; + struct rdp_monitor_mode *monitorMode; + MONITOR_DEF *resetMonitorDef; + + ASSERT_COMPOSITOR_THREAD(b); + + rdp_debug(b, "Client: DisplayControl: monitor count:0x%x\n", displayControl->NumMonitors); + + assert(settings->HiDefRemoteApp); + + if (displayControl->NumMonitors > RDP_MAX_MONITOR) { + weston_log("\nWARNING\nWARNING\nWARNING: client reports more monitors then expected:(%d)\nWARNING\nWARNING\n", + displayControl->NumMonitors); + return; + } + + monitorMode = malloc(sizeof(struct rdp_monitor_mode) * displayControl->NumMonitors); + if (!monitorMode) + return; + resetMonitorDef = malloc(sizeof(MONITOR_DEF) * displayControl->NumMonitors); + if (!resetMonitorDef) { + free(monitorMode); + return; + } + + for (UINT i = 0; i < displayControl->NumMonitors; i++, monitorLayout++) { + monitorMode[i].monitorDef.x = resetMonitorDef[i].left = monitorLayout->Left; + monitorMode[i].monitorDef.y = resetMonitorDef[i].top = monitorLayout->Top; + monitorMode[i].monitorDef.width = resetMonitorDef[i].right = monitorLayout->Width; + monitorMode[i].monitorDef.height = resetMonitorDef[i].bottom = monitorLayout->Height; + monitorMode[i].monitorDef.is_primary = resetMonitorDef[i].flags = monitorLayout->Flags & DISPLAY_CONTROL_MONITOR_PRIMARY ? 1 : 0; + monitorMode[i].monitorDef.orig_screen = 0; + monitorMode[i].monitorDef.attributes.physicalWidth = monitorLayout->PhysicalWidth; + monitorMode[i].monitorDef.attributes.physicalHeight = monitorLayout->PhysicalHeight; + monitorMode[i].monitorDef.attributes.orientation = monitorLayout->Orientation; + monitorMode[i].monitorDef.attributes.desktopScaleFactor = monitorLayout->DesktopScaleFactor; + monitorMode[i].monitorDef.attributes.deviceScaleFactor = monitorLayout->DeviceScaleFactor; + monitorMode[i].scale = disp_get_output_scale_from_monitor(peerCtx, &monitorMode[i]); + monitorMode[i].clientScale = disp_get_client_scale_from_monitor(peerCtx, &monitorMode[i]); + } + + if (!disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, displayControl->NumMonitors)) + goto Exit; + + int doneIndex = 0; + disp_start_monitor_layout_change(client, monitorMode, displayControl->NumMonitors, &doneIndex); + for (UINT i = 0; i < displayControl->NumMonitors; i++) { + if ((doneIndex & (1 << i)) == 0) { + if (disp_set_monitor_layout_change(client, &monitorMode[i]) != 0) + goto Exit; + } + } + disp_end_monitor_layout_change(client); + + /* tell client the server updated the monitor layout */ + RDPGFX_RESET_GRAPHICS_PDU resetGraphics = {}; + resetGraphics.width = peerCtx->regionClientHeads.extents.x2 - peerCtx->regionClientHeads.extents.x1; + resetGraphics.height = peerCtx->regionClientHeads.extents.y2 - peerCtx->regionClientHeads.extents.x1; + resetGraphics.monitorCount = displayControl->NumMonitors; + resetGraphics.monitorDefArray = resetMonitorDef; + peerCtx->rail_grfx_server_context->ResetGraphics(peerCtx->rail_grfx_server_context, &resetGraphics); + + /* force recreate all surface and redraw. */ + hash_table_for_each(peerCtx->windowId.hash_table, disp_force_recreate_iter, NULL); + weston_compositor_damage_all(b->compositor); + +Exit: + free(monitorMode); + free(resetMonitorDef); + return; +} + +struct disp_schedule_monitor_layout_change_data { + struct rdp_loop_event_source _base; + DispServerContext* context; + DISPLAY_CONTROL_MONITOR_LAYOUT_PDU displayControl; +}; + +static void +disp_monitor_layout_change_callback(void* dataIn) +{ + struct disp_schedule_monitor_layout_change_data *data = (struct disp_schedule_monitor_layout_change_data *)dataIn; + DispServerContext* context = data->context; + wl_list_remove(&data->_base.link); + disp_monitor_layout_change(context, &data->displayControl); + free(dataIn); +} + +UINT +disp_client_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MONITOR_LAYOUT_PDU* displayControl) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + rdpSettings *settings = client->settings; + struct rdp_backend *b = peerCtx->rdpBackend; + struct disp_schedule_monitor_layout_change_data *data; + + ASSERT_NOT_COMPOSITOR_THREAD(b); + + rdp_debug(b, "Client: DisplayLayoutChange: monitor count:0x%x\n", displayControl->NumMonitors); + + assert(settings->HiDefRemoteApp); + + data = malloc(sizeof(*data) + (sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT) * displayControl->NumMonitors)); + if (!data) + return ERROR_INTERNAL_ERROR; + + data->context = context; + data->displayControl = *displayControl; + data->displayControl.Monitors = (DISPLAY_CONTROL_MONITOR_LAYOUT*)(data+1); + memcpy(data->displayControl.Monitors, displayControl->Monitors, + sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT) * displayControl->NumMonitors); + + pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ + wl_list_insert(&peerCtx->loop_event_source_list, &data->_base.link); + pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ + data->_base.event_source = rdp_defer_rdp_task_to_display_loop(peerCtx, + disp_monitor_layout_change_callback, + data); + if (!data->_base.event_source) { + pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ + wl_list_remove(&data->_base.link); + pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ + free(data); + return ERROR_INTERNAL_ERROR; + } + + return 0; +} + +BOOL +xf_peer_adjust_monitor_layout(freerdp_peer* client) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + rdpSettings *settings = client->settings; + BOOL success = TRUE; + + 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); + + /* these settings must have no impact in RAIL mode */ + /* In RAIL mode, it must mirror client's monitor settings */ + /* If not in RAIL mode, or RAIL-shell is not used, only signle mon is allowed */ + if (!settings->HiDefRemoteApp || b->rdprail_shell_api == NULL) { + if (settings->MonitorCount > 1) { + weston_log("\nWARNING\nWARNING\nWARNING: multiple monitor is not supported in non HiDef RAIL mode\nWARNING\nWARNING\n"); + return FALSE; + } + } + if (settings->MonitorCount > RDP_MAX_MONITOR) { + weston_log("\nWARNING\nWARNING\nWARNING: client reports more monitors then expected:(%d)\nWARNING\nWARNING\n", + settings->MonitorCount); + return FALSE; + } + struct rdp_monitor_mode _monitorMode = {}; + struct rdp_monitor_mode *monitorMode = NULL; + UINT32 monitorCount; + if (settings->MonitorCount > 0 && settings->MonitorDefArray) { + rdpMonitor *rdp_monitor = settings->MonitorDefArray; + monitorCount = settings->MonitorCount; + monitorMode = malloc(sizeof(struct rdp_monitor_mode) * monitorCount); + if (!monitorMode) + return FALSE; + for (UINT32 i = 0; i < monitorCount; i++) { + monitorMode[i].monitorDef = rdp_monitor[i]; + if (!settings->HasMonitorAttributes) { + monitorMode[i].monitorDef.attributes.physicalWidth = 0; + monitorMode[i].monitorDef.attributes.physicalHeight = 0; + monitorMode[i].monitorDef.attributes.orientation = ORIENTATION_LANDSCAPE; + monitorMode[i].monitorDef.attributes.desktopScaleFactor = 100; + monitorMode[i].monitorDef.attributes.deviceScaleFactor = 100; + } + monitorMode[i].scale = disp_get_output_scale_from_monitor(peerCtx, &monitorMode[i]); + monitorMode[i].clientScale = disp_get_client_scale_from_monitor(peerCtx, &monitorMode[i]); + } + } else { + /* when no monitor array provided, generate from desktop settings */ + _monitorMode.monitorDef.x = 0; // settings->DesktopPosX; + _monitorMode.monitorDef.y = 0; // settings->DesktopPosY; + _monitorMode.monitorDef.width = settings->DesktopWidth; + _monitorMode.monitorDef.height = settings->DesktopHeight; + _monitorMode.monitorDef.is_primary = 1; + _monitorMode.monitorDef.attributes.physicalWidth = settings->DesktopPhysicalWidth; + _monitorMode.monitorDef.attributes.physicalHeight = settings->DesktopPhysicalHeight; + _monitorMode.monitorDef.attributes.orientation = settings->DesktopOrientation; + _monitorMode.monitorDef.attributes.desktopScaleFactor = settings->DesktopScaleFactor; + _monitorMode.monitorDef.attributes.deviceScaleFactor = settings->DeviceScaleFactor; + _monitorMode.scale = disp_get_output_scale_from_monitor(peerCtx, &_monitorMode); + _monitorMode.clientScale = disp_get_client_scale_from_monitor(peerCtx, &_monitorMode); + monitorCount = 1; + monitorMode = &_monitorMode; + } + + if (!disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitorCount)) { + success = FALSE; + goto Exit; + } + + int doneIndex = 0; + disp_start_monitor_layout_change(client, monitorMode, monitorCount, &doneIndex); + for (UINT32 i = 0; i < monitorCount; i++) { + if ((doneIndex & (1 << i)) == 0) + if (disp_set_monitor_layout_change(client, &monitorMode[i]) != 0) { + success = FALSE; + goto Exit; + } + } + disp_end_monitor_layout_change(client); + +Exit: + if (monitorMode != &_monitorMode) + free(monitorMode); + + return success; +} diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c new file mode 100644 index 000000000..4845c0d6d --- /dev/null +++ b/libweston/backend-rdp/rdprail.c @@ -0,0 +1,3929 @@ +/* + * 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 + +#include +#include +#include + +#include "rdp.h" + +#include "libweston-internal.h" + +extern PWtsApiFunctionTable FreeRDP_InitWtsApi(void); + +static void rdp_rail_destroy_window(struct wl_listener *listener, void *data); +static void rdp_rail_schedule_update_window(struct wl_listener *listener, void *data); + +struct rdp_dispatch_data { + struct rdp_loop_event_source _base; + freerdp_peer *client; + union { + RAIL_SYSPARAM_ORDER u_sysParam; + RAIL_SYSCOMMAND_ORDER u_sysCommand; + RAIL_ACTIVATE_ORDER u_activate; + RAIL_EXEC_ORDER u_exec; + RAIL_WINDOW_MOVE_ORDER u_windowMove; + RAIL_SNAP_ARRANGE u_snapArrange; + RAIL_GET_APPID_REQ_ORDER u_getAppidReq; + RAIL_LANGUAGEIME_INFO_ORDER u_languageImeInfo; +#ifdef HAVE_FREERDP_RDPAPPLIST_H + RDPAPPLIST_CLIENT_CAPS_PDU u_appListCaps; +#endif // HAVE_FREERDP_RDPAPPLIST_H + }; +}; + +#define RDP_DISPATCH_TO_DISPLAY_LOOP(context, arg_type, arg, callback) \ + { \ + struct rdp_dispatch_data *dispatch_data; \ + dispatch_data = (struct rdp_dispatch_data *)malloc(sizeof(*dispatch_data)); \ + if (dispatch_data) { \ + freerdp_peer *client = (freerdp_peer*)(context)->custom; \ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; \ + struct rdp_backend *b = peerCtx->rdpBackend; \ + ASSERT_NOT_COMPOSITOR_THREAD(b); \ + dispatch_data->client = client; \ + dispatch_data->u_##arg_type = *(arg); \ + pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ + wl_list_insert(&peerCtx->loop_event_source_list, &dispatch_data->_base.link); \ + pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ + dispatch_data->_base.event_source = \ + rdp_defer_rdp_task_to_display_loop(peerCtx, callback, dispatch_data); \ + if (!dispatch_data->_base.event_source) { \ + weston_log("%s: rdp_queue_deferred_task failed\n", __func__); \ + pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ + wl_list_remove(&dispatch_data->_base.link); \ + pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ + free(dispatch_data); \ + } \ + } else { \ + weston_log("%s: malloc failed\n", __func__); \ + } \ + } + +#define RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, dispatch_data) \ + { \ + ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); \ + pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ + wl_list_remove(&dispatch_data->_base.link); \ + pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ + free(dispatch_data); \ + } + +#ifdef HAVE_FREERDP_RDPAPPLIST_H +static void +applist_client_Caps_callback(void *arg) +{ + struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + const RDPAPPLIST_CLIENT_CAPS_PDU* caps = &data->u_appListCaps; + freerdp_peer *client = data->client; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + char clientLanguageId[RDPAPPLIST_LANG_SIZE + 1] = {}; // +1 to ensure null-terminate. + + rdp_debug(b, "Client AppList caps version:%d\n", caps->version); + + ASSERT_COMPOSITOR_THREAD(b); + + if (b->rdprail_shell_api && + b->rdprail_shell_api->start_app_list_update) { + + strncpy(clientLanguageId, caps->clientLanguageId, RDPAPPLIST_LANG_SIZE); + rdp_debug(b, "Client AppList client language id: %s\n", clientLanguageId); + + peerCtx->isAppListEnabled = + b->rdprail_shell_api->start_app_list_update( + b->rdprail_shell_context, + clientLanguageId); + } + + RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); +} + +static UINT +applist_client_Caps(RdpAppListServerContext *context, const RDPAPPLIST_CLIENT_CAPS_PDU* arg) +{ + RDP_DISPATCH_TO_DISPLAY_LOOP(context, appListCaps, arg, applist_client_Caps_callback); + return CHANNEL_RC_OK; +} +#endif // HAVE_FREERDP_RDPAPPLIST_H + +static UINT +rail_client_Handshake(RailServerContext* context, const RAIL_HANDSHAKE_ORDER* handshake) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug(b, "Client HandShake buildNumber:%d\n", handshake->buildNumber); + + peerCtx->handshakeCompleted = TRUE; + return CHANNEL_RC_OK; +} + +static void +rail_ClientExec_destroy(struct wl_listener *listener, void *data) +{ + RdpPeerContext *peerCtx = container_of(listener, RdpPeerContext, + clientExec_destroy_listener); + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug(b, "Client ExecOrder program terminated\n"); + + wl_list_remove(&peerCtx->clientExec_destroy_listener.link); + peerCtx->clientExec_destroy_listener.notify = NULL; + peerCtx->clientExec = NULL; +} + +static void +rail_client_Exec_callback(void *arg) +{ + struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + const RAIL_EXEC_ORDER* exec = &data->u_exec; + freerdp_peer *client = data->client; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + UINT result = RAIL_EXEC_E_FAIL; + RAIL_EXEC_RESULT_ORDER orderResult = {}; + char *remoteProgramAndArgs = exec->RemoteApplicationProgram; + + rdp_debug(b, "Client ExecOrder:0x%08X, Program:%s, WorkingDir:%s, RemoteApplicationArguments:%s\n", + (UINT)exec->flags, + exec->RemoteApplicationProgram, + exec->RemoteApplicationWorkingDir, + exec->RemoteApplicationArguments); + + ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); + + if (exec->RemoteApplicationProgram) { + if (!utf8_string_to_rail_string(exec->RemoteApplicationProgram, &orderResult.exeOrFile)) + goto send_result; + + if (exec->RemoteApplicationArguments) { + /* construct remote program path and arguments */ + remoteProgramAndArgs = malloc(strlen(exec->RemoteApplicationProgram) + + strlen(exec->RemoteApplicationArguments) + + 2); // space between program and args + null terminate. + if (!remoteProgramAndArgs) + goto send_result; + sprintf(remoteProgramAndArgs, "%s %s", exec->RemoteApplicationProgram, exec->RemoteApplicationArguments); + } + + /* TODO: server state machine, wait until activation complated */ + while (!peerCtx->activationRailCompleted) + USleep(10000); + + /* launch the process specified by RDP client. */ + rdp_debug(b, "Client ExecOrder launching %s\n", remoteProgramAndArgs); + if (b->rdprail_shell_api && + b->rdprail_shell_api->request_launch_shell_process) { + peerCtx->clientExec = + b->rdprail_shell_api->request_launch_shell_process( + b->rdprail_shell_context, remoteProgramAndArgs); + } + if (peerCtx->clientExec) { + assert(NULL == peerCtx->clientExec_destroy_listener.notify); + peerCtx->clientExec_destroy_listener.notify = rail_ClientExec_destroy; + wl_client_add_destroy_listener(peerCtx->clientExec, + &peerCtx->clientExec_destroy_listener); + result = RAIL_EXEC_S_OK; + } else { + weston_log("%s: fail to launch shell process %s\n", + __func__, remoteProgramAndArgs); + } + } + +send_result: + orderResult.flags = exec->flags; + orderResult.execResult = result; + orderResult.rawResult = 0; + peerCtx->rail_server_context->ServerExecResult(peerCtx->rail_server_context, &orderResult); + + if (orderResult.exeOrFile.string) + free(orderResult.exeOrFile.string); + if (remoteProgramAndArgs && remoteProgramAndArgs != exec->RemoteApplicationProgram) + free(remoteProgramAndArgs); + if (exec->RemoteApplicationProgram) + free(exec->RemoteApplicationProgram); + if (exec->RemoteApplicationWorkingDir) + free(exec->RemoteApplicationWorkingDir); + if (exec->RemoteApplicationArguments) + free(exec->RemoteApplicationArguments); + + RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); +} + +static UINT +rail_client_Exec(RailServerContext* context, const RAIL_EXEC_ORDER* arg) +{ + RAIL_EXEC_ORDER execOrder = {}; + execOrder.flags = arg->flags; + if (arg->RemoteApplicationProgram) { + execOrder.RemoteApplicationProgram = malloc(strlen(arg->RemoteApplicationProgram)+1); + if (!execOrder.RemoteApplicationProgram) + goto Exit_Error; + strcpy(execOrder.RemoteApplicationProgram, arg->RemoteApplicationProgram); + } + if (arg->RemoteApplicationWorkingDir) { + execOrder.RemoteApplicationWorkingDir = malloc(strlen(arg->RemoteApplicationWorkingDir)+1); + if (!execOrder.RemoteApplicationWorkingDir) + goto Exit_Error; + strcpy(execOrder.RemoteApplicationWorkingDir, arg->RemoteApplicationWorkingDir); + } + if (arg->RemoteApplicationArguments) { + execOrder.RemoteApplicationArguments = malloc(strlen(arg->RemoteApplicationArguments)+1); + if (!execOrder.RemoteApplicationArguments) + goto Exit_Error; + strcpy(execOrder.RemoteApplicationArguments, arg->RemoteApplicationArguments); + } + RDP_DISPATCH_TO_DISPLAY_LOOP(context, exec, &execOrder, rail_client_Exec_callback); + return CHANNEL_RC_OK; + +Exit_Error: + if (execOrder.RemoteApplicationProgram) + free(execOrder.RemoteApplicationProgram); + if (execOrder.RemoteApplicationWorkingDir) + free(execOrder.RemoteApplicationWorkingDir); + if (execOrder.RemoteApplicationArguments) + free(execOrder.RemoteApplicationArguments); + return CHANNEL_RC_NO_BUFFER; +} + +static void +rail_client_Activate_callback(void *arg) +{ + struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + const RAIL_ACTIVATE_ORDER* activate = &data->u_activate; + freerdp_peer *client = data->client; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct weston_surface *surface; + + rdp_debug_verbose(b, "Client: ClientActivate: WindowId:0x%x, enabled:%d\n", activate->windowId, activate->enabled); + + ASSERT_COMPOSITOR_THREAD(b); + + if (b->rdprail_shell_api && + b->rdprail_shell_api->request_window_activate) { + surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, activate->windowId); + if (surface) + b->rdprail_shell_api->request_window_activate(surface, peerCtx->item.seat); + else + weston_log("Client: ClientActivate: WindowId:0x%x is not found.\n", activate->windowId); + } + + RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); +} + +static UINT +rail_client_Activate(RailServerContext* context, const RAIL_ACTIVATE_ORDER* arg) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + if (arg->windowId == RDP_RAIL_MARKER_WINDOW_ID) { + rdp_debug_verbose(b, "Client: ClientActivate: marker window is %s.\n", arg->enabled ? "enabled" : "disabled"); + } else if (arg->enabled) { + RDP_DISPATCH_TO_DISPLAY_LOOP(context, activate, arg, rail_client_Activate_callback); + } else { + /* no need to handle deactivate from client, just */ + /* keep current focus until new focus reported. */ + } + return CHANNEL_RC_OK; +} + +static void +rail_client_SnapArrange_callback(void *arg) +{ + struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + const RAIL_SNAP_ARRANGE* snap = &data->u_snapArrange; + freerdp_peer *client = data->client; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct weston_surface *surface; + struct weston_surface_rail_state *rail_state; + + rdp_debug(b, "SnapArrange(%d) - (%d, %d, %d, %d)\n", + snap->windowId, + snap->left, + snap->top, + snap->right - snap->left, + snap->bottom - snap->top); + + ASSERT_COMPOSITOR_THREAD(b); + + surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, snap->windowId); + if (surface) { + rail_state = (struct weston_surface_rail_state *)surface->backend_state; + if (b->rdprail_shell_api && + b->rdprail_shell_api->request_window_move) { + /* TODO: HI-DPI MULTIMON */ + b->rdprail_shell_api->request_window_snap(surface, + to_weston_x(peerCtx, snap->left), + to_weston_y(peerCtx, snap->top), + snap->right - snap->left, + snap->bottom - snap->top); + } + + rail_state->forceUpdateWindowState = true; + rdp_rail_schedule_update_window(NULL, (void*)surface); + } + + RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); +} + +static UINT +rail_client_SnapArrange(RailServerContext* context, const RAIL_SNAP_ARRANGE* arg) +{ + RDP_DISPATCH_TO_DISPLAY_LOOP(context, snapArrange, arg, rail_client_SnapArrange_callback); + return CHANNEL_RC_OK; +} + +static void +rail_client_WindowMove_callback(void *arg) +{ + struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + const RAIL_WINDOW_MOVE_ORDER* windowMove = &data->u_windowMove; + freerdp_peer *client = data->client; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct weston_surface *surface; + + rdp_debug(b, "WindowMove(%d) - (%d, %d, %d, %d)\n", + windowMove->windowId, + windowMove->left, + windowMove->top, + windowMove->right - windowMove->left, + windowMove->bottom - windowMove->top); + + ASSERT_COMPOSITOR_THREAD(b); + + surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, windowMove->windowId); + if (surface) { + if (b->rdprail_shell_api && + b->rdprail_shell_api->request_window_move) { + /* TODO: HI-DPI MULTIMON */ + b->rdprail_shell_api->request_window_move(surface, + to_weston_x(peerCtx, windowMove->left), + to_weston_y(peerCtx, windowMove->top)); + } + } + + rdp_debug(b, "Surface Size (%d, %d)\n", surface->width, surface->height); + + RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); +} + +static UINT +rail_client_WindowMove(RailServerContext* context, const RAIL_WINDOW_MOVE_ORDER* arg) +{ + RDP_DISPATCH_TO_DISPLAY_LOOP(context, windowMove, arg, rail_client_WindowMove_callback); + return CHANNEL_RC_OK; +} + +static void +rail_client_Syscommand_callback(void *arg) +{ + struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + const RAIL_SYSCOMMAND_ORDER* syscommand = &data->u_sysCommand; + freerdp_peer *client = data->client; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct weston_surface* surface; + + ASSERT_COMPOSITOR_THREAD(b); + + surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, syscommand->windowId); + if (!surface) { + weston_log("Client: ClientSyscommand: WindowId:0x%x is not found.\n", syscommand->windowId); + goto Exit; + } + + char *commandString = NULL; + switch (syscommand->command) { + case SC_SIZE: + commandString = "SC_SIZE"; + break; + case SC_MOVE: + commandString = "SC_MOVE"; + break; + case SC_MINIMIZE: + commandString = "SC_MINIMIZE"; + if (b->rdprail_shell_api && + b->rdprail_shell_api->request_window_minimize) + b->rdprail_shell_api->request_window_minimize(surface); + break; + case SC_MAXIMIZE: + commandString = "SC_MAXIMIZE"; + if (b->rdprail_shell_api && + b->rdprail_shell_api->request_window_maximize) + b->rdprail_shell_api->request_window_maximize(surface); + break; + case SC_CLOSE: + commandString = "SC_CLOSE"; + if (b->rdprail_shell_api && + b->rdprail_shell_api->request_window_close) + b->rdprail_shell_api->request_window_close(surface); + break; + case SC_KEYMENU: + commandString = "SC_KEYMENU"; + break; + case SC_RESTORE: + commandString = "SC_RESTORE"; + if (b->rdprail_shell_api && + b->rdprail_shell_api->request_window_restore) + b->rdprail_shell_api->request_window_restore(surface); + break; + case SC_DEFAULT: + commandString = "SC_DEFAULT"; + break; + default: + commandString = "Unknown"; + break; + } + + rdp_debug(b, "Client: ClientSyscommand: WindowId:0x%x, surface:0x%p, command:%s (0x%x)\n", + syscommand->windowId, surface, commandString, syscommand->command); + +Exit: + RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); +} + +static UINT +rail_client_Syscommand(RailServerContext* context, const RAIL_SYSCOMMAND_ORDER* arg) +{ + RDP_DISPATCH_TO_DISPLAY_LOOP(context, sysCommand, arg, rail_client_Syscommand_callback); + return CHANNEL_RC_OK; +} + +static void +rail_client_ClientSysparam_callback(void *arg) +{ + struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + const RAIL_SYSPARAM_ORDER* sysparam = &data->u_sysParam; + freerdp_peer *client = data->client; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + pixman_rectangle32_t workareaRect; + pixman_rectangle32_t workareaRectClient; + struct weston_output *base_output; + struct weston_head *base_head_iter; + + ASSERT_COMPOSITOR_THREAD(b); + + if (sysparam->params & SPI_MASK_SET_DRAG_FULL_WINDOWS) { + rdp_debug(b, "Client: ClientSysparam: dragFullWindows:%d\n", sysparam->dragFullWindows); + } + + if (sysparam->params & SPI_MASK_SET_KEYBOARD_CUES) { + rdp_debug(b, "Client: ClientSysparam: keyboardCues:%d\n", sysparam->keyboardCues); + } + + if (sysparam->params & SPI_MASK_SET_KEYBOARD_PREF) { + rdp_debug(b, "Client: ClientSysparam: keyboardPref:%d\n", sysparam->keyboardPref); + } + + if (sysparam->params & SPI_MASK_SET_MOUSE_BUTTON_SWAP) { + rdp_debug(b, "Client: ClientSysparam: mouseButtonSwap:%d\n", sysparam->mouseButtonSwap); + } + + if (sysparam->params & SPI_MASK_SET_WORK_AREA) { + rdp_debug(b, "Client: ClientSysparam: workArea:(left:%d, top:%d, right:%d, bottom:%d)\n", + (INT32)(INT16)sysparam->workArea.left, + (INT32)(INT16)sysparam->workArea.top, + (INT32)(INT16)sysparam->workArea.right, + (INT32)(INT16)sysparam->workArea.bottom); + } + + if (sysparam->params & SPI_MASK_DISPLAY_CHANGE) { + rdp_debug(b, "Client: ClientSysparam: displayChange:(left:%d, top:%d, right:%d, bottom:%d)\n", + (INT32)(INT16)sysparam->displayChange.left, + (INT32)(INT16)sysparam->displayChange.top, + (INT32)(INT16)sysparam->displayChange.right, + (INT32)(INT16)sysparam->displayChange.bottom); + } + + if (sysparam->params & SPI_MASK_TASKBAR_POS) { + rdp_debug(b, "Client: ClientSysparam: taskbarPos:(left:%d, top:%d, right:%d, bottom:%d)\n", + (INT32)(INT16)sysparam->taskbarPos.left, + (INT32)(INT16)sysparam->taskbarPos.top, + (INT32)(INT16)sysparam->taskbarPos.right, + (INT32)(INT16)sysparam->taskbarPos.bottom); + } + + if (sysparam->params & SPI_MASK_SET_HIGH_CONTRAST) { + rdp_debug(b, "Client: ClientSysparam: highContrast\n"); + } + + if (sysparam->params & SPI_MASK_SET_CARET_WIDTH) { + rdp_debug(b, "Client: ClientSysparam: caretWidth:%d\n", sysparam->caretWidth); + } + + if (sysparam->params & SPI_MASK_SET_STICKY_KEYS) { + rdp_debug(b, "Client: ClientSysparam: stickyKeys:%d\n", sysparam->stickyKeys); + } + + if (sysparam->params & SPI_MASK_SET_TOGGLE_KEYS) { + rdp_debug(b, "Client: ClientSysparam: toggleKeys:%d\n", sysparam->toggleKeys); + } + + if (sysparam->params & SPI_MASK_SET_FILTER_KEYS) { + rdp_debug(b, "Client: ClientSysparam: filterKeys\n"); + } + + if (sysparam->params & SPI_MASK_SET_SCREEN_SAVE_ACTIVE) { + rdp_debug(b, "Client: ClientSysparam: setScreenSaveActive:%d\n", sysparam->setScreenSaveActive); + } + + if (sysparam->params & SPI_MASK_SET_SET_SCREEN_SAVE_SECURE) { + rdp_debug(b, "Client: ClientSysparam: setScreenSaveSecure:%d\n", sysparam->setScreenSaveSecure); + } + + if (sysparam->params & SPI_MASK_SET_WORK_AREA) { + if (b->rdprail_shell_api && + b->rdprail_shell_api->set_desktop_workarea) { + workareaRectClient.x = (INT32)(INT16)sysparam->workArea.left; + workareaRectClient.y = (INT32)(INT16)sysparam->workArea.top; + workareaRectClient.width = (INT32)(INT16)sysparam->workArea.right - workareaRectClient.x; + workareaRectClient.height = (INT32)(INT16)sysparam->workArea.bottom - workareaRectClient.y; + /* Workarea is reported in client coordinate where primary monitor' upper-left is (0,0). */ + /* traslate to weston coordinate where entire desktop's upper-left is (0,0). */ + workareaRect = workareaRectClient; + base_output = to_weston_coordinate(peerCtx, + &workareaRect.x, &workareaRect.y, + &workareaRect.width, &workareaRect.height); + if (base_output) { + b->rdprail_shell_api->set_desktop_workarea(base_output, b->rdprail_shell_context, &workareaRect); + wl_list_for_each(base_head_iter, &base_output->head_list, output_link) { + to_rdp_head(base_head_iter)->workarea = workareaRect; + to_rdp_head(base_head_iter)->workareaClient = workareaRectClient; + } + } else { + weston_log("Client: ClientSysparam: workArea isn't belonging to an output\n"); + } + } + } + + RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); +} + +static UINT +rail_client_ClientSysparam(RailServerContext* context, const RAIL_SYSPARAM_ORDER* arg) +{ + RDP_DISPATCH_TO_DISPLAY_LOOP(context, sysParam, arg, rail_client_ClientSysparam_callback); + return CHANNEL_RC_OK; +} + +static void +rail_client_ClientGetAppidReq_callback(void *arg) +{ + struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + const RAIL_GET_APPID_REQ_ORDER* getAppidReq = &data->u_getAppidReq; + freerdp_peer *client = data->client; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + char appId[520] = {}; + char imageName[520] = {}; + pid_t pid; + size_t i; + unsigned short *p; + struct weston_surface *surface; + + rdp_debug_verbose(b, "Client: ClientGetAppidReq: WindowId:0x%x\n", getAppidReq->windowId); + + ASSERT_COMPOSITOR_THREAD(b); + + if (b->rdprail_shell_api && + b->rdprail_shell_api->get_window_app_id) { + + surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, getAppidReq->windowId); + if (!surface) { + weston_log("Client: ClientGetAppidReq: WindowId:0x%x is not found.\n", getAppidReq->windowId); + goto Exit; + } + + pid = b->rdprail_shell_api->get_window_app_id( + surface, &appId[0], sizeof(appId)-1, &imageName[0], sizeof(imageName)-1); + if (appId[0] == '\0') { + weston_log("Client: ClientGetAppidReq: WindowId:0x%x does not have appId, or not top level window.\n", getAppidReq->windowId); + goto Exit; + } + + rdp_debug(b, "Client: ClientGetAppidReq: pid:%d appId:%s\n", (UINT32)pid, appId); + rdp_debug_verbose(b, "Client: ClientGetAppidReq: pid:%d imageName:%s\n", (UINT32)pid, imageName); + + /* Reply with RAIL_GET_APPID_RESP_EX when pid/imageName is valid and client supports it */ + if ((pid >= 0) && (imageName[0] != '\0') && + peerCtx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_GET_APPID_RESPONSE_EX_SUPPORTED) { + RAIL_GET_APPID_RESP_EX getAppIdRespEx = {}; + getAppIdRespEx.windowID = getAppidReq->windowId; + for (i = 0, p = &getAppIdRespEx.applicationID[0]; i < strlen(appId); i++, p++) + *p = (unsigned short)appId[i]; + getAppIdRespEx.processId = (UINT32) pid; + for (i = 0, p = &getAppIdRespEx.processImageName[0]; i < strlen(imageName); i++, p++) + *p = (unsigned short)imageName[i]; + peerCtx->rail_server_context->ServerGetAppidRespEx(peerCtx->rail_server_context, &getAppIdRespEx); + } else { + RAIL_GET_APPID_RESP_ORDER getAppIdResp = {}; + getAppIdResp.windowId = getAppidReq->windowId; + for (i = 0, p = &getAppIdResp.applicationId[0]; i < strlen(appId); i++, p++) + *p = (unsigned short)appId[i]; + peerCtx->rail_server_context->ServerGetAppidResp(peerCtx->rail_server_context, &getAppIdResp); + } + } + +Exit: + RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); +} + +static UINT +rail_client_ClientGetAppidReq(RailServerContext* context, + const RAIL_GET_APPID_REQ_ORDER* arg) +{ + RDP_DISPATCH_TO_DISPLAY_LOOP(context, getAppidReq, arg, rail_client_ClientGetAppidReq_callback); + return CHANNEL_RC_OK; +} + +static UINT +rail_client_ClientStatus(RailServerContext* context, + const RAIL_CLIENT_STATUS_ORDER* clientStatus) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug(b, "Client: ClientStatus:0x%x\n", clientStatus->flags); + if (clientStatus->flags & TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE) + rdp_debug(b, " - TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE\n"); + if (clientStatus->flags & TS_RAIL_CLIENTSTATUS_AUTORECONNECT) + rdp_debug(b, " - TS_RAIL_CLIENTSTATUS_AUTORECONNECT\n"); + if (clientStatus->flags & TS_RAIL_CLIENTSTATUS_ZORDER_SYNC) + rdp_debug(b, " - TS_RAIL_CLIENTSTATUS_ZORDER_SYNC\n"); + if (clientStatus->flags & TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED) + rdp_debug(b, " - TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED\n"); + if (clientStatus->flags & TS_RAIL_CLIENTSTATUS_HIGH_DPI_ICONS_SUPPORTED) + rdp_debug(b, " - TS_RAIL_CLIENTSTATUS_HIGH_DPI_ICONS_SUPPORTED\n"); + if (clientStatus->flags & TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED) + rdp_debug(b, " - TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED\n"); + if (clientStatus->flags & TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED) + rdp_debug(b, " - TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED\n"); + if (clientStatus->flags & TS_RAIL_CLIENTSTATUS_GET_APPID_RESPONSE_EX_SUPPORTED) + rdp_debug(b, " - TS_RAIL_CLIENTSTATUS_GET_APPID_RESPONSE_EX_SUPPORTED\n"); + if (clientStatus->flags & TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED) + rdp_debug(b, " - TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED\n"); + + peerCtx->clientStatusFlags = clientStatus->flags; + return CHANNEL_RC_OK; +} + +static UINT +rail_client_LangbarInfo(RailServerContext* context, + const RAIL_LANGBAR_INFO_ORDER* langbarInfo) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug(b, "Client: LangbarInfo: LanguageBarStatus:%d\n", langbarInfo->languageBarStatus); + + return CHANNEL_RC_OK; +} + +static char * +languageGuid_to_string(const GUID *guid) +{ + typedef struct _lang_GUID + { + UINT32 Data1; + UINT16 Data2; + UINT16 Data3; + BYTE Data4_0; + BYTE Data4_1; + BYTE Data4_2; + BYTE Data4_3; + BYTE Data4_4; + BYTE Data4_5; + BYTE Data4_6; + BYTE Data4_7; + } lang_GUID; + + static const lang_GUID c_GUID_NULL = GUID_NULL; + static const lang_GUID c_GUID_MSIME_JPN = GUID_MSIME_JPN; + static const lang_GUID c_GUID_MSIME_KOR = GUID_MSIME_KOR; + static const lang_GUID c_GUID_CHSIME = GUID_CHSIME; + static const lang_GUID c_GUID_CHTIME = GUID_CHTIME; + static const lang_GUID c_GUID_PROFILE_NEWPHONETIC = GUID_PROFILE_NEWPHONETIC; + static const lang_GUID c_GUID_PROFILE_CHANGJIE = GUID_PROFILE_CHANGJIE; + static const lang_GUID c_GUID_PROFILE_QUICK = GUID_PROFILE_QUICK; + static const lang_GUID c_GUID_PROFILE_CANTONESE = GUID_PROFILE_CANTONESE; + static const lang_GUID c_GUID_PROFILE_PINYIN = GUID_PROFILE_PINYIN; + static const lang_GUID c_GUID_PROFILE_SIMPLEFAST = GUID_PROFILE_SIMPLEFAST; + static const lang_GUID c_GUID_PROFILE_MSIME_JPN = GUID_GUID_PROFILE_MSIME_JPN; + static const lang_GUID c_GUID_PROFILE_MSIME_KOR = GUID_PROFILE_MSIME_KOR; + + RPC_STATUS rpc_status; + if (UuidEqual(guid, (GUID *)&c_GUID_NULL, &rpc_status)) + return "GUID_NULL"; + else if (UuidEqual(guid, (GUID *)&c_GUID_MSIME_JPN, &rpc_status)) + return "GUID_MSIME_JPN"; + else if (UuidEqual(guid, (GUID *)&c_GUID_MSIME_KOR, &rpc_status)) + return "GUID_MSIME_KOR"; + else if (UuidEqual(guid, (GUID *)&c_GUID_CHSIME, &rpc_status)) + return "GUID_CHSIME"; + else if (UuidEqual(guid, (GUID *)&c_GUID_CHTIME, &rpc_status)) + return "GUID_CHTIME"; + else if (UuidEqual(guid, (GUID *)&c_GUID_PROFILE_NEWPHONETIC, &rpc_status)) + return "GUID_PROFILE_NEWPHONETIC"; + else if (UuidEqual(guid, (GUID *)&c_GUID_PROFILE_CHANGJIE, &rpc_status)) + return "GUID_PROFILE_CHANGJIE"; + else if (UuidEqual(guid, (GUID *)&c_GUID_PROFILE_QUICK, &rpc_status)) + return "GUID_PROFILE_QUICK"; + else if (UuidEqual(guid, (GUID *)&c_GUID_PROFILE_CANTONESE, &rpc_status)) + return "GUID_PROFILE_CANTONESE"; + else if (UuidEqual(guid, (GUID *)&c_GUID_PROFILE_PINYIN, &rpc_status)) + return "GUID_PROFILE_PINYIN"; + else if (UuidEqual(guid, (GUID *)&c_GUID_PROFILE_SIMPLEFAST, &rpc_status)) + return "GUID_PROFILE_SIMPLEFAST"; + else if (UuidEqual(guid, (GUID *)&c_GUID_PROFILE_MSIME_JPN, &rpc_status)) + return "GUID_PROFILE_MSIME_JPN"; + else if (UuidEqual(guid, (GUID *)&c_GUID_PROFILE_MSIME_KOR, &rpc_status)) + return "GUID_PROFILE_MSIME_KOR"; + else + return "Unknown GUID"; +} + +static void +rail_client_LanguageImeInfo_callback(void *arg) +{ + struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + const RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo = &data->u_languageImeInfo; + freerdp_peer *client = data->client; + rdpSettings *settings = client->settings; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + char *s; + + ASSERT_COMPOSITOR_THREAD(b); + + switch (languageImeInfo->ProfileType) + { + case TF_PROFILETYPE_INPUTPROCESSOR: + s = "TF_PROFILETYPE_INPUTPROCESSOR"; + break; + case TF_PROFILETYPE_KEYBOARDLAYOUT: + s = "TF_PROFILETYPE_KEYBOARDLAYOUT"; + break; + default: + s = "Unknown profile type"; + break; + } + rdp_debug(b, "Client: LanguageImeInfo: ProfileType: %d (%s)\n", languageImeInfo->ProfileType, s); + rdp_debug(b, "Client: LanguageImeInfo: LanguageID: 0x%x\n", languageImeInfo->LanguageID); + rdp_debug(b, "Client: LanguageImeInfo: LanguageProfileCLSID: %s\n", + languageGuid_to_string(&languageImeInfo->LanguageProfileCLSID)); + rdp_debug(b, "Client: LanguageImeInfo: ProfileGUID: %s\n", + languageGuid_to_string(&languageImeInfo->ProfileGUID)); + rdp_debug(b, "Client: LanguageImeInfo: KeyboardLayout: 0x%x\n", languageImeInfo->KeyboardLayout); + + if (languageImeInfo->ProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT) { + struct xkb_rule_names xkbRuleNames; + struct xkb_keymap *keymap = NULL; + convert_rdp_keyboard_to_xkb_rule_names(settings->KeyboardType, + languageImeInfo->KeyboardLayout, + &xkbRuleNames); + if (xkbRuleNames.layout) { + keymap = xkb_keymap_new_from_names(b->compositor->xkb_context, + &xkbRuleNames, 0); + if (keymap) { + weston_seat_update_keymap(peerCtx->item.seat, keymap); + xkb_keymap_unref(keymap); + } + } + } + + RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); +} + +static UINT +rail_client_LanguageImeInfo(RailServerContext* context, + const RAIL_LANGUAGEIME_INFO_ORDER* arg) +{ + RDP_DISPATCH_TO_DISPLAY_LOOP(context, languageImeInfo, arg, rail_client_LanguageImeInfo_callback); + return CHANNEL_RC_OK; +} + +static UINT +rail_client_CompartmentInfo(RailServerContext* context, + const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug(b, "Client: CompartmentInfo: ImeStatus: %s\n", compartmentInfo->ImeState ? "OPEN" : "CLOSED"); + rdp_debug(b, "Client: CompartmentInfo: ImeConvMode: 0x%x\n", compartmentInfo->ImeConvMode); + rdp_debug(b, "Client: CompartmentInfo: ImeSentenceMode: 0x%x\n", compartmentInfo->ImeSentenceMode); + rdp_debug(b, "Client: CompartmentInfo: KanaMode: %s\n", compartmentInfo->KanaMode ? "ON" : "OFF"); + + return CHANNEL_RC_OK; +} + +static UINT +rail_grfx_client_caps_advertise(RdpgfxServerContext* context, const RDPGFX_CAPS_ADVERTISE_PDU* capsAdvertise) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + rdp_debug(b, "Client: GrfxCaps count:0x%x\n", capsAdvertise->capsSetCount); + for (int i = 0; i < capsAdvertise->capsSetCount; i++) { + RDPGFX_CAPSET* capsSet = &(capsAdvertise->capsSets[i]); + rdp_debug(b, "Client: GrfxCaps[%d] version:0x%x length:%d flags:0x%x\n", + i, capsSet->version, capsSet->length, capsSet->flags); + switch(capsSet->version) + { + case RDPGFX_CAPVERSION_8: + rdp_debug(b, " Version : RDPGFX_CAPVERSION_8\n"); + break; + case RDPGFX_CAPVERSION_81: + rdp_debug(b, " Version : RDPGFX_CAPVERSION_81\n"); + break; + case RDPGFX_CAPVERSION_10: + rdp_debug(b, " Version : RDPGFX_CAPVERSION_10\n"); + break; + case RDPGFX_CAPVERSION_101: + rdp_debug(b, " Version : RDPGFX_CAPVERSION_101\n"); + break; + case RDPGFX_CAPVERSION_102: + rdp_debug(b, " Version : RDPGFX_CAPVERSION_102\n"); + break; + case RDPGFX_CAPVERSION_103: + rdp_debug(b, " Version : RDPGFX_CAPVERSION_103\n"); + break; + case RDPGFX_CAPVERSION_104: + rdp_debug(b, " Version : RDPGFX_CAPVERSION_104\n"); + break; + case RDPGFX_CAPVERSION_105: + rdp_debug(b, " Version : RDPGFX_CAPVERSION_105\n"); + break; + case RDPGFX_CAPVERSION_106: + rdp_debug(b, " Version : RDPGFX_CAPVERSION_106\n"); + break; + } + + if (capsSet->flags & RDPGFX_CAPS_FLAG_THINCLIENT) + rdp_debug(b, " - RDPGFX_CAPS_FLAG_THINCLIENT\n"); + if (capsSet->flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) + rdp_debug(b, " - RDPGFX_CAPS_FLAG_SMALL_CACHE\n"); + if (capsSet->flags & RDPGFX_CAPS_FLAG_AVC420_ENABLED) + rdp_debug(b, " - RDPGFX_CAPS_FLAG_AVC420_ENABLED\n"); + if (capsSet->flags & RDPGFX_CAPS_FLAG_AVC_DISABLED) + rdp_debug(b, " - RDPGFX_CAPS_FLAG_AVC_DISABLED\n"); + if (capsSet->flags & RDPGFX_CAPS_FLAG_AVC_THINCLIENT) + rdp_debug(b, " - RDPGFX_CAPS_FLAG_AVC_THINCLIENT\n"); + + switch(capsSet->version) + { + case RDPGFX_CAPVERSION_8: + { + //RDPGFX_CAPSET_VERSION8 *caps8 = (RDPGFX_CAPSET_VERSION8 *)capsSet; + break; + } + case RDPGFX_CAPVERSION_81: + { + //RDPGFX_CAPSET_VERSION81 *caps81 = (RDPGFX_CAPSET_VERSION81 *)capsSet; + break; + } + case RDPGFX_CAPVERSION_10: + case RDPGFX_CAPVERSION_101: + case RDPGFX_CAPVERSION_102: + case RDPGFX_CAPVERSION_103: + case RDPGFX_CAPVERSION_104: + case RDPGFX_CAPVERSION_105: + case RDPGFX_CAPVERSION_106: + { + //RDPGFX_CAPSET_VERSION10 *caps10 = (RDPGFX_CAPSET_VERSION10 *)capsSet; + break; + } + default: + weston_log(" Version : UNKNOWN(%d)\n", capsSet->version); + } + } + + /* send caps confirm */ + RDPGFX_CAPS_CONFIRM_PDU capsConfirm = {}; + capsConfirm.capsSet = capsAdvertise->capsSets; // TODO: choose right one. + peerCtx->rail_grfx_server_context->CapsConfirm(peerCtx->rail_grfx_server_context, &capsConfirm); + + /* ready to use graphics channel */ + peerCtx->activationGraphicsCompleted = TRUE; + return CHANNEL_RC_OK; +} + +static UINT +rail_grfx_client_cache_import_offer(RdpgfxServerContext* context, const RDPGFX_CACHE_IMPORT_OFFER_PDU* cacheImportOffer) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + rdp_debug_verbose(b, "Client: GrfxCacheImportOffer\n"); + return CHANNEL_RC_OK; +} + +static UINT +rail_grfx_client_frame_acknowledge(RdpgfxServerContext* context, const RDPGFX_FRAME_ACKNOWLEDGE_PDU* frameAcknowledge) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + rdp_debug_verbose(b, "Client: GrfxFrameAcknowledge(queueDepth = 0x%x, frameId = 0x%x, decodedFrame = %d)\n", + frameAcknowledge->queueDepth, frameAcknowledge->frameId, frameAcknowledge->totalFramesDecoded); + peerCtx->acknowledgedFrameId = frameAcknowledge->frameId; + peerCtx->isAcknowledgedSuspended = (frameAcknowledge->queueDepth == 0xffffffff); + return CHANNEL_RC_OK; +} + +#ifdef HAVE_FREERDP_GFXREDIR_H +static UINT +gfxredir_client_graphics_redirection_legacy_caps(GfxRedirServerContext* context, const GFXREDIR_LEGACY_CAPS_PDU* redirectionCaps) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug(b, "Client: gfxredir_caps: version:%d\n", redirectionCaps->version); + /* This is legacy caps callback, version must be 1 */ + if (redirectionCaps->version != GFXREDIR_CHANNEL_VERSION_LEGACY) { + weston_log("Client: gfxredir_caps: invalid version:%d\n", redirectionCaps->version); + return ERROR_INTERNAL_ERROR; + } + + /* Legacy version 1 client is not supported, so don't set 'activationGraphicsRedirectionCompleted'. */ + weston_log("Client: gfxredir_caps: version 1 is not supported.\n"); + + return CHANNEL_RC_OK; +} + +static UINT +gfxredir_client_graphics_redirection_caps_advertise(GfxRedirServerContext* context, const GFXREDIR_CAPS_ADVERTISE_PDU* redirectionCaps) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + const GFXREDIR_CAPS_HEADER *current = (const GFXREDIR_CAPS_HEADER *)redirectionCaps->caps; + const GFXREDIR_CAPS_V2_0_PDU *capsV2 = NULL; + + /* dump client caps */ + uint32_t i = 0; + uint32_t length = redirectionCaps->length; + rdp_debug(b, "Client: gfxredir_caps: length:%d\n", redirectionCaps->length); + while (length <= redirectionCaps->length && + length >= sizeof(GFXREDIR_CAPS_HEADER)) { + rdp_debug(b, "Client: gfxredir_caps[%d]: signature:0x%x\n", i, current->signature); + rdp_debug(b, "Client: gfxredir_caps[%d]: version:0x%x\n", i, current->version); + rdp_debug(b, "Client: gfxredir_caps[%d]: length:%d\n", i, current->length); + if (current->version == GFXREDIR_CAPS_VERSION2_0) { + capsV2 = (GFXREDIR_CAPS_V2_0_PDU *)current; + rdp_debug(b, "Client: gfxredir_caps[%d]: supportedFeatures:0x%x\n", i, capsV2->supportedFeatures); + } + i++; + length -= current->length; + current = (const GFXREDIR_CAPS_HEADER *)((BYTE*)current + current->length); + } + + /* select client caps */ + const GFXREDIR_CAPS_HEADER *selected = NULL; + uint32_t selectedVersion = 0; + current = (const GFXREDIR_CAPS_HEADER *)redirectionCaps->caps; + length = redirectionCaps->length; + while (length <= redirectionCaps->length && + length >= sizeof(GFXREDIR_CAPS_HEADER)) { + if (current->signature != GFXREDIR_CAPS_SIGNATURE) + return ERROR_INVALID_DATA; + /* Choose >= ver. 2_0 */ + if (current->version >= selectedVersion) { + selected = current; + selectedVersion = current->version; + } + length -= current->length; + current = (const GFXREDIR_CAPS_HEADER *)((BYTE*)current + current->length); + } + + /* reply selected caps */ + if (selected) { + GFXREDIR_CAPS_CONFIRM_PDU confirmPdu = {}; + + rdp_debug(b, "Client: gfxredir selected caps: version:0x%x\n", selected->version); + + confirmPdu.version = selected->version; /* return the version of selected caps */ + confirmPdu.length = selected->length; /* must return same length as selected caps from advertised */ + confirmPdu.capsData = &selected->capsData[0]; /* return caps data in selected caps */ + + peerCtx->gfxredir_server_context->GraphicsRedirectionCapsConfirm(context, &confirmPdu); + } + + /* ready to use graphics redirection channel */ + peerCtx->activationGraphicsRedirectionCompleted = TRUE; + return CHANNEL_RC_OK; +} + +static UINT +gfxredir_client_present_buffer_ack(GfxRedirServerContext* context, const GFXREDIR_PRESENT_BUFFER_ACK_PDU* presentAck) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct weston_surface *surface; + struct weston_surface_rail_state *rail_state; + + rdp_debug_verbose(b, "Client: gfxredir_present_buffer_ack: windowId:0x%lx\n", presentAck->windowId); + rdp_debug_verbose(b, "Client: gfxredir_present_buffer_ack: presentId:0x%lx\n", presentAck->presentId); + + peerCtx->acknowledgedFrameId = (UINT32)presentAck->presentId; + + surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, presentAck->windowId); + if (surface) { + rail_state = (struct weston_surface_rail_state *)surface->backend_state; + rail_state->isUpdatePending = FALSE; + } else { + weston_log("Client: PresentBufferAck: WindowId:0x%lx is not found.\n", presentAck->windowId); + } + + return CHANNEL_RC_OK; +} +#endif // HAVE_FREERDP_GFXREDIR_H + +static int +rdp_rail_create_cursor(struct weston_surface *surface) +{ + struct weston_compositor *compositor = surface->compositor; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; + + ASSERT_COMPOSITOR_THREAD(b); + + if (peerCtx->cursorSurface) + weston_log("cursor surface already exists old %p vs new %p\n", peerCtx->cursorSurface, surface); + peerCtx->cursorSurface = surface; + return 0; +} + +static int +rdp_rail_update_cursor(struct weston_surface *surface) +{ + struct weston_pointer *pointer = surface->committed_private; + struct weston_compositor *compositor = surface->compositor; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; + BOOL isCursorResized = FALSE; + BOOL isCursorHidden = FALSE; + BOOL isCursorDamanged = FALSE; + int numViews; + struct weston_view *view; + struct weston_rdp_rail_window_pos newPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; + struct weston_rdp_rail_window_pos newClientPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; + int contentBufferWidth; + int contentBufferHeight; + + ASSERT_COMPOSITOR_THREAD(b); + assert(rail_state); + + /* obtain view's global position */ + numViews = 0; + wl_list_for_each(view, &surface->views, surface_link) { + float sx, sy; + weston_view_to_global_float(view, 0, 0, &sx, &sy); + newPos.x = (int)sx; + newPos.y = (int)sy; + numViews++; + break; // just handle the first view for this hack + } + if (numViews == 0) { + view = NULL; + rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); + } + + if (newPos.x < 0 || newPos.y < 0) + isCursorHidden = TRUE; + + weston_surface_get_content_size(surface, &contentBufferWidth, &contentBufferHeight); + newClientPos.width = contentBufferWidth; + newClientPos.height = contentBufferHeight; + if (surface->output) + to_client_coordinate(peerCtx, surface->output, + &newClientPos.x, &newClientPos.y, &newClientPos.width, &newClientPos.height); + + if (newClientPos.width > 0 && newClientPos.height > 0) + isCursorResized = TRUE; + else + isCursorHidden = TRUE; + + rail_state->pos = newPos; + rail_state->clientPos = newClientPos; + + if (!isCursorHidden && !isCursorResized) { + if ((surface->damage.extents.x2 - surface->damage.extents.x1) > 0 || + (surface->damage.extents.y2 - surface->damage.extents.y1) > 0) + isCursorDamanged = TRUE; + } + + if (isCursorHidden) { + /* hide pointer */ + POINTER_SYSTEM_UPDATE pointerSystem = {}; + pointerSystem.type = SYSPTR_NULL; + b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); + b->rdp_peer->update->pointer->PointerSystem(b->rdp_peer->update->context, &pointerSystem); + b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + } else if (isCursorResized || isCursorDamanged) { + POINTER_LARGE_UPDATE pointerUpdate = {}; + int cursorBpp = 4; // Bytes Per Pixel. + int pointerBitsSize = newClientPos.width*cursorBpp*newClientPos.height; + BYTE *pointerBits = malloc(pointerBitsSize); + if (!pointerBits) { + weston_log("malloc failed for cursor shape\n"); + return -1; + } + + /* client expects y-flip image for cursor */ + if (weston_surface_copy_content(surface, + pointerBits, pointerBitsSize, 0, + newClientPos.width, newClientPos.height, + 0, 0, contentBufferWidth, contentBufferHeight, + true /* y-flip */, true /* is_argb */) < 0) { + weston_log("weston_surface_copy_content failed for cursor shape\n"); + free(pointerBits); + return -1; + } + + pointerUpdate.xorBpp = cursorBpp*8; // Bits Per Pixel. + pointerUpdate.cacheIndex = 0; + pointerUpdate.hotSpotX = pointer ? pointer->hotspot_x : 0; + pointerUpdate.hotSpotY = pointer ? pointer->hotspot_y : 0; + pointerUpdate.width = newClientPos.width; + pointerUpdate.height = newClientPos.height; + pointerUpdate.lengthAndMask = 0; + pointerUpdate.lengthXorMask = pointerBitsSize; + pointerUpdate.xorMaskData = pointerBits; + pointerUpdate.andMaskData = NULL; + + rdp_debug_verbose(b, "CursorUpdate(width %d, height %d)\n", newPos.width, newPos.height); + b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); + b->rdp_peer->update->pointer->PointerLarge(b->rdp_peer->update->context, &pointerUpdate); + b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + + free(pointerBits); + } + + return 0; +} + +static void +rdp_rail_create_window(struct wl_listener *listener, void *data) +{ + struct weston_surface *surface = (struct weston_surface *)data; + struct weston_compositor *compositor = surface->compositor; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + WINDOW_ORDER_INFO window_order_info = {}; + WINDOW_STATE_ORDER window_state_order = {}; + struct weston_rdp_rail_window_pos pos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; + struct weston_rdp_rail_window_pos clientPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; + RECTANGLE_16 window_rect = { 0, 0, surface->width, surface->height }; + RECTANGLE_16 window_vis = { 0, 0, surface->width, surface->height }; + int numViews; + struct weston_view *view; + UINT32 window_id; + RdpPeerContext *peerCtx; + + /* negative width/height is not allowed, allow window to be created with zeros */ + if (surface->width < 0 || surface->height < 0) { + weston_log("surface width and height are negative\n"); + return; + } + + if (!b || !b->rdp_peer) { + weston_log("CreateWndow(): rdp_peer is not initalized\n"); + return; + } + + if (!b->rdp_peer->settings->HiDefRemoteApp) + return; + + if (!b->rdp_peer->context) { + rdp_debug_verbose(b, "CreateWndow(): rdp_peer->context is not initalized\n"); + return; + } + + peerCtx = (RdpPeerContext *)b->rdp_peer->context; + + ASSERT_COMPOSITOR_THREAD(b); + + if (!peerCtx->activationRailCompleted) { + rdp_debug_verbose(b, "CreateWindow(): rdp_peer rail is not activated.\n"); + return; + } + + /* HiDef requires graphics channel to be ready */ + if (!peerCtx->activationGraphicsCompleted) { + rdp_debug_verbose(b, "CreateWindow(): graphics channel is not activated.\n"); + return; + } + + if (!rail_state) { + rail_state = zalloc(sizeof *rail_state); + if (!rail_state) + return; + surface->backend_state = (void *)rail_state; + } else { + /* If ever encouter error for this window, no more attempt to create window */ + if (rail_state->error) + return; + } + + /* windowId can be assigned only after activation completed */ + if (!rdp_id_manager_allocate_id(&peerCtx->windowId, (void*)surface, &window_id)) { + rail_state->error = true; + weston_log("CreateWindow(): fail to insert windowId.hash_table (windowId:%d surface:%p.\n", + window_id, surface); + return; + } + rail_state->window_id = window_id; + /* Once this surface is inserted to hash table, we want to be notified for destroy */ + assert(NULL == rail_state->destroy_listener.notify); + rail_state->destroy_listener.notify = rdp_rail_destroy_window; + wl_signal_add(&surface->destroy_signal, &rail_state->destroy_listener); + + if (surface->role_name != NULL) { + if (strncmp(surface->role_name, "wl_subsurface", sizeof("wl_subsurface")) == 0) { + rail_state->parent_surface = weston_surface_get_main_surface(surface); + assert(surface != rail_state->parent_surface); + } else if (strncmp(surface->role_name, "wl_pointer-cursor", sizeof("wl_pointer-cursor")) == 0) { + rail_state->isCursor = true; + } + } + if (rail_state->isCursor) { + if (rdp_rail_create_cursor(surface) < 0) + rail_state->error = true; + goto Exit; + } + + /* obtain view's global position */ + numViews = 0; + wl_list_for_each(view, &surface->views, surface_link) { + float sx, sy; + weston_view_to_global_float(view, 0, 0, &sx, &sy); + clientPos.x = pos.x = (int)sx; + clientPos.y = pos.y = (int)sy; + numViews++; + break; // just handle the first view for this hack + } + if (numViews == 0) { + view = NULL; + rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); + } + + /* apply global to output transform, and translate to client coordinate */ + if (surface->output) + to_client_coordinate(peerCtx, surface->output, + &clientPos.x, &clientPos.y, &clientPos.width, &clientPos.height); + + window_rect.top = window_vis.top = clientPos.y; + window_rect.left = window_vis.left = clientPos.x; + window_rect.right = window_vis.right = clientPos.x + clientPos.width; + window_rect.bottom = window_vis.bottom = clientPos.y + clientPos.height; + + window_order_info.fieldFlags = + (WINDOW_ORDER_TYPE_WINDOW | WINDOW_ORDER_STATE_NEW); + window_order_info.windowId = window_id; + + /* + #define WS_OVERLAPPED 0x00000000L + #define WS_POPUP 0x80000000L + #define WS_CHILD 0x40000000L + #define WS_MINIMIZE 0x20000000L + #define WS_VISIBLE 0x10000000L + #define WS_DISABLED 0x08000000L + #define WS_CLIPSIBLINGS 0x04000000L + #define WS_CLIPCHILDREN 0x02000000L + #define WS_MAXIMIZE 0x01000000L + #define WS_CAPTION 0x00C00000L + #define WS_BORDER 0x00800000L + #define WS_DLGFRAME 0x00400000L + #define WS_VSCROLL 0x00200000L + #define WS_HSCROLL 0x00100000L + #define WS_SYSMENU 0x00080000L + #define WS_THICKFRAME 0x00040000L + #define WS_GROUP 0x00020000L + #define WS_TABSTOP 0x00010000L + #define WS_MINIMIZEBOX 0x00020000L + #define WS_MAXIMIZEBOX 0x00010000L + + #define WS_EX_DLGMODALFRAME 0x00000001L + #define WS_EX_NOPARENTNOTIFY 0x00000004L + #define WS_EX_TOPMOST 0x00000008L + #define WS_EX_ACCEPTFILES 0x00000010L + #define WS_EX_TRANSPARENT 0x00000020L + #define WS_EX_MDICHILD 0x00000040L + #define WS_EX_TOOLWINDOW 0x00000080L + #define WS_EX_WINDOWEDGE 0x00000100L + #define WS_EX_CLIENTEDGE 0x00000200L + #define WS_EX_CONTEXTHELP 0x00000400L + #define WS_EX_RIGHT 0x00001000L + #define WS_EX_LEFT 0x00000000L + #define WS_EX_RTLREADING 0x00002000L + #define WS_EX_LTRREADING 0x00000000L + #define WS_EX_LEFTSCROLLBAR 0x00004000L + #define WS_EX_RIGHTSCROLLBAR 0x00000000L + #define WS_EX_CONTROLPARENT 0x00010000L + #define WS_EX_STATICEDGE 0x00020000L + #define WS_EX_APPWINDOW 0x00040000L + #define WS_EX_LAYERED 0x00080000L + */ + + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_STYLE; + window_state_order.style = (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_THICKFRAME | WS_GROUP | WS_TABSTOP); + window_state_order.extendedStyle = WS_EX_LAYERED; + + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_OWNER; + if (rail_state->parent_surface && + rail_state->parent_surface->backend_state) { + struct weston_surface_rail_state *parent_rail_state = + (struct weston_surface_rail_state *)rail_state->parent_surface->backend_state; + window_state_order.ownerWindowId = parent_rail_state->window_id; + } else { + window_state_order.ownerWindowId = RDP_RAIL_DESKTOP_WINDOW_ID; + } + + /* window is created with hidden and no taskbar icon always, and + it become visbile when window has some contents to show. */ + window_order_info.fieldFlags |= + (WINDOW_ORDER_FIELD_SHOW | WINDOW_ORDER_FIELD_TASKBAR_BUTTON); + window_state_order.showState = WINDOW_HIDE; + window_state_order.TaskbarButton = 1; + + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET; + window_state_order.clientOffsetX = 0; + window_state_order.clientOffsetY = 0; + + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE; + window_state_order.clientAreaWidth = clientPos.width; + window_state_order.clientAreaHeight = clientPos.height; + + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_WND_OFFSET; + window_state_order.windowOffsetX = clientPos.x; + window_state_order.windowOffsetY = clientPos.y; + + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_WND_CLIENT_DELTA; + window_state_order.windowClientDeltaX = 0; + window_state_order.windowClientDeltaY = 0; + + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_WND_SIZE; + window_state_order.windowWidth = clientPos.width; + window_state_order.windowHeight = clientPos.height; + + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_WND_RECTS; + window_state_order.numWindowRects = 1; + window_state_order.windowRects = &window_rect; + + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_VIS_OFFSET; + window_state_order.visibleOffsetX = 0; + window_state_order.visibleOffsetY = 0; + + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_VISIBILITY; + window_state_order.numVisibilityRects = 1; + window_state_order.visibilityRects = &window_vis; + + /*window_state_order.titleInfo = NULL; */ + /*window_state_order.OverlayDescription = 0;*/ + + rdp_debug_verbose(b, "WindowCreate(0x%x - (%d, %d, %d, %d)\n", + window_id, clientPos.x, clientPos.y, clientPos.width, clientPos.height); + b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); + b->rdp_peer->update->window->WindowCreate(b->rdp_peer->update->context, &window_order_info, &window_state_order); + b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + + rail_state->parent_window_id = window_state_order.ownerWindowId; + rail_state->pos = pos; + rail_state->clientPos = clientPos; + rail_state->isWindowCreated = TRUE; + rail_state->get_label = (void *)-1; // label to be re-checked at update. + rail_state->taskbarButton = window_state_order.TaskbarButton; + pixman_region32_init_rect(&rail_state->damage, + 0, 0, surface->width_from_buffer, surface->height_from_buffer); + +Exit: + /* once window is successfully created, start listening repaint update */ + if (!rail_state->error) { + assert(NULL == rail_state->repaint_listener.notify); + rail_state->repaint_listener.notify = rdp_rail_schedule_update_window; + wl_signal_add(&surface->repaint_signal, &rail_state->repaint_listener); + } + + return; +} + +static void +rdp_destroy_shared_buffer(struct weston_surface *surface) +{ + struct weston_compositor *compositor = surface->compositor; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; + + assert(b->use_gfxredir); + + if (rail_state->buffer_id) { + GFXREDIR_DESTROY_BUFFER_PDU destroyBuffer = {}; + + destroyBuffer.bufferId = rail_state->buffer_id; + peerCtx->gfxredir_server_context->DestroyBuffer(peerCtx->gfxredir_server_context, &destroyBuffer); + + rdp_id_manager_free_id(&peerCtx->bufferId, rail_state->buffer_id); + rail_state->buffer_id = 0; + } + + if (rail_state->pool_id) { + GFXREDIR_CLOSE_POOL_PDU closePool = {}; + + closePool.poolId = rail_state->pool_id; + peerCtx->gfxredir_server_context->ClosePool(peerCtx->gfxredir_server_context, &closePool); + + rdp_id_manager_free_id(&peerCtx->poolId, rail_state->pool_id); + rail_state->pool_id = 0; + } + + rdp_free_shared_memory(b, &rail_state->shared_memory); + + rail_state->surfaceBuffer = NULL; +} + +static void +rdp_rail_destroy_window(struct wl_listener *listener, void *data) +{ + struct weston_surface *surface = (struct weston_surface *)data; + struct weston_compositor *compositor = surface->compositor; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + WINDOW_ORDER_INFO window_order_info = {}; + POINTER_SYSTEM_UPDATE pointerSystem = {}; + UINT32 window_id; + RdpPeerContext *peerCtx; + + if (!rail_state) + return; + + window_id = rail_state->window_id; + if (!window_id) + goto Exit; + + assert(b && b->rdp_peer); + + ASSERT_COMPOSITOR_THREAD(b); + + peerCtx = (RdpPeerContext *)b->rdp_peer->context; + if (rail_state->isCursor) { + pointerSystem.type = SYSPTR_NULL; + b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); + b->rdp_peer->update->pointer->PointerSystem(b->rdp_peer->update->context, &pointerSystem); + b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + if (peerCtx->cursorSurface == surface) + peerCtx->cursorSurface = NULL; + rail_state->isCursor = false; + } else { + if (rail_state->isWindowCreated) { + + if (rail_state->surface_id || rail_state->buffer_id) { + /* When update is pending, need to wait reply from client */ + /* TODO: Defer destroy to FreeRDP callback ? */ + freerdp_peer *client = (freerdp_peer*)peerCtx->rail_grfx_server_context->custom; + int waitRetry = 0; + while (rail_state->isUpdatePending || + (peerCtx->currentFrameId != peerCtx->acknowledgedFrameId && + !peerCtx->isAcknowledgedSuspended)) { + if (++waitRetry > 1000) { // timeout after 10 sec. + weston_log("%s: update is still pending in client side (windowId:0x%x)\n", + __func__, window_id); + break; + } + USleep(10000); // wait 0.01 sec. + client->CheckFileDescriptor(client); + WTSVirtualChannelManagerCheckFileDescriptor(peerCtx->vcm); + } + } + +#ifdef HAVE_FREERDP_GFXREDIR_H + if (b->use_gfxredir) + rdp_destroy_shared_buffer(surface); +#endif // HAVE_FREERDP_GFXREDIR_H + + window_order_info.windowId = window_id; + window_order_info.fieldFlags = WINDOW_ORDER_TYPE_WINDOW | WINDOW_ORDER_STATE_DELETED; + + rdp_debug_verbose(b, "WindowDestroy(0x%x)\n", window_id); + b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); + b->rdp_peer->update->window->WindowDelete(b->rdp_peer->update->context, &window_order_info); + b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + + if (rail_state->surface_id) { + + RDPGFX_DELETE_SURFACE_PDU deleteSurface = {}; + + rdp_debug_verbose(b, "DeleteSurface(surfaceId:0x%x for windowsId:0x%x)\n", rail_state->surface_id, window_id); + deleteSurface.surfaceId = (UINT16)rail_state->surface_id; + peerCtx->rail_grfx_server_context->DeleteSurface(peerCtx->rail_grfx_server_context, &deleteSurface); + + rdp_id_manager_free_id(&peerCtx->surfaceId, rail_state->surface_id); + rail_state->surface_id = 0; + } + rail_state->isWindowCreated = FALSE; + } + pixman_region32_fini(&rail_state->damage); + } + + rdp_id_manager_free_id(&peerCtx->windowId, window_id); + rail_state->window_id = 0; + + if (rail_state->repaint_listener.notify) { + wl_list_remove(&rail_state->repaint_listener.link); + rail_state->repaint_listener.notify = NULL; + } + + if (rail_state->destroy_listener.notify) { + wl_list_remove(&rail_state->destroy_listener.link); + rail_state->destroy_listener.notify = NULL; + } + +Exit: + free(rail_state); + surface->backend_state = NULL; + + return; +} + +static void +rdp_rail_schedule_update_window(struct wl_listener *listener, void *data) +{ + struct weston_surface *surface = (struct weston_surface *)data; + struct weston_compositor *compositor = surface->compositor; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + UINT32 window_id; + + if (!rail_state || rail_state->error) + return; + + window_id = rail_state->window_id; + if (!window_id) + return; + + ASSERT_COMPOSITOR_THREAD(b); + + /* negative width/height is not allowed */ + if (surface->width < 0 || surface->height < 0) { + weston_log("surface width and height are negative\n"); + return; + } + + /* TODO: what width or hight 0 means? should window be hidden? */ + if (surface->width == 0 || surface->height == 0) { + rdp_debug_verbose(b, "surface width and height are zero WindowId:0x%x (%dx%d)\n", + rail_state->window_id, surface->width, surface->height); + return; + } + + if (!pixman_region32_union(&rail_state->damage, &rail_state->damage, &surface->damage)) { + /* if union failed, make entire size of bufer based on current buffer */ + pixman_region32_clear(&rail_state->damage); + pixman_region32_init_rect(&rail_state->damage, + 0, 0, surface->width_from_buffer, surface->height_from_buffer); + } + + return; +} + +static int +rdp_rail_update_window(struct weston_surface *surface, BOOL *needEndFrame, UINT32 *startedFrameId, BOOL *isUpdatePending) +{ + struct weston_compositor *compositor = surface->compositor; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + WINDOW_ORDER_INFO window_order_info = {}; + WINDOW_STATE_ORDER window_state_order = {}; + struct weston_rdp_rail_window_pos newPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; + struct weston_rdp_rail_window_pos newClientPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; + RECTANGLE_16 window_rect; + RECTANGLE_16 window_vis; + int numViews; + struct weston_view *view; + UINT32 window_id; + RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; + UINT32 new_surface_id = 0; + UINT32 old_surface_id = 0; + RAIL_UNICODE_STRING rail_window_title_string = { 0, NULL }; + char window_title[256]; + char window_title_mod[256]; + char *title = NULL; + + ASSERT_COMPOSITOR_THREAD(b); + + if (!rail_state || rail_state->error) + return 0; + + window_id = rail_state->window_id; + if (!window_id) + return 0; + + if (surface->role_name != NULL) { + if (!rail_state->parent_surface) { + if (strncmp(surface->role_name, "wl_subsurface", sizeof("wl_subsurface")) == 0) { + rail_state->parent_surface = weston_surface_get_main_surface(surface); + assert(surface != rail_state->parent_surface); + } + } + if (!rail_state->isCursor) { + if (strncmp(surface->role_name, "wl_pointer-cursor", sizeof("wl_pointer-cursor")) == 0) { + weston_log("!!!cursor role is added after creation - WindowId:0x%x\n", window_id); + + /* convert to RDP cursor */ + rdp_rail_destroy_window(NULL, (void *)surface); + assert(!surface->backend_state); + + rdp_rail_create_window(NULL, (void *)surface); + rail_state = (struct weston_surface_rail_state *)surface->backend_state; + if (!rail_state || rail_state->window_id == 0) { + weston_log("Fail to convert to RDP cursor - surface:0x%p\n", surface); + return 0; + } + assert(rail_state->isCursor); + return rdp_rail_update_cursor(surface); + } + } + } + + /* some spews for future investigation */ + { + if (surface->width == 0 || surface->height == 0) + rdp_debug_verbose(b, "update_window: surface width and height is zero windowId:0x%x (%dx%d)\n", window_id, surface->width, surface->height); + + if ((surface->width_from_buffer != surface->width) || + (surface->height_from_buffer != surface->height)) { + rdp_debug(b, "surface width/height doesn't match with buffer (windowId:0x%x)\n", window_id); + rdp_debug(b, " surface width %d, height %d\n", surface->width, surface->height); + rdp_debug(b, " buffer width %d, height %d\n", surface->width_from_buffer, surface->height_from_buffer); + } + + static const struct weston_matrix identity = { + .d = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }, + .type = 0, + }; + +// if (memcmp(&surface->buffer_to_surface_matrix.d, &identity.d, sizeof(identity.d)) != 0) +// rdp_debug(b, "buffer to surface matrix is not identity matrix type:0x%x (windowId:0x%x)\n", +// surface->buffer_to_surface_matrix.type, window_id); + + if (!surface->is_opaque && pixman_region32_not_empty(&surface->opaque)) { + int numRects = 0; + pixman_box32_t *rects = pixman_region32_rectangles(&surface->opaque, &numRects); + rdp_debug_verbose(b, "Window has opaque region: numRects:%d (windowId:0x%x)\n", numRects, window_id); + for (int n = 0; n < numRects; n++) + rdp_debug_verbose(b, " [%d]: (%d, %d) - (%d, %d)\n", + n, rects[n].x1, rects[n].y1, rects[n].x2, rects[n].y2); + } + + numViews = 0; + wl_list_for_each(view, &surface->views, surface_link) { + numViews++; + if (view->transform.enabled) + if (memcmp(&view->transform.matrix.d, &identity.d, sizeof(identity.d)) != 0) { + if (view->transform.matrix.type != WESTON_MATRIX_TRANSFORM_TRANSLATE) { + rdp_debug(b, "view[%d] matrix is not identity or translate (windowId:0x%x)\n", numViews, window_id); + if (view->transform.dirty) + rdp_debug(b, "view[%d] transform is dirty (windowId:0x%x)\n", numViews, window_id); + } + } + + if (view->alpha != 1.0) + rdp_debug(b, "view[%d] alpha is not 1 (%f) (windowId:0x%x)\n", numViews, view->alpha, window_id); + } + if (numViews > 1) { + view = NULL; + rdp_debug(b, "suface has more than 1 views. numViews = %d (windowId:0x%x)\n", numViews, window_id); + } + + //TODO: when surface is not associated to any output, it looks must not be visible. Need to verify. + if (!surface->output) + rdp_debug_verbose(b, "surface has no output assigned. (windowId:0x%x)\n", window_id); + + /* test with weston-subsurfaces */ + if (surface->subsurface_list.prev != surface->subsurface_list.next) + rdp_debug_verbose(b, "suface has subsurface (windowId:0x%x)\n", window_id); + } + /* end of some spews for future investigation */ + + /* obtain view's global position */ + numViews = 0; + wl_list_for_each(view, &surface->views, surface_link) { + float sx, sy; + weston_view_to_global_float(view, 0, 0, &sx, &sy); + newClientPos.x = newPos.x = (int)sx; + newClientPos.y = newPos.y = (int)sy; + numViews++; + break; // just handle the first view for this hack + } + if (numViews == 0) { + view = NULL; + rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); + } + + /* apply global to output transform, and translate to client coordinate */ + if (surface->output) + to_client_coordinate(peerCtx, surface->output, + &newClientPos.x, &newClientPos.y, &newClientPos.width, &newClientPos.height); + + /* Adjust the Windows size and position on the screen */ + if (rail_state->clientPos.x != newClientPos.x || + rail_state->clientPos.y != newClientPos.y || + rail_state->clientPos.width != newClientPos.width || + rail_state->clientPos.height != newClientPos.height || + rail_state->is_minimized != rail_state->is_minimized_requested || + rail_state->get_label != surface->get_label || + rail_state->forceUpdateWindowState) { + + window_order_info.windowId = window_id; + window_order_info.fieldFlags = + WINDOW_ORDER_TYPE_WINDOW; + + if (rail_state->parent_surface && + rail_state->parent_surface->backend_state) { + struct weston_surface_rail_state *parent_rail_state = + (struct weston_surface_rail_state *)rail_state->parent_surface->backend_state; + if (rail_state->parent_window_id != parent_rail_state->window_id) { + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_OWNER; + + window_state_order.ownerWindowId = parent_rail_state->window_id; + + rail_state->parent_window_id = parent_rail_state->window_id; + + rdp_debug_verbose(b, "WindowUpdate(0x%x - parent window id:%x)\n", + window_id, rail_state->parent_window_id); + } + } + + if (rail_state->forceUpdateWindowState || + rail_state->is_minimized != rail_state->is_minimized_requested) { + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_SHOW; + + window_state_order.showState = rail_state->is_minimized_requested ? WINDOW_SHOW_MINIMIZED : WINDOW_SHOW; + + rail_state->is_minimized = rail_state->is_minimized_requested; + + rdp_debug_verbose(b, "WindowUpdate(0x%x - is_minimized:%d)\n", + window_id, rail_state->is_minimized_requested); + } + + if (rail_state->is_maximized != rail_state->is_maximized_requested) + rdp_debug_verbose(b, "WindowUpdate(0x%x - is_maximized:%d)\n", + window_id, rail_state->is_maximized_requested); + rail_state->is_maximized = rail_state->is_maximized_requested; + + if (rail_state->forceUpdateWindowState || + rail_state->get_label != surface->get_label) { + window_order_info.fieldFlags |= + WINDOW_ORDER_FIELD_TASKBAR_BUTTON; + if (rail_state->parent_surface || (surface->get_label == NULL)) + window_state_order.TaskbarButton = 1; + else + window_state_order.TaskbarButton = 0; + + if (surface->get_label && surface->get_label(surface, window_title, sizeof(window_title))) { + /* see rdprail-shell for naming convension for label */ + /* TODO: For X11 app, ideally it should check "override" property, but somehow + Andriod Studio's (at least 4.1.1) dropdown menu is not "override" window, + thus here checks child window, but this causes the other issue that + the pop up window, such as "Confirm Exit" (in Andriod Studio) is not shown + in taskbar. */ + if (strncmp(window_title, "child window", sizeof("child window") - 1) == 0) + window_state_order.TaskbarButton = 1; + title = strchr(window_title, 39); + if (title) { + char *end = strrchr(window_title, 39); + if (end != title) { + *title++ = '\0'; + *end = '\0'; + } + } else { + title = window_title; + } +#ifdef HAVE_FREERDP_GFXREDIR_H + /* this is for debugging only */ + if (b->enable_copy_warning_title) { + if (snprintf(window_title_mod, + sizeof window_title_mod, + "[WARN:COPY MODE] %s (%s)", + title, b->rdprail_shell_name ? b->rdprail_shell_name : "Linux") > 0) + title = window_title_mod; + } else +#endif // HAVE_FREERDP_GFXREDIR_H + if (b->enable_distro_name_title) { + if (snprintf(window_title_mod, + sizeof window_title_mod, + "%s (%s)", + title, b->rdprail_shell_name ? b->rdprail_shell_name : "Linux") > 0) + title = window_title_mod; + } else { + if (snprintf(window_title_mod, + sizeof window_title_mod, + "%s", + title) > 0) + title = window_title_mod; + } + if (utf8_string_to_rail_string(title, &rail_window_title_string)) { + window_order_info.fieldFlags |= + WINDOW_ORDER_FIELD_TITLE; + window_state_order.titleInfo = rail_window_title_string; + } + } + + rail_state->get_label = surface->get_label; + rail_state->taskbarButton = window_state_order.TaskbarButton; + + rdp_debug_verbose(b, "WindowUpdate(0x%x - title \"%s\") TaskbarButton:%d\n", + window_id, title, window_state_order.TaskbarButton); + } else { + /* There seems a bug in mstsc client that previous taskbar button state is + not preserved, thus sending taskbar field always. */ + window_order_info.fieldFlags |= + WINDOW_ORDER_FIELD_TASKBAR_BUTTON; + window_state_order.TaskbarButton = (BYTE) rail_state->taskbarButton; + } + + if (rail_state->forceUpdateWindowState || + rail_state->clientPos.width != newClientPos.width || + rail_state->clientPos.height != newClientPos.height || + rail_state->output != surface->output) { + window_order_info.fieldFlags |= + (WINDOW_ORDER_FIELD_WND_SIZE | + WINDOW_ORDER_FIELD_WND_RECTS | + WINDOW_ORDER_FIELD_VISIBILITY | + WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE); + + window_rect.top = window_vis.top = newClientPos.y; + window_rect.left = window_vis.left = newClientPos.x; + window_rect.right = window_vis.right = newClientPos.x + newClientPos.width; + window_rect.bottom = window_vis.bottom = newClientPos.y + newClientPos.height; + + window_state_order.windowWidth = newClientPos.width; + window_state_order.windowHeight = newClientPos.height; + window_state_order.numWindowRects = 1; + window_state_order.windowRects = &window_rect; + window_state_order.numVisibilityRects = 1; + window_state_order.visibilityRects = &window_vis; + window_state_order.clientAreaWidth = newClientPos.width; + window_state_order.clientAreaHeight = newClientPos.height; + + /* if previous window size is 0 and new window is not, + show and place in taskbar (if not set yet) */ + if (rail_state->output == NULL || + ((rail_state->clientPos.width == 0 || rail_state->clientPos.height == 0) && + newClientPos.width && newClientPos.height)) { + if ((window_order_info.fieldFlags & WINDOW_ORDER_FIELD_TASKBAR_BUTTON) == 0) { + window_order_info.fieldFlags |= + WINDOW_ORDER_FIELD_TASKBAR_BUTTON; + window_state_order.TaskbarButton = 0; + } + if ((window_order_info.fieldFlags & WINDOW_ORDER_FIELD_SHOW) == 0) { + window_order_info.fieldFlags |= + WINDOW_ORDER_FIELD_SHOW; + window_state_order.showState = WINDOW_SHOW; + } + + rdp_debug_verbose(b, "WindowUpdate(0x%x - taskbar:%d showState:%d))\n", + window_id, + window_state_order.TaskbarButton, + window_state_order.showState); + } + /* if new window size is 0, and previous is not, or + no output assigned, do not show window and + do not place in taskbar */ + if (surface->output == NULL || + ((newClientPos.width == 0 || newClientPos.height == 0) && + rail_state->clientPos.width && rail_state->clientPos.height)) { + window_order_info.fieldFlags |= + (WINDOW_ORDER_FIELD_SHOW | + WINDOW_ORDER_FIELD_TASKBAR_BUTTON); + window_state_order.TaskbarButton = 1; + window_state_order.showState = WINDOW_HIDE; + + rdp_debug_verbose(b, "WindowUpdate(0x%x - taskbar:%d showState:%d))\n", + window_id, + window_state_order.TaskbarButton, + window_state_order.showState); + } + + rail_state->pos.width = newPos.width; + rail_state->pos.height = newPos.height; + rail_state->clientPos.width = newClientPos.width; + rail_state->clientPos.height = newClientPos.height; + rail_state->output = surface->output; + + rdp_debug_verbose(b, "WindowUpdate(0x%x - size (%d, %d) in RDP client size (%d, %d)\n", + window_id, newPos.width, newPos.height, newClientPos.width, newClientPos.height); + } + + if (rail_state->forceUpdateWindowState || + rail_state->clientPos.x != newClientPos.x || + rail_state->clientPos.y != newClientPos.y) { + window_order_info.fieldFlags |= + WINDOW_ORDER_FIELD_WND_OFFSET; + + window_state_order.windowOffsetX = newClientPos.x; + window_state_order.windowOffsetY = newClientPos.y; + + rail_state->pos.x = newPos.x; + rail_state->pos.y = newPos.y; + rail_state->clientPos.x = newClientPos.x; + rail_state->clientPos.y = newClientPos.y; + + rdp_debug_verbose(b, "WindowUpdate(0x%x - pos (%d, %d) - RDP client pos (%d, %d)\n", + window_id, newPos.x, newPos.y, newClientPos.x, newClientPos.y); + } + + b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); + b->rdp_peer->update->window->WindowUpdate(b->rdp_peer->update->context, &window_order_info, &window_state_order); + b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + + if (rail_window_title_string.string) + free(rail_window_title_string.string); + + rail_state->forceUpdateWindowState = false; + } + + /* update window buffer contents */ + { + BOOL isBufferSizeChanged = FALSE; + float scaleWidth = 1.0f, scaleHeight = 1.0f; + int damageWidth, damageHeight; + int copyBufferStride, copyBufferSize; + int clientBufferWidth, clientBufferHeight; + int contentBufferStride, contentBufferSize; + int contentBufferWidth, contentBufferHeight; + int bufferBpp = 4; // Bytes Per Pixel. + bool hasAlpha = view ? !weston_view_is_opaque(view, &view->transform.boundingbox) : false; + pixman_box32_t damageBox = *pixman_region32_extents(&rail_state->damage); + long page_size = sysconf(_SC_PAGESIZE); + + clientBufferWidth = newClientPos.width; + clientBufferHeight = newClientPos.height; + + weston_surface_get_content_size(surface, &contentBufferWidth, &contentBufferHeight); + contentBufferStride = contentBufferWidth * bufferBpp; + contentBufferSize = contentBufferStride * contentBufferHeight; + + copyBufferSize = (contentBufferSize + page_size - 1) & ~(page_size - 1); + copyBufferStride = contentBufferStride; + + if (contentBufferWidth && contentBufferHeight) { + +#ifdef HAVE_FREERDP_GFXREDIR_H + if (b->use_gfxredir) { + scaleWidth = 1.0f; // scaling is done by client. + scaleHeight = 1.0f; // scaling is done by client. + + if (rail_state->bufferWidth != contentBufferWidth || + rail_state->bufferHeight != contentBufferHeight) + isBufferSizeChanged = TRUE; + } else { +#else + { +#endif // HAVE_FREERDP_GFXREDIR_H + scaleWidth = (float)clientBufferWidth / contentBufferWidth; + scaleHeight = (float)clientBufferHeight / contentBufferHeight; + + if (rail_state->bufferWidth != contentBufferWidth || + rail_state->bufferHeight != contentBufferHeight) + isBufferSizeChanged = TRUE; + } + + if (isBufferSizeChanged || rail_state->forceRecreateSurface || + (rail_state->surfaceBuffer == NULL && rail_state->surface_id == 0)) { + +#ifdef HAVE_FREERDP_GFXREDIR_H + if (b->use_gfxredir) { + assert(rail_state->isUpdatePending == FALSE); + + if (rail_state->surfaceBuffer) + rdp_destroy_shared_buffer(surface); + + assert(rail_state->surfaceBuffer == NULL); + assert(rail_state->shared_memory.addr == NULL); + rail_state->shared_memory.size = copyBufferSize; + if (rdp_allocate_shared_memory(b, &rail_state->shared_memory)) { + UINT32 new_pool_id = 0; + if (rdp_id_manager_allocate_id(&peerCtx->poolId, (void*)surface, &new_pool_id)) { + // +1 for NULL terminate. + unsigned short sectionName[RDP_SHARED_MEMORY_NAME_SIZE + 1]; + // In Linux wchar_t is 4 types, but Windows wants 2 bytes wchar... + // convert to 2 bytes wchar_t. + for (uint32_t i = 0; i < RDP_SHARED_MEMORY_NAME_SIZE; i++) + sectionName[i] = rail_state->shared_memory.name[i]; + sectionName[RDP_SHARED_MEMORY_NAME_SIZE] = 0; + + GFXREDIR_OPEN_POOL_PDU openPool = {}; + openPool.poolId = new_pool_id; + openPool.poolSize = copyBufferSize; + openPool.sectionNameLength = RDP_SHARED_MEMORY_NAME_SIZE + 1; + openPool.sectionName = sectionName; + if (peerCtx->gfxredir_server_context->OpenPool( + peerCtx->gfxredir_server_context, &openPool) == 0) { + UINT32 new_buffer_id = 0; + if (rdp_id_manager_allocate_id(&peerCtx->bufferId, (void*)surface, &new_buffer_id)) { + GFXREDIR_CREATE_BUFFER_PDU createBuffer = {}; + createBuffer.poolId = openPool.poolId; + createBuffer.bufferId = new_buffer_id; + createBuffer.offset = 0; + createBuffer.stride = contentBufferStride; + createBuffer.width = contentBufferWidth; + createBuffer.height = contentBufferHeight; + createBuffer.format = GFXREDIR_BUFFER_PIXEL_FORMAT_ARGB_8888; + if (peerCtx->gfxredir_server_context->CreateBuffer( + peerCtx->gfxredir_server_context, &createBuffer) == 0) { + rail_state->surfaceBuffer = rail_state->shared_memory.addr; + rail_state->buffer_id = createBuffer.bufferId; + rail_state->pool_id = openPool.poolId; + rail_state->bufferWidth = contentBufferWidth; + rail_state->bufferHeight = contentBufferHeight; + } + } + } + } + /* if failed, clean up */ + if (!rail_state->surfaceBuffer) + rdp_destroy_shared_buffer(surface); + } + } else { +#else + { +#endif // HAVE_FREERDP_GFXREDIR_H + if (rdp_id_manager_allocate_id(&peerCtx->surfaceId, (void*)surface, &new_surface_id)) { + RDPGFX_CREATE_SURFACE_PDU createSurface = {}; + /* create surface */ + rdp_debug_verbose(b, "CreateSurface(surfaceId:0x%x - (%d, %d) size:%d for windowsId:0x%x)\n", + new_surface_id, contentBufferWidth, contentBufferHeight, contentBufferSize, window_id); + createSurface.surfaceId = (UINT16)new_surface_id; + createSurface.width = contentBufferWidth; + createSurface.height = contentBufferHeight; + /* regardless buffer as alpha or not, always use alpha to avoid mstsc bug */ + createSurface.pixelFormat = GFX_PIXEL_FORMAT_ARGB_8888; + if (peerCtx->rail_grfx_server_context->CreateSurface(peerCtx->rail_grfx_server_context, &createSurface) == 0) { + /* store new surface id */ + old_surface_id = rail_state->surface_id; + rail_state->surface_id = new_surface_id; + rail_state->bufferWidth = contentBufferWidth; + rail_state->bufferHeight = contentBufferHeight; + } + } + } + rail_state->forceRecreateSurface = false; + + /* When creating a new surface we need to upload it's entire content, expand damage */ + damageBox.x1 = 0; + damageBox.y1 = 0; + damageBox.x2 = contentBufferWidth; + damageBox.y2 = contentBufferHeight; + } else if (damageBox.x2 > 0 && damageBox.y2 > 0) { + /* scale damage using surface to buffer matrix */ + rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, &damageBox.x1, &damageBox.y1); + rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, &damageBox.x2, &damageBox.y2); + } + } else { + /* no content buffer bound, thus no damage */ + damageBox.x1 = 0; + damageBox.y1 = 0; + damageBox.x2 = 0; + damageBox.y2 = 0; + } + + damageWidth = damageBox.x2 - damageBox.x1; + if (damageWidth > contentBufferWidth) { + rdp_debug(b, "damageWidth (%d) is larger than content width(%d), clamp to avoid protocol error.\n", + damageWidth, contentBufferWidth); + damageBox.x1 = 0; + damageBox.x2 = contentBufferWidth; + damageWidth = contentBufferWidth; + } + damageHeight = damageBox.y2 - damageBox.y1; + if (damageHeight > contentBufferHeight) { + rdp_debug(b, "damageHeight (%d) is larger than content height(%d), clamp to avoid protocol error.\n", + damageHeight, contentBufferHeight); + damageBox.y1 = 0; + damageBox.y2 = contentBufferHeight; + damageHeight = contentBufferHeight; + } + + /* Check to see if we have any content update to send to the new surface */ + if (damageWidth > 0 && damageHeight > 0) { +#ifdef HAVE_FREERDP_GFXREDIR_H + if (b->use_gfxredir && + rail_state->surfaceBuffer) { + + int copyDamageX1 = (float)damageBox.x1 * scaleWidth; + int copyDamageY1 = (float)damageBox.y1 * scaleHeight; + int copyDamageWidth = (float)damageWidth * scaleWidth; + int copyDamageHeight = (float)damageHeight * scaleHeight; + int copyStartOffset = copyDamageX1*bufferBpp + copyDamageY1*copyBufferStride; + BYTE *copyBufferBits = (BYTE*)(rail_state->surfaceBuffer) + copyStartOffset; + + rdp_debug_verbose(b, "copy source: x:%d, y:%d, width:%d, height:%d\n", + damageBox.x1, damageBox.y1, damageWidth, damageHeight); + rdp_debug_verbose(b, "copy target: x:%d, y:%d, width:%d, height:%d, stride:%d\n", + copyDamageX1, copyDamageY1, copyDamageWidth, copyDamageHeight, copyBufferStride); + rdp_debug_verbose(b, "copy scale: scaleWidth:%5.3f, scaleHeight:%5.3f\n", + scaleWidth, scaleHeight); + + if (weston_surface_copy_content(surface, + copyBufferBits, copyBufferSize, copyBufferStride, + copyDamageWidth, copyDamageHeight, + damageBox.x1, damageBox.y1, damageWidth, damageHeight, + false /* y-flip */, true /* is_argb */) < 0) { + weston_log("weston_surface_copy_content failed for windowId:0x%x\n",window_id); + return -1; + } + + GFXREDIR_PRESENT_BUFFER_PDU presentBuffer = {}; + RECTANGLE_32 opaqueRect; + + /* specify opaque area */ + if (!hasAlpha) { + opaqueRect.left = copyDamageX1; + opaqueRect.top = copyDamageY1; + opaqueRect.width = copyDamageWidth; + opaqueRect.height = copyDamageHeight; + } + + presentBuffer.timestamp = 0; /* set 0 to disable A/V sync at client side */ + presentBuffer.presentId = ++peerCtx->currentFrameId; + presentBuffer.windowId = window_id; + presentBuffer.bufferId = rail_state->buffer_id; + presentBuffer.orientation = 0; // 0, 90, 180 or 270. + presentBuffer.targetWidth = newClientPos.width; + presentBuffer.targetHeight = newClientPos.height; + presentBuffer.dirtyRect.left = copyDamageX1; + presentBuffer.dirtyRect.top = copyDamageY1; + presentBuffer.dirtyRect.width = copyDamageWidth; + presentBuffer.dirtyRect.height = copyDamageHeight; + if (!hasAlpha) { + presentBuffer.numOpaqueRects = 1; + presentBuffer.opaqueRects = &opaqueRect; + } else { + presentBuffer.numOpaqueRects = 0; + presentBuffer.opaqueRects = NULL; + } + + if (peerCtx->gfxredir_server_context->PresentBuffer(peerCtx->gfxredir_server_context, &presentBuffer) == 0) { + rail_state->isUpdatePending = TRUE; + *isUpdatePending = TRUE; + } else { + weston_log("PresentBuffer failed for windowId:0x%x\n",window_id); + } + } else +#endif // HAVE_FREERDP_GFXREDIR_H + if (rail_state->surface_id) { + + RDPGFX_SURFACE_COMMAND surfaceCommand = {}; + int damageStride = damageWidth*bufferBpp; + int damageSize = damageStride*damageHeight; + BYTE *data = NULL; + int alphaCodecHeaderSize = 4; + BYTE *alpha = NULL; + int alphaSize; + + data = malloc(damageSize); + if (!data) { + // need better handling to avoid leaking surface on host. + weston_log("Couldn't allocate memory for bitmap update.\n"); + return -1; + } + + if (hasAlpha) + alphaSize = alphaCodecHeaderSize+damageWidth*damageHeight; + else { + alphaSize = alphaCodecHeaderSize+8; // 8 = max of ALPHA_RLE_SEGMENT for single alpha value. + } + alpha = malloc(alphaSize); + if (!alpha) { + free(data); + // need better handling to avoid leaking surface on host. + weston_log("Couldn't allocate memory for alpha update.\n"); + return -1; + } + + if (weston_surface_copy_content(surface, + data, damageSize, 0, 0, 0, + damageBox.x1, damageBox.y1, damageWidth, damageHeight, + false /* y-flip */, true /* is_argb */) < 0) { + weston_log("weston_surface_copy_content failed for cursor shape\n"); + free(data); + free(alpha); + return -1; + } + + /* generate alpha only bitmap */ + /* set up alpha codec header */ + alpha[0] = 'L'; // signature + alpha[1] = 'A'; // signature + alpha[2] = hasAlpha ? 0 : 1; // compression: RDP spec inticate this is non-zero value for compressed, but it must be 1. + alpha[3] = 0; // compression + + if (hasAlpha) { + BYTE *alphaBits = &data[0]; + for (int i = 0; i < damageHeight; i++, alphaBits+=damageStride) { + BYTE *srcAlphaPixel = alphaBits + 3; // 3 = xxxA. + BYTE *dstAlphaPixel = &alpha[alphaCodecHeaderSize+(i*damageWidth)]; + for (int j = 0; j < damageWidth; j++, srcAlphaPixel+=bufferBpp, dstAlphaPixel++) { + *dstAlphaPixel = *srcAlphaPixel; + } + } + } else { + /* regardless buffer as alpha or not, always use alpha to avoid mstsc bug */ + /* CLEARCODEC_ALPHA_RLE_SEGMENT */ + int bitmapSize = damageWidth*damageHeight; + alpha[alphaCodecHeaderSize] = 0xFF; // alpha value (opaque) + if (bitmapSize < 0xFF) { + alpha[alphaCodecHeaderSize+1] = (BYTE)bitmapSize; + alphaSize = alphaCodecHeaderSize+2; // alpha value + size in byte. + } else if (bitmapSize < 0xFFFF) { + alpha[alphaCodecHeaderSize+1] = 0xFF; + *(short*)&(alpha[alphaCodecHeaderSize+2]) = (short)bitmapSize; + alphaSize = alphaCodecHeaderSize+4; // alpha value + 1 + size in short. + } else { + alpha[alphaCodecHeaderSize+1] = 0xFF; + *(short*)&(alpha[alphaCodecHeaderSize+2]) = 0xFFFF; + *(int*)&(alpha[alphaCodecHeaderSize+4]) = bitmapSize; + alphaSize = alphaCodecHeaderSize+8; // alpha value + 1 + 2 + size in int. + } + } + + if (*needEndFrame == FALSE) { + /* if frame is not started yet, send StartFrame first before sendng surface command. */ + RDPGFX_START_FRAME_PDU startFrame = {}; + startFrame.frameId = ++peerCtx->currentFrameId; + rdp_debug_verbose(b, "StartFrame(frameId:0x%x, windowId:0x%x)\n", startFrame.frameId, window_id); + peerCtx->rail_grfx_server_context->StartFrame(peerCtx->rail_grfx_server_context, &startFrame); + *startedFrameId = startFrame.frameId; + *needEndFrame = TRUE; + *isUpdatePending = TRUE; + } + + surfaceCommand.surfaceId = rail_state->surface_id; + surfaceCommand.contextId = 0; + surfaceCommand.format = PIXEL_FORMAT_BGRA32; + surfaceCommand.left = damageBox.x1; + surfaceCommand.top = damageBox.y1; + surfaceCommand.right = damageBox.x2; + surfaceCommand.bottom = damageBox.y2; + surfaceCommand.width = damageWidth; + surfaceCommand.height = damageHeight; + surfaceCommand.extra = NULL; + + /* send alpha channel */ + surfaceCommand.codecId = RDPGFX_CODECID_ALPHA; + surfaceCommand.length = alphaSize; + surfaceCommand.data = &alpha[0]; + rdp_debug_verbose(b, "SurfaceCommand(frameId:0x%x, windowId:0x%x) for alpha\n", *startedFrameId, window_id); + peerCtx->rail_grfx_server_context->SurfaceCommand(peerCtx->rail_grfx_server_context, &surfaceCommand); + + /* send bitmap data */ + surfaceCommand.codecId = RDPGFX_CODECID_UNCOMPRESSED; + surfaceCommand.length = damageSize; + surfaceCommand.data = &data[0]; + rdp_debug_verbose(b, "SurfaceCommand(frameId:0x%x, windowId:0x%x) for bitmap\n", *startedFrameId, window_id); + peerCtx->rail_grfx_server_context->SurfaceCommand(peerCtx->rail_grfx_server_context, &surfaceCommand); + + free(data); + free(alpha); + } + + pixman_region32_clear(&rail_state->damage); + } + +#ifdef HAVE_FREERDP_GFXREDIR_H + if (!b->use_gfxredir) { +#else + { +#endif // HAVE_FREERDP_GFXREDIR_H + if (new_surface_id || rail_state->bufferScaleWidth != scaleWidth || rail_state->bufferScaleHeight != scaleHeight) { + /* map surface to window */ + assert(new_surface_id == 0 || (new_surface_id == rail_state->surface_id)); + rdp_debug_verbose(b, "MapSurfaceToWindow(surfaceId:0x%x - windowsId:%x)\n", + rail_state->surface_id, window_id); + rdp_debug_verbose(b, " targetWidth:0x%d - targetWidth:%d)\n", + newClientPos.width, newClientPos.height); + rdp_debug_verbose(b, " mappedWidth:0x%d - mappedHeight:%d)\n", + contentBufferWidth, contentBufferHeight); + // Always use scaled version to avoid bug in mstsc.exe, mstsc.exe + // seems can't handle mixed of scale and non-scaled version of procotols. + RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU mapSurfaceToScaledWindow = {}; + mapSurfaceToScaledWindow.surfaceId = (UINT16)rail_state->surface_id; + mapSurfaceToScaledWindow.windowId = window_id; + mapSurfaceToScaledWindow.mappedWidth = contentBufferWidth; + mapSurfaceToScaledWindow.mappedHeight = contentBufferHeight; + mapSurfaceToScaledWindow.targetWidth = newClientPos.width; + mapSurfaceToScaledWindow.targetHeight = newClientPos.height; + peerCtx->rail_grfx_server_context->MapSurfaceToScaledWindow(peerCtx->rail_grfx_server_context, &mapSurfaceToScaledWindow); + rail_state->bufferScaleWidth = scaleWidth; + rail_state->bufferScaleHeight = scaleHeight; + } + + /* destroy old surface */ + if (old_surface_id) { + RDPGFX_DELETE_SURFACE_PDU deleteSurface = {}; + rdp_debug_verbose(b, "DeleteSurface(surfaceId:0x%x for windowId:0x%x)\n", old_surface_id, window_id); + deleteSurface.surfaceId = (UINT16)old_surface_id; + peerCtx->rail_grfx_server_context->DeleteSurface(peerCtx->rail_grfx_server_context, &deleteSurface); + } + } + } + + return 0; +} + +struct update_window_iter_data { + uint32_t output_id; + UINT32 startedFrameId; + BOOL needEndFrame; + BOOL isUpdatePending; +}; + +static void +rdp_rail_update_window_iter(void *element, void *data) +{ + struct weston_surface *surface = (struct weston_surface *)element; + struct weston_compositor *compositor = surface->compositor; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + struct update_window_iter_data *iter_data = (struct update_window_iter_data *)data; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + assert(rail_state); // this iter is looping from window hash table, thus it must have rail_state initialized. + if (surface->output_mask & (1u << iter_data->output_id)) { + if (rail_state->isCursor) { + rdp_rail_update_cursor(surface); + } else if (rail_state->isUpdatePending == FALSE) { + rdp_rail_update_window(surface, + &iter_data->needEndFrame, + &iter_data->startedFrameId, + &iter_data->isUpdatePending); + } else { + rdp_debug_verbose(b, "window update is skipped for windowId:0x%x, isUpdatePending = %d\n", + rail_state->window_id, rail_state->isUpdatePending); + } + } +} + +void +rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage) +{ + struct weston_compositor *ec = output->compositor; + struct rdp_backend *b = to_rdp_backend(ec); + RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; + + if (peerCtx->isAcknowledgedSuspended || ((peerCtx->currentFrameId - peerCtx->acknowledgedFrameId) < 2)) { + rdp_debug_verbose(b, "currentFrameId:0x%x, acknowledgedFrameId:0x%x, isAcknowledgedSuspended:%d\n", + peerCtx->currentFrameId, peerCtx->acknowledgedFrameId, peerCtx->isAcknowledgedSuspended); + struct update_window_iter_data iter_data = {}; + iter_data.output_id = output->id; + hash_table_for_each(peerCtx->windowId.hash_table, rdp_rail_update_window_iter, (void*) &iter_data); + if (iter_data.needEndFrame) { + /* if frame is started at above iteration, send EndFrame here. */ + RDPGFX_END_FRAME_PDU endFrame = {}; + endFrame.frameId = iter_data.startedFrameId; + rdp_debug_verbose(b, "EndFrame(frameId:0x%x)\n", endFrame.frameId); + peerCtx->rail_grfx_server_context->EndFrame(peerCtx->rail_grfx_server_context, &endFrame); + } + if (iter_data.isUpdatePending) { + /* By default, compositor won't update idle timer by screen activity, + thus, here manually call wake function to postpone idle timer when + RDP backend sends frame to client. */ + weston_compositor_wake(b->compositor); + } + } else { + rdp_debug_verbose(b, "frame update is skipped. currentFrameId:%d, acknowledgedFrameId:%d, isAcknowledgedSuspended:%d\n", + peerCtx->currentFrameId, peerCtx->acknowledgedFrameId, peerCtx->isAcknowledgedSuspended); + } + return; +} + +BOOL +rdp_rail_peer_activate(freerdp_peer* client) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + rdpSettings *settings = client->settings; + BOOL rail_server_started = FALSE; + BOOL disp_server_opened = FALSE; + BOOL rail_grfx_server_opened = FALSE; +#ifdef HAVE_FREERDP_GFXREDIR_H + BOOL gfxredir_server_opened = FALSE; +#endif // HAVE_FREERDP_GFXREDIR_H +#ifdef HAVE_FREERDP_RDPAPPLIST_H + BOOL applist_server_opened = FALSE; + RDPAPPLIST_SERVER_CAPS_PDU appListCaps = {}; +#endif // HAVE_FREERDP_RDPAPPLIST_H + UINT waitRetry; + + ASSERT_COMPOSITOR_THREAD(b); + + /* In RAIL mode, client must not be resized */ + assert(b->no_clients_resize == 0); + /* Server must not ask client to resize */ + settings->DesktopResize = FALSE; + + /* HiDef requires graphics pipeline to be supported */ + if (settings->SupportGraphicsPipeline == FALSE) { + if (settings->HiDefRemoteApp) { + weston_log("HiDef remoting is going to be disabled because client doesn't support graphics pipeline\n"); + settings->HiDefRemoteApp = FALSE; + } + } + + /* Start RAIL server */ + peerCtx->rail_server_context = rail_server_context_new(peerCtx->vcm); + if (!peerCtx->rail_server_context) + goto error_exit; + peerCtx->rail_server_context->custom = (void*)client; + peerCtx->rail_server_context->ClientHandshake = rail_client_Handshake; + peerCtx->rail_server_context->ClientClientStatus = rail_client_ClientStatus; + peerCtx->rail_server_context->ClientExec = rail_client_Exec; + peerCtx->rail_server_context->ClientActivate = rail_client_Activate; + peerCtx->rail_server_context->ClientSyscommand = rail_client_Syscommand; + peerCtx->rail_server_context->ClientSysparam = rail_client_ClientSysparam; + peerCtx->rail_server_context->ClientGetAppidReq = rail_client_ClientGetAppidReq; + peerCtx->rail_server_context->ClientWindowMove = rail_client_WindowMove; + peerCtx->rail_server_context->ClientSnapArrange = rail_client_SnapArrange; + peerCtx->rail_server_context->ClientLangbarInfo = rail_client_LangbarInfo; + peerCtx->rail_server_context->ClientLanguageImeInfo = rail_client_LanguageImeInfo; + peerCtx->rail_server_context->ClientCompartmentInfo = rail_client_CompartmentInfo; + if (peerCtx->rail_server_context->Start(peerCtx->rail_server_context) != CHANNEL_RC_OK) + goto error_exit; + rail_server_started = TRUE; + + /* send handshake to client */ + if (settings->RemoteApplicationSupportLevel & RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED) { + RAIL_HANDSHAKE_EX_ORDER handshakeEx = {}; + UINT32 railHandshakeFlags = + (TS_RAIL_ORDER_HANDSHAKEEX_FLAGS_HIDEF + | TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED + /*| TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED*/); + handshakeEx.buildNumber = 0; + handshakeEx.railHandshakeFlags = railHandshakeFlags; + if (peerCtx->rail_server_context->ServerHandshakeEx(peerCtx->rail_server_context, &handshakeEx) != CHANNEL_RC_OK) + goto error_exit; + client->DrainOutputBuffer(client); + } else { + RAIL_HANDSHAKE_ORDER handshake = {}; + handshake.buildNumber = 0; + if (peerCtx->rail_server_context->ServerHandshake(peerCtx->rail_server_context, &handshake) != CHANNEL_RC_OK) + goto error_exit; + client->DrainOutputBuffer(client); + } + + /* wait handshake reponse from client */ + waitRetry = 0; + while (!peerCtx->handshakeCompleted) { + if (++waitRetry > 10000) // timeout after 100 sec. + goto error_exit; + USleep(10000); // wait 0.01 sec. + client->CheckFileDescriptor(client); + WTSVirtualChannelManagerCheckFileDescriptor(peerCtx->vcm); + } + + /* open Disp channel */ + peerCtx->disp_server_context = disp_server_context_new(peerCtx->vcm); + if (!peerCtx->disp_server_context) + goto error_exit; + peerCtx->disp_server_context->custom = (void*)client; + peerCtx->disp_server_context->MaxNumMonitors = RDP_MAX_MONITOR; + peerCtx->disp_server_context->MaxMonitorAreaFactorA = DISPLAY_CONTROL_MAX_MONITOR_WIDTH; + peerCtx->disp_server_context->MaxMonitorAreaFactorB = DISPLAY_CONTROL_MAX_MONITOR_HEIGHT; + peerCtx->disp_server_context->DispMonitorLayout = disp_client_monitor_layout_change; + if (peerCtx->disp_server_context->Open(peerCtx->disp_server_context) != CHANNEL_RC_OK) + goto error_exit; + disp_server_opened = TRUE; + if (peerCtx->disp_server_context->DisplayControlCaps(peerCtx->disp_server_context) != CHANNEL_RC_OK) + goto error_exit; + + /* open HiDef (aka rdpgfx) channel. */ + peerCtx->rail_grfx_server_context = rdpgfx_server_context_new(peerCtx->vcm); + if (!peerCtx->rail_grfx_server_context) + goto error_exit; + peerCtx->rail_grfx_server_context->custom = (void*)client; + peerCtx->rail_grfx_server_context->CapsAdvertise = rail_grfx_client_caps_advertise; + peerCtx->rail_grfx_server_context->CacheImportOffer = rail_grfx_client_cache_import_offer; + peerCtx->rail_grfx_server_context->FrameAcknowledge = rail_grfx_client_frame_acknowledge; + if (!peerCtx->rail_grfx_server_context->Open(peerCtx->rail_grfx_server_context)) + goto error_exit; + rail_grfx_server_opened = TRUE; + +#ifdef HAVE_FREERDP_GFXREDIR_H + /* open Graphics Redirection channel. */ + if (b->use_gfxredir) { + peerCtx->gfxredir_server_context = b->gfxredir_server_context_new(peerCtx->vcm); + if (!peerCtx->gfxredir_server_context) + goto error_exit; + peerCtx->gfxredir_server_context->custom = (void*)client; + peerCtx->gfxredir_server_context->GraphicsRedirectionLegacyCaps = gfxredir_client_graphics_redirection_legacy_caps; + peerCtx->gfxredir_server_context->GraphicsRedirectionCapsAdvertise = gfxredir_client_graphics_redirection_caps_advertise; + peerCtx->gfxredir_server_context->PresentBufferAck = gfxredir_client_present_buffer_ack; + if (peerCtx->gfxredir_server_context->Open(peerCtx->gfxredir_server_context) != CHANNEL_RC_OK) + goto error_exit; + gfxredir_server_opened = TRUE; + } +#endif // HAVE_FREERDP_GFXREDIR_H + +#ifdef HAVE_FREERDP_RDPAPPLIST_H + /* open Application List channel. */ + if (b->rdprail_shell_api && + b->rdprail_shell_name && + b->rdpapplist_server_context_new && + b->rdpapplist_server_context_free) { + peerCtx->applist_server_context = b->rdpapplist_server_context_new(peerCtx->vcm); + if (!peerCtx->applist_server_context) + goto error_exit; + peerCtx->applist_server_context->custom = (void *)client; + peerCtx->applist_server_context->ApplicationListClientCaps = applist_client_Caps; + if (peerCtx->applist_server_context->Open(peerCtx->applist_server_context) != CHANNEL_RC_OK) + goto error_exit; + applist_server_opened = TRUE; + + rdp_debug(b, "Server AppList caps version:%d\n", RDPAPPLIST_CHANNEL_VERSION); + appListCaps.version = RDPAPPLIST_CHANNEL_VERSION; + if (!utf8_string_to_rail_string(b->rdprail_shell_name, &appListCaps.appListProviderName)) + goto error_exit; + if (peerCtx->applist_server_context->ApplicationListCaps(peerCtx->applist_server_context, &appListCaps) != CHANNEL_RC_OK) + goto error_exit; + free(appListCaps.appListProviderName.string); + } +#endif // HAVE_FREERDP_RDPAPPLIST_H + + /* wait graphics channel (and optionally graphics redir channel) reponse from client */ + waitRetry = 0; + while (!peerCtx->activationGraphicsCompleted || + (gfxredir_server_opened && !peerCtx->activationGraphicsRedirectionCompleted)) { + if (++waitRetry > 10000) // timeout after 100 sec. + goto error_exit; + USleep(10000); // wait 0.01 sec. + client->CheckFileDescriptor(client); + WTSVirtualChannelManagerCheckFileDescriptor(peerCtx->vcm); + } + + return TRUE; + +error_exit: + +#ifdef HAVE_FREERDP_RDPAPPLIST_H + if (applist_server_opened) { + peerCtx->applist_server_context->Close(peerCtx->applist_server_context); + if (appListCaps.appListProviderName.string) + free(appListCaps.appListProviderName.string); + } + if (peerCtx->applist_server_context) { + assert(b->rdpapplist_server_context_free); + b->rdpapplist_server_context_free(peerCtx->applist_server_context); + peerCtx->applist_server_context = NULL; + } +#endif // HAVE_FREERDP_RDPAPPLIST_H + +#ifdef HAVE_FREERDP_GFXREDIR_H + if (gfxredir_server_opened) + peerCtx->gfxredir_server_context->Close(peerCtx->gfxredir_server_context); + if (peerCtx->gfxredir_server_context) { + assert(b->gfxredir_server_context_free); + b->gfxredir_server_context_free(peerCtx->gfxredir_server_context); + peerCtx->gfxredir_server_context = NULL; + peerCtx->activationGraphicsRedirectionCompleted = FALSE; + } +#endif // HAVE_FREERDP_GFXREDIR_H + + if (rail_grfx_server_opened) + peerCtx->rail_grfx_server_context->Close(peerCtx->rail_grfx_server_context); + if (peerCtx->rail_grfx_server_context) { + rdpgfx_server_context_free(peerCtx->rail_grfx_server_context); + peerCtx->rail_grfx_server_context = NULL; + peerCtx->activationGraphicsCompleted = FALSE; + } + + if (disp_server_opened) + peerCtx->disp_server_context->Close(peerCtx->disp_server_context); + if (peerCtx->disp_server_context) { + disp_server_context_free(peerCtx->disp_server_context); + peerCtx->disp_server_context = NULL; + } + + if (rail_server_started) + peerCtx->rail_server_context->Stop(peerCtx->rail_server_context); + if (peerCtx->rail_server_context) { + rail_server_context_free(peerCtx->rail_server_context); + peerCtx->rail_server_context = NULL; + } + + return FALSE; +} + +static void +rdp_rail_idle_handler(struct wl_listener *listener, void *data) +{ + RAIL_POWER_DISPLAY_REQUEST displayRequest; + RdpPeerContext *peerCtx = + container_of(listener, RdpPeerContext, idle_listener); + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug(b, "%s is called on peerCtx:%p\n", __func__, peerCtx); + + displayRequest.active = FALSE; + peerCtx->rail_server_context->ServerPowerDisplayRequest( + peerCtx->rail_server_context, &displayRequest); +} + +static void +rdp_rail_wake_handler(struct wl_listener *listener, void *data) +{ + RAIL_POWER_DISPLAY_REQUEST displayRequest; + RdpPeerContext *peerCtx = + container_of(listener, RdpPeerContext, wake_listener); + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug(b, "%s is called on peerCtx:%p\n", __func__, peerCtx); + + displayRequest.active = TRUE; + peerCtx->rail_server_context->ServerPowerDisplayRequest( + peerCtx->rail_server_context, &displayRequest); +} + +void +rdp_rail_sync_window_status(freerdp_peer* client) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct weston_view *view; + + ASSERT_COMPOSITOR_THREAD(b); + + { + RAIL_SYSPARAM_ORDER sysParamOrder = {}; + sysParamOrder.param = SPI_SETSCREENSAVESECURE; + sysParamOrder.setScreenSaveSecure = 0; + peerCtx->rail_server_context->ServerSysparam(peerCtx->rail_server_context, &sysParamOrder); + client->DrainOutputBuffer(client); + } + + { + RAIL_SYSPARAM_ORDER sysParamOrder = {}; + sysParamOrder.param = SPI_SETSCREENSAVEACTIVE; + sysParamOrder.setScreenSaveActive = 0; + peerCtx->rail_server_context->ServerSysparam(peerCtx->rail_server_context, &sysParamOrder); + client->DrainOutputBuffer(client); + } + + { + RAIL_ZORDER_SYNC zOrderSync = {}; + zOrderSync.windowIdMarker = RDP_RAIL_MARKER_WINDOW_ID; + peerCtx->rail_server_context->ServerZOrderSync(peerCtx->rail_server_context, &zOrderSync); + client->DrainOutputBuffer(client); + } + + { + WINDOW_ORDER_INFO window_order_info = {}; + MONITORED_DESKTOP_ORDER monitored_desktop_order = {}; + + window_order_info.windowId = RDP_RAIL_MARKER_WINDOW_ID; + window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_HOOKED | WINDOW_ORDER_FIELD_DESKTOP_ARC_BEGAN; + + client->update->window->MonitoredDesktop(client->update->context, &window_order_info, &monitored_desktop_order); + client->DrainOutputBuffer(client); + } + + { + UINT32 windowsIdArray[1] = {}; + WINDOW_ORDER_INFO window_order_info = {}; + MONITORED_DESKTOP_ORDER monitored_desktop_order = {}; + + window_order_info.windowId = RDP_RAIL_MARKER_WINDOW_ID; + window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_ZORDER | WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND; + + monitored_desktop_order.activeWindowId = RDP_RAIL_DESKTOP_WINDOW_ID; + monitored_desktop_order.numWindowIds = 1; + windowsIdArray[0] = RDP_RAIL_MARKER_WINDOW_ID; + monitored_desktop_order.windowIds = (UINT*)&windowsIdArray; + + client->update->window->MonitoredDesktop(client->update->context, &window_order_info, &monitored_desktop_order); + client->DrainOutputBuffer(client); + } + + { + WINDOW_ORDER_INFO window_order_info = {}; + MONITORED_DESKTOP_ORDER monitored_desktop_order = {}; + + window_order_info.windowId = RDP_RAIL_MARKER_WINDOW_ID; + window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED; + + client->update->window->MonitoredDesktop(client->update->context, &window_order_info, &monitored_desktop_order); + client->DrainOutputBuffer(client); + } + + peerCtx->activationRailCompleted = true; + + { + wl_list_for_each(view, &b->compositor->view_list, link) { + struct weston_surface *surface = view->surface; + struct weston_subsurface *sub; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + if (!rail_state || rail_state->window_id == 0) { + rdp_rail_create_window(NULL, (void *)surface); + rail_state = (struct weston_surface_rail_state *)surface->backend_state; + if (rail_state && rail_state->window_id) { + if (b->rdprail_shell_api && + b->rdprail_shell_api->request_window_icon) + b->rdprail_shell_api->request_window_icon(surface); + } + wl_list_for_each(sub, &surface->subsurface_list, parent_link) { + struct weston_surface_rail_state *sub_rail_state = (struct weston_surface_rail_state *)sub->surface->backend_state; + if (sub->surface == surface) + continue; + if (!sub_rail_state || sub_rail_state->window_id == 0) + rdp_rail_create_window(NULL, (void *)sub->surface); + } + } + } + + /* TODO: Z-order sync */ + + /* this assume repaint to be scheduled on idle loop, not directly from here */ + weston_compositor_damage_all(b->compositor); + } + + if (peerCtx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED) { + RAIL_POWER_DISPLAY_REQUEST displayRequest; + + /* subscribe idle/wake signal from compositor */ + peerCtx->idle_listener.notify = rdp_rail_idle_handler; + wl_signal_add(&b->compositor->idle_signal, &peerCtx->idle_listener); + peerCtx->wake_listener.notify = rdp_rail_wake_handler; + wl_signal_add(&b->compositor->wake_signal, &peerCtx->wake_listener); + + displayRequest.active = TRUE; + peerCtx->rail_server_context->ServerPowerDisplayRequest( + peerCtx->rail_server_context, &displayRequest); + + /* Upon client connection, make sure compositor is in wake state */ + weston_compositor_wake(b->compositor); + } +} + +void +rdp_rail_start_window_move( + struct weston_surface* surface, + int pointerGrabX, + int pointerGrabY, + struct weston_size minSize, + struct weston_size maxSize) +{ + struct weston_compositor *compositor = surface->compositor; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context;; + RAIL_MINMAXINFO_ORDER minmax_order; + RAIL_LOCALMOVESIZE_ORDER move_order; + + if (!b->rdp_peer || !b->rdp_peer->settings->HiDefRemoteApp) { + return; + } + + ASSERT_COMPOSITOR_THREAD(b); + assert(rail_state); + + int posX=0, posY=0; + int numViews = 0; + struct weston_view* view; + wl_list_for_each(view, &surface->views, surface_link) { + numViews++; + posX = view->geometry.x; + posY = view->geometry.y; + break; + } + if (numViews == 0) { + view = NULL; + rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); + } + + /* TODO: HI-DPI MULTIMON */ + + rdp_debug(b, "====================== StartWindowMove =============================\n"); + rdp_debug(b, "WindowsPosition - Pre-move (%d, %d, %d, %d).\n", + to_client_x(peerCtx, posX), to_client_y(peerCtx, posY), surface->width, surface->height); + + /* Inform the RDP client about the minimum/maximum width and height allowed + * on this window. + */ + minmax_order.windowId = rail_state->window_id; + minmax_order.maxPosX = 0; + minmax_order.maxPosY = 0; + minmax_order.maxWidth = 0; + minmax_order.maxHeight = 0; + minmax_order.minTrackWidth = minSize.width; + minmax_order.minTrackHeight = minSize.height; + minmax_order.maxTrackWidth = maxSize.width; + minmax_order.maxTrackHeight = maxSize.height; + + rdp_debug(b, "maxPosX: %d, maxPosY: %d, maxWidth: %d, maxHeight: %d, minTrackWidth: %d, minTrackHeight: %d, maxTrackWidth: %d, maxTrackHeight: %d\n", + minmax_order.maxPosX, + minmax_order.maxPosY, + minmax_order.maxWidth, + minmax_order.maxHeight, + minmax_order.minTrackWidth, + minmax_order.minTrackHeight, + minmax_order.maxTrackWidth, + minmax_order.maxTrackHeight); + + peerCtx->rail_server_context->ServerMinMaxInfo( + peerCtx->rail_server_context, &minmax_order); + + /* Start the local Window move. + */ + move_order.windowId = rail_state->window_id; + move_order.isMoveSizeStart = true; + move_order.moveSizeType = RAIL_WMSZ_MOVE; + move_order.posX = pointerGrabX - posX; + move_order.posY = pointerGrabY - posY; + + rdp_debug(b, "posX: %d, posY: %d \n", move_order.posX, move_order.posY); + + peerCtx->rail_server_context->ServerLocalMoveSize( + peerCtx->rail_server_context, &move_order); +} + +void +rdp_rail_end_window_move(struct weston_surface* surface) +{ + struct weston_compositor *compositor = surface->compositor; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + RdpPeerContext *peerCtx = NULL; + RAIL_LOCALMOVESIZE_ORDER move_order; + + if (!b->rdp_peer || !b->rdp_peer->settings->HiDefRemoteApp) { + return; + } + + ASSERT_COMPOSITOR_THREAD(b); + assert(rail_state); + + peerCtx = (RdpPeerContext *)b->rdp_peer->context; + + int posX=0, posY=0; + int numViews = 0; + struct weston_view *view; + wl_list_for_each(view, &surface->views, surface_link) { + numViews++; + posX = to_client_x(peerCtx, view->geometry.x); + posY = to_client_y(peerCtx, view->geometry.y); + break; + } + if (numViews == 0) { + view = NULL; + rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); + } + + /* TODO: HI-DPI MULTIMON */ + + move_order.windowId = rail_state->window_id; + move_order.isMoveSizeStart = false; + move_order.moveSizeType = RAIL_WMSZ_MOVE; + move_order.posX = posX; + move_order.posY = posY; + + peerCtx->rail_server_context->ServerLocalMoveSize( + peerCtx->rail_server_context, &move_order); + + rdp_debug(b, "WindowsPosition - Post-move (%d, %d, %d, %d).\n", posX, posY, surface->width, surface->height); + rdp_debug(b, "====================== EndWindowMove =============================\n"); +} + +static void +rdp_rail_destroy_window_iter(void *element, void *data) +{ + struct weston_surface *surface = (struct weston_surface *)element; + rdp_rail_destroy_window(NULL, (void *)surface); +} + +void +rdp_rail_peer_context_free(freerdp_peer* client, RdpPeerContext* context) +{ + struct rdp_loop_event_source *current, *next; + + if (context->windowId.hash_table) + hash_table_for_each(context->windowId.hash_table, rdp_rail_destroy_window_iter, NULL); + +#ifdef HAVE_FREERDP_RDPAPPLIST_H + if (context->applist_server_context) { + struct rdp_backend *b = context->rdpBackend; + if (context->isAppListEnabled) + context->rdpBackend->rdprail_shell_api->stop_app_list_update(context->rdpBackend->rdprail_shell_context); + context->applist_server_context->Close(context->applist_server_context); + assert(b->rdpapplist_server_context_free); + b->rdpapplist_server_context_free(context->applist_server_context); + } +#endif // HAVE_FREERDP_RDPAPPLIST_H + +#ifdef HAVE_FREERDP_GFXREDIR_H + if (context->gfxredir_server_context) { + struct rdp_backend *b = context->rdpBackend; + context->gfxredir_server_context->Close(context->gfxredir_server_context); + assert(b->gfxredir_server_context_free); + b->gfxredir_server_context_free(context->gfxredir_server_context); + } +#endif // HAVE_FREERDP_GFXREDIR_H + + if (context->rail_grfx_server_context) { + context->rail_grfx_server_context->Close(context->rail_grfx_server_context); + rdpgfx_server_context_free(context->rail_grfx_server_context); + } + + if (context->disp_server_context) { + context->disp_server_context->Close(context->disp_server_context); + disp_server_context_free(context->disp_server_context); + } + + if (context->rail_server_context) { + context->rail_server_context->Stop(context->rail_server_context); + rail_server_context_free(context->rail_server_context); + } + + /* after stopping all FreeRDP server context, no more work to be queued, free anything remained */ + wl_list_for_each_safe(current, next, &context->loop_event_source_list, link) { + wl_event_source_remove(current->event_source); + wl_list_remove(¤t->link); + free(current); + } + pthread_mutex_destroy(&context->loop_event_source_list_mutex); + + if (context->clientExec_destroy_listener.notify) { + wl_list_remove(&context->clientExec_destroy_listener.link); + context->clientExec_destroy_listener.notify = NULL; + } + + if (context->idle_listener.notify) { + wl_list_remove(&context->idle_listener.link); + context->idle_listener.notify = NULL; + } + + if (context->wake_listener.notify) { + wl_list_remove(&context->wake_listener.link); + context->wake_listener.notify = NULL; + } + +#ifdef HAVE_FREERDP_GFXREDIR_H + rdp_id_manager_free(&context->bufferId); + rdp_id_manager_free(&context->poolId); +#endif // HAVE_FREERDP_GFXREDIR_H + rdp_id_manager_free(&context->surfaceId); + rdp_id_manager_free(&context->windowId); + + pixman_region32_fini(&context->regionClientHeads); + pixman_region32_fini(&context->regionWestonHeads); +} + +BOOL +rdp_drdynvc_init(freerdp_peer *client) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + + ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); + + /* Open Dynamic virtual channel */ + peerCtx->drdynvc_server_context = drdynvc_server_context_new(peerCtx->vcm); + if (!peerCtx->drdynvc_server_context) + return FALSE; + if (peerCtx->drdynvc_server_context->Start(peerCtx->drdynvc_server_context) != CHANNEL_RC_OK) { + drdynvc_server_context_free(peerCtx->drdynvc_server_context); + peerCtx->drdynvc_server_context = NULL; + return FALSE; + } + + /* Force Dynamic virtual channel to exchange caps */ + if (WTSVirtualChannelManagerGetDrdynvcState(peerCtx->vcm) == DRDYNVC_STATE_NONE) { + client->activated = TRUE; + /* Wait reply to arrive from client */ + UINT waitRetry = 0; + while (WTSVirtualChannelManagerGetDrdynvcState(peerCtx->vcm) != DRDYNVC_STATE_READY) { + if (++waitRetry > 10000) { // timeout after 100 sec. + rdp_drdynvc_destroy(peerCtx); + return FALSE; + } + USleep(10000); // wait 0.01 sec. + client->CheckFileDescriptor(client); + WTSVirtualChannelManagerCheckFileDescriptor(peerCtx->vcm); + } + } + + return TRUE; +} + +void +rdp_drdynvc_destroy(RdpPeerContext* context) +{ + if (context->drdynvc_server_context) { + context->drdynvc_server_context->Stop(context->drdynvc_server_context); + drdynvc_server_context_free(context->drdynvc_server_context); + } +} + +BOOL +rdp_rail_peer_init(freerdp_peer *client, RdpPeerContext *peerCtx) +{ + /* RDP window ID must be within 31 bits range. MSB is reserved and exclude 0. */ + if (!rdp_id_manager_init(&peerCtx->windowId, 0x1, 0x7FFFFFFF)) { + weston_log("unable to create windowId.\n"); + goto error_return; + } + /* RDP surface ID must be within 16 bits range, exclude 0. */ + if (!rdp_id_manager_init(&peerCtx->surfaceId, 0x1, 0xFFFF)) { + weston_log("unable to create windowId.\n"); + goto error_return; + } +#ifdef HAVE_FREERDP_GFXREDIR_H + /* RDP pool ID must be within 32 bits range, exclude 0. */ + if (!rdp_id_manager_init(&peerCtx->poolId, 0x1, 0xFFFFFFFF)) { + weston_log("unable to create windowId.\n"); + goto error_return; + } + /* RDP buffer ID must be within 32 bits range, exclude 0. */ + if (!rdp_id_manager_init(&peerCtx->bufferId, 0x1, 0xFFFFFFFF)) { + weston_log("unable to create windowId.\n"); + goto error_return; + } +#endif // HAVE_FREERDP_GFXREDIR_H + + pthread_mutex_init(&peerCtx->loop_event_source_list_mutex, NULL); + wl_list_init(&peerCtx->loop_event_source_list); + + peerCtx->currentFrameId = 0; + peerCtx->acknowledgedFrameId = 0; + + pixman_region32_init(&peerCtx->regionClientHeads); + pixman_region32_init(&peerCtx->regionWestonHeads); + + return TRUE; + +error_return: + +#ifdef HAVE_FREERDP_GFXREDIR_H + rdp_id_manager_free(&peerCtx->bufferId); + rdp_id_manager_free(&peerCtx->poolId); +#endif // HAVE_FREERDP_GFXREDIR_H + rdp_id_manager_free(&peerCtx->surfaceId); + rdp_id_manager_free(&peerCtx->windowId); + + return FALSE; +} + +static void +print_matrix_type(FILE *fp, unsigned int type) +{ + fprintf(fp, " matrix type: %x: ", type); + if (type == 0) { + fprintf(fp, "identify "); + } else { + if (type & WESTON_MATRIX_TRANSFORM_TRANSLATE) + fprintf(fp, "translate "); + if (type & WESTON_MATRIX_TRANSFORM_SCALE) + fprintf(fp, "scale "); + if (type & WESTON_MATRIX_TRANSFORM_ROTATE) + fprintf(fp, "rotate "); + if (type & WESTON_MATRIX_TRANSFORM_OTHER) + fprintf(fp, "other "); + } + fprintf(fp, "\n"); +} + +static void +print_matrix(FILE *fp, const char *name, const struct weston_matrix *matrix) +{ + int i; + if (name) + fprintf(fp," %s\n", name); + print_matrix_type(fp, matrix->type); + for (i = 0; i < 4; i++) + fprintf(fp," %8.2f, %8.2f, %8.2f, %8.2f\n", + matrix->d[4*i+0], matrix->d[4*i+1], matrix->d[4*1+2], matrix->d[4*i+3]); +} + +static void +print_rdp_head(FILE *fp, const struct rdp_head *current) +{ + fprintf(fp," rdp_head: %s: index:%d: is_primary:%d\n", + current->base.name, current->index, + current->monitorMode.monitorDef.is_primary); + fprintf(fp," x:%d, y:%d, RDP client x:%d, y:%d\n", + current->base.output->x, current->base.output->y, + current->monitorMode.monitorDef.x, current->monitorMode.monitorDef.y); + fprintf(fp," width:%d, height:%d, RDP client width:%d, height: %d\n", + current->base.output->width, current->base.output->height, + current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height); + fprintf(fp," physicalWidth:%dmm, physicalHeight:%dmm, orientation:%d\n", + current->monitorMode.monitorDef.attributes.physicalWidth, + current->monitorMode.monitorDef.attributes.physicalHeight, + current->monitorMode.monitorDef.attributes.orientation); + fprintf(fp," desktopScaleFactor:%d, deviceScaleFactor:%d\n", + current->monitorMode.monitorDef.attributes.desktopScaleFactor, + current->monitorMode.monitorDef.attributes.deviceScaleFactor); + fprintf(fp," scale:%d, client scale :%3.2f\n", + current->monitorMode.scale, current->monitorMode.clientScale); + fprintf(fp," regionClient: x1:%d, y1:%d, x2:%d, y2:%d\n", + current->regionClient.extents.x1, current->regionClient.extents.y1, + current->regionClient.extents.x2, current->regionClient.extents.y2); + fprintf(fp," regionWeston: x1:%d, y1:%d, x2:%d, y2:%d\n", + current->regionWeston.extents.x1, current->regionWeston.extents.y1, + current->regionWeston.extents.x2, current->regionWeston.extents.y2); + fprintf(fp," workarea: x:%d, y:%d, width:%d, height:%d\n", + current->workarea.x, current->workarea.y, + current->workarea.width, current->workarea.height); + fprintf(fp," RDP client workarea: x:%d, y:%d, width:%d, height%d\n", + current->workareaClient.x, current->workareaClient.y, + current->workareaClient.width, current->workareaClient.height); + fprintf(fp," connected:%d, non_desktop:%d\n", + current->base.connected, current->base.non_desktop); + fprintf(fp," assigned output: %s\n", + current->base.output ? current->base.output->name : "(no output)"); + if (current->base.output) { + fprintf(fp," output extents box: x1:%d, y1:%d, x2:%d, y2:%d\n", + current->base.output->region.extents.x1, current->base.output->region.extents.y1, + current->base.output->region.extents.x2, current->base.output->region.extents.y2); + fprintf(fp," output scale:%d, output native_scale:%d\n", + current->base.output->scale, current->base.output->native_scale); + print_matrix(fp, "global to output matrix:", ¤t->base.output->matrix); + print_matrix(fp, "output to global matrix:", ¤t->base.output->inverse_matrix); + } +} + +static void +rdp_rail_dump_monitor_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) +{ + struct rdp_backend *b = (struct rdp_backend *)data; + if (b) { + struct rdp_head *current; + int err; + char *str; + size_t len; + FILE *fp = open_memstream(&str, &len); + assert(fp); + fprintf(fp,"\nrdp debug binding 'M' - dump all monitor.\n"); + wl_list_for_each(current, &b->head_list, link) { + print_rdp_head(fp, current); + fprintf(fp,"\n"); + } + err = fclose(fp); + assert(err == 0); + weston_log("%s", str); + free(str); + } +} + +struct rdp_rail_dump_window_context { + FILE *fp; + RdpPeerContext *peerCtx; +}; + +static void +rdp_rail_dump_window_iter(void *element, void *data) +{ + struct weston_surface *surface = (struct weston_surface *)element; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct rdp_rail_dump_window_context *context = (struct rdp_rail_dump_window_context *)data; + assert(rail_state); // this iter is looping from window hash table, thus it must have rail_state initialized. + FILE *fp = context->fp; + char label[256] = {}; + struct weston_view *view; + int contentBufferWidth, contentBufferHeight; + weston_surface_get_content_size(surface, &contentBufferWidth, &contentBufferHeight); + + if (surface->role_name || surface->get_label) { + if (surface->role_name) + fprintf(fp," RoleName: %s\n", surface->role_name); + if (surface->get_label) { + label[0] = '\0'; + surface->get_label(surface, label, sizeof(label)); + fprintf(fp," Label: %s\n", label); + } + } else { + fprintf(fp," (No Label, No Role name)\n"); + } + fprintf(fp," WindowId:0x%x, SurfaceId:0x%x\n", + rail_state->window_id, rail_state->surface_id); + fprintf(fp," PoolId:0x%x, BufferId:0x%x\n", + rail_state->pool_id, rail_state->buffer_id); + fprintf(fp," Position x:%d, y:%d\n", + rail_state->pos.x, rail_state->pos.y); + fprintf(fp," width:%d, height:%d\n", + rail_state->pos.width, rail_state->pos.height); + fprintf(fp," RDP client position x:%d, y:%d\n", + rail_state->clientPos.x, rail_state->clientPos.y); + fprintf(fp," RDP client width:%d, height:%d\n", + rail_state->clientPos.width, rail_state->clientPos.height); + fprintf(fp," bufferWidth:%d, bufferHeight:%d\n", + rail_state->bufferWidth, rail_state->bufferHeight); + fprintf(fp," bufferScaleWidth:%.2f, bufferScaleHeight:%.2f\n", + rail_state->bufferScaleWidth, rail_state->bufferScaleHeight); + fprintf(fp," contentBufferWidth:%d, contentBufferHeight:%d\n", + contentBufferWidth, contentBufferHeight); + fprintf(fp," input extents: x1:%d, y1:%d, x2:%d, y2:%d\n", + surface->input.extents.x1, surface->input.extents.y1, + surface->input.extents.x2, surface->input.extents.y2); + fprintf(fp," is_opaque:%d\n", surface->is_opaque); + if (!surface->is_opaque && pixman_region32_not_empty(&surface->opaque)) { + int numRects = 0; + pixman_box32_t *rects = pixman_region32_rectangles(&surface->opaque, &numRects); + fprintf(fp, " opaque region: numRects:%d\n", numRects); + for (int n = 0; n < numRects; n++) + fprintf(fp, " [%d]: (%d, %d) - (%d, %d)\n", + n, rects[n].x1, rects[n].y1, rects[n].x2, rects[n].y2); + } + fprintf(fp," parent_surface:%p, isCursor:%d, isWindowCreated:%d\n", + rail_state->parent_surface, rail_state->isCursor, rail_state->isWindowCreated); + fprintf(fp," isWindowMinimized:%d, isWindowMinimizedRequested:%d\n", + rail_state->is_minimized, rail_state->is_minimized_requested); + fprintf(fp," isWindowMaximized:%d, isWindowMaximizedRequested:%d\n", + rail_state->is_maximized, rail_state->is_maximized_requested); + fprintf(fp," forceRecreateSurface:%d, error:%d\n", + rail_state->forceRecreateSurface, rail_state->error); + fprintf(fp," isUdatePending:%d\n", + rail_state->isUpdatePending); + fprintf(fp," surface:0x%p\n", surface); + wl_list_for_each(view, &surface->views, surface_link) { + fprintf(fp," view: %p\n", view); + fprintf(fp," view's alpha: %3.2f\n", view->alpha); + fprintf(fp," view's opaque region: x1:%d, y1:%d, x2:%d, y2:%d\n", + view->transform.opaque.extents.x1, + view->transform.opaque.extents.y1, + view->transform.opaque.extents.x2, + view->transform.opaque.extents.y2); + if (pixman_region32_not_empty(&view->transform.opaque)) { + int numRects = 0; + pixman_box32_t *rects = pixman_region32_rectangles(&view->transform.opaque, &numRects); + fprintf(fp," view's opaque region: numRects:%d\n", numRects); + for (int n = 0; n < numRects; n++) + fprintf(fp, " [%d]: (%d, %d) - (%d, %d)\n", + n, rects[n].x1, rects[n].y1, rects[n].x2, rects[n].y2); + } + fprintf(fp," view's boundingbox: x1:%d, y1:%d, x2:%d, y2:%d\n", + view->transform.boundingbox.extents.x1, + view->transform.boundingbox.extents.y1, + view->transform.boundingbox.extents.x2, + view->transform.boundingbox.extents.y2); + fprintf(fp," view's scissor: x1:%d, y1:%d, x2:%d, y2:%d\n", + view->geometry.scissor.extents.x1, + view->geometry.scissor.extents.y1, + view->geometry.scissor.extents.x2, + view->geometry.scissor.extents.y2); + fprintf(fp," view's transform: enabled:%d\n", + view->transform.enabled); + if (view->transform.enabled) + print_matrix(fp, NULL, &view->transform.matrix); + } + print_matrix(fp, "buffer to surface matrix:", &surface->buffer_to_surface_matrix); + print_matrix(fp, "surface to buffer matrix:", &surface->surface_to_buffer_matrix); + fprintf(fp," output:0x%p (%s)\n", surface->output, + surface->output ? surface->output->name : "(no output assigned)"); + if (surface->output) { + struct weston_head *base_head; + wl_list_for_each(base_head, &surface->output->head_list, output_link) + print_rdp_head(fp, to_rdp_head(base_head)); + } + fprintf(fp,"\n"); +} + +static void +rdp_rail_dump_window_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) +{ + struct rdp_backend *b = (struct rdp_backend *)data; + RdpPeerContext *peerCtx; + if (b && b->rdp_peer && b->rdp_peer->context) { + /* print window from window hash table */ + struct rdp_rail_dump_window_context context; + int err; + char *str; + size_t len; + FILE *fp = open_memstream(&str, &len); + assert(fp); + fprintf(fp,"\nrdp debug binding 'W' - dump all window from window hash_table.\n"); + peerCtx = (RdpPeerContext *)b->rdp_peer->context; + dump_id_manager_state(fp, &peerCtx->windowId, "windowId"); + dump_id_manager_state(fp, &peerCtx->surfaceId, "surfaceId"); +#ifdef HAVE_FREERDP_GFXREDIR_H + dump_id_manager_state(fp, &peerCtx->poolId, "poolId"); + dump_id_manager_state(fp, &peerCtx->bufferId, "bufferId"); +#endif // HAVE_FREERDP_GFXREDIR_H + context.peerCtx = peerCtx; + context.fp = fp; + hash_table_for_each(peerCtx->windowId.hash_table, rdp_rail_dump_window_iter, (void*)&context); + err = fclose(fp); + assert(err == 0); + weston_log("%s", str); + free(str); + + /* print out compositor's scene graph */ + str = weston_compositor_print_scene_graph(b->compositor); + weston_log("%s", str); + free(str); + } +} + +static void * +rdp_rail_shell_initialize_notify(struct weston_compositor *compositor, const struct weston_rdprail_shell_api *rdprail_shell_api, void *context, char *name) +{ + struct rdp_backend *b = to_rdp_backend(compositor); + b->rdprail_shell_api = rdprail_shell_api; + b->rdprail_shell_context = context; + if (b->rdprail_shell_name) + free(b->rdprail_shell_name); + b->rdprail_shell_name = name ? strdup(name) : NULL; + rdp_debug(b, "%s: shell: distro name: %s\n",__func__, b->rdprail_shell_name); + return (void *) b; +} + +#define WINDOW_ORDER_ICON_ROWLENGTH( W, BPP ) ((((W) * (BPP) + 31) / 32) * 4) + +static void +rdp_rail_set_window_icon(struct weston_surface *surface, pixman_image_t *icon) +{ + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct weston_compositor *compositor = surface->compositor; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + RdpPeerContext *peerCtx; + WINDOW_ORDER_INFO orderInfo = {}; + WINDOW_ICON_ORDER iconOrder = {}; + ICON_INFO iconInfo = {}; + pixman_image_t *scaledIcon = NULL; + bool bitsColorAllocated = false; + void *bitsColor = NULL; + void *bitsMask = NULL; + int width; + int height; + int stride; + double xScale; + double yScale; + struct pixman_transform transform; + pixman_format_code_t format; + int maxIconWidth; + int maxIconHeight; + int targetIconWidth; + int targetIconHeight; + + if (!b || !b->rdp_peer) { + weston_log("set_window_icon(): rdp_peer is not initalized\n"); + return; + } + + peerCtx = (RdpPeerContext *)b->rdp_peer->context; + + if (!b->rdp_peer->settings->HiDefRemoteApp) + return; + + ASSERT_COMPOSITOR_THREAD(b); + + if (!rail_state || rail_state->window_id == 0) { + rdp_rail_create_window(NULL, (void *)surface); + rail_state = (struct weston_surface_rail_state *)surface->backend_state; + if (!rail_state || rail_state->window_id == 0) + return; + } + + width = pixman_image_get_width(icon); + height = pixman_image_get_height(icon); + format = pixman_image_get_format(icon); + stride = pixman_image_get_stride(icon); + + if (width == 0 || height == 0) + return; + + rdp_debug(b, "rdp_rail_set_window_icon: original icon width:%d height:%d format:%d\n", + width, height, format); + + /* TS_RAIL_CLIENTSTATUS_HIGH_DPI_ICONS_SUPPORTED + Indicates that the client supports icons up to 96 pixels in size in the + Window Icon PDU. If this flag is not present, icon dimensions are limited + to 32 pixels. */ + if (peerCtx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_HIGH_DPI_ICONS_SUPPORTED) { + maxIconWidth = 96; + maxIconHeight = 96; + } else { + maxIconWidth = 32; + maxIconHeight = 32; + } + + if (width > maxIconWidth) + targetIconWidth = maxIconWidth; + else + targetIconWidth = width; + + if (height > maxIconHeight) + targetIconHeight = maxIconHeight; + else + targetIconHeight = height; + + /* create icon bitmap with flip in Y-axis, and client always expects a8r8g8b8 format. */ + scaledIcon = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, + targetIconWidth, targetIconHeight, NULL, 0); + if (!scaledIcon) + return; + + xScale = (double)width / targetIconWidth; + yScale = (double)height / targetIconHeight; + pixman_transform_init_scale(&transform, + pixman_double_to_fixed(xScale), + pixman_double_to_fixed(yScale * -1)); // flip Y. + pixman_transform_translate(&transform, NULL, + 0, pixman_int_to_fixed(height)); + pixman_image_set_transform(icon, &transform); + pixman_image_set_filter(icon, PIXMAN_FILTER_BILINEAR, NULL, 0); + + pixman_image_composite32(PIXMAN_OP_SRC, + icon, /* src */ + NULL, /* mask */ + scaledIcon, /* dest */ + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + targetIconWidth, /* width */ + targetIconHeight /* height */); + + pixman_image_set_filter(icon, PIXMAN_FILTER_NEAREST, NULL, 0); + pixman_image_set_transform(icon, NULL); + + icon = scaledIcon; + width = pixman_image_get_width(icon); + height = pixman_image_get_height(icon); + format = pixman_image_get_format(icon); + stride = pixman_image_get_stride(icon); + + assert(width == targetIconWidth); + assert(height == targetIconHeight); + assert(format == PIXMAN_a8r8g8b8); + + rdp_debug(b, "rdp_rail_set_window_icon: converted icon width:%d height:%d format:%d\n", + width, height, format); + + /* color bitmap is 32 bits */ + int strideColor = WINDOW_ORDER_ICON_ROWLENGTH(width, 32); + int sizeColor = strideColor * height; + if (strideColor != stride) { + /* when pixman's stride is differnt from client's expetation, need to adjust. */ + sizeColor = strideColor * height; + bitsColor = malloc(sizeColor); + if (!bitsColor) + goto exit; + bitsColorAllocated = true; + } else { + bitsColor = (char *)pixman_image_get_data(icon); + } + + /* Mask is 1 bit */ + int strideMask = WINDOW_ORDER_ICON_ROWLENGTH(width, 1); + int sizeMask = strideMask * height; + bitsMask = zalloc(sizeMask); + if (!bitsMask) + goto exit; + + /* generate mask and copy color bits, match to the stride RDP wants when different. */ + char *srcColor = (char *)pixman_image_get_data(icon); + char *dstColor = (char *)bitsColor; + char *dstMask = (char *)bitsMask; + for (int i = 0; i < height; i++) { + uint32_t *src = (uint32_t *)srcColor; + uint32_t *dst = (uint32_t *)dstColor; + char *mask = dstMask; + for (int j = 0; j < width; j++) { + if (dst != src) + *dst = *src; + if (*dst & 0xFF000000) + mask[j / 8] |= (0x80 >> (j % 8)); + dst++; src++; + } + srcColor += stride; + dstColor += strideColor; + dstMask += strideMask; + } + + orderInfo.windowId = rail_state->window_id; + orderInfo.fieldFlags = WINDOW_ORDER_TYPE_WINDOW | WINDOW_ORDER_ICON; + iconInfo.cacheEntry = 0xFFFF; // no cache + iconInfo.cacheId = 0xFF; // no cache + iconInfo.bpp = 32; + iconInfo.width = (UINT32)width; + iconInfo.height = (UINT32)height; + iconInfo.cbColorTable = 0; + iconInfo.cbBitsMask = sizeMask; + iconInfo.cbBitsColor = sizeColor; + iconInfo.bitsMask = bitsMask; + iconInfo.colorTable = NULL; + iconInfo.bitsColor = bitsColor; + iconOrder.iconInfo = &iconInfo; + + b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); + b->rdp_peer->update->window->WindowIcon(b->rdp_peer->update->context, &orderInfo, &iconOrder); + b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + +exit: + if (bitsMask) + free(bitsMask); + if (bitsColorAllocated) + free(bitsColor); + if (scaledIcon) + pixman_image_unref(scaledIcon); + + return; +} + +#ifdef HAVE_FREERDP_RDPAPPLIST_H +static bool +rdp_rail_notify_app_list(void *rdp_backend, struct weston_rdprail_app_list_data *app_list_data) +{ + struct rdp_backend *b = (struct rdp_backend*)rdp_backend; + RdpPeerContext *peerCtx; + + if (!b || !b->rdp_peer) { + weston_log("rdp_rail_notify_app_list(): rdp_peer is not initalized\n"); + return false; // return false only when peer is not ready for possible re-send. + } + + if (!b->rdp_peer->settings->HiDefRemoteApp) + return true; + + peerCtx = (RdpPeerContext *)b->rdp_peer->context; + + if (!peerCtx->applist_server_context) + return false; + + rdp_debug(b, "rdp_rail_notify_app_list(): rdp_peer %p\n", peerCtx); + rdp_debug(b, " inSync: %d\n", app_list_data->inSync); + rdp_debug(b, " syncStart: %d\n", app_list_data->syncStart); + rdp_debug(b, " syncEnd: %d\n", app_list_data->syncEnd); + rdp_debug(b, " newAppId: %d\n", app_list_data->newAppId); + rdp_debug(b, " deleteAppId: %d\n", app_list_data->deleteAppId); + rdp_debug(b, " deleteAppProvider: %d\n", app_list_data->deleteAppProvider); + rdp_debug(b, " appId: %s\n", app_list_data->appId); + rdp_debug(b, " appGroup: %s\n", app_list_data->appGroup); + rdp_debug(b, " appExecPath: %s\n", app_list_data->appExecPath); + rdp_debug(b, " appWorkingDir: %s\n", app_list_data->appWorkingDir); + rdp_debug(b, " appDesc: %s\n", app_list_data->appDesc); + rdp_debug(b, " appIcon: %p\n", app_list_data->appIcon); + rdp_debug(b, " appProvider: %s\n", app_list_data->appProvider); + + if (app_list_data->deleteAppId) { + RDPAPPLIST_DELETE_APPLIST_PDU deleteAppList = {}; + assert(app_list_data->appProvider == NULL); // provider must be NULL. + deleteAppList.flags = RDPAPPLIST_FIELD_ID; + if (app_list_data->appId == NULL || // appId is required. + !utf8_string_to_rail_string(app_list_data->appId, &deleteAppList.appId)) + goto Exit_deletePath; + + if (app_list_data->appGroup && // appGroup is optional. + utf8_string_to_rail_string(app_list_data->appGroup, &deleteAppList.appGroup)) { + deleteAppList.flags |= RDPAPPLIST_FIELD_GROUP; + } + peerCtx->applist_server_context->DeleteApplicationList(peerCtx->applist_server_context, &deleteAppList); + Exit_deletePath: + if (deleteAppList.appId.string) + free(deleteAppList.appId.string); + if (deleteAppList.appGroup.string) + free(deleteAppList.appGroup.string); + } else if (app_list_data->deleteAppProvider) { + RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU deleteAppListProvider = {}; + deleteAppListProvider.flags = RDPAPPLIST_FIELD_PROVIDER; + if (app_list_data->appProvider && // appProvider is required. + utf8_string_to_rail_string(app_list_data->appProvider, &deleteAppListProvider.appListProviderName)) + peerCtx->applist_server_context->DeleteApplicationListProvider(peerCtx->applist_server_context, &deleteAppListProvider); + if (deleteAppListProvider.appListProviderName.string) + free(deleteAppListProvider.appListProviderName.string); + } else { + RDPAPPLIST_UPDATE_APPLIST_PDU updateAppList = {}; + RDPAPPLIST_ICON_DATA iconData = {}; + assert(app_list_data->appProvider == NULL); // group must be NULL. + updateAppList.flags = app_list_data->newAppId ? RDPAPPLIST_HINT_NEWID : 0; + if (app_list_data->inSync) + updateAppList.flags |= RDPAPPLIST_HINT_SYNC; + if (app_list_data->syncStart) { + assert(app_list_data->inSync); + updateAppList.flags |= RDPAPPLIST_HINT_SYNC_START; + } + if (app_list_data->syncEnd) { + assert(app_list_data->inSync); + updateAppList.flags |= RDPAPPLIST_HINT_SYNC_END; + } + updateAppList.flags |= (RDPAPPLIST_FIELD_ID | + RDPAPPLIST_FIELD_EXECPATH | + RDPAPPLIST_FIELD_DESC); + if (app_list_data->appId == NULL || // id is required. + !utf8_string_to_rail_string(app_list_data->appId, &updateAppList.appId)) + goto Exit_updatePath; + if (app_list_data->appExecPath == NULL || // exePath is required. + !utf8_string_to_rail_string(app_list_data->appExecPath, &updateAppList.appExecPath)) + goto Exit_updatePath; + if (app_list_data->appDesc == NULL || // desc is required. + !utf8_string_to_rail_string(app_list_data->appDesc, &updateAppList.appDesc)) + goto Exit_updatePath; + + if (app_list_data->appGroup && // group is optional. + utf8_string_to_rail_string(app_list_data->appGroup, &updateAppList.appGroup)) { + updateAppList.flags |= RDPAPPLIST_FIELD_GROUP; + } + if (app_list_data->appWorkingDir && // workingDir is optional. + utf8_string_to_rail_string(app_list_data->appWorkingDir, &updateAppList.appWorkingDir)) { + updateAppList.flags |= RDPAPPLIST_FIELD_WORKINGDIR; + } + if (app_list_data->appIcon) { // icon is optional. + iconData.flags = 0; + iconData.iconWidth = pixman_image_get_width(app_list_data->appIcon); + iconData.iconHeight = pixman_image_get_height(app_list_data->appIcon); + iconData.iconStride = pixman_image_get_stride(app_list_data->appIcon); + iconData.iconBpp = 32; + if (pixman_image_get_format(app_list_data->appIcon) != PIXMAN_a8r8g8b8) + goto Exit_updatePath; + iconData.iconFormat = RDPAPPLIST_ICON_FORMAT_BMP; + iconData.iconBitsLength = iconData.iconHeight * iconData.iconStride; + iconData.iconBits = malloc(iconData.iconBitsLength); + if (!iconData.iconBits) + goto Exit_updatePath; + char *src = (char *)pixman_image_get_data(app_list_data->appIcon); + char *dst = (char *)iconData.iconBits + (iconData.iconHeight-1) * iconData.iconStride; + for (UINT32 i = 0; i < iconData.iconHeight; i++) { + memcpy(dst, src, iconData.iconStride); + src += iconData.iconStride; + dst -= iconData.iconStride; + } + updateAppList.appIcon = &iconData; + updateAppList.flags |= RDPAPPLIST_FIELD_ICON; + } + peerCtx->applist_server_context->UpdateApplicationList(peerCtx->applist_server_context, &updateAppList); + Exit_updatePath: + if (iconData.iconBits) + free(iconData.iconBits); + if (updateAppList.appId.string) + free(updateAppList.appId.string); + if (updateAppList.appGroup.string) + free(updateAppList.appGroup.string); + if (updateAppList.appExecPath.string) + free(updateAppList.appExecPath.string); + if (updateAppList.appWorkingDir.string) + free(updateAppList.appWorkingDir.string); + if (updateAppList.appDesc.string) + free(updateAppList.appDesc.string); + } + return true; +} +#endif // HAVE_FREERDP_RDPAPPLIST_H + +static struct weston_output * +rdp_rail_get_primary_output(void *rdp_backend) +{ + struct rdp_backend *b = (struct rdp_backend*)rdp_backend; + struct rdp_head *current; + wl_list_for_each(current, &b->head_list, link) { + if (current->monitorMode.monitorDef.is_primary) + return current->base.output; + } + return NULL; +} + +struct weston_rdprail_api rdprail_api = { + .shell_initialize_notify = rdp_rail_shell_initialize_notify, + .start_window_move = rdp_rail_start_window_move, + .end_window_move = rdp_rail_end_window_move, + .set_window_icon = rdp_rail_set_window_icon, +#ifdef HAVE_FREERDP_RDPAPPLIST_H + .notify_app_list = rdp_rail_notify_app_list, +#else + .notify_app_list = NULL, +#endif // HAVE_FREERDP_RDPAPPLIST_H + .get_primary_output = rdp_rail_get_primary_output, +}; + +int +rdp_rail_backend_create(struct rdp_backend *b) +{ + char *s; + int ret = weston_plugin_api_register(b->compositor, WESTON_RDPRAIL_API_NAME, + &rdprail_api, sizeof(rdprail_api)); + if (ret < 0) { + weston_log("Failed to register rdprail API.\n"); + return -1; + } + +#if defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) + dlerror(); /* clear error */ + b->libFreeRDPServer = dlopen("libfreerdp-server2.so", RTLD_NOW); + if (!b->libFreeRDPServer) + weston_log("dlopen(libfreerdp-server2.so) failed with %s\n", dlerror()); +#endif // defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H)o + +#ifdef HAVE_FREERDP_RDPAPPLIST_H + if (b->libFreeRDPServer) { + *(void **)(&b->rdpapplist_server_context_new) = dlsym(b->libFreeRDPServer, "rdpapplist_server_context_new"); + *(void **)(&b->rdpapplist_server_context_free) = dlsym(b->libFreeRDPServer, "rdpapplist_server_context_free"); + if (!b->rdpapplist_server_context_new || !b->rdpapplist_server_context_free) + rdp_debug(b, "libfreerdp-server2.so doesn't support applist API.\n"); + } +#endif // HAVE_FREERDP_RDPAPPLIST_H + +#ifdef HAVE_FREERDP_GFXREDIR_H + bool use_gfxredir = true; + + s = getenv("WESTON_RDP_DISABLE_SHARED_MEMORY"); + if (s) { + rdp_debug(b, "WESTON_RDP_DISABLE_SHARED_MEMORY is set to %s.\n", s); + if (strcmp(s, "true") == 0) + use_gfxredir = false; + } + + /* check if shared memory mount path is set */ + if (use_gfxredir) { + use_gfxredir = false; + s = getenv("WSL2_SHARED_MEMORY_MOUNT_POINT"); + if (s) { + b->shared_memory_mount_path = s; + b->shared_memory_mount_path_size = strlen(b->shared_memory_mount_path); + use_gfxredir = true; + } else { + rdp_debug(b, "WSL2_SHARED_MEMORY_MOUNT_POINT is not set.\n"); + } + } + + /* check if FreeRDP server lib supports graphics redirection channel API */ + if (use_gfxredir) { + use_gfxredir = false; + if (b->libFreeRDPServer) { + *(void **)(&b->gfxredir_server_context_new) = dlsym(b->libFreeRDPServer, "gfxredir_server_context_new"); + *(void **)(&b->gfxredir_server_context_free) = dlsym(b->libFreeRDPServer, "gfxredir_server_context_free"); + if (b->gfxredir_server_context_new && b->gfxredir_server_context_new) + use_gfxredir = true; + else + rdp_debug(b, "libfreerdp-server2.so doesn't support graphics redirection API.\n"); + } + } + + /* Test virtfsio actually works */ + if (use_gfxredir) { + use_gfxredir = false; + struct weston_rdp_shared_memory shmem = {}; + shmem.size = sysconf(_SC_PAGESIZE); + if (rdp_allocate_shared_memory(b, &shmem)) { + *(UINT32*)shmem.addr = 0x12344321; + assert(*(UINT32*)shmem.addr == 0x12344321); + rdp_free_shared_memory(b, &shmem); + use_gfxredir = true; + } + } + + b->use_gfxredir = use_gfxredir; + rdp_debug(b, "RDP backend: use_gfxredir = %d\n", b->use_gfxredir); +#endif // HAVE_FREERDP_GFXREDIR_H + + /* + * Configure HI-DPI scaling. + */ + b->enable_hi_dpi_support = true; + s = getenv("WESTON_RDP_DISABLE_HI_DPI_SCALING"); + if (s) { + if (strcmp(s, "true") == 0) + b->enable_hi_dpi_support = false; + else if (strcmp(s, "false") == 0) + b->enable_hi_dpi_support = true; + } + rdp_debug(b, "RDP backend: enable_hi_dpi_support = %d\n", b->enable_hi_dpi_support); + + b->enable_fractional_hi_dpi_support = false; + if (b->enable_hi_dpi_support) { + /* Disable by default for now. b->enable_fractional_hi_dpi_support = true; */ + s = getenv("WESTON_RDP_DISABLE_FRACTIONAL_HI_DPI_SCALING"); + if (s) { + if (strcmp(s, "true") == 0) + b->enable_fractional_hi_dpi_support = false; + else if (strcmp(s, "false") == 0) + b->enable_fractional_hi_dpi_support = true; + } + } + rdp_debug(b, "RDP backend: enable_fractional_hi_dpi_support = %d\n", b->enable_fractional_hi_dpi_support); + + b->debug_desktop_scaling_factor = 0; + if (b->enable_hi_dpi_support) { + char *debug_desktop_scaling_factor = getenv("WESTON_RDP_DEBUG_DESKTOP_SCALING_FACTOR"); + if (debug_desktop_scaling_factor) { + if (!safe_strtoint(debug_desktop_scaling_factor, &b->debug_desktop_scaling_factor) || + (b->debug_desktop_scaling_factor < 100 || b->debug_desktop_scaling_factor > 500)) { + b->debug_desktop_scaling_factor = 0; + rdp_debug(b, "WESTON_RDP_DEBUG_DESKTOP_SCALING_FACTOR = %s is invalid and ignored.\n", + debug_desktop_scaling_factor); + } else { + rdp_debug(b, "WESTON_RDP_DEBUG_DESKTOP_SCALING_FACTOR = %d is set.\n", + b->debug_desktop_scaling_factor); + } + } + } + rdp_debug(b, "RDP backend: debug_desktop_scaling_factor = %d\n", b->debug_desktop_scaling_factor); + + b->rdprail_shell_name = NULL; + + b->enable_distro_name_title = true; + s = getenv("WESTON_RDP_DISABLE_APPEND_DISTRONAME_TITLE"); + if (s) { + if (strcmp(s, "true") == 0) + b->enable_distro_name_title = false; + } + rdp_debug(b, "RDP backend: enable_distro_name_title = %d\n", b->enable_distro_name_title); + + b->enable_copy_warning_title = false; + if (b->debugLevel >= RDP_DEBUG_LEVEL_WARN && + !b->use_gfxredir) { + b->enable_copy_warning_title = true; + s = getenv("WESTON_RDP_DISABLE_COPY_WARNING_TITLE"); + if (s) { + if (strcmp(s, "true") == 0) + b->enable_copy_warning_title = false; + } + } + rdp_debug(b, "RDP backend: enable_copy_warning_title = %d\n", b->enable_copy_warning_title); + + /* M to dump all outstanding monitor info */ + b->debug_binding_M = weston_compositor_add_debug_binding(b->compositor, KEY_M, + rdp_rail_dump_monitor_binding, b); + /* W to dump all outstanding window info */ + b->debug_binding_W = weston_compositor_add_debug_binding(b->compositor, KEY_W, + rdp_rail_dump_window_binding, b); + /* Trigger to enter debug key : CTRL+SHIFT+SPACE */ + weston_install_debug_key_binding(b->compositor, MODIFIER_CTRL); + + /* start listening surface creation */ + b->create_window_listener.notify = rdp_rail_create_window; + wl_signal_add(&b->compositor->create_surface_signal, &b->create_window_listener); + + return 0; +} + +void +rdp_rail_destroy(struct rdp_backend *b) +{ + if (b->create_window_listener.notify) { + wl_list_remove(&b->create_window_listener.link); + b->create_window_listener.notify = NULL; + } + + if (b->rdprail_shell_name) + free(b->rdprail_shell_name); + + if (b->debug_binding_M) + weston_binding_destroy(b->debug_binding_M); + + if (b->debug_binding_W) + weston_binding_destroy(b->debug_binding_W); + +#if defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) + if (b->libFreeRDPServer) + dlclose(b->libFreeRDPServer); +#endif // defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) +} diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c new file mode 100644 index 000000000..8c0f14fcd --- /dev/null +++ b/libweston/backend-rdp/rdputil.c @@ -0,0 +1,235 @@ +/* + * 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 + +#include "rdp.h" + +pid_t rdp_get_tid() +{ +#ifdef SYS_gettid + return syscall(SYS_gettid); +#else + return gettid(); +#endif +} + +#ifdef ENABLE_RDP_THREAD_CHECK +void assert_compositor_thread(struct rdp_backend *b) +{ + assert(b->compositor_tid == rdp_get_tid()); +} +void assert_not_compositor_thread(struct rdp_backend *b) +{ + assert(b->compositor_tid != rdp_get_tid()); +} +#endif // ENABLE_RDP_THREAD_CHECK + +BOOL +rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory) +{ + int fd = -1; + void *addr = NULL; + // + 1 for '/' and + 1 for NULL. + char path[b->shared_memory_mount_path_size + 1 + RDP_SHARED_MEMORY_NAME_SIZE + 1]; + + if (shared_memory->size <= 0) { + weston_log("%s: invalid size %ld\n", + __func__, shared_memory->size); + goto error_exit; + } + // validate page size sysconf(_SC_PAGESIZE); + + // name must be guid format, 32 chars + 4 of '-' + '{' + '}' + // if not provided, read from kernel. + if (shared_memory->name[0] == '\0') { + int fd_uuid = open("/proc/sys/kernel/random/uuid", O_RDONLY); + if (fd_uuid < 0) { + weston_log("%s: open uuid failed with error %s\n", + __func__, strerror(errno)); + goto error_exit; + } + if (read(fd_uuid, &shared_memory->name[1], 32 + 4) < 0) { + weston_log("%s: read uuid failed with error %s\n", + __func__, strerror(errno)); + goto error_exit; + } + close(fd_uuid); + shared_memory->name[0] = '{'; + shared_memory->name[RDP_SHARED_MEMORY_NAME_SIZE - 1] = '}'; + shared_memory->name[RDP_SHARED_MEMORY_NAME_SIZE] = '\0'; + } else if ((strlen(shared_memory->name) != RDP_SHARED_MEMORY_NAME_SIZE) || + (shared_memory->name[0] != '{') || + (shared_memory->name[RDP_SHARED_MEMORY_NAME_SIZE - 1] != '}') || + (shared_memory->name[RDP_SHARED_MEMORY_NAME_SIZE] != '\0')) { + weston_log("%s: name is not in GUID form \"%s\"\n", + __func__, shared_memory->name); + goto error_exit; + } + + strcpy(path, b->shared_memory_mount_path); + strcat(path, "/"); + strcat(path, shared_memory->name); + + fd = open(path, O_CREAT | O_RDWR); + if (fd < 0) { + weston_log("%s: Failed to open \"%s\" with error: %s\n", + __func__, path, strerror(errno)); + goto error_exit; + } + + if (fallocate(fd, 0, 0, shared_memory->size) < 0) { + weston_log("%s: Failed to allocate %d: \"%s\" %ld bytes with error: %s\n", + __func__, fd, path, shared_memory->size, strerror(errno)); + goto error_exit; + } + + addr = mmap(NULL, shared_memory->size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) { + weston_log("%s: Failed to mmmap %d: \"%s\" %ld bytes with error: %s\n", + __func__, fd, path, shared_memory->size, strerror(errno)); + goto error_exit; + } + + rdp_debug_verbose(b, "%s: allocated %d: %s (%ld bytes) at %p\n", + __func__, fd, shared_memory->name, shared_memory->size, shared_memory->addr); + + shared_memory->fd = fd; + shared_memory->addr = addr; + + return TRUE; + +error_exit: + + if (fd > 0) + close(fd); + + if (addr) + munmap(addr, shared_memory->size); + + shared_memory->fd = -1; + shared_memory->addr = NULL; + + return FALSE; +} + +void +rdp_free_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory) +{ + if (shared_memory->addr) { + munmap(shared_memory->addr, shared_memory->size); + shared_memory->addr = NULL; + } + + if (shared_memory->fd > 0) { + close(shared_memory->fd); + shared_memory->fd = -1; + } +} + +BOOL +rdp_id_manager_init(struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 high_limit) +{ + assert(low_limit < high_limit); + id_manager->id_total = high_limit - low_limit; + id_manager->id_used = 0; + id_manager->id_low_limit = low_limit; + id_manager->id_high_limit = high_limit; + id_manager->id = low_limit; + id_manager->hash_table = hash_table_create(); + if (!id_manager->hash_table) + weston_log("%s: unable to create hash_table.\n", __func__); + return id_manager->hash_table != NULL; +} + +void +rdp_id_manager_free(struct rdp_id_manager *id_manager) +{ + if (id_manager->id_used != 0) + weston_log("%s: possible id leak: %d\n", __func__, id_manager->id_used); + if (id_manager->hash_table) { + hash_table_destroy(id_manager->hash_table); + id_manager->hash_table = NULL; + } + id_manager->id = 0; + id_manager->id_low_limit = 0; + id_manager->id_high_limit = 0; + id_manager->id_total = 0; + id_manager->id_used = 0; +} + +BOOL +rdp_id_manager_allocate_id(struct rdp_id_manager *id_manager, void *object, UINT32 *new_id) +{ + UINT32 id = 0; + for(;id_manager->id_used < id_manager->id_total;) { + id = id_manager->id++; + if (id_manager->id == id_manager->id_high_limit) + id_manager->id = id_manager->id_low_limit; + /* Make sure this id is not currently used */ + if (hash_table_lookup(id_manager->hash_table, id) == NULL) { + if (hash_table_insert(id_manager->hash_table, id, object) < 0) + break; + /* successfully to reserve new id for given object */ + id_manager->id_used++; + *new_id = id; + return TRUE; + } + } + return FALSE; +} + +void +rdp_id_manager_free_id(struct rdp_id_manager *id_manager, UINT32 id) +{ + hash_table_remove(id_manager->hash_table, id); + id_manager->id_used--; +} + +void +dump_id_manager_state(FILE *fp, struct rdp_id_manager *id_manager, char* title) +{ + fprintf(fp,"ID Manager status: %s\n", title); + fprintf(fp," current ID: %u\n", id_manager->id); + fprintf(fp," lowest ID: %u\n", id_manager->id_low_limit); + fprintf(fp," hightest ID: %u\n", id_manager->id_high_limit); + fprintf(fp," total IDs: %u\n", id_manager->id_total); + fprintf(fp," used IDs: %u\n", id_manager->id_used); + fprintf(fp,"\n"); +} + + diff --git a/libweston/compositor.c b/libweston/compositor.c index 7fd4cc1ef..a76bf9752 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -537,6 +537,7 @@ weston_surface_create(struct weston_compositor *compositor) wl_signal_init(&surface->destroy_signal); wl_signal_init(&surface->commit_signal); + wl_signal_init(&surface->repaint_signal); surface->compositor = compositor; surface->ref_count = 1; @@ -1677,9 +1678,11 @@ weston_surface_schedule_repaint(struct weston_surface *surface) { struct weston_output *output; - wl_list_for_each(output, &surface->compositor->output_list, link) + wl_signal_emit(&surface->repaint_signal, surface); + wl_list_for_each(output, &surface->compositor->output_list, link) { if (surface->output_mask & (1u << output->id)) weston_output_schedule_repaint(output); + } } /** @@ -1693,9 +1696,11 @@ weston_view_schedule_repaint(struct weston_view *view) { struct weston_output *output; - wl_list_for_each(output, &view->surface->compositor->output_list, link) + wl_signal_emit(&view->surface->repaint_signal, view->surface); + wl_list_for_each(output, &view->surface->compositor->output_list, link) { if (view->output_mask & (1u << output->id)) weston_output_schedule_repaint(output); + } } /** @@ -4326,14 +4331,25 @@ weston_surface_get_bounding_box(struct weston_surface *surface) */ WL_EXPORT int weston_surface_copy_content(struct weston_surface *surface, - void *target, size_t size, + void *target, size_t size, size_t target_stride, + int target_width, int target_height, int src_x, int src_y, - int width, int height) + int src_width, int src_height, + bool y_flip, bool is_argb) { struct weston_renderer *rer = surface->compositor->renderer; int cw, ch; const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ + if (!target_width) + target_width = src_width; + + if (!target_height) + target_height = src_height; + + if (!target_stride) + target_stride = target_width * bytespp; + if (!rer->surface_copy_content) return -1; @@ -4342,17 +4358,17 @@ weston_surface_copy_content(struct weston_surface *surface, if (src_x < 0 || src_y < 0) return -1; - if (width <= 0 || height <= 0) + if (src_width <= 0 || src_height <= 0) return -1; - if (src_x + width > cw || src_y + height > ch) + if (src_x + src_width > cw || src_y + src_height > ch) return -1; - if (width * bytespp * height > size) + if (target_stride * target_height > size) return -1; - return rer->surface_copy_content(surface, target, size, - src_x, src_y, width, height); + return rer->surface_copy_content(surface, target, size, target_stride, target_width, target_height, + src_x, src_y, src_width, src_height, y_flip, is_argb); } static void @@ -6372,6 +6388,10 @@ weston_output_enable(struct weston_output *output) iterator = container_of(c->output_list.prev, struct weston_output, link); + /* TODO: no need for auto arrange position for HiRAIL with RDP-backend, */ + /* it's better here does query position from backend and init */ + /* geomerty with it, so no need to re-arrage and re-init with */ + /* the position specified by backend */ if (!wl_list_empty(&c->output_list)) x = iterator->x + iterator->width; @@ -7138,9 +7158,9 @@ debug_scene_view_print(FILE *fp, struct weston_view *view, int view_idx) view->surface->get_label(view->surface, desc, sizeof(desc)) < 0) { strcpy(desc, "[no description available]"); } - fprintf(fp, "\tView %d (role %s, PID %d, surface ID %u, %s, %p):\n", + fprintf(fp, "\tView %d (role %s, PID %d, surface ID %u, %s, %p, %p):\n", view_idx, view->surface->role_name, pid, surface_id, - desc, view); + desc, view, view->surface); box = pixman_region32_extents(&view->transform.boundingbox); fprintf(fp, "\t\tposition: (%d, %d) -> (%d, %d)\n", diff --git a/libweston/input.c b/libweston/input.c index b4e2c66fa..dd3a1c070 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -1890,10 +1890,12 @@ notify_button(struct weston_seat *seat, const struct timespec *time, pointer->grab_x = pointer->x; pointer->grab_y = pointer->y; } - pointer->button_count++; + pointer->button_count=1; //TODO: Need to keep track of button indivually, avoid overflow/underflowing when the same button state is provided multiple time. For now just cap to 1/0. + //pointer->button_count++; } else { weston_compositor_idle_release(compositor); - pointer->button_count--; + pointer->button_count=0; //TODO: Need to keep track of button indivually, avoid overflow/underflowing when the same button state is provided multiple time. For now just cap to 1/0. + //pointer->button_count--; } weston_compositor_run_button_binding(compositor, pointer, time, button, @@ -3621,15 +3623,15 @@ weston_seat_set_keyboard_focus(struct weston_seat *seat, if (keyboard && keyboard->focus != surface) { weston_keyboard_set_focus(keyboard, surface); wl_data_device_set_keyboard_focus(seat); - } - - inc_activate_serial(compositor); + + inc_activate_serial(compositor); - activation_data = (struct weston_surface_activation_data) { - .surface = surface, - .seat = seat, - }; - wl_signal_emit(&compositor->activate_signal, &activation_data); + activation_data = (struct weston_surface_activation_data) { + .surface = surface, + .seat = seat, + }; + wl_signal_emit(&compositor->activate_signal, &activation_data); + } } static void diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c index 59b1f0ff2..a40087483 100644 --- a/libweston/pixman-renderer.c +++ b/libweston/pixman-renderer.c @@ -811,22 +811,43 @@ pixman_renderer_surface_get_content_size(struct weston_surface *surface, static int pixman_renderer_surface_copy_content(struct weston_surface *surface, - void *target, size_t size, + void *target, size_t size, size_t target_stride, + int target_width, int target_height, int src_x, int src_y, - int width, int height) + int src_width, int src_height, + bool y_flip, bool is_argb) { - const pixman_format_code_t format = PIXMAN_a8b8g8r8; - const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ + const pixman_format_code_t format = is_argb ? PIXMAN_a8r8g8b8 : PIXMAN_a8b8g8r8; struct pixman_surface_state *ps = get_surface_state(surface); pixman_image_t *out_buf; + pixman_transform_t transform; + double scale_x; + double scale_y; if (!ps->image) return -1; - out_buf = pixman_image_create_bits(format, width, height, - target, width * bytespp); - - pixman_image_set_transform(ps->image, NULL); + out_buf = pixman_image_create_bits(format, target_width, target_height, + target, target_stride); + + scale_x = (double)src_width / target_width; + scale_y = (double)src_height / target_height; + src_x *= (1.0f / scale_x); + src_y *= (1.0f / scale_y); + + if (y_flip) { + pixman_transform_init_scale(&transform, + pixman_double_to_fixed(scale_x), + pixman_double_to_fixed(-scale_y)); + pixman_transform_translate(&transform, NULL, + 0, + pixman_int_to_fixed(src_height)); + } else { + pixman_transform_init_scale(&transform, + pixman_double_to_fixed(scale_x), + pixman_double_to_fixed(scale_y)); + } + pixman_image_set_transform(ps->image, &transform); pixman_image_composite32(PIXMAN_OP_SRC, ps->image, /* src */ NULL, /* mask */ @@ -834,8 +855,8 @@ pixman_renderer_surface_copy_content(struct weston_surface *surface, src_x, src_y, /* src_x, src_y */ 0, 0, /* mask_x, mask_y */ 0, 0, /* dest_x, dest_y */ - width, height); - + target_width, target_height); + pixman_image_set_transform(ps->image, NULL); pixman_image_unref(out_buf); return 0; diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index d5bfe8303..22992e4a6 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -2702,6 +2702,8 @@ pack_color(pixman_format_code_t format, float *c) switch (format) { case PIXMAN_a8b8g8r8: return (a << 24) | (b << 16) | (g << 8) | r; + case PIXMAN_a8r8g8b8: + return (a << 24) | (r << 16) | (g << 8) | b; default: assert(0); return 0; @@ -2710,10 +2712,16 @@ pack_color(pixman_format_code_t format, float *c) static int gl_renderer_surface_copy_content(struct weston_surface *surface, - void *target, size_t size, + void *target, size_t size, size_t stride, + int target_width, int target_height, int src_x, int src_y, - int width, int height) + int width, int height, + bool y_flip, bool is_argb) { + /*TODO:add scaling support*/ + assert(target_width == width); + assert(target_width == height); + static const GLfloat verts[4 * 2] = { 0.0f, 0.0f, 1.0f, 0.0f, @@ -2732,9 +2740,9 @@ gl_renderer_surface_copy_content(struct weston_surface *surface, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f }; - const pixman_format_code_t format = PIXMAN_a8b8g8r8; + const pixman_format_code_t format = is_argb ? PIXMAN_a8r8g8b8 : PIXMAN_a8b8g8r8; const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ - const GLenum gl_format = GL_RGBA; /* PIXMAN_a8b8g8r8 little-endian */ + const GLenum gl_format = is_argb ? GL_BGRA_EXT : GL_RGBA; /* PIXMAN_a8b8g8r8 little-endian */ struct gl_renderer *gr = get_renderer(surface->compositor); struct gl_surface_state *gs = get_surface_state(surface); int cw, ch; @@ -2781,7 +2789,7 @@ gl_renderer_surface_copy_content(struct weston_surface *surface, glViewport(0, 0, cw, ch); glDisable(GL_BLEND); use_shader(gr, gs->shader); - if (gs->y_inverted) + if (gs->y_inverted ^ y_flip) proj = projmat_normal; else proj = projmat_yinvert; @@ -2812,8 +2820,18 @@ gl_renderer_surface_copy_content(struct weston_surface *surface, glDisableVertexAttribArray(0); glPixelStorei(GL_PACK_ALIGNMENT, bytespp); - glReadPixels(src_x, src_y, width, height, gl_format, - GL_UNSIGNED_BYTE, target); + if (stride) { + /* GL_PACK_ROW_LENGTH can be used when supported, + but it's not supported with EGL2, thus copy row by row. */ + char *dst = (char *)target; + for (int i = 0; i < height; i++, dst += stride) { + glReadPixels(src_x, src_y+i, width, 1, gl_format, + GL_UNSIGNED_BYTE, dst); + } + } else { + glReadPixels(src_x, src_y, width, height, gl_format, + GL_UNSIGNED_BYTE, target); + } glDeleteFramebuffers(1, &fbo); glDeleteTextures(1, &tex); diff --git a/meson.build b/meson.build index e7d053b98..55b654107 100644 --- a/meson.build +++ b/meson.build @@ -119,7 +119,7 @@ config_h.set10('TEST_GL_RENDERER', get_option('test-gl-renderer')) backend_default = get_option('backend-default') if backend_default == 'auto' - foreach b : [ 'headless', 'fbdev', 'x11', 'wayland', 'drm' ] + foreach b : [ 'headless', 'fbdev', 'x11', 'wayland', 'drm', 'rdp' ] if get_option('backend-' + b) backend_default = b endif @@ -159,6 +159,7 @@ subdir('desktop-shell') subdir('fullscreen-shell') subdir('ivi-shell') subdir('kiosk-shell') +subdir('rdprail-shell') subdir('remoting') subdir('pipewire') subdir('clients') diff --git a/meson_options.txt b/meson_options.txt index 239bd2da2..701b22fd5 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -53,7 +53,7 @@ option( option( 'backend-default', type: 'combo', - choices: [ 'auto', 'drm', 'wayland', 'x11', 'fbdev', 'headless' ], + choices: [ 'auto', 'drm', 'wayland', 'x11', 'fbdev', 'headless', 'rdp' ], value: 'drm', description: 'Default backend when no parent display server detected' ) @@ -130,6 +130,12 @@ option( value: true, description: 'Weston shell UI: kiosk (desktop apps)' ) +option( + 'shell-rdprail', + type: 'boolean', + value: true, + description: 'Weston shell UI: RDP remote application shell' +) option( 'desktop-shell-client-default', diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c new file mode 100644 index 000000000..e346241e9 --- /dev/null +++ b/rdprail-shell/app-list.c @@ -0,0 +1,1147 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include +#include + +#include "shell.h" +#include "shared/helpers.h" +#include "shared/image-loader.h" + +#ifdef HAVE_WINPR2 +#include +#include +#include +#include +#include +#include + +#define NUM_CONTROL_EVENT 4 + +struct app_list_context { + wHashTable* table; + HANDLE thread; + HANDLE stopEvent; // control event: wait index 0 + HANDLE startRdpNotifyEvent; // control event: wait index 1 + HANDLE stopRdpNotifyEvent; // control event: wait index 2 + HANDLE loadIconEvent; // control event: wait index 3 + HANDLE replyEvent; + bool isRdpNotifyStarted; + bool isAppListNamespaceAttached; + int app_list_pidfd; + int weston_pidfd; + pixman_image_t* default_icon; + pixman_image_t* default_overlay_icon; + struct { + CRITICAL_SECTION lock; // use at load_icon_file. + pixman_image_t* image; // use as reply message at load_icon_file. + const char *key; // use as send message at load_icon_file. + } load_icon; + struct { + char requestedClientLanguageId[32]; // 32 = RDPAPPLIST_LANG_SIZE. + char currentClientLanguageId[32]; + } lang_info; +}; + +struct app_entry { + struct desktop_shell *shell; + char *file; + char *name; + char *exec; + char *try_exec; + char *working_dir; + char *icon; + char *icon_file; +}; + +/* TODO: obtain additional path from $XDG_DATA_DIRS, default path is defined here */ +char *app_list_folder[] = { + "/usr/share/applications", + "/usr/local/share/applications", +}; + +/* list of folders to look for icon in specific orders */ +/* TODO: follow icon search path desribed at "Icon Lookup" section at + https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ +char *icon_folder[] = { + "/usr/share/pixmaps/", + "/usr/share/icons/hicolor/96x96/apps/", + "/usr/share/icons/hicolor/128x128/apps/", + "/usr/share/icons/hicolor/48x48/apps/", + "/usr/share/icons/hicolor/32x32/apps/", + "/usr/share/icons/hicolor/24x24/apps/", + "/usr/share/icons/hicolor/22x22/apps/", + "/usr/share/icons/hicolor/16x16/apps/", + "/usr/share/icons/HighContrast/96x96/apps/", + "/usr/share/icons/HighContrast/128x128/apps/", + "/usr/share/icons/HighContrast/48x48/apps/", + "/usr/share/icons/HighContrast/32x32/apps/", + "/usr/share/icons/HighContrast/24x24/apps/", + "/usr/share/icons/HighContrast/22x22/apps/", + "/usr/share/icons/HighContrast/16x16/apps/", + "/usr/share/icons/hicolor/scalable/apps/", /* use scalable(= svg) only when no png. */ + "/usr/share/icons/HighContrast/scalable/apps/", /* use scalable (= svg) only when no png. */ +}; + +/* copy chars from 's' to 'd', up to count 'c', ensure 'd' is terminated by '\0' char */ +/* returns number of chars copy to 'd' excluding terminating '\0' char */ +/* this fucntion does not tell caller if d is truncated */ +/* this function assume 's' is accesible up to count 'c' or terminated before count 'c', otherwise may fault */ +/* this function does not pad remaining buffer at 'd'. */ +static size_t +copy_string(char *d, size_t c, const char *s) +{ + size_t i = 0; + + assert(d); + assert(c > 0); // c can't be zero or nagative. + assert(s); + + while (c > 1) { + if (*s == '\0') + break; + *d++ = *s++; + i++; c--; + } + *d = '\0'; + + return i; +} + +static size_t +append_string(char *d, size_t c, const char *s) +{ + size_t i = 0; + + assert(d); + assert(c > 0); // c can't be zero or nagative. + assert(s); + + while (c > 1) { + if (*d == '\0') + break; + d++; + i++; c--; + } + + if (c) + i += copy_string(d, c, s); + + return i; +} + +static void +attach_app_list_namespace(struct desktop_shell *shell) +{ + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + assert(false == context->isAppListNamespaceAttached); + if (context && context->app_list_pidfd > 0) { + assert(context->weston_pidfd > 0); + if (setns(context->app_list_pidfd, 0) == -1) + weston_log("attach_app_list_namespace failed %s\n", strerror(errno)); + else + context->isAppListNamespaceAttached = true; + } +} + +static void +detach_app_list_namespace(struct desktop_shell *shell) +{ + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + if (context && context->weston_pidfd > 0 && context->isAppListNamespaceAttached) { + if (setns(context->weston_pidfd, 0) == -1) { + /* TODO: when failed to go back, this is fatal, should terminate weston and restart? */ + weston_log("detach_app_list_namespace failed %s\n", strerror(errno)); + } else { + context->isAppListNamespaceAttached = false; + } + } +} + +static bool +is_file_exist(char *file) +{ + struct stat buffer; + return (stat(file, &buffer) == 0); +} + +static char * +is_desktop_file(char *file) +{ + char *ext = strrchr(file, '.'); + if (ext) { + if (strcmp(ext, ".desktop") == 0) + return ext; + } + return NULL; +} + +static char * +find_icon_file(char *name) +{ + char buf[512]; + int len; + + /* TODO: follow icon search path desribed at "Icon Lookup" section at + https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ + for (int i = 0; i < (int)ARRAY_LENGTH(icon_folder); i++) { + copy_string(buf, sizeof buf, icon_folder[i]); + append_string(buf, sizeof buf, name); + len = strlen(buf); + + /* first, try given file name as is */ + if (is_file_exist(buf)) + return strdup(buf); + + /* if not found, try again with .png extension appended */ + append_string(buf, sizeof buf, ".png"); + if (is_file_exist(buf)) + return strdup(buf); + + /* if not found, try again with .svg extension appended */ + copy_string(&buf[len], sizeof buf - len, ".svg"); + if (is_file_exist(buf)) + return strdup(buf); + } + + return NULL; +} + +static void +free_app_entry(void *arg) +{ + struct app_entry *e = (struct app_entry *)arg; + if (e) { + shell_rdp_debug(e->shell, "free_app_entry(): %s: %s\n", e->name, e->file); + if (e->file) free(e->file); + if (e->name) free(e->name); + if (e->exec) free(e->exec); + if (e->try_exec) free(e->try_exec); + if (e->working_dir) free(e->working_dir); + if (e->icon) free(e->icon); + if (e->icon_file) free(e->icon_file); + free(e); + } +} + +static void +send_app_entry(struct desktop_shell *shell, char *key, struct app_entry *entry, + bool newApp, bool deleteApp, bool deleteProvider, bool in_sync, bool sync_start, bool sync_end) +{ + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + struct weston_rdprail_app_list_data app_list_data = {}; + + if (!shell->rdprail_api->notify_app_list) + return; + + app_list_data.newAppId = newApp; + app_list_data.deleteAppId = deleteApp; + app_list_data.deleteAppProvider = deleteProvider; + if (deleteProvider) { + assert(!in_sync); + assert(!sync_start); + assert(!sync_end); + app_list_data.appProvider = shell->distroName; + } else if (deleteApp) { + assert(!in_sync); + assert(!sync_start); + assert(!sync_end); + app_list_data.appProvider = NULL; + app_list_data.appId = key; + app_list_data.appGroup = NULL; + } else { + /* new or updating app entry */ + assert(entry); + app_list_data.inSync = in_sync; + if (in_sync) { + app_list_data.syncStart = sync_start; + app_list_data.syncEnd = sync_end; + } else { + assert(!sync_start); + assert(!sync_end); + } + app_list_data.appProvider = NULL; + app_list_data.appId = key; + app_list_data.appGroup = NULL; + /* TODO: support "actions" as "tasks" in client side */ + /* https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html#exec-variables */ + app_list_data.appExecPath = entry->try_exec ? entry->try_exec : entry->exec; + app_list_data.appWorkingDir = entry->working_dir; + app_list_data.appDesc = entry->name; + if (entry->icon) { + attach_app_list_namespace(shell); + if (!entry->icon_file) + entry->icon_file = find_icon_file(entry->icon); + if (entry->icon_file) + app_list_data.appIcon = load_image(entry->icon_file); + detach_app_list_namespace(shell); + } + if (!app_list_data.appIcon) { + app_list_data.appIcon = context->default_icon; + if (app_list_data.appIcon) + pixman_image_ref(app_list_data.appIcon); + } + if (app_list_data.appIcon && + shell->is_blend_overlay_icon_app_list && + context->default_overlay_icon) + shell_blend_overlay_icon(shell, + app_list_data.appIcon, + context->default_overlay_icon); + } + + shell->rdprail_api->notify_app_list(shell->rdp_backend, &app_list_data); + + if (app_list_data.appIcon) + pixman_image_unref(app_list_data.appIcon); +} + +static char * +trim_command_exec(char *s) +{ + /* https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html + * A command line may contain at most one %f, %u, %F or %U field code. + * If the application should not open any file the %f, %u, %F and %U field + * codes must be removed from the command line and ignored. + */ + char *p = strrchr(s,'%'); + if (p) { + if (*(p+1) == 'f' || *(p+1) == 'u' || + *(p+1) == 'F' || *(p+1) == 'U') + *p = '\0'; + } + return s; +} + +static int +app_list_config_section_get_string_by_language_id( + struct weston_config_section *section, + const char *base_key, + char **value, + const char *default_value, + const char *lang_id) +{ + char key[32]; + char *patch; + + if (lang_id && *lang_id != '\0') { + /* append language code and contry code to base key, such as Key[zh_TW] */ + copy_string(key, sizeof key, base_key); + append_string(key, sizeof key, "["); + append_string(key, sizeof key, lang_id); + append_string(key, sizeof key, "]"); + + /* first, try language code and country code, such as Key[zh_CN] */ + if (weston_config_section_get_string(section, + key, value, default_value) == 0) + return 0; + + /* second, try language code only, such as Key[ja] */ + if (strchr(lang_id, '_')) { + patch = strrchr(key, '_'); + assert(patch); + *patch++ = ']'; + *patch = '\0'; + if (weston_config_section_get_string(section, + key, value, default_value) == 0) + return 0; + } + } + + /* finally try with base key only without language/country code */ + return weston_config_section_get_string(section, + base_key, value, default_value); +} + +static bool +update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entry) +{ + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + char *lang_id = context->lang_info.currentClientLanguageId; + struct weston_config *config; + struct weston_config_section *section; + char *s; + char *icon_file; + bool is_terminal, is_no_display; + + entry->shell = shell; + + entry->file = strdup(file); + if (!entry->file) + return false; + + attach_app_list_namespace(shell); + config = weston_config_parse(file); + detach_app_list_namespace(shell); + + if (config) { + section = weston_config_get_section(config, + "Desktop Entry", NULL, NULL); + if (!section) + return false; + if (weston_config_section_get_string(section, + "Type", &s, NULL) == 0) { + if (strcmp(s, "Application") != 0) { + shell_rdp_debug(shell, "desktop file: %s is not app (%s)\n", entry->file, s); + free(s); + return false; // not application. + } + free(s); + } + if (weston_config_section_get_bool(section, + "NoDisplay", &is_no_display, false) == 0) { + if (is_no_display) { + shell_rdp_debug(shell, "desktop file: %s has NoDisplay specified\n", entry->file); + return false; // terminal based app is not included. + } + } + if (weston_config_section_get_bool(section, + "Terminal", &is_terminal, false) == 0) { + if (is_terminal) { + shell_rdp_debug(shell, "desktop file: %s is terminal based app\n", entry->file); + return false; // terminal based app is not included. + } + } + /*TODO: OnlyShowIn/NotShowIn support for WSL environment. */ + /* Need $XDG_CURRENT_DESKTOP keyword for WSL GUI environment */ + if (weston_config_section_get_string(section, + "OnlyShowIn", &s, NULL) == 0) { + shell_rdp_debug(shell, "desktop file: %s has OnlyShowIn %s\n", entry->file, s); + free(s); + return false; // terminal based app is not included. + } + if (app_list_config_section_get_string_by_language_id(section, + "Name", &s, NULL, lang_id) == 0) { + if (shell->is_appid_with_distro_name) { + char *t; + size_t len = strlen(s); + /* 4 = ' ' + '(' + ')' + null */ + len += (4 + shell->distroNameLength); + t = zalloc(len); + if (t) { + copy_string(t, len, s); + append_string(t, len, " ("); + append_string(t, len, shell->distroName); + append_string(t, len, ")"); + entry->name = t; + free(s); + } else { + entry->name = s; + } + } else { + entry->name = s; + } + } else { + /* name is required */ + return false; + } + if (weston_config_section_get_string(section, + "Exec", &s, NULL) == 0) { + entry->exec = trim_command_exec(s); + } else { + /* exec is required */ + return false; + } + if (weston_config_section_get_string(section, + "TryExec", &s, NULL) == 0) { + entry->try_exec = trim_command_exec(s); + } + if (weston_config_section_get_string(section, + "Path", &s, NULL) == 0) { + entry->working_dir = s; + } + if (weston_config_section_get_string(section, + "Icon", &s, NULL) == 0) { + entry->icon = s; + + attach_app_list_namespace(shell); + icon_file = find_icon_file(s); + detach_app_list_namespace(shell); + + if (icon_file) + entry->icon_file = icon_file; + } + weston_config_destroy(config); + + shell_rdp_debug(shell, "desktop file: %s\n", entry->file); + shell_rdp_debug(shell, " Name[%s]:%s\n", lang_id, entry->name); + shell_rdp_debug(shell, " Exec:%s\n", entry->exec); + shell_rdp_debug(shell, " TryExec:%s\n", entry->try_exec); + shell_rdp_debug(shell, " WorkingDir:%s\n", entry->working_dir); + shell_rdp_debug(shell, " Icon name:%s\n", entry->icon); + shell_rdp_debug(shell, " Icon file:%s\n", entry->icon_file); + + return true; + } + + return false; +} + +static void +app_list_desktop_file_removed(struct desktop_shell *shell, char *file) +{ + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + struct app_entry *entry; + char key[512]; + char *ext; + + copy_string(key, sizeof key, file); + ext = is_desktop_file(key); + assert(ext); + key[ext-key] = '\0'; // drop extention for key. + + if (context->isRdpNotifyStarted) { + entry = (struct app_entry *)HashTable_GetItemValue(context->table, (void*)key); + if (entry) + send_app_entry(shell, key, entry, false, true, false, false, false, false); + } + + HashTable_Remove(context->table, key); +} + +static void +app_list_desktop_file_changed(struct desktop_shell *shell, char *folder, char *file) +{ + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + char key[512]; + char full_path[512]; + char *ext; + bool entry_filled = false; + struct app_entry *entry; + struct app_entry *entry_old; + + copy_string(key, sizeof key, file); + ext = is_desktop_file(key); + assert(ext); + key[ext-key] = '\0'; // drop extention for key. + + copy_string(full_path, sizeof full_path, folder); + append_string(full_path, sizeof full_path, "/"); + append_string(full_path, sizeof full_path, file); + + entry_old = (struct app_entry *)HashTable_GetItemValue(context->table, (void*)key); + + entry = zalloc(sizeof *entry); + if (entry) + entry_filled = update_app_entry(shell, full_path, entry); + + if (entry_filled) { + shell_rdp_debug(shell, "app list entry updated: Key:%s, Name:%s\n", key, entry->name); + if (entry_old) { + if (HashTable_SetItemValue(context->table, key, (void*)entry) < 0) { + /* failed to update with new entry, remove this desktop entry as data is stale */ + app_list_desktop_file_removed(shell, file); + free_app_entry(entry); + } else { + free_app_entry(entry_old); + if (context->isRdpNotifyStarted) + send_app_entry(shell, key, entry, false, false, false, false, false, false); + } + } else { + if (HashTable_Add(context->table, key, (void *)entry) < 0) + free_app_entry(entry); + else if (context->isRdpNotifyStarted) + send_app_entry(shell, key, entry, true, false, false, false, false, false); + } + } else if (entry) { + shell_rdp_debug(shell, "app list entry failed to update: Key:%s\n", key); + if (entry_old) + app_list_desktop_file_removed(shell, file); + free_app_entry(entry); + } +} + +static void +app_list_update_all(struct desktop_shell *shell) +{ + char path[512]; + DIR *dir; + struct dirent *ent; + char *folder; + char *home; + + for (int i = 0; i < (int)ARRAY_LENGTH(app_list_folder); i++) { + attach_app_list_namespace(shell); + folder = app_list_folder[i]; + if (*folder == '~') { + home = getenv("HOME"); + if (!home) + continue; + copy_string(path, sizeof path, home); + append_string(path, sizeof path, folder+1); // skip '~'. + folder = path; + } + shell_rdp_debug(shell, "app list folder[%d]: %s\n", i, folder); + dir = opendir(folder); + detach_app_list_namespace(shell); + if (dir != NULL) { + while ((ent = readdir(dir)) != NULL) { + if (is_desktop_file(ent->d_name)) + app_list_desktop_file_changed(shell, folder, ent->d_name); + } + closedir (dir); + } + } +} + +static void +app_list_start_rdp_notify(struct desktop_shell *shell) +{ + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + struct app_entry *entry; + char **keys; + char **cur; + int num_keys; + + if (!shell->rdprail_api->notify_app_list) + return; + + if (strcmp(context->lang_info.currentClientLanguageId, + context->lang_info.requestedClientLanguageId) != 0) { + shell_rdp_debug(shell, "app_list_start_rdp_notify(): client language is changed from %s to %s\n", + context->lang_info.currentClientLanguageId, + context->lang_info.requestedClientLanguageId); + strcpy(context->lang_info.currentClientLanguageId, + context->lang_info.requestedClientLanguageId); + /* update with requested language */ + app_list_update_all(shell); + } + + keys = NULL; + num_keys = HashTable_GetKeys(context->table, (ULONG_PTR**)&keys); + if (num_keys < 0) + return; + + cur = keys; + for (int i = 0; i < num_keys; i++, cur++) { + entry = (struct app_entry *)HashTable_GetItemValue(context->table, (void *)*cur); + if (entry) + send_app_entry(shell, *cur, entry, true, false, false, true, (i == 0), (i+1 == num_keys)); + } + + free(keys); +} + +static void +app_list_stop_rdp_notify(struct desktop_shell *shell) +{ + send_app_entry(shell, NULL, NULL, false, false, true, false, false, false); +} + +static DWORD WINAPI +app_list_monitor_thread(LPVOID arg) +{ + struct desktop_shell *shell = (struct desktop_shell *)arg; + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + struct app_entry *entry; + int fd[ARRAY_LENGTH(app_list_folder)] = {}; + int wd[ARRAY_LENGTH(app_list_folder)] = {}; + char *home; + char *folder; + int len, cur; + UINT error = 0; + DWORD status = 0; + DWORD num_events = 0; + HANDLE events[NUM_CONTROL_EVENT + ARRAY_LENGTH(app_list_folder)] = {}; + struct inotify_event *event; + char buf[1024 * (sizeof *event + 16)]; + char path[512]; + + if (is_system_distro()) { + char *pidfd_path; + /* running inside system-distro */ + shell_rdp_debug(shell, "app_list_monitor_thread: running in system-distro with user-distro: %s\n", shell->distroName); + + if (unshare(CLONE_FS) < 0) + weston_log("app_list_monitor_thread: unshare(CLONE_FS) failed %s\n", strerror(errno)); + + /* obtain pidfd for current process */ + pidfd_path = "/proc/self/ns/mnt"; + shell_rdp_debug(shell, "app_list_monitor_thread: open(%s)\n", pidfd_path); + context->weston_pidfd = open(pidfd_path, O_RDONLY | O_CLOEXEC); + if (context->weston_pidfd < 0) { + weston_log("app_list_monitor_thread: open(%s) failed %s\n", pidfd_path, strerror(errno)); + goto Exit; + } + + /* obtain pidfd for user-distro */ + pidfd_path = "/proc/2/ns/mnt"; + shell_rdp_debug(shell, "app_list_monitor_thread: open(%s)\n", pidfd_path); + context->app_list_pidfd = open(pidfd_path, O_RDONLY | O_CLOEXEC); + if (context->app_list_pidfd < 0) { + weston_log("app_list_monitor_thread: open(%s) failed %s\n", pidfd_path, strerror(errno)); + goto Exit; + } + } else { + shell_rdp_debug(shell, "app_list_monitor_thread: running in user-distro: %s\n", shell->distroName); + } + + /* fill up with control events first */ + events[num_events++] = context->stopEvent; + events[num_events++] = context->startRdpNotifyEvent; + events[num_events++] = context->stopRdpNotifyEvent; + events[num_events++] = context->loadIconEvent; + assert(num_events == NUM_CONTROL_EVENT); + + if (shell->rdprail_api->notify_app_list) { + for (int i = 0; i < (int)ARRAY_LENGTH(app_list_folder); i++) { + fd[i] = inotify_init(); + if (fd[i] < 0) { + weston_log("app_list_monitor_thread: inotify_init[%d] failed %s\n", i, strerror(errno)); + goto Exit; + } + + attach_app_list_namespace(shell); + folder = app_list_folder[i]; + if (*folder == '~') { + home = getenv("HOME"); + if (!home) { + detach_app_list_namespace(shell); + continue; + } + copy_string(path, sizeof path, home); + append_string(path, sizeof path, folder+1); // skip '~'. + folder = path; + } + + if (!is_file_exist(folder)) { + shell_rdp_debug(shell, "app_list_monitor_thread: %s doesn't exist, skipping.\n", folder); + detach_app_list_namespace(shell); + continue; + } + + shell_rdp_debug(shell, "app_list_monitor_thread: inotify_add_watch(%s)\n", folder); + wd[i] = inotify_add_watch(fd[i], folder, IN_CREATE|IN_DELETE|IN_MODIFY|IN_MOVED_TO|IN_MOVED_FROM); + if (wd[i] < 0) { + weston_log("app_list_monitor_thread: inotify_add_watch failed: %s\n", strerror(errno)); + detach_app_list_namespace(shell); + goto Exit; + } + detach_app_list_namespace(shell); + + events[num_events] = GetFileHandleForFileDescriptor(fd[i]); + if (!events[num_events]) { + weston_log("app_list_monitor_thread: GetFileHandleForFileDescriptor failed\n"); + goto Exit; + } + num_events++; + } + assert(false == context->isAppListNamespaceAttached); + + /* first scan folders to update all existing .desktop files */ + app_list_update_all(shell); + } + + /* now loop as changes are made or stop event is signaled */ + while (TRUE) { + status = WaitForMultipleObjects(num_events, events, FALSE, INFINITE); + if (status == WAIT_FAILED) { + error = GetLastError(); + break; + } + + /* winpr doesn't support auto-reset event */ + ResetEvent(events[status - WAIT_OBJECT_0]); + + /* Stop Event */ + if (status == WAIT_OBJECT_0) { + shell_rdp_debug(shell, "app_list_monitor_thread: stopEvent is signalled\n"); + break; + } + + /* Start RDP notify event */ + if (status == WAIT_OBJECT_0 + 1) { + shell_rdp_debug(shell, "app_list_monitor_thread: startRdpNotifyEvent is signalled. %d - %s\n", + context->isRdpNotifyStarted, context->lang_info.requestedClientLanguageId); + if (!context->isRdpNotifyStarted) { + app_list_start_rdp_notify(shell); + context->isRdpNotifyStarted = true; + } + continue; + } + + /* Stop RDP notify event */ + if (status == WAIT_OBJECT_0 + 2) { + shell_rdp_debug(shell, "app_list_monitor_thread: stopRdpNotifyEvent is signalled. %d\n", context->isRdpNotifyStarted); + if (context->isRdpNotifyStarted) { + app_list_stop_rdp_notify(shell); + context->isRdpNotifyStarted = false; + } + SetEvent(context->replyEvent); + continue; + } + + /* Load Icon event */ + if (status == WAIT_OBJECT_0 + 3) { + shell_rdp_debug(shell, "app_list_monitor_thread: loadIconEvent is signalled. %s\n", context->load_icon.key); + if (context->load_icon.key) { + entry = (struct app_entry *)HashTable_GetItemValue(context->table, (void*)context->load_icon.key); + if (entry && entry->icon) { + attach_app_list_namespace(shell); + if (!entry->icon_file) + entry->icon_file = find_icon_file(entry->icon); + if (entry->icon_file) + context->load_icon.image = load_image(entry->icon_file); + detach_app_list_namespace(shell); + } + shell_rdp_debug(shell, "app_list_monitor_thread: entry %p, image %p\n", entry, context->load_icon.image); + } + SetEvent(context->replyEvent); + continue; + } + + if (shell->rdprail_api->notify_app_list) { + /* Somethings are changed in watch folders */ + len = read(fd[status - WAIT_OBJECT_0 - NUM_CONTROL_EVENT], buf, sizeof buf); + cur = 0; + while (cur < len) { + event = (struct inotify_event *)&buf[cur]; + if (event->len && + (event->mask & IN_ISDIR) == 0 && + is_desktop_file(event->name)) { + if (event->mask & (IN_CREATE|IN_MODIFY|IN_MOVED_TO)) { + shell_rdp_debug(shell, "app_list_monitor_thread: file created/updated (%s)\n", event->name); + app_list_desktop_file_changed(shell, app_list_folder[status - WAIT_OBJECT_0 - NUM_CONTROL_EVENT], event->name); + } + else if (event->mask & (IN_DELETE|IN_MOVED_FROM)) { + shell_rdp_debug(shell, "app_list_monitor_thread: file removed (%s)\n", event->name); + app_list_desktop_file_removed(shell, event->name); + } + } + cur += (sizeof *event + event->len); + } + } + } + +Exit: + assert(false == context->isAppListNamespaceAttached); + + for (int i = 0; i < (int)ARRAY_LENGTH(app_list_folder); i++) { + if (fd[i] > 0) { + if (wd[i] > 0) + inotify_rm_watch(fd[i], wd[i]); + close(fd[i]); + } + if (events[i + NUM_CONTROL_EVENT]) + CloseHandle(events[i + NUM_CONTROL_EVENT]); + } + + if (context->weston_pidfd > 0) { + close(context->weston_pidfd); + context->weston_pidfd = 0; + } + if (context->app_list_pidfd > 0) { + close(context->app_list_pidfd); + context->app_list_pidfd = 0; + } + + ExitThread(error); + return error; +} + +static void +start_app_list_monitor(struct desktop_shell *shell) +{ + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + + context->isRdpNotifyStarted = false; + + InitializeCriticalSectionAndSpinCount(&(context->load_icon.lock), 4000); + + context->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!context->stopEvent) + goto Error_Exit; + + /* bManualReset = TRUE, ideally here needs FALSE, but winpr doesn't support it */ + context->startRdpNotifyEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!context->startRdpNotifyEvent) + goto Error_Exit; + + /* bManualReset = TRUE, ideally here needs FALSE, but winpr doesn't support it */ + context->stopRdpNotifyEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!context->stopRdpNotifyEvent) + goto Error_Exit; + + /* bManualReset = TRUE, ideally here needs FALSE, but winpr doesn't support it */ + context->loadIconEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!context->loadIconEvent) + goto Error_Exit; + + /* bManualReset = TRUE, ideally here needs FALSE, but winpr doesn't support it */ + context->replyEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!context->replyEvent) + goto Error_Exit; + + context->thread = + CreateThread(NULL, 0, app_list_monitor_thread, (void*)shell, 0, NULL); + if (!context->thread) + goto Error_Exit; + + return; + +Error_Exit: + if (context->replyEvent) { + CloseHandle(context->replyEvent); + context->replyEvent = NULL; + } + + if (context->loadIconEvent) { + CloseHandle(context->loadIconEvent); + context->loadIconEvent = NULL; + } + + if (context->stopRdpNotifyEvent) { + CloseHandle(context->stopRdpNotifyEvent); + context->stopRdpNotifyEvent = NULL; + } + + if (context->startRdpNotifyEvent) { + CloseHandle(context->startRdpNotifyEvent); + context->startRdpNotifyEvent = NULL; + } + + if (context->stopEvent) { + CloseHandle(context->stopEvent); + context->stopEvent = NULL; + } + + DeleteCriticalSection(&(context->load_icon.lock)); + + return; +} + +static void +stop_app_list_monitor(struct desktop_shell *shell) +{ + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + + if (context->stopRdpNotifyEvent) { + SetEvent(context->stopRdpNotifyEvent); + /* wait reply event to make sure worker thread process stopRdpNotify before stopEvent */ + WaitForSingleObject(context->replyEvent, INFINITE); + /* no need to reset back since event is going to be destroyed */ + } + + if (context->stopEvent) + SetEvent(context->stopEvent); + + if (context->thread) + WaitForSingleObject(context->thread, INFINITE); + + if (context->thread) { + CloseHandle(context->thread); + context->thread = NULL; + } + + if (context->replyEvent) { + CloseHandle(context->replyEvent); + context->replyEvent = NULL; + } + + if (context->loadIconEvent) { + CloseHandle(context->loadIconEvent); + context->loadIconEvent = NULL; + } + + if (context->stopRdpNotifyEvent) { + CloseHandle(context->stopRdpNotifyEvent); + context->stopRdpNotifyEvent = NULL; + } + + if (context->stopEvent) { + CloseHandle(context->stopEvent); + context->stopEvent = NULL; + } + + DeleteCriticalSection(&(context->load_icon.lock)); + + context->isRdpNotifyStarted = false; + + assert(context->weston_pidfd <= 0); + assert(context->app_list_pidfd <= 0); +} +#endif // HAVE_WINPR2 + +pixman_image_t* app_list_load_icon_file(struct desktop_shell *shell, const char *key) +{ +#ifdef HAVE_WINPR2 + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + pixman_image_t* image = NULL; + + if (context) { + /* hand off to worker thread where can access user-distro files */ + /* TODO: should not need critical secion as long as this is called only + from display loop thread */ + EnterCriticalSection(&context->load_icon.lock); + assert(context->load_icon.image == NULL); + assert(context->load_icon.key == NULL); + context->load_icon.key = key; + + /* signal worker thread to load icon at worker thread */ + SetEvent(context->loadIconEvent); + WaitForSingleObject(context->replyEvent, INFINITE); + /* here must reset since winpr doesn't support auto reset event */ + ResetEvent(context->replyEvent); + + image = context->load_icon.image; + context->load_icon.image = NULL; + context->load_icon.key = NULL; + LeaveCriticalSection(&context->load_icon.lock); + + return image; + } +#endif + return NULL; +} + +bool app_list_start_backend_update(struct desktop_shell *shell, char *clientLanguageId) +{ +#ifdef HAVE_WINPR2 + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + if (context) { + if (!clientLanguageId || *clientLanguageId == '\0') + clientLanguageId = "en_US"; + copy_string(context->lang_info.requestedClientLanguageId, + sizeof(context->lang_info.requestedClientLanguageId), + clientLanguageId); + SetEvent(context->startRdpNotifyEvent); + return true; + } +#endif + return false; +} + +void app_list_stop_backend_update(struct desktop_shell *shell) +{ +#ifdef HAVE_WINPR2 + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + if (context) { + SetEvent(context->stopRdpNotifyEvent); + WaitForSingleObject(context->replyEvent, INFINITE); + /* here reset since winpr doesn't support auto reset event */ + ResetEvent(context->replyEvent); + } +#endif +} + +void app_list_init(struct desktop_shell *shell) +{ +#ifdef HAVE_WINPR2 + struct app_list_context *context; + wHashTable* table; + char *iconpath; + + shell->app_list_context = NULL; + + context = (struct app_list_context *)zalloc(sizeof *context); + if (!context) + return; + + table = HashTable_New(FALSE /* synchronized */); + if (!table) { + free(context); + return; + } + table->hash = HashTable_StringHash; + table->keyCompare = HashTable_StringCompare; + //table->valueCompare = HashTable_StringCompare; + table->keyClone = HashTable_StringClone; + table->valueClone = NULL; // make sure value won't be cloned. + table->keyFree = HashTable_StringFree; + table->valueFree = free_app_entry; + + context->table = table; + shell->app_list_context = (void *)context; + + /* load default icon */ + iconpath = getenv("WSL2_DEFAULT_APP_ICON"); + if (iconpath && (strcmp(iconpath, "disabled") != 0)) + context->default_icon = load_image(iconpath); + + iconpath = getenv("WSL2_DEFAULT_APP_OVERLAY_ICON"); + if (iconpath && (strcmp(iconpath, "disabled") != 0)) + context->default_overlay_icon = load_image(iconpath); + + /* set default language as "en_US". this will be updated once client connected */ + strcpy(context->lang_info.requestedClientLanguageId, "en_US"); + strcpy(context->lang_info.currentClientLanguageId, + context->lang_info.requestedClientLanguageId); + + start_app_list_monitor(shell); +#else + shell->app_list_context = NULL; +#endif // HAVE_WINPR2 +} + +void app_list_destroy(struct desktop_shell *shell) +{ +#ifdef HAVE_WINPR2 + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + wHashTable* table; + int count; + + if (context) { + table = context->table; + + stop_app_list_monitor(shell); + + if (context->default_overlay_icon) + pixman_image_unref(context->default_overlay_icon); + + if (context->default_icon) + pixman_image_unref(context->default_icon); + + HashTable_Clear(table); + count = HashTable_Count(table); + assert(count == 0); + HashTable_Free(table); + + free(context); + shell->app_list_context = NULL; + } +#else + asseer(!shell->app_list_context); +#endif // HAVE_WINPR2 +} diff --git a/rdprail-shell/input-panel.c b/rdprail-shell/input-panel.c new file mode 100644 index 000000000..ad837c23f --- /dev/null +++ b/rdprail-shell/input-panel.c @@ -0,0 +1,267 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * + * 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 "shell.h" +#include "input-method-unstable-v1-server-protocol.h" +#include "shared/helpers.h" + +struct input_panel_surface { + struct wl_resource *resource; + struct wl_signal destroy_signal; + + struct desktop_shell *shell; + + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; +}; + +static void +show_input_panels(struct wl_listener *listener, void *data) +{ + struct weston_surface *surface = (struct weston_surface*)data; + + /* Output who requested to show input panels */ + if (surface && surface->resource) { + pid_t pid; + uid_t uid; + gid_t gid; + struct wl_client *client = wl_resource_get_client(surface->resource); + wl_client_get_credentials(client, &pid, &uid, &gid); + weston_log("%s pid:%d, uid:%d, gid:%d is requesting to show input panel\n", + __func__, pid, uid, gid); + if (pid > 0 && !is_system_distro()) { + char path[32] = {}; + char image_name[256] = {}; + sprintf(path, "/proc/%d/exe", pid); + if (readlink(path, image_name, sizeof image_name) > 0) + weston_log("%s pid:%d, image_name:%s\n",__func__, pid, image_name); + } + } +} + +static int +input_panel_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + return snprintf(buf, len, "rdprail-shell input panel"); +} + +static void +input_panel_committed(struct weston_surface *surface, int32_t sx, int32_t sy) +{ + weston_log("%s is not expected to be called\n", __func__); +} + +static void +destroy_input_panel_surface(struct input_panel_surface *input_panel_surface) +{ + wl_signal_emit(&input_panel_surface->destroy_signal, input_panel_surface); + + wl_list_remove(&input_panel_surface->surface_destroy_listener.link); + + input_panel_surface->surface->committed = NULL; + input_panel_surface->surface->committed_private = NULL; + weston_surface_set_label_func(input_panel_surface->surface, NULL); + + free(input_panel_surface); +} + +static void +input_panel_handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct input_panel_surface *ipsurface = container_of(listener, + struct input_panel_surface, + surface_destroy_listener); + + if (ipsurface->resource) { + wl_resource_destroy(ipsurface->resource); + } else { + destroy_input_panel_surface(ipsurface); + } +} + +static struct input_panel_surface * +create_input_panel_surface(struct desktop_shell *shell, + struct weston_surface *surface) +{ + struct input_panel_surface *input_panel_surface; + + input_panel_surface = calloc(1, sizeof *input_panel_surface); + if (!input_panel_surface) + return NULL; + + surface->committed = input_panel_committed; + surface->committed_private = input_panel_surface; + weston_surface_set_label_func(surface, input_panel_get_label); + + input_panel_surface->shell = shell; + input_panel_surface->surface = surface; + + wl_signal_init(&input_panel_surface->destroy_signal); + input_panel_surface->surface_destroy_listener.notify = input_panel_handle_surface_destroy; + wl_signal_add(&surface->destroy_signal, + &input_panel_surface->surface_destroy_listener); + + return input_panel_surface; +} + +static void +input_panel_surface_set_toplevel(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *output_resource, + uint32_t position) +{ +} + +static void +input_panel_surface_set_overlay_panel(struct wl_client *client, + struct wl_resource *resource) +{ +} + +static const struct zwp_input_panel_surface_v1_interface input_panel_surface_implementation = { + input_panel_surface_set_toplevel, + input_panel_surface_set_overlay_panel +}; + +static struct input_panel_surface * +get_input_panel_surface(struct weston_surface *surface) +{ + if (surface->committed == input_panel_committed) { + return surface->committed_private; + } else { + return NULL; + } +} + +static void +destroy_input_panel_surface_resource(struct wl_resource *resource) +{ + struct input_panel_surface *ipsurf = + wl_resource_get_user_data(resource); + + destroy_input_panel_surface(ipsurf); +} + +static void +input_panel_get_input_panel_surface(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource) +{ + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct desktop_shell *shell = wl_resource_get_user_data(resource); + struct input_panel_surface *ipsurf; + + if (get_input_panel_surface(surface)) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "wl_input_panel::get_input_panel_surface already requested"); + return; + } + + ipsurf = create_input_panel_surface(shell, surface); + if (!ipsurf) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "surface->committed already set"); + return; + } + + ipsurf->resource = + wl_resource_create(client, + &zwp_input_panel_surface_v1_interface, + 1, + id); + wl_resource_set_implementation(ipsurf->resource, + &input_panel_surface_implementation, + ipsurf, + destroy_input_panel_surface_resource); +} + +static const struct zwp_input_panel_v1_interface input_panel_implementation = { + input_panel_get_input_panel_surface +}; + +static void +unbind_input_panel(struct wl_resource *resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + + shell->input_panel.binding = NULL; +} + +static void +bind_input_panel(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct desktop_shell *shell = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, + &zwp_input_panel_v1_interface, 1, id); + + if (shell->input_panel.binding == NULL) { + wl_resource_set_implementation(resource, + &input_panel_implementation, + shell, unbind_input_panel); + shell->input_panel.binding = resource; + return; + } + + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "interface object already bound"); +} + +void +input_panel_destroy(struct desktop_shell *shell) +{ + wl_list_remove(&shell->show_input_panel_listener.link); +} + +int +input_panel_setup(struct desktop_shell *shell) +{ + struct weston_compositor *ec = shell->compositor; + + shell->show_input_panel_listener.notify = show_input_panels; + wl_signal_add(&ec->show_input_panel_signal, + &shell->show_input_panel_listener); + + if (wl_global_create(shell->compositor->wl_display, + &zwp_input_panel_v1_interface, 1, + shell, bind_input_panel) == NULL) + return -1; + + return 0; +} + diff --git a/rdprail-shell/meson.build b/rdprail-shell/meson.build new file mode 100644 index 000000000..34a6486b0 --- /dev/null +++ b/rdprail-shell/meson.build @@ -0,0 +1,35 @@ +if get_option('shell-rdprail') + dep_winpr = dependency('winpr2', version: '>= 2.0.0', required: false) + if dep_winpr.found() + config_h.set('HAVE_WINPR2', '1') + endif + srcs_shell_rdprail = [ + 'shell.c', + 'input-panel.c', + 'app-list.c', + weston_desktop_shell_server_protocol_h, + weston_desktop_shell_protocol_c, + input_method_unstable_v1_server_protocol_h, + input_method_unstable_v1_protocol_c, + ] + deps_shell_rdprail = [ + dep_libm, + dep_libexec_weston, + dep_libshared, + dep_lib_desktop, + dep_libweston_public, + dep_winpr, + ] + plugin_shell_rdprail = shared_library( + 'rdprail-shell', + srcs_shell_rdprail, + link_with: lib_cairo_shared, + include_directories: common_inc, + dependencies: deps_shell_rdprail, + name_prefix: '', + install: true, + install_dir: dir_module_weston, + install_rpath: '$ORIGIN' + ) + env_modmap += 'rdprail-shell.so=@0@;'.format(plugin_shell_rdprail.full_path()) +endif diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c new file mode 100644 index 000000000..1626f5e78 --- /dev/null +++ b/rdprail-shell/shell.c @@ -0,0 +1,4201 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * + * 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 + +#include "shell.h" +#include "compositor/weston.h" +#include "weston-desktop-shell-server-protocol.h" +#include +#include "shared/helpers.h" +#include "shared/timespec-util.h" +#include "shared/image-loader.h" +#include +#include +#include +#include + +struct focus_state { + struct desktop_shell *shell; + struct weston_seat *seat; + struct workspace *ws; + struct weston_surface *keyboard_focus; + struct wl_list link; + struct wl_listener seat_destroy_listener; + struct wl_listener surface_destroy_listener; +}; + +/* + * Surface stacking and ordering. + * + * This is handled using several linked lists of surfaces, organised into + * ‘layers’. The layers are ordered, and each of the surfaces in one layer are + * above all of the surfaces in the layer below. The set of layers is static and + * in the following order (top-most first): + * • Cursor layer + * • Fullscreen layer + * • Workspace layers + * + * The list of layers may be manipulated to remove whole layers of surfaces from + * display. For example, when locking the screen, all layers except the lock + * layer are removed. + * + * A surface’s layer is modified on configuring the surface, in + * set_surface_type() (which is only called when the surface’s type change is + * _committed_). If a surface’s type changes (e.g. when making a window + * fullscreen) its layer changes too. + * + * In order to allow popup and transient surfaces to be correctly stacked above + * their parent surfaces, each surface tracks both its parent surface, and a + * linked list of its children. When a surface’s layer is updated, so are the + * layers of its children. Note that child surfaces are *not* the same as + * subsurfaces — child/parent surfaces are purely for maintaining stacking + * order. + * + * The children_link list of siblings of a surface (i.e. those surfaces which + * have the same parent) only contains weston_surfaces which have a + * shell_surface. Stacking is not implemented for non-shell_surface + * weston_surfaces. This means that the following implication does *not* hold: + * (shsurf->parent != NULL) ⇒ !wl_list_is_empty(shsurf->children_link) + */ + +struct shell_surface { + struct wl_signal destroy_signal; + + struct weston_desktop_surface *desktop_surface; + struct weston_view *view; + int32_t last_width, last_height; + + struct desktop_shell *shell; + + struct shell_surface *parent; + struct wl_list children_list; + struct wl_list children_link; + + int32_t saved_x, saved_y; + bool saved_position_valid; + bool saved_rotation_valid; + int unresponsive, grabbed; + uint32_t resize_edges; + + struct { + struct weston_transform transform; + struct weston_matrix rotation; + } rotation; + + struct { + struct weston_transform transform; /* matrix from x, y */ + struct weston_view *black_view; + } fullscreen; + + struct weston_output *fullscreen_output; + struct weston_output *output; + struct wl_listener output_destroy_listener; + + struct surface_state { + bool fullscreen; + bool maximized; + bool lowered; + } state; + + struct { + bool is_set; + int32_t x; + int32_t y; + } xwayland; + + int focus_count; + + bool destroying; + + struct { + bool is_snapped; + bool is_maximized_requested; + int x; + int y; + int width; + int height; + int saved_width; + int saved_height; + int last_grab_x; + int last_grab_y; + } snapped; + + struct { + bool is_default_icon_used; + bool is_icon_set; + } icon; + + struct wl_listener metadata_listener; +}; + +struct shell_grab { + struct weston_pointer_grab grab; + struct shell_surface *shsurf; + struct wl_listener shsurf_destroy_listener; +}; + +struct shell_touch_grab { + struct weston_touch_grab grab; + struct shell_surface *shsurf; + struct wl_listener shsurf_destroy_listener; + struct weston_touch *touch; +}; + +struct weston_move_grab { + struct shell_grab base; + wl_fixed_t dx, dy; + bool client_initiated; +}; + +struct weston_touch_move_grab { + struct shell_touch_grab base; + int active; + wl_fixed_t dx, dy; +}; + +struct rotate_grab { + struct shell_grab base; + struct weston_matrix rotation; + struct { + float x; + float y; + } center; +}; + +struct shell_seat { + struct weston_seat *seat; + struct wl_listener seat_destroy_listener; + struct weston_surface *focused_surface; + + struct wl_listener caps_changed_listener; + struct wl_listener pointer_focus_listener; + struct wl_listener keyboard_focus_listener; +}; + +static const struct weston_pointer_grab_interface move_grab_interface; + +static void set_unsnap(struct shell_surface *shsurf, int grabX, int grabY); + +static struct desktop_shell * +shell_surface_get_shell(struct shell_surface *shsurf); + +static void +set_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer); + +static void +surface_rotate(struct shell_surface *surface, struct weston_pointer *pointer); + +static struct shell_seat * +get_shell_seat(struct weston_seat *seat); + +static struct shell_output * +find_shell_output_from_weston_output(struct desktop_shell *shell, + struct weston_output *output); + +static void +shell_surface_update_child_surface_layers(struct shell_surface *shsurf); + +#define ICON_STRIDE( W, BPP ) ((((W) * (BPP) + 31) / 32) * 4) + +void +shell_blend_overlay_icon(struct desktop_shell *shell, pixman_image_t *app_image, pixman_image_t *overlay_image) +{ + int app_width, overlay_width; + int app_height, overlay_height; + double overlay_scale_width, overlay_scale_height; + pixman_transform_t transform; + + /* can't overlay to itself */ + assert(app_image); + assert(overlay_image); + assert(app_image != overlay_image); + + app_width = pixman_image_get_width(app_image); + app_height = pixman_image_get_height(app_image); + if (!app_width || !app_height) + return; + + overlay_width = pixman_image_get_width(overlay_image); + overlay_height = pixman_image_get_height(overlay_image); + if (!overlay_width || !overlay_height) + return; + + overlay_scale_width = 1.0f / (((double)app_width / overlay_width) / 1.75f); + overlay_scale_height = 1.0f / (((double)app_height / overlay_height) / 1.75f); + + shell_rdp_debug(shell, "%s: app %dx%d; overlay %dx%d; scale %4.2fx%4.2f\n", + __func__, app_width, app_height, overlay_width, overlay_height, + overlay_scale_width, overlay_scale_height); + + pixman_transform_init_scale(&transform, + pixman_double_to_fixed(overlay_scale_width), + pixman_double_to_fixed(overlay_scale_height)); + pixman_image_set_transform(overlay_image, &transform); + pixman_image_set_filter(overlay_image, PIXMAN_FILTER_BILINEAR, NULL, 0); + + pixman_image_composite32(PIXMAN_OP_OVER, + overlay_image, /* src */ + NULL, /* mask */ + app_image, /* dest */ + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + app_width/2 , app_height/2, /* dest_x, dest_y */ + app_width, /* width */ + app_height /* height */); + + pixman_image_set_filter(overlay_image, PIXMAN_FILTER_NEAREST, NULL, 0); + pixman_image_set_transform(overlay_image, NULL); +} + +static void +shell_surface_set_window_icon(struct weston_desktop_surface *desktop_surface, + int32_t width, int32_t height, int32_t bpp, + void *bits, void *user_data) +{ + struct shell_surface *shsurf; + struct weston_surface *surface; + const struct weston_xwayland_surface_api *api; + pixman_image_t *image = NULL; + pixman_format_code_t format; + const char *id; + + shsurf = weston_desktop_surface_get_user_data(desktop_surface); + if (!shsurf) + return; + + surface = weston_desktop_surface_get_surface(desktop_surface); + if (!surface) + return; + + if (shsurf->shell->rdprail_api->set_window_icon) { + if (width && height && bpp && bits) { + /* When caller supplied custom image, it's always be used */ + switch (bpp) { + case 32: + format = PIXMAN_a8r8g8b8; + break; + default: + weston_log("shell_surface_set_window_icon(): unsupported bpp: %d\n", bpp); + return; + } + image = pixman_image_create_bits_no_clear(format, + width, height, bits, ICON_STRIDE(width, bpp)); + if (image) + shsurf->icon.is_default_icon_used = false; + } + if (!image) { + /* Try .desktop for icon for non-overlay icon */ + id = weston_desktop_surface_get_app_id(desktop_surface); + if (id) + image = app_list_load_icon_file(shsurf->shell, id); + if (!image) { + /* If this is X app, query from X */ + api = shsurf->shell->xwayland_surface_api; + if (!api) { + api = weston_xwayland_surface_get_api(shsurf->shell->compositor); + shsurf->shell->xwayland_surface_api = api; + } + if (api && api->is_xwayland_surface(surface)) { + /* trigger_set_window_icon calls back this function + with custom icon image obtained from X app. */ + if (api->trigger_set_window_icon(surface)) + return; + } + } + if (image) + shsurf->icon.is_default_icon_used = false; + } + if (!image) { + /* When caller doens't supply custom image, look for default images */ + image = shsurf->shell->image_default_app_icon; + if (image) { + pixman_image_ref(image); + shsurf->icon.is_default_icon_used = true; + } + } + if (!image) + return; + if (shsurf->shell->is_blend_overlay_icon_taskbar && + shsurf->shell->image_default_app_overlay_icon) + shell_blend_overlay_icon(shsurf->shell, + image, + shsurf->shell->image_default_app_overlay_icon); + shsurf->shell->rdprail_api->set_window_icon(surface, image); + pixman_image_unref(image); + } +} + +static int +shell_surface_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + const char *t, *c; + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + struct shell_surface *shsurf = + shsurf = get_shell_surface(surface); + + t = weston_desktop_surface_get_title(desktop_surface); + c = weston_desktop_surface_get_app_id(desktop_surface); + + return snprintf(buf, len, "%s window%s%s%s%s%s", + shsurf && shsurf->parent ? "child" : "top-level", + t ? " '" : "", t ?: "", t ? "'" : "", + c ? " of " : "", c ?: ""); +} + +static void +destroy_shell_grab_shsurf(struct wl_listener *listener, void *data) +{ + struct shell_grab *grab; + + grab = container_of(listener, struct shell_grab, + shsurf_destroy_listener); + + grab->shsurf = NULL; +} + +struct weston_view * +get_default_view(struct weston_surface *surface) +{ + struct shell_surface *shsurf; + struct weston_view *view; + + if (!surface || wl_list_empty(&surface->views)) + return NULL; + + shsurf = get_shell_surface(surface); + if (shsurf) + return shsurf->view; + + wl_list_for_each(view, &surface->views, surface_link) + if (weston_view_is_mapped(view)) + return view; + + return container_of(surface->views.next, struct weston_view, surface_link); +} + +static void +shell_grab_start(struct shell_grab *grab, + const struct weston_pointer_grab_interface *interface, + struct shell_surface *shsurf, + struct weston_pointer *pointer, + enum weston_desktop_shell_cursor cursor) +{ + struct desktop_shell *shell = shsurf->shell; + + weston_seat_break_desktop_grabs(pointer->seat); + + grab->grab.interface = interface; + grab->shsurf = shsurf; + grab->shsurf_destroy_listener.notify = destroy_shell_grab_shsurf; + wl_signal_add(&shsurf->destroy_signal, + &grab->shsurf_destroy_listener); + + shsurf->grabbed = 1; + weston_pointer_start_grab(pointer, &grab->grab); + + if (shell->is_localmove_supported && + (interface == &move_grab_interface) && + shell->rdprail_api->start_window_move) { + + struct weston_size min_size; + struct weston_size max_size; + + if (grab->shsurf->snapped.is_snapped) { + set_unsnap(grab->shsurf, wl_fixed_to_int(pointer->grab_x), wl_fixed_to_int(pointer->grab_y)); + } + + min_size = weston_desktop_surface_get_min_size(shsurf->desktop_surface); + max_size = weston_desktop_surface_get_max_size(shsurf->desktop_surface); + + shell->is_localmove_pending = true; + + shell->rdprail_api->start_window_move( + weston_desktop_surface_get_surface(shsurf->desktop_surface), + wl_fixed_to_int(pointer->grab_x), + wl_fixed_to_int(pointer->grab_y), + min_size, + max_size); + } else if (grab->shsurf->snapped.is_snapped) { + /** Cancel snap state on anything but a move grab + */ + grab->shsurf->snapped.is_snapped = false; + } +} + +static void +shell_grab_end(struct shell_grab *grab) +{ + if (grab->shsurf) { + struct desktop_shell* shell = grab->shsurf->shell; + struct weston_surface* surface = + weston_desktop_surface_get_surface(grab->shsurf->desktop_surface); + + wl_list_remove(&grab->shsurf_destroy_listener.link); + grab->shsurf->grabbed = 0; + + if (shell->is_localmove_supported && + (grab->grab.interface == &move_grab_interface) && + shell->rdprail_api->end_window_move) { + + grab->shsurf->snapped.last_grab_x = wl_fixed_to_int(grab->grab.pointer->x); + grab->shsurf->snapped.last_grab_y = wl_fixed_to_int(grab->grab.pointer->y); + + shell->rdprail_api->end_window_move(surface); + } + + if (grab->shsurf->resize_edges) { + grab->shsurf->resize_edges = 0; + } else { + /* This is nessesary to make "double check" on title bar to max/restore + with X applications. When title bar is clicked first time, Xwayland + enters "grab_move" (see where FRAME_STATUS_MOVE), and when left button + is released, grab_move ends. On desktop-shell, when entered grab_move, + the focus is moved to shell "grab_surface" (see desktop-shell/shell.c: + shell_grab_start() where setting focus to shell->grab_surface. + But RDP shell doesn't have expclicit grab surface, thus + focus remains at the window who owned the title bar clicked. + This itself is OK, but when X and Xwayland depends on seeing + focus change when mouse button is released when grab_move ends, + so that they can recognized that mouse button is released *without + receiving explicit mouse message* using weston_pointer_send_button. + thus, here patch pointer's sx/sy to (0,0), and this trigger refocus + at weston_pointer_set_focus even focus isn't changed, and sx/sy will + be updated at weston_pointer_set_focus. */ + grab->grab.pointer->sx = 0; + grab->grab.pointer->sy = 0; + } + } + + weston_pointer_end_grab(grab->grab.pointer); +} + +static void +shell_touch_grab_start(struct shell_touch_grab *grab, + const struct weston_touch_grab_interface *interface, + struct shell_surface *shsurf, + struct weston_touch *touch) +{ + weston_seat_break_desktop_grabs(touch->seat); + + grab->grab.interface = interface; + grab->shsurf = shsurf; + grab->shsurf_destroy_listener.notify = destroy_shell_grab_shsurf; + wl_signal_add(&shsurf->destroy_signal, + &grab->shsurf_destroy_listener); + + grab->touch = touch; + shsurf->grabbed = 1; + + weston_touch_start_grab(touch, &grab->grab); +} + +static void +shell_touch_grab_end(struct shell_touch_grab *grab) +{ + if (grab->shsurf) { + wl_list_remove(&grab->shsurf_destroy_listener.link); + grab->shsurf->grabbed = 0; + } + + weston_touch_end_grab(grab->touch); +} + +static void +get_output_work_area(struct desktop_shell *shell, + struct weston_output *output, + pixman_rectangle32_t *area) +{ + if (!output) { + area->x = 0; + area->y = 0; + area->width = 0; + area->height = 0; + + return; + } + + struct shell_output *shell_output = + find_shell_output_from_weston_output(shell, output); + + if (shell_output) { + *area = shell_output->desktop_workarea; + } else { + area->x = output->x; + area->y = output->y; + area->width = output->width; + area->height = output->height; + } + + return; +} + +static void +center_on_output(struct weston_view *view, + struct weston_output *output); + +static enum weston_keyboard_modifier +get_modifier(char *modifier) +{ + if (!modifier) + return MODIFIER_SUPER; + + if (!strcmp("ctrl", modifier)) + return MODIFIER_CTRL; + else if (!strcmp("alt", modifier)) + return MODIFIER_ALT; + else if (!strcmp("super", modifier)) + return MODIFIER_SUPER; + else if (!strcmp("none", modifier)) + return 0; + else + return MODIFIER_SUPER; +} + +static void +shell_configuration(struct desktop_shell *shell) +{ + struct weston_config_section *section; + char *s; + bool allow_zap; + bool is_localmove_supported; + + section = weston_config_get_section(wet_get_config(shell->compositor), + "shell", NULL, NULL); + + weston_config_section_get_bool(section, + "allow-zap", &allow_zap, true); + shell->allow_zap = allow_zap; + + weston_config_section_get_string(section, + "binding-modifier", &s, "super"); + shell->binding_modifier = get_modifier(s); + free(s); + + weston_config_section_get_bool(section, + "local-move", &is_localmove_supported, false); + shell->is_localmove_supported = is_localmove_supported; + + shell->workspaces.num = 1; +} + +struct weston_output * +get_default_output(struct weston_compositor *compositor) +{ + if (wl_list_empty(&compositor->output_list)) + return NULL; + + return container_of(compositor->output_list.next, + struct weston_output, link); +} + +static struct weston_output * +get_output_containing(struct weston_compositor *compositor, int x, int y) +{ + if (wl_list_empty(&compositor->output_list)) + return NULL; + + struct weston_output *output; + wl_list_for_each(output, &compositor->output_list, link) { + if (x >= output->region.extents.x1 && x < output->region.extents.x2 && + y >= output->region.extents.y1 && y < output->region.extents.y2) { + return output; + } + } + weston_log("Didn't find output containing (%d, %d), return default\n", x, y); + return get_default_output(compositor); +} + + +/* no-op func for checking focus surface */ +static void +focus_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) +{ +} + +static bool +is_focus_surface (struct weston_surface *es) +{ + return (es->committed == focus_surface_committed); +} + +static bool +is_focus_view (struct weston_view *view) +{ + return is_focus_surface (view->surface); +} + +static void +focus_state_destroy(struct focus_state *state) +{ + wl_list_remove(&state->seat_destroy_listener.link); + wl_list_remove(&state->surface_destroy_listener.link); + free(state); +} + +static void +focus_state_seat_destroy(struct wl_listener *listener, void *data) +{ + struct focus_state *state = container_of(listener, + struct focus_state, + seat_destroy_listener); + + wl_list_remove(&state->link); + focus_state_destroy(state); +} + +static void +focus_state_surface_destroy(struct wl_listener *listener, void *data) +{ + struct focus_state *state = container_of(listener, + struct focus_state, + surface_destroy_listener); + struct weston_surface *main_surface; + struct weston_view *next; + struct weston_view *view; + + main_surface = weston_surface_get_main_surface(state->keyboard_focus); + + next = NULL; + wl_list_for_each(view, + &state->ws->layer.view_list.link, layer_link.link) { + if (view->surface == main_surface) + continue; + if (is_focus_view(view)) + continue; + if (!get_shell_surface(view->surface)) + continue; + + next = view; + break; + } + + /* if the focus was a sub-surface, activate its main surface */ + if (main_surface != state->keyboard_focus) + next = get_default_view(main_surface); + + if (next) { + if (state->keyboard_focus) { + wl_list_remove(&state->surface_destroy_listener.link); + wl_list_init(&state->surface_destroy_listener.link); + } + state->keyboard_focus = NULL; + activate(state->shell, next, state->seat, + WESTON_ACTIVATE_FLAG_CONFIGURE); + } else { + wl_list_remove(&state->link); + focus_state_destroy(state); + } +} + +static struct focus_state * +focus_state_create(struct desktop_shell *shell, struct weston_seat *seat, + struct workspace *ws) +{ + struct focus_state *state; + + state = malloc(sizeof *state); + if (state == NULL) + return NULL; + + state->shell = shell; + state->keyboard_focus = NULL; + state->ws = ws; + state->seat = seat; + wl_list_insert(&ws->focus_list, &state->link); + + state->seat_destroy_listener.notify = focus_state_seat_destroy; + state->surface_destroy_listener.notify = focus_state_surface_destroy; + wl_signal_add(&seat->destroy_signal, + &state->seat_destroy_listener); + wl_list_init(&state->surface_destroy_listener.link); + + return state; +} + +static struct focus_state * +ensure_focus_state(struct desktop_shell *shell, struct weston_seat *seat) +{ + struct workspace *ws = get_current_workspace(shell); + struct focus_state *state; + + wl_list_for_each(state, &ws->focus_list, link) + if (state->seat == seat) + break; + + if (&state->link == &ws->focus_list) + state = focus_state_create(shell, seat, ws); + + return state; +} + +static void +focus_state_set_focus(struct focus_state *state, + struct weston_surface *surface) +{ + if (state->keyboard_focus) { + wl_list_remove(&state->surface_destroy_listener.link); + wl_list_init(&state->surface_destroy_listener.link); + } + + state->keyboard_focus = surface; + if (surface) + wl_signal_add(&surface->destroy_signal, + &state->surface_destroy_listener); +} + +static void +drop_focus_state(struct desktop_shell *shell, struct workspace *ws, + struct weston_surface *surface) +{ + struct focus_state *state; + + wl_list_for_each(state, &ws->focus_list, link) + if (state->keyboard_focus == surface) + focus_state_set_focus(state, NULL); +} + +static void +workspace_destroy(struct workspace *ws) +{ + struct focus_state *state, *next; + + wl_list_for_each_safe(state, next, &ws->focus_list, link) + focus_state_destroy(state); + + free(ws); +} + +static void +seat_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_seat *seat = data; + struct focus_state *state, *next; + struct workspace *ws = container_of(listener, + struct workspace, + seat_destroyed_listener); + + wl_list_for_each_safe(state, next, &ws->focus_list, link) + if (state->seat == seat) + wl_list_remove(&state->link); +} + +static struct workspace * +workspace_create(struct desktop_shell *shell) +{ + struct workspace *ws = malloc(sizeof *ws); + if (ws == NULL) + return NULL; + + weston_layer_init(&ws->layer, shell->compositor); + + wl_list_init(&ws->focus_list); + wl_list_init(&ws->seat_destroyed_listener.link); + ws->seat_destroyed_listener.notify = seat_destroyed; + + return ws; +} + +static struct workspace * +get_workspace(struct desktop_shell *shell, unsigned int index) +{ + struct workspace **pws = shell->workspaces.array.data; + assert(index < shell->workspaces.num); + pws += index; + return *pws; +} + +struct workspace * +get_current_workspace(struct desktop_shell *shell) +{ + return get_workspace(shell, shell->workspaces.current); +} + +static void +activate_workspace(struct desktop_shell *shell, unsigned int index) +{ + struct workspace *ws; + + ws = get_workspace(shell, index); + weston_layer_set_position(&ws->layer, WESTON_LAYER_POSITION_NORMAL); + + shell->workspaces.current = index; +} + +static void +surface_keyboard_focus_lost(struct weston_surface *surface) +{ + struct weston_compositor *compositor = surface->compositor; + struct weston_seat *seat; + struct weston_surface *focus; + + wl_list_for_each(seat, &compositor->seat_list, link) { + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + if (!keyboard) + continue; + + focus = weston_surface_get_main_surface(keyboard->focus); + if (focus == surface) + weston_keyboard_set_focus(keyboard, NULL); + } +} + +static void +touch_move_grab_down(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, wl_fixed_t x, wl_fixed_t y) +{ +} + +static void +touch_move_grab_up(struct weston_touch_grab *grab, const struct timespec *time, + int touch_id) +{ + struct weston_touch_move_grab *move = + (struct weston_touch_move_grab *) container_of( + grab, struct shell_touch_grab, grab); + + if (touch_id == 0) + move->active = 0; + + if (grab->touch->num_tp == 0) { + shell_touch_grab_end(&move->base); + free(move); + } +} + +static void +touch_move_grab_motion(struct weston_touch_grab *grab, + const struct timespec *time, int touch_id, + wl_fixed_t x, wl_fixed_t y) +{ + struct weston_touch_move_grab *move = (struct weston_touch_move_grab *) grab; + struct shell_surface *shsurf = move->base.shsurf; + struct weston_surface *es; + int dx = wl_fixed_to_int(grab->touch->grab_x + move->dx); + int dy = wl_fixed_to_int(grab->touch->grab_y + move->dy); + + if (!shsurf || !move->active) + return; + + es = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + weston_view_set_position(shsurf->view, dx, dy); + + weston_compositor_schedule_repaint(es->compositor); +} + +static void +touch_move_grab_frame(struct weston_touch_grab *grab) +{ +} + +static void +touch_move_grab_cancel(struct weston_touch_grab *grab) +{ + struct weston_touch_move_grab *move = + (struct weston_touch_move_grab *) container_of( + grab, struct shell_touch_grab, grab); + + shell_touch_grab_end(&move->base); + free(move); +} + +static const struct weston_touch_grab_interface touch_move_grab_interface = { + touch_move_grab_down, + touch_move_grab_up, + touch_move_grab_motion, + touch_move_grab_frame, + touch_move_grab_cancel, +}; + +static int +surface_touch_move(struct shell_surface *shsurf, struct weston_touch *touch) +{ + struct weston_touch_move_grab *move; + + if (!shsurf) + return -1; + + if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return 0; + + move = malloc(sizeof *move); + if (!move) + return -1; + + move->active = 1; + move->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + touch->grab_x; + move->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + touch->grab_y; + + shell_touch_grab_start(&move->base, &touch_move_grab_interface, shsurf, + touch); + + return 0; +} + +static void +noop_grab_focus(struct weston_pointer_grab *grab) +{ +} + +static void +noop_grab_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ +} + +static void +noop_grab_axis_source(struct weston_pointer_grab *grab, + uint32_t source) +{ +} + +static void +noop_grab_frame(struct weston_pointer_grab *grab) +{ +} + +static void +constrain_position(struct weston_move_grab *move, int *cx, int *cy) +{ + struct weston_pointer *pointer = move->base.grab.pointer; + *cx = wl_fixed_to_int(pointer->x + move->dx); + *cy = wl_fixed_to_int(pointer->y + move->dy); +} + +static void +move_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct weston_move_grab *move = (struct weston_move_grab *) grab; + struct weston_pointer *pointer = grab->pointer; + struct shell_surface *shsurf = move->base.shsurf; + struct weston_surface *surface; + int cx, cy; + + weston_pointer_move(pointer, event); + if (!shsurf) + return; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + constrain_position(move, &cx, &cy); + + weston_view_set_position(shsurf->view, cx, cy); + + weston_compositor_schedule_repaint(surface->compositor); +} + +static void +move_grab_button(struct weston_pointer_grab *grab, + const struct timespec *time, uint32_t button, uint32_t state_w) +{ + struct shell_grab *shell_grab = container_of(grab, struct shell_grab, + grab); + struct weston_pointer *pointer = grab->pointer; + enum wl_pointer_button_state state = state_w; + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + shell_grab_end(shell_grab); + free(grab); + } +} + +static void +move_grab_cancel(struct weston_pointer_grab *grab) +{ + struct shell_grab *shell_grab = + container_of(grab, struct shell_grab, grab); + + shell_grab_end(shell_grab); + free(grab); +} + +static const struct weston_pointer_grab_interface move_grab_interface = { + noop_grab_focus, + move_grab_motion, + move_grab_button, + noop_grab_axis, + noop_grab_axis_source, + noop_grab_frame, + move_grab_cancel, +}; + +static int +surface_move(struct shell_surface *shsurf, struct weston_pointer *pointer, + bool client_initiated) +{ + struct weston_move_grab *move; + + if (!shsurf) + return -1; + + if (shsurf->grabbed || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return 0; + + move = malloc(sizeof *move); + if (!move) + return -1; + + move->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + pointer->grab_x; + move->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + pointer->grab_y; + move->client_initiated = client_initiated; + + shell_grab_start(&move->base, &move_grab_interface, shsurf, + pointer, WESTON_DESKTOP_SHELL_CURSOR_MOVE); + + return 0; +} + +struct weston_resize_grab { + struct shell_grab base; + uint32_t edges; + int32_t width, height; +}; + +static void +resize_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; + struct weston_pointer *pointer = grab->pointer; + struct shell_surface *shsurf = resize->base.shsurf; + int32_t width, height; + struct weston_size min_size, max_size; + wl_fixed_t from_x, from_y; + wl_fixed_t to_x, to_y; + + weston_pointer_move(pointer, event); + + if (!shsurf) + return; + + weston_view_from_global_fixed(shsurf->view, + pointer->grab_x, pointer->grab_y, + &from_x, &from_y); + weston_view_from_global_fixed(shsurf->view, + pointer->x, pointer->y, &to_x, &to_y); + + width = resize->width; + if (resize->edges & WL_SHELL_SURFACE_RESIZE_LEFT) { + width += wl_fixed_to_int(from_x - to_x); + } else if (resize->edges & WL_SHELL_SURFACE_RESIZE_RIGHT) { + width += wl_fixed_to_int(to_x - from_x); + } + + height = resize->height; + if (resize->edges & WL_SHELL_SURFACE_RESIZE_TOP) { + height += wl_fixed_to_int(from_y - to_y); + } else if (resize->edges & WL_SHELL_SURFACE_RESIZE_BOTTOM) { + height += wl_fixed_to_int(to_y - from_y); + } + + max_size = weston_desktop_surface_get_max_size(shsurf->desktop_surface); + min_size = weston_desktop_surface_get_min_size(shsurf->desktop_surface); + + min_size.width = MAX(1, min_size.width); + min_size.height = MAX(1, min_size.height); + + if (width < min_size.width) + width = min_size.width; + else if (max_size.width > 0 && width > max_size.width) + width = max_size.width; + if (height < min_size.height) + height = min_size.height; + else if (max_size.width > 0 && width > max_size.width) + width = max_size.width; + weston_desktop_surface_set_size(shsurf->desktop_surface, width, height); +} + +static void +resize_grab_button(struct weston_pointer_grab *grab, + const struct timespec *time, + uint32_t button, uint32_t state_w) +{ + struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; + struct weston_pointer *pointer = grab->pointer; + enum wl_pointer_button_state state = state_w; + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + if (resize->base.shsurf != NULL) { + struct weston_desktop_surface *desktop_surface = + resize->base.shsurf->desktop_surface; + weston_desktop_surface_set_resizing(desktop_surface, + false); + } + + shell_grab_end(&resize->base); + free(grab); + } +} + +static void +resize_grab_cancel(struct weston_pointer_grab *grab) +{ + struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; + + if (resize->base.shsurf != NULL) { + struct weston_desktop_surface *desktop_surface = + resize->base.shsurf->desktop_surface; + weston_desktop_surface_set_resizing(desktop_surface, false); + } + + shell_grab_end(&resize->base); + free(grab); +} + +static const struct weston_pointer_grab_interface resize_grab_interface = { + noop_grab_focus, + resize_grab_motion, + resize_grab_button, + noop_grab_axis, + noop_grab_axis_source, + noop_grab_frame, + resize_grab_cancel, +}; + +/* + * Returns the bounding box of a surface and all its sub-surfaces, + * in surface-local coordinates. */ +static void +surface_subsurfaces_boundingbox(struct weston_surface *surface, int32_t *x, + int32_t *y, int32_t *w, int32_t *h) { + pixman_region32_t region; + pixman_box32_t *box; + struct weston_subsurface *subsurface; + + pixman_region32_init_rect(®ion, 0, 0, + surface->width, + surface->height); + + wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) { + pixman_region32_union_rect(®ion, ®ion, + subsurface->position.x, + subsurface->position.y, + subsurface->surface->width, + subsurface->surface->height); + } + + box = pixman_region32_extents(®ion); + if (x) + *x = box->x1; + if (y) + *y = box->y1; + if (w) + *w = box->x2 - box->x1; + if (h) + *h = box->y2 - box->y1; + + pixman_region32_fini(®ion); +} + +static int +surface_resize(struct shell_surface *shsurf, + struct weston_pointer *pointer, uint32_t edges) +{ + struct weston_resize_grab *resize; + const unsigned resize_topbottom = + WL_SHELL_SURFACE_RESIZE_TOP | WL_SHELL_SURFACE_RESIZE_BOTTOM; + const unsigned resize_leftright = + WL_SHELL_SURFACE_RESIZE_LEFT | WL_SHELL_SURFACE_RESIZE_RIGHT; + const unsigned resize_any = resize_topbottom | resize_leftright; + struct weston_geometry geometry; + + if (shsurf->grabbed || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return 0; + + /* Check for invalid edge combinations. */ + if (edges == WL_SHELL_SURFACE_RESIZE_NONE || edges > resize_any || + (edges & resize_topbottom) == resize_topbottom || + (edges & resize_leftright) == resize_leftright) + return 0; + + resize = malloc(sizeof *resize); + if (!resize) + return -1; + + resize->edges = edges; + + geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + resize->width = geometry.width; + resize->height = geometry.height; + + shsurf->resize_edges = edges; + weston_desktop_surface_set_resizing(shsurf->desktop_surface, true); + shell_grab_start(&resize->base, &resize_grab_interface, shsurf, + pointer, edges); + + return 0; +} + +static void +busy_cursor_grab_focus(struct weston_pointer_grab *base) +{ + struct shell_grab *grab = (struct shell_grab *) base; + struct weston_pointer *pointer = base->pointer; + struct weston_desktop_surface *desktop_surface = NULL; + struct weston_view *view; + wl_fixed_t sx, sy; + + view = weston_compositor_pick_view(pointer->seat->compositor, + pointer->x, pointer->y, + &sx, &sy); + /* With RAIL, it's possible that cursor can be at where has no view underneath. */ + if (view) + desktop_surface = weston_surface_get_desktop_surface(view->surface); + + if (desktop_surface == NULL || !grab->shsurf || grab->shsurf->desktop_surface != desktop_surface) { + shell_grab_end(grab); + free(grab); + } +} + +static void +busy_cursor_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + weston_pointer_move(grab->pointer, event); +} + +static void +busy_cursor_grab_button(struct weston_pointer_grab *base, + const struct timespec *time, + uint32_t button, uint32_t state) +{ + struct shell_grab *grab = (struct shell_grab *) base; + struct shell_surface *shsurf = grab->shsurf; + struct weston_pointer *pointer = grab->grab.pointer; + struct weston_seat *seat = pointer->seat; + + if (shsurf && button == BTN_LEFT && state) { + activate(shsurf->shell, shsurf->view, seat, + WESTON_ACTIVATE_FLAG_CONFIGURE); + surface_move(shsurf, pointer, false); + } else if (shsurf && button == BTN_RIGHT && state) { + activate(shsurf->shell, shsurf->view, seat, + WESTON_ACTIVATE_FLAG_CONFIGURE); + surface_rotate(shsurf, pointer); + } +} + +static void +busy_cursor_grab_cancel(struct weston_pointer_grab *base) +{ + struct shell_grab *grab = (struct shell_grab *) base; + + shell_grab_end(grab); + free(grab); +} + +static const struct weston_pointer_grab_interface busy_cursor_grab_interface = { + busy_cursor_grab_focus, + busy_cursor_grab_motion, + busy_cursor_grab_button, + noop_grab_axis, + noop_grab_axis_source, + noop_grab_frame, + busy_cursor_grab_cancel, +}; + +static void +handle_pointer_focus(struct wl_listener *listener, void *data) +{ + struct weston_pointer *pointer = data; + struct weston_view *view = pointer->focus; + struct shell_surface *shsurf; + struct weston_desktop_client *client; + + if (!view) + return; + + shsurf = get_shell_surface(view->surface); + if (!shsurf) + return; + + client = weston_desktop_surface_get_client(shsurf->desktop_surface); + + if (shsurf->unresponsive) + set_busy_cursor(shsurf, pointer); + else + weston_desktop_client_ping(client); +} + +static void +shell_surface_lose_keyboard_focus(struct shell_surface *shsurf) +{ + if (--shsurf->focus_count == 0) + weston_desktop_surface_set_activated(shsurf->desktop_surface, false); +} + +static void +shell_surface_gain_keyboard_focus(struct shell_surface *shsurf) +{ + if (shsurf->focus_count++ == 0) + weston_desktop_surface_set_activated(shsurf->desktop_surface, true); +} + +static void +handle_keyboard_focus(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard = data; + struct shell_seat *seat = get_shell_seat(keyboard->seat); + + if (seat->focused_surface) { + struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); + if (shsurf) + shell_surface_lose_keyboard_focus(shsurf); + } + + seat->focused_surface = weston_surface_get_main_surface(keyboard->focus); + + if (seat->focused_surface) { + struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); + if (shsurf) + shell_surface_gain_keyboard_focus(shsurf); + } +} + +/* The surface will be inserted into the list immediately after the link + * returned by this function (i.e. will be stacked immediately above the + * returned link). */ +static struct weston_layer_entry * +shell_surface_calculate_layer_link (struct shell_surface *shsurf) +{ + struct workspace *ws; + + if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) && + !shsurf->state.lowered) { + return &shsurf->shell->fullscreen_layer.view_list; + } + + /* Move the surface to a normal workspace layer so that surfaces + * which were previously fullscreen or transient are no longer + * rendered on top. */ + ws = get_current_workspace(shsurf->shell); + return &ws->layer.view_list; +} + +static void +shell_surface_update_child_surface_layers (struct shell_surface *shsurf) +{ + weston_desktop_surface_propagate_layer(shsurf->desktop_surface); +} + +/* Update the surface’s layer. Mark both the old and new views as having dirty + * geometry to ensure the changes are redrawn. + * + * If any child surfaces exist and are mapped, ensure they’re in the same layer + * as this surface. */ +static void +shell_surface_update_layer(struct shell_surface *shsurf) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_layer_entry *new_layer_link; + + new_layer_link = shell_surface_calculate_layer_link(shsurf); + + if (new_layer_link == NULL) + return; + if (new_layer_link == &shsurf->view->layer_link) + return; + + weston_view_geometry_dirty(shsurf->view); + weston_layer_entry_remove(&shsurf->view->layer_link); + weston_layer_entry_insert(new_layer_link, &shsurf->view->layer_link); + weston_view_geometry_dirty(shsurf->view); + weston_surface_damage(surface); + + shell_surface_update_child_surface_layers(shsurf); +} + +static void +notify_output_destroy(struct wl_listener *listener, void *data) +{ + struct shell_surface *shsurf = + container_of(listener, + struct shell_surface, output_destroy_listener); + + shsurf->output = NULL; + shsurf->output_destroy_listener.notify = NULL; +} + +static void +shell_surface_set_output(struct shell_surface *shsurf, + struct weston_output *output) +{ + struct weston_surface *es = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + + /* get the default output, if the client set it as NULL + check whether the output is available */ + if (output) + shsurf->output = output; + else if (es->output) + shsurf->output = es->output; + else + shsurf->output = get_default_output(es->compositor); + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + if (!shsurf->output) + return; + + shsurf->output_destroy_listener.notify = notify_output_destroy; + wl_signal_add(&shsurf->output->destroy_signal, + &shsurf->output_destroy_listener); +} + +static void +weston_view_set_initial_position(struct weston_view *view, + struct desktop_shell *shell); + +static void +unset_fullscreen(struct shell_surface *shsurf) +{ + /* Unset the fullscreen output, driver configuration and transforms. */ + wl_list_remove(&shsurf->fullscreen.transform.link); + wl_list_init(&shsurf->fullscreen.transform.link); + + if (shsurf->fullscreen.black_view) + weston_surface_destroy(shsurf->fullscreen.black_view->surface); + shsurf->fullscreen.black_view = NULL; + + if (shsurf->saved_position_valid) + weston_view_set_position(shsurf->view, + shsurf->saved_x, shsurf->saved_y); + else + weston_view_set_initial_position(shsurf->view, shsurf->shell); + shsurf->saved_position_valid = false; + + if (shsurf->saved_rotation_valid) { + wl_list_insert(&shsurf->view->geometry.transformation_list, + &shsurf->rotation.transform.link); + shsurf->saved_rotation_valid = false; + } +} + +static void +unset_maximized(struct shell_surface *shsurf) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; + + if (!rail_state) + return; + rail_state->is_maximized_requested = false; + + /* if shell surface has already output assigned, leave where it is. (don't move to primary). */ + if (!shsurf->output) + shell_surface_set_output(shsurf, get_default_output(surface->compositor)); + + if (shsurf->snapped.is_snapped) { + /* Restore to snap state. + */ + weston_desktop_surface_set_size(shsurf->desktop_surface, shsurf->snapped.width, shsurf->snapped.height); + weston_view_set_position(shsurf->view, shsurf->snapped.x, shsurf->snapped.y); + } else { + /* Restore to previous size or make up one if the window started maximized. + */ + if (shsurf->saved_position_valid) + weston_view_set_position(shsurf->view, + shsurf->saved_x, shsurf->saved_y); + else + weston_view_set_initial_position(shsurf->view, shsurf->shell); + shsurf->saved_position_valid = false; + } + + if (shsurf->saved_rotation_valid) { + wl_list_insert(&shsurf->view->geometry.transformation_list, + &shsurf->rotation.transform.link); + shsurf->saved_rotation_valid = false; + } +} + +static void +set_minimized(struct weston_surface *surface) +{ + struct shell_surface *shsurf; + struct workspace *current_ws; + struct weston_view *view; + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; + + view = get_default_view(surface); + if (!view) + return; + + if (!rail_state) + return; + rail_state->is_minimized_requested = true; + + assert(weston_surface_get_main_surface(view->surface) == view->surface); + + shsurf = get_shell_surface(surface); + current_ws = get_current_workspace(shsurf->shell); + + weston_layer_entry_remove(&view->layer_link); + weston_layer_entry_insert(&shsurf->shell->minimized_layer.view_list, &view->layer_link); + + drop_focus_state(shsurf->shell, current_ws, view->surface); + surface_keyboard_focus_lost(surface); + + shell_surface_update_child_surface_layers(shsurf); + weston_view_damage_below(view); +} + +static void +set_unminimized(struct weston_surface *surface) +{ + struct shell_surface *shsurf; + struct workspace *current_ws; + struct weston_view *view; + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; + + view = get_default_view(surface); + if (!view) + return; + + if (!rail_state) + return; + rail_state->is_minimized_requested = false; + + assert(weston_surface_get_main_surface(view->surface) == view->surface); + + shsurf = get_shell_surface(surface); + current_ws = get_current_workspace(shsurf->shell); + + weston_layer_entry_remove(&view->layer_link); + weston_layer_entry_insert(¤t_ws->layer.view_list, &view->layer_link); + + shell_surface_update_child_surface_layers(shsurf); + weston_view_damage_below(view); +} + +static void +set_unsnap(struct shell_surface *shsurf, int grabX, int grabY) +{ + if (!shsurf->snapped.is_snapped) + return; + + /* + * Reposition the window such that the mouse remain within the + * new bound of the window after resize. + */ + /* Need to fix RDP event processing while doing a local move first otherwise this undo the move! + if (grabX - shsurf->view->geometry.x > shsurf->snapped.saved_width) { + weston_view_set_position(shsurf->view, grabX - shsurf->snapped.saved_width/2, shsurf->view->geometry.y); + } + + weston_desktop_surface_set_size(shsurf->desktop_surface, shsurf->snapped.saved_width, shsurf->snapped.saved_height);*/ + shsurf->snapped.is_snapped = false; +} + +static struct desktop_shell * +shell_surface_get_shell(struct shell_surface *shsurf) +{ + return shsurf->shell; +} + +static int +black_surface_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + struct weston_view *fs_view = surface->committed_private; + struct weston_surface *fs_surface = fs_view->surface; + int n; + int rem; + int ret; + + n = snprintf(buf, len, "black background surface for "); + if (n < 0) + return n; + + rem = (int)len - n; + if (rem < 0) + rem = 0; + + if (fs_surface->get_label) + ret = fs_surface->get_label(fs_surface, buf + n, rem); + else + ret = snprintf(buf + n, rem, ""); + + if (ret < 0) + return n; + + return n + ret; +} + +static void +black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy); + +static struct weston_view * +create_black_surface(struct weston_compositor *ec, + struct weston_view *fs_view, + float x, float y, int w, int h) +{ + struct weston_surface *surface = NULL; + struct weston_view *view; + + surface = weston_surface_create(ec); + if (surface == NULL) { + weston_log("%s: no memory\n", __func__); + return NULL; + } + view = weston_view_create(surface); + if (surface == NULL) { + weston_log("%s: no memory\n", __func__); + weston_surface_destroy(surface); + return NULL; + } + + surface->committed = black_surface_committed; + surface->committed_private = fs_view; + weston_surface_set_label_func(surface, black_surface_get_label); + weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1); + pixman_region32_fini(&surface->opaque); + pixman_region32_init_rect(&surface->opaque, 0, 0, w, h); + pixman_region32_fini(&surface->input); + pixman_region32_init_rect(&surface->input, 0, 0, w, h); + + weston_surface_set_size(surface, w, h); + weston_view_set_position(view, x, y); + + return view; +} + +static void +shell_ensure_fullscreen_black_view(struct shell_surface *shsurf) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_output *output = shsurf->fullscreen_output; + + assert(weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)); + + if (!shsurf->fullscreen.black_view) + shsurf->fullscreen.black_view = + create_black_surface(surface->compositor, + shsurf->view, + output->x, output->y, + output->width, + output->height); + + weston_view_geometry_dirty(shsurf->fullscreen.black_view); + weston_layer_entry_remove(&shsurf->fullscreen.black_view->layer_link); + weston_layer_entry_insert(&shsurf->view->layer_link, + &shsurf->fullscreen.black_view->layer_link); + weston_view_geometry_dirty(shsurf->fullscreen.black_view); + weston_surface_damage(surface); + + shsurf->fullscreen.black_view->is_mapped = true; + shsurf->state.lowered = false; +} + +/* Create black surface and append it to the associated fullscreen surface. + * Handle size dismatch and positioning according to the method. */ +static void +shell_configure_fullscreen(struct shell_surface *shsurf) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + int32_t surf_x, surf_y, surf_width, surf_height; + + /* Reverse the effect of lower_fullscreen_layer() */ + weston_layer_entry_remove(&shsurf->view->layer_link); + weston_layer_entry_insert(&shsurf->shell->fullscreen_layer.view_list, + &shsurf->view->layer_link); + + if (!shsurf->fullscreen_output) { + /* If there is no output, there's not much we can do. + * Position the window somewhere, whatever. */ + weston_view_set_position(shsurf->view, 0, 0); + return; + } + + shell_ensure_fullscreen_black_view(shsurf); + + surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y, + &surf_width, &surf_height); + + if (surface->buffer_ref.buffer) + center_on_output(shsurf->view, shsurf->fullscreen_output); +} + +static void +shell_map_fullscreen(struct shell_surface *shsurf) +{ + shell_configure_fullscreen(shsurf); +} + +static struct weston_output * +get_focused_output(struct weston_compositor *compositor) +{ + struct weston_seat *seat; + struct weston_output *output = NULL; + + wl_list_for_each(seat, &compositor->seat_list, link) { + struct weston_touch *touch = weston_seat_get_touch(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + /* Priority has touch focus, then pointer and + * then keyboard focus. We should probably have + * three for loops and check first for touch, + * then for pointer, etc. but unless somebody has some + * objections, I think this is sufficient. */ + if (touch && touch->focus) + output = touch->focus->output; + else if (pointer && pointer->focus) + output = pointer->focus->output; + else if (keyboard && keyboard->focus) + output = keyboard->focus->output; + + if (output) + break; + } + + return output; +} + +static void +destroy_shell_seat(struct wl_listener *listener, void *data) +{ + struct shell_seat *shseat = + container_of(listener, + struct shell_seat, seat_destroy_listener); + + wl_list_remove(&shseat->seat_destroy_listener.link); + free(shseat); +} + +static void +shell_seat_caps_changed(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard; + struct weston_pointer *pointer; + struct shell_seat *seat; + + seat = container_of(listener, struct shell_seat, caps_changed_listener); + keyboard = weston_seat_get_keyboard(seat->seat); + pointer = weston_seat_get_pointer(seat->seat); + + if (keyboard && + wl_list_empty(&seat->keyboard_focus_listener.link)) { + wl_signal_add(&keyboard->focus_signal, + &seat->keyboard_focus_listener); + } else if (!keyboard) { + wl_list_remove(&seat->keyboard_focus_listener.link); + wl_list_init(&seat->keyboard_focus_listener.link); + } + + if (pointer && + wl_list_empty(&seat->pointer_focus_listener.link)) { + wl_signal_add(&pointer->focus_signal, + &seat->pointer_focus_listener); + } else if (!pointer) { + wl_list_remove(&seat->pointer_focus_listener.link); + wl_list_init(&seat->pointer_focus_listener.link); + } +} + +static struct shell_seat * +create_shell_seat(struct weston_seat *seat) +{ + struct shell_seat *shseat; + + shseat = calloc(1, sizeof *shseat); + if (!shseat) { + weston_log("%s: no memory to allocate shell seat\n", __func__); + return NULL; + } + + shseat->seat = seat; + + shseat->seat_destroy_listener.notify = destroy_shell_seat; + wl_signal_add(&seat->destroy_signal, + &shseat->seat_destroy_listener); + + shseat->keyboard_focus_listener.notify = handle_keyboard_focus; + wl_list_init(&shseat->keyboard_focus_listener.link); + + shseat->pointer_focus_listener.notify = handle_pointer_focus; + wl_list_init(&shseat->pointer_focus_listener.link); + + shseat->caps_changed_listener.notify = shell_seat_caps_changed; + wl_signal_add(&seat->updated_caps_signal, + &shseat->caps_changed_listener); + shell_seat_caps_changed(&shseat->caps_changed_listener, NULL); + + return shseat; +} + +static struct shell_seat * +get_shell_seat(struct weston_seat *seat) +{ + struct wl_listener *listener; + + listener = wl_signal_get(&seat->destroy_signal, destroy_shell_seat); + assert(listener != NULL); + + return container_of(listener, + struct shell_seat, seat_destroy_listener); +} + +struct shell_surface * +get_shell_surface(struct weston_surface *surface) +{ + if (weston_surface_is_desktop_surface(surface)) { + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + return weston_desktop_surface_get_user_data(desktop_surface); + } + return NULL; +} + +/* + * libweston-desktop + */ + +static void +handle_metadata_change(struct wl_listener *listener, void *data) +{ + struct weston_desktop_surface *desktop_surface = + (struct weston_desktop_surface *)data; + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; + + /* invalidate get_label, this force update title at next update */ + if (rail_state) + rail_state->get_label = NULL; +} + +static void +desktop_surface_added(struct weston_desktop_surface *desktop_surface, + void *shell) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(desktop_surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + struct weston_view *view; + struct shell_surface *shsurf; + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + + view = weston_desktop_surface_create_view(desktop_surface); + if (!view) + return; + + shsurf = calloc(1, sizeof *shsurf); + if (!shsurf) { + if (wl_client) + wl_client_post_no_memory(wl_client); + else + weston_log("%s: no memory to allocate shell surface\n", __func__); + return; + } + + weston_surface_set_label_func(surface, shell_surface_get_label); + + shsurf->shell = (struct desktop_shell *) shell; + shsurf->unresponsive = 0; + shsurf->saved_position_valid = false; + shsurf->saved_rotation_valid = false; + shsurf->desktop_surface = desktop_surface; + shsurf->view = view; + shsurf->fullscreen.black_view = NULL; + wl_list_init(&shsurf->fullscreen.transform.link); + + shell_surface_set_output( + shsurf, get_default_output(shsurf->shell->compositor)); + + wl_signal_init(&shsurf->destroy_signal); + + /* empty when not in use */ + wl_list_init(&shsurf->rotation.transform.link); + weston_matrix_init(&shsurf->rotation.rotation); + + /* + * initialize list as well as link. The latter allows to use + * wl_list_remove() even when this surface is not in another list. + */ + wl_list_init(&shsurf->children_list); + wl_list_init(&shsurf->children_link); + + weston_desktop_surface_set_user_data(desktop_surface, shsurf); + weston_desktop_surface_set_activated(desktop_surface, + shsurf->focus_count > 0); + + shsurf->metadata_listener.notify = handle_metadata_change; + weston_desktop_surface_add_metadata_listener(desktop_surface, + &shsurf->metadata_listener); +} + +static void +desktop_surface_removed(struct weston_desktop_surface *desktop_surface, + void *shell) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct shell_surface *shsurf_child, *tmp; + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + + if (!shsurf) + return; + + wl_list_for_each_safe(shsurf_child, tmp, &shsurf->children_list, children_link) { + wl_list_remove(&shsurf_child->children_link); + wl_list_init(&shsurf_child->children_link); + } + wl_list_remove(&shsurf->children_link); + + wl_signal_emit(&shsurf->destroy_signal, shsurf); + + if (shsurf->fullscreen.black_view) + weston_surface_destroy(shsurf->fullscreen.black_view->surface); + + weston_surface_set_label_func(surface, NULL); + weston_desktop_surface_set_user_data(shsurf->desktop_surface, NULL); + shsurf->desktop_surface = NULL; + + weston_desktop_surface_unlink_view(shsurf->view); + weston_view_destroy(shsurf->view); + + if (shsurf->output_destroy_listener.notify) { + wl_list_remove(&shsurf->output_destroy_listener.link); + shsurf->output_destroy_listener.notify = NULL; + } + + if (shsurf->metadata_listener.notify) { + wl_list_remove(&shsurf->metadata_listener.link); + shsurf->metadata_listener.notify = NULL; + } + + free(shsurf); +} + +static void +set_maximized_position(struct desktop_shell *shell, + struct shell_surface *shsurf) +{ + pixman_rectangle32_t area; + struct weston_geometry geometry; + + get_output_work_area(shell, shsurf->output, &area); + geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + + weston_view_set_position(shsurf->view, + area.x - geometry.x, + area.y - geometry.y); +} + +static void +set_position_from_xwayland(struct shell_surface *shsurf) +{ + struct weston_geometry geometry; + int x; + int y; + struct weston_output *output; + pixman_rectangle32_t area; + + assert(shsurf->xwayland.is_set); + + geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + x = shsurf->xwayland.x - geometry.x; + y = shsurf->xwayland.y - geometry.y; + + /* Make sure the position given from xwayland is a part of workarea */ + output = get_output_containing(shsurf->shell->compositor, x, y); + if (output) { + get_output_work_area(shsurf->shell, output, &area); + /* Use xwayland position as this is the X app's origin of client area */ + if (shsurf->xwayland.x >= area.x && + shsurf->xwayland.y >= area.y && + shsurf->xwayland.x <= (int32_t)(area.x + area.width - (area.width / 10)) && + shsurf->xwayland.y <= (int32_t)(area.y + area.width - (area.width / 10))) { + + weston_view_set_position(shsurf->view, x, y); + + shell_rdp_debug(shsurf->shell, "%s: XWM %d, %d; geometry %d, %d; view %d, %d\n", + __func__, shsurf->xwayland.x, shsurf->xwayland.y, + geometry.x, geometry.y, x, y); + + return; + } + } + + /* Otherwise, move to default initial position */ + weston_view_set_initial_position(shsurf->view, shsurf->shell); +} + +static void +set_default_position_from_parent(struct shell_surface *shsurf) +{ + struct weston_geometry parent_geometry, geometry; + int32_t x; + int32_t y; + + parent_geometry = weston_desktop_surface_get_geometry(shsurf->parent->desktop_surface); + geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + + x = parent_geometry.x + (parent_geometry.width - geometry.width) / 2; + y = parent_geometry.y + (parent_geometry.height - geometry.height) / 2; + + x += shsurf->parent->view->geometry.x; + y += shsurf->parent->view->geometry.y; + + shell_rdp_debug_verbose(shsurf->shell, "%s: view:%p, (%d, %d)\n", + __func__, shsurf->view, x, y); + + weston_view_set_position(shsurf->view, x, y); +} + +static void +map(struct desktop_shell *shell, struct shell_surface *shsurf, + int32_t sx, int32_t sy) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_compositor *compositor = shell->compositor; + struct weston_seat *seat; + + /* initial positioning, see also configure() */ + if (shsurf->state.fullscreen) { + center_on_output(shsurf->view, shsurf->fullscreen_output); + shell_map_fullscreen(shsurf); + } else if (shsurf->state.maximized) { + set_maximized_position(shell, shsurf); + } else if (shsurf->xwayland.is_set) { + set_position_from_xwayland(shsurf); + } else if (shsurf->parent) { + set_default_position_from_parent(shsurf); + } else { + weston_view_set_initial_position(shsurf->view, shell); + } + + /* Surface stacking order, see also activate(). */ + shell_surface_update_layer(shsurf); + + weston_view_update_transform(shsurf->view); + shsurf->view->is_mapped = true; + if (shsurf->state.maximized) { + surface->output = shsurf->output; + weston_view_set_output(shsurf->view, shsurf->output); + } + + wl_list_for_each(seat, &compositor->seat_list, link) + activate(shell, shsurf->view, seat, + WESTON_ACTIVATE_FLAG_CONFIGURE); +} + +static void +desktop_surface_committed(struct weston_desktop_surface *desktop_surface, + int32_t sx, int32_t sy, void *data) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + struct weston_view *view = shsurf->view; + struct desktop_shell *shell = data; + bool was_fullscreen; + bool was_maximized; + + if (surface->width == 0) + return; + + was_fullscreen = shsurf->state.fullscreen; + was_maximized = shsurf->state.maximized; + + shsurf->state.fullscreen = + weston_desktop_surface_get_fullscreen(desktop_surface); + shsurf->state.maximized = + weston_desktop_surface_get_maximized(desktop_surface); + + if (!weston_surface_is_mapped(surface)) { + map(shell, shsurf, sx, sy); + surface->is_mapped = true; + return; + } + + if (sx == 0 && sy == 0 && + shsurf->last_width == surface->width && + shsurf->last_height == surface->height && + was_fullscreen == shsurf->state.fullscreen && + was_maximized == shsurf->state.maximized) + return; + + if (was_fullscreen) + unset_fullscreen(shsurf); + if (was_maximized) + unset_maximized(shsurf); + + if ((shsurf->state.fullscreen || shsurf->state.maximized) && + !shsurf->saved_position_valid) { + shsurf->saved_x = shsurf->view->geometry.x; + shsurf->saved_y = shsurf->view->geometry.y; + shsurf->saved_position_valid = true; + + if (!wl_list_empty(&shsurf->rotation.transform.link)) { + wl_list_remove(&shsurf->rotation.transform.link); + wl_list_init(&shsurf->rotation.transform.link); + weston_view_geometry_dirty(shsurf->view); + shsurf->saved_rotation_valid = true; + } + } + + if (shsurf->state.fullscreen) { + shell_configure_fullscreen(shsurf); + } else if (shsurf->state.maximized) { + set_maximized_position(shell, shsurf); + surface->output = shsurf->output; + } else if (shsurf->snapped.is_snapped) { + weston_view_set_position(shsurf->view, shsurf->snapped.x, shsurf->snapped.y); + } else { + float from_x, from_y; + float to_x, to_y; + float x, y; + + if (shsurf->resize_edges) { + sx = 0; + sy = 0; + + if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_LEFT) + sx = shsurf->last_width - surface->width; + if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_TOP) + sy = shsurf->last_height - surface->height; + + weston_view_to_global_float(shsurf->view, 0, 0, &from_x, &from_y); + weston_view_to_global_float(shsurf->view, sx, sy, &to_x, &to_y); + x = shsurf->view->geometry.x + to_x - from_x; + y = shsurf->view->geometry.y + to_y - from_y; + + weston_view_set_position(shsurf->view, x, y); + } + } + + shsurf->last_width = surface->width; + shsurf->last_height = surface->height; + + /* XXX: would a fullscreen surface need the same handling? */ + if (surface->output) { + wl_list_for_each(view, &surface->views, surface_link) + weston_view_update_transform(view); + } + + if (!shsurf->icon.is_icon_set) { + /* TODO hook to meta data change notification */ + shell_surface_set_window_icon(desktop_surface, 0, 0, 0, NULL, NULL); + shsurf->icon.is_icon_set = true; + } +} + +static void +get_maximized_size(struct shell_surface *shsurf, int32_t *width, int32_t *height) +{ + struct desktop_shell *shell; + pixman_rectangle32_t area; + + shell = shell_surface_get_shell(shsurf); + get_output_work_area(shell, shsurf->output, &area); + + *width = area.width; + *height = area.height; +} + +static void +set_fullscreen(struct shell_surface *shsurf, bool fullscreen, + struct weston_output *output) +{ + struct weston_desktop_surface *desktop_surface = shsurf->desktop_surface; + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + int32_t width = 0, height = 0; + + if (fullscreen) { + /* handle clients launching in fullscreen */ + if (output == NULL && !weston_surface_is_mapped(surface)) { + /* Set the output to the one that has focus currently. */ + output = get_focused_output(surface->compositor); + } + + shell_surface_set_output(shsurf, output); + shsurf->fullscreen_output = shsurf->output; + + width = shsurf->output->width; + height = shsurf->output->height; + } else if (weston_desktop_surface_get_maximized(desktop_surface)) { + get_maximized_size(shsurf, &width, &height); + } + weston_desktop_surface_set_fullscreen(desktop_surface, fullscreen); + weston_desktop_surface_set_size(desktop_surface, width, height); +} + +static void +desktop_surface_move(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, void *shell) +{ + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_touch *touch = weston_seat_get_touch(seat); + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct wl_resource *resource = surface->resource; + struct weston_surface *focus; + + if (pointer && + pointer->focus && + pointer->button_count > 0 && + pointer->grab_serial == serial) { + focus = weston_surface_get_main_surface(pointer->focus->surface); + if ((focus == surface) && + (surface_move(shsurf, pointer, true) < 0)) + wl_resource_post_no_memory(resource); + } else if (touch && + touch->focus && + touch->grab_serial == serial) { + focus = weston_surface_get_main_surface(touch->focus->surface); + if ((focus == surface) && + (surface_touch_move(shsurf, touch) < 0)) + wl_resource_post_no_memory(resource); + } +} + +static void +desktop_surface_resize(struct weston_desktop_surface *desktop_surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges, void *shell) +{ + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct wl_resource *resource = surface->resource; + struct weston_surface *focus; + + if (!pointer || + pointer->button_count == 0 || + pointer->grab_serial != serial || + pointer->focus == NULL) + return; + + focus = weston_surface_get_main_surface(pointer->focus->surface); + if (focus != surface) + return; + + if (surface_resize(shsurf, pointer, edges) < 0) + wl_resource_post_no_memory(resource); +} + +static void +desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, + struct weston_desktop_surface *parent, + void *shell) +{ + struct shell_surface *shsurf_parent; + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + /* unlink any potential child */ + wl_list_remove(&shsurf->children_link); + + if (parent) { + shsurf_parent = weston_desktop_surface_get_user_data(parent); + wl_list_insert(shsurf_parent->children_list.prev, + &shsurf->children_link); + /* libweston-desktop doesn't establish parent/child relationship + with weston_desktop_api shell_desktop_api.set_parent call, + thus calling weston_desktop_surface_get_parent won't work, + so shell need to track by itself. This also means child's + geometry won't be adjusted to relative to parent. */ + shsurf->parent = shsurf_parent; + } else { + wl_list_init(&shsurf->children_link); + shsurf->parent = NULL; + } +} + +static void +desktop_surface_fullscreen_requested(struct weston_desktop_surface *desktop_surface, + bool fullscreen, + struct weston_output *output, void *shell) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + set_fullscreen(shsurf, fullscreen, output); +} + +static void +set_maximized(struct shell_surface *shsurf, bool maximized) +{ + struct weston_desktop_surface *desktop_surface = shsurf->desktop_surface; + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; + int32_t width = 0, height = 0; + + if (!rail_state) + return; + rail_state->is_maximized_requested = maximized; + + if (maximized) { + struct weston_output *output; + + if (!weston_surface_is_mapped(surface)) + output = get_focused_output(surface->compositor); + else + /* TODO: Need to revisit here for local move. */ + output = surface->output; + + shell_surface_set_output(shsurf, output); + + get_maximized_size(shsurf, &width, &height); + } + weston_desktop_surface_set_maximized(desktop_surface, maximized); + weston_desktop_surface_set_size(desktop_surface, width, height); +} + +static void +desktop_surface_maximized_requested(struct weston_desktop_surface *desktop_surface, + bool maximized, void *shell) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + set_maximized(shsurf, maximized); +} + +static void +desktop_surface_minimized_requested(struct weston_desktop_surface *desktop_surface, + void *shell) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + + /* apply compositor's own minimization logic (hide) */ + set_minimized(surface); +} + +static void +desktop_surface_set_window_icon(struct weston_desktop_surface *desktop_surface, + int32_t width, int32_t height, int32_t bpp, + void *bits, void *user_data) +{ + shell_surface_set_window_icon(desktop_surface, width, height, bpp, bits, user_data); +} + +static void +shell_backend_request_window_minimize(struct weston_surface *surface) +{ + set_minimized(surface); +} + +static void +shell_backend_request_window_maximize(struct weston_surface *surface) +{ + struct shell_surface *shsurf = get_shell_surface(surface); + const struct weston_xwayland_surface_api *api; + + if (!shsurf) + return; + + if (shsurf->shell->is_localmove_pending) { + /* Delay maximizing the surface until the move ends. The client + * will send up a snap request once the move ends, we'll + * maximize the window at that time once we know which monitor + * to maximize on. + */ + shsurf->snapped.is_maximized_requested = true; + return; + } + + api = shsurf->shell->xwayland_surface_api; + if (!api) { + api = weston_xwayland_surface_get_api(shsurf->shell->compositor); + shsurf->shell->xwayland_surface_api = api; + } + if (api && api->is_xwayland_surface(surface)) { + api->set_maximized(surface, true); + } else { + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + + weston_desktop_api_maximized_requested(shsurf->shell->desktop, desktop_surface, true); + } +} + +static void +shell_backend_request_window_restore(struct weston_surface *surface) +{ + struct shell_surface *shsurf = get_shell_surface(surface); + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; + const struct weston_xwayland_surface_api *api; + + if (!shsurf) + return; + + if (!rail_state) + return; + + if (rail_state->is_minimized_requested) { + set_unminimized(surface); + } else if (shsurf->state.maximized) { + api = shsurf->shell->xwayland_surface_api; + if (!api) { + api = weston_xwayland_surface_get_api(shsurf->shell->compositor); + shsurf->shell->xwayland_surface_api = api; + } + if (api && api->is_xwayland_surface(surface)) { + api->set_maximized(surface, false); + } else { + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + + weston_desktop_api_maximized_requested(shsurf->shell->desktop, desktop_surface, false); + } + } +} + +static void +shell_backend_request_window_move(struct weston_surface *surface, int x, int y) +{ + struct weston_view *view; + struct shell_surface *shsurf = get_shell_surface(surface); + + view = get_default_view(surface); + if (!view) + return; + + if (shsurf && shsurf->shell->is_localmove_pending) { + shsurf->shell->is_localmove_pending = false; + } + + assert(!shsurf->snapped.is_maximized_requested); + weston_view_set_position(view, x, y); +} + +static void +shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, int width, int height) +{ + struct weston_view *view; + struct shell_surface *shsurf = get_shell_surface(surface); + + view = get_default_view(surface); + if (!view || !shsurf) + return; + + if (shsurf->shell->is_localmove_pending) { + shsurf->shell->is_localmove_pending = false; + } + + if (shsurf->state.maximized) { + return; + } + + if (shsurf->snapped.is_maximized_requested) { + assert(!shsurf->shell->is_localmove_pending); + + shsurf->snapped.is_maximized_requested = false; + + /* We may need to pick a new output for the window + * based on the last position of the mouse when the + * grab event finished. + */ + struct weston_output *output = get_output_containing(surface->compositor, + shsurf->snapped.last_grab_x, + shsurf->snapped.last_grab_y); + + weston_view_set_output(shsurf->view, output); + shell_surface_set_output(shsurf, output); + + shell_backend_request_window_maximize(surface); + return; + } + + if (!shsurf->snapped.is_snapped) { + shsurf->snapped.saved_width = surface->width; + shsurf->snapped.saved_height = surface->height; + } + shsurf->snapped.is_snapped = true; + + if (surface->width != width || surface->height != height) { + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + + struct weston_size max_size = weston_desktop_surface_get_max_size(desktop_surface); + struct weston_size min_size = weston_desktop_surface_get_min_size(desktop_surface); + + min_size.width = MAX(1, min_size.width); + min_size.height = MAX(1, min_size.height); + + if (width < min_size.width) + width = min_size.width; + else if (max_size.width > 0 && width > max_size.width) + width = max_size.width; + if (height < min_size.height) + height = min_size.height; + else if (max_size.width > 0 && width > max_size.width) + width = max_size.width; + + weston_desktop_surface_set_size(desktop_surface, width, height); + + x -= surface->input.extents.x1; + y -= surface->input.extents.y1; + } + + weston_view_set_position(view, x, y); + + shsurf->snapped.x = x; + shsurf->snapped.y = y; + shsurf->snapped.width = width; + shsurf->snapped.height = height; +} + +static void +set_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer) +{ + struct shell_grab *grab; + + if (pointer->grab->interface == &busy_cursor_grab_interface) + return; + + grab = malloc(sizeof *grab); + if (!grab) + return; + + shell_grab_start(grab, &busy_cursor_grab_interface, shsurf, pointer, + WESTON_DESKTOP_SHELL_CURSOR_BUSY); + /* Mark the shsurf as ungrabbed so that button binding is able + * to move it. */ + shsurf->grabbed = 0; +} + +static void +end_busy_cursor(struct weston_compositor *compositor, + struct weston_desktop_client *desktop_client) +{ + struct shell_surface *shsurf; + struct shell_grab *grab; + struct weston_seat *seat; + + wl_list_for_each(seat, &compositor->seat_list, link) { + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_desktop_client *grab_client; + + if (!pointer) + continue; + + if (pointer->grab->interface != &busy_cursor_grab_interface) + continue; + + grab = (struct shell_grab *) pointer->grab; + shsurf = grab->shsurf; + if (!shsurf) + continue; + + grab_client = + weston_desktop_surface_get_client(shsurf->desktop_surface); + if (grab_client == desktop_client) { + shell_grab_end(grab); + free(grab); + } + } +} + +static void +desktop_surface_set_unresponsive(struct weston_desktop_surface *desktop_surface, + void *user_data) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + bool *unresponsive = user_data; + + shsurf->unresponsive = *unresponsive; +} + +static void +desktop_surface_ping_timeout(struct weston_desktop_client *desktop_client, + void *shell_) +{ + struct desktop_shell *shell = shell_; + struct shell_surface *shsurf; + struct weston_seat *seat; + bool unresponsive = true; + + weston_desktop_client_for_each_surface(desktop_client, + desktop_surface_set_unresponsive, + &unresponsive); + + + wl_list_for_each(seat, &shell->compositor->seat_list, link) { + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_desktop_client *grab_client; + + if (!pointer || !pointer->focus) + continue; + + shsurf = get_shell_surface(pointer->focus->surface); + if (!shsurf) + continue; + + grab_client = + weston_desktop_surface_get_client(shsurf->desktop_surface); + if (grab_client == desktop_client) + set_busy_cursor(shsurf, pointer); + } +} + +static void +desktop_surface_pong(struct weston_desktop_client *desktop_client, + void *shell_) +{ + struct desktop_shell *shell = shell_; + bool unresponsive = false; + + weston_desktop_client_for_each_surface(desktop_client, + desktop_surface_set_unresponsive, + &unresponsive); + end_busy_cursor(shell->compositor, desktop_client); +} + +static void +desktop_surface_set_xwayland_position(struct weston_desktop_surface *surface, + int32_t x, int32_t y, void *shell_) +{ + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(surface); + + shsurf->xwayland.x = x; + shsurf->xwayland.y = y; + shsurf->xwayland.is_set = true; +} + +static const struct weston_desktop_api shell_desktop_api = { + .struct_size = sizeof(struct weston_desktop_api), + .surface_added = desktop_surface_added, + .surface_removed = desktop_surface_removed, + .committed = desktop_surface_committed, + .move = desktop_surface_move, + .resize = desktop_surface_resize, + .set_parent = desktop_surface_set_parent, + .fullscreen_requested = desktop_surface_fullscreen_requested, + .maximized_requested = desktop_surface_maximized_requested, + .minimized_requested = desktop_surface_minimized_requested, + .ping_timeout = desktop_surface_ping_timeout, + .pong = desktop_surface_pong, + .set_xwayland_position = desktop_surface_set_xwayland_position, + .set_window_icon = desktop_surface_set_window_icon, +}; + +/* ************************ * + * end of libweston-desktop * + * ************************ */ + +static struct shell_output * +find_shell_output_from_weston_output(struct desktop_shell *shell, + struct weston_output *output) +{ + struct shell_output *shell_output; + + wl_list_for_each(shell_output, &shell->output_list, link) { + if (shell_output->output == output) + return shell_output; + } + + return NULL; +} + +static void +move_binding(struct weston_pointer *pointer, const struct timespec *time, + uint32_t button, void *data) +{ + struct weston_surface *focus; + struct weston_surface *surface; + struct shell_surface *shsurf; + + if (pointer->focus == NULL) + return; + + focus = pointer->focus->surface; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return; + + surface_move(shsurf, pointer, false); +} + +static void +maximize_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t button, void *data) +{ + struct weston_surface *focus = keyboard->focus; + struct weston_surface *surface; + struct shell_surface *shsurf; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL) + return; + + set_maximized(shsurf, !weston_desktop_surface_get_maximized(shsurf->desktop_surface)); +} + +static void +fullscreen_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t button, void *data) +{ + struct weston_surface *focus = keyboard->focus; + struct weston_surface *surface; + struct shell_surface *shsurf; + bool fullscreen; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL) + return; + + fullscreen = + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface); + + set_fullscreen(shsurf, !fullscreen, NULL); +} + +static void +touch_move_binding(struct weston_touch *touch, const struct timespec *time, void *data) +{ + struct weston_surface *focus; + struct weston_surface *surface; + struct shell_surface *shsurf; + + if (touch->focus == NULL) + return; + + focus = touch->focus->surface; + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return; + + surface_touch_move(shsurf, touch); +} + +static void +resize_binding(struct weston_pointer *pointer, const struct timespec *time, + uint32_t button, void *data) +{ + struct weston_surface *focus; + struct weston_surface *surface; + uint32_t edges = 0; + int32_t x, y; + struct shell_surface *shsurf; + + if (pointer->focus == NULL) + return; + + focus = pointer->focus->surface; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL || + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || + weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + return; + + weston_view_from_global(shsurf->view, + wl_fixed_to_int(pointer->grab_x), + wl_fixed_to_int(pointer->grab_y), + &x, &y); + + if (x < surface->width / 3) + edges |= WL_SHELL_SURFACE_RESIZE_LEFT; + else if (x < 2 * surface->width / 3) + edges |= 0; + else + edges |= WL_SHELL_SURFACE_RESIZE_RIGHT; + + if (y < surface->height / 3) + edges |= WL_SHELL_SURFACE_RESIZE_TOP; + else if (y < 2 * surface->height / 3) + edges |= 0; + else + edges |= WL_SHELL_SURFACE_RESIZE_BOTTOM; + + surface_resize(shsurf, pointer, edges); +} + +static void +surface_opacity_binding(struct weston_pointer *pointer, + const struct timespec *time, + struct weston_pointer_axis_event *event, + void *data) +{ + float step = 0.005; + struct shell_surface *shsurf; + struct weston_surface *focus = pointer->focus->surface; + struct weston_surface *surface; + + /* XXX: broken for windows containing sub-surfaces */ + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (!shsurf) + return; + + shsurf->view->alpha -= event->value * step; + + if (shsurf->view->alpha > 1.0) + shsurf->view->alpha = 1.0; + if (shsurf->view->alpha < step) + shsurf->view->alpha = step; + + weston_view_geometry_dirty(shsurf->view); + weston_surface_damage(surface); +} + +static void +do_zoom(struct weston_seat *seat, const struct timespec *time, uint32_t key, + uint32_t axis, double value) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_output *output; + float increment; + + if (!pointer) { + weston_log("Zoom hotkey pressed but seat '%s' contains no pointer.\n", seat->seat_name); + return; + } + + wl_list_for_each(output, &compositor->output_list, link) { + if (pixman_region32_contains_point(&output->region, + wl_fixed_to_double(pointer->x), + wl_fixed_to_double(pointer->y), + NULL)) { + if (key == KEY_PAGEUP) + increment = output->zoom.increment; + else if (key == KEY_PAGEDOWN) + increment = -output->zoom.increment; + else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) + /* For every pixel zoom 20th of a step */ + increment = output->zoom.increment * + -value / 20.0; + else + increment = 0; + + output->zoom.level += increment; + + if (output->zoom.level < 0.0) + output->zoom.level = 0.0; + else if (output->zoom.level > output->zoom.max_level) + output->zoom.level = output->zoom.max_level; + + if (!output->zoom.active) { + if (output->zoom.level <= 0.0) + continue; + weston_output_activate_zoom(output, seat); + } + + output->zoom.spring_z.target = output->zoom.level; + + weston_output_update_zoom(output); + } + } +} + +static void +zoom_axis_binding(struct weston_pointer *pointer, const struct timespec *time, + struct weston_pointer_axis_event *event, + void *data) +{ + do_zoom(pointer->seat, time, 0, event->axis, event->value); +} + +static void +zoom_key_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + do_zoom(keyboard->seat, time, key, 0, 0); +} + +static void +terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + struct weston_compositor *compositor = data; + + weston_compositor_exit(compositor); +} + +static void +rotate_grab_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct rotate_grab *rotate = + container_of(grab, struct rotate_grab, base.grab); + struct weston_pointer *pointer = grab->pointer; + struct shell_surface *shsurf = rotate->base.shsurf; + struct weston_surface *surface; + float cx, cy, dx, dy, cposx, cposy, dposx, dposy, r; + + weston_pointer_move(pointer, event); + + if (!shsurf) + return; + + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + + cx = 0.5f * surface->width; + cy = 0.5f * surface->height; + + dx = wl_fixed_to_double(pointer->x) - rotate->center.x; + dy = wl_fixed_to_double(pointer->y) - rotate->center.y; + r = sqrtf(dx * dx + dy * dy); + + wl_list_remove(&shsurf->rotation.transform.link); + weston_view_geometry_dirty(shsurf->view); + + if (r > 20.0f) { + struct weston_matrix *matrix = + &shsurf->rotation.transform.matrix; + + weston_matrix_init(&rotate->rotation); + weston_matrix_rotate_xy(&rotate->rotation, dx / r, dy / r); + + weston_matrix_init(matrix); + weston_matrix_translate(matrix, -cx, -cy, 0.0f); + weston_matrix_multiply(matrix, &shsurf->rotation.rotation); + weston_matrix_multiply(matrix, &rotate->rotation); + weston_matrix_translate(matrix, cx, cy, 0.0f); + + wl_list_insert( + &shsurf->view->geometry.transformation_list, + &shsurf->rotation.transform.link); + } else { + wl_list_init(&shsurf->rotation.transform.link); + weston_matrix_init(&shsurf->rotation.rotation); + weston_matrix_init(&rotate->rotation); + } + + /* We need to adjust the position of the surface + * in case it was resized in a rotated state before */ + cposx = shsurf->view->geometry.x + cx; + cposy = shsurf->view->geometry.y + cy; + dposx = rotate->center.x - cposx; + dposy = rotate->center.y - cposy; + if (dposx != 0.0f || dposy != 0.0f) { + weston_view_set_position(shsurf->view, + shsurf->view->geometry.x + dposx, + shsurf->view->geometry.y + dposy); + } + + /* Repaint implies weston_view_update_transform(), which + * lazily applies the damage due to rotation update. + */ + weston_compositor_schedule_repaint(surface->compositor); +} + +static void +rotate_grab_button(struct weston_pointer_grab *grab, + const struct timespec *time, + uint32_t button, uint32_t state_w) +{ + struct rotate_grab *rotate = + container_of(grab, struct rotate_grab, base.grab); + struct weston_pointer *pointer = grab->pointer; + struct shell_surface *shsurf = rotate->base.shsurf; + enum wl_pointer_button_state state = state_w; + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + if (shsurf) + weston_matrix_multiply(&shsurf->rotation.rotation, + &rotate->rotation); + shell_grab_end(&rotate->base); + free(rotate); + } +} + +static void +rotate_grab_cancel(struct weston_pointer_grab *grab) +{ + struct rotate_grab *rotate = + container_of(grab, struct rotate_grab, base.grab); + + shell_grab_end(&rotate->base); + free(rotate); +} + +static const struct weston_pointer_grab_interface rotate_grab_interface = { + noop_grab_focus, + rotate_grab_motion, + rotate_grab_button, + noop_grab_axis, + noop_grab_axis_source, + noop_grab_frame, + rotate_grab_cancel, +}; + +static void +surface_rotate(struct shell_surface *shsurf, struct weston_pointer *pointer) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct rotate_grab *rotate; + float dx, dy; + float r; + + rotate = malloc(sizeof *rotate); + if (!rotate) + return; + + weston_view_to_global_float(shsurf->view, + surface->width * 0.5f, + surface->height * 0.5f, + &rotate->center.x, &rotate->center.y); + + dx = wl_fixed_to_double(pointer->x) - rotate->center.x; + dy = wl_fixed_to_double(pointer->y) - rotate->center.y; + r = sqrtf(dx * dx + dy * dy); + if (r > 20.0f) { + struct weston_matrix inverse; + + weston_matrix_init(&inverse); + weston_matrix_rotate_xy(&inverse, dx / r, -dy / r); + weston_matrix_multiply(&shsurf->rotation.rotation, &inverse); + + weston_matrix_init(&rotate->rotation); + weston_matrix_rotate_xy(&rotate->rotation, dx / r, dy / r); + } else { + weston_matrix_init(&shsurf->rotation.rotation); + weston_matrix_init(&rotate->rotation); + } + + shell_grab_start(&rotate->base, &rotate_grab_interface, shsurf, + pointer, WESTON_DESKTOP_SHELL_CURSOR_ARROW); +} + +static void +rotate_binding(struct weston_pointer *pointer, const struct timespec *time, + uint32_t button, void *data) +{ + struct weston_surface *focus; + struct weston_surface *base_surface; + struct shell_surface *surface; + + if (pointer->focus == NULL) + return; + + focus = pointer->focus->surface; + + base_surface = weston_surface_get_main_surface(focus); + if (base_surface == NULL) + return; + + surface = get_shell_surface(base_surface); + if (surface == NULL || + weston_desktop_surface_get_fullscreen(surface->desktop_surface) || + weston_desktop_surface_get_maximized(surface->desktop_surface)) + return; + + surface_rotate(surface, pointer); +} + +/* Move all fullscreen layers down to the current workspace and hide their + * black views. The surfaces' state is set to both fullscreen and lowered, + * and this is reversed when such a surface is re-configured, see + * shell_configure_fullscreen() and shell_ensure_fullscreen_black_view(). + * + * lowering_output = NULL - Lower on all outputs, else only lower on the + * specified output. + * + * This should be used when implementing shell-wide overlays, such as + * the alt-tab switcher, which need to de-promote fullscreen layers. */ +void +lower_fullscreen_layer(struct desktop_shell *shell, + struct weston_output *lowering_output) +{ + struct workspace *ws; + struct weston_view *view, *prev; + + ws = get_current_workspace(shell); + wl_list_for_each_reverse_safe(view, prev, + &shell->fullscreen_layer.view_list.link, + layer_link.link) { + struct shell_surface *shsurf = get_shell_surface(view->surface); + + if (!shsurf) + continue; + + /* Only lower surfaces which have lowering_output as their fullscreen + * output, unless a NULL output asks for lowering on all outputs. + */ + if (lowering_output && (shsurf->fullscreen_output != lowering_output)) + continue; + + /* We can have a non-fullscreen popup for a fullscreen surface + * in the fullscreen layer. */ + if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)) { + /* Hide the black view */ + weston_layer_entry_remove(&shsurf->fullscreen.black_view->layer_link); + wl_list_init(&shsurf->fullscreen.black_view->layer_link.link); + weston_view_damage_below(shsurf->fullscreen.black_view); + + } + + /* Lower the view to the workspace layer */ + weston_layer_entry_remove(&view->layer_link); + weston_layer_entry_insert(&ws->layer.view_list, &view->layer_link); + weston_view_damage_below(view); + weston_surface_damage(view->surface); + + shsurf->state.lowered = true; + } +} + +static struct shell_surface *get_last_child(struct shell_surface *shsurf) +{ + struct shell_surface *shsurf_child; + + wl_list_for_each_reverse(shsurf_child, &shsurf->children_list, children_link) { + if (weston_view_is_mapped(shsurf_child->view)) + return shsurf_child; + } + + return NULL; +} + +void +activate(struct desktop_shell *shell, struct weston_view *view, + struct weston_seat *seat, uint32_t flags) +{ + struct weston_surface *es = view->surface; + struct weston_surface *main_surface; + struct focus_state *state; + struct shell_surface *shsurf, *shsurf_child; + + main_surface = weston_surface_get_main_surface(es); + shsurf = get_shell_surface(main_surface); + assert(shsurf); + + shsurf_child = get_last_child(shsurf); + if (shsurf_child) { + /* Activate last xdg child instead of parent. */ + activate(shell, shsurf_child->view, seat, flags); + return; + } + + /* Only demote fullscreen surfaces on the output of activated shsurf. + * Leave fullscreen surfaces on unrelated outputs alone. */ + if (shsurf->output) + lower_fullscreen_layer(shell, shsurf->output); + + weston_view_activate(view, seat, flags); + + state = ensure_focus_state(shell, seat); + if (state == NULL) + return; + + focus_state_set_focus(state, es); + + if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) && + flags & WESTON_ACTIVATE_FLAG_CONFIGURE) + shell_configure_fullscreen(shsurf); + + /* Update the surface’s layer. This brings it to the top of the stacking + * order as appropriate. */ + shell_surface_update_layer(shsurf); +} + +/* no-op func for checking black surface */ +static void +black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) +{ +} + +static bool +is_black_surface_view(struct weston_view *view, struct weston_view **fs_view) +{ + struct weston_surface *surface = view->surface; + + if (surface->committed == black_surface_committed) { + if (fs_view) + *fs_view = surface->committed_private; + return true; + } + return false; +} + +static void +activate_binding(struct weston_seat *seat, + struct desktop_shell *shell, + struct weston_view *focus_view, + uint32_t flags) +{ + struct weston_view *main_view; + struct weston_surface *main_surface; + + if (!focus_view) + return; + + if (is_black_surface_view(focus_view, &main_view)) + focus_view = main_view; + + main_surface = weston_surface_get_main_surface(focus_view->surface); + if (!get_shell_surface(main_surface)) + return; + + activate(shell, focus_view, seat, flags); +} + +static void +click_to_activate_binding(struct weston_pointer *pointer, + const struct timespec *time, + uint32_t button, void *data) +{ + if (pointer->grab != &pointer->default_grab) + return; + if (pointer->focus == NULL) + return; + + activate_binding(pointer->seat, data, pointer->focus, + WESTON_ACTIVATE_FLAG_CLICKED | + WESTON_ACTIVATE_FLAG_CONFIGURE); +} + +static void +touch_to_activate_binding(struct weston_touch *touch, + const struct timespec *time, + void *data) +{ + if (touch->grab != &touch->default_grab) + return; + if (touch->focus == NULL) + return; + + activate_binding(touch->seat, data, touch->focus, + WESTON_ACTIVATE_FLAG_CONFIGURE); +} + +static void +shell_backend_request_window_activate(struct weston_surface *surface, struct weston_seat *seat) +{ + struct weston_view *view; + struct shell_surface *shsurf; + struct desktop_shell *shell; + + view = NULL; + wl_list_for_each(view, &surface->views, surface_link) + break; + if (!view) + return; + + shsurf = get_shell_surface(surface); + if (!shsurf) + return; + + shell = shell_surface_get_shell(shsurf); + if (!shell) + return; + + activate_binding(seat, shell, view, + WESTON_ACTIVATE_FLAG_CLICKED | + WESTON_ACTIVATE_FLAG_CONFIGURE); +} + +static void +shell_backend_request_window_close(struct weston_surface *surface) +{ + struct shell_surface *shsurf; + const struct weston_xwayland_surface_api *api; + + shsurf = get_shell_surface(surface); + if (!shsurf) + return; + + api = shsurf->shell->xwayland_surface_api; + if (!api) { + api = weston_xwayland_surface_get_api(shsurf->shell->compositor); + shsurf->shell->xwayland_surface_api = api; + } + if (api && api->is_xwayland_surface(surface)) { + api->close_window(surface); + } else { + weston_desktop_surface_close(shsurf->desktop_surface); + } +} + +static void +transform_handler(struct wl_listener *listener, void *data) +{ + struct weston_surface *surface = data; + struct shell_surface *shsurf = get_shell_surface(surface); + const struct weston_xwayland_surface_api *api; + int x, y; + + if (!shsurf) + return; + + api = shsurf->shell->xwayland_surface_api; + if (!api) { + api = weston_xwayland_surface_get_api(shsurf->shell->compositor); + shsurf->shell->xwayland_surface_api = api; + } + + if (!api || !api->is_xwayland_surface(surface)) + return; + + if (!weston_view_is_mapped(shsurf->view)) + return; + + x = shsurf->view->geometry.x; + y = shsurf->view->geometry.y; + + api->send_position(surface, x, y); +} + +static void +center_on_output(struct weston_view *view, struct weston_output *output) +{ + int32_t surf_x, surf_y, width, height; + float x, y; + + if (!output) { + weston_view_set_position(view, 0, 0); + return; + } + + surface_subsurfaces_boundingbox(view->surface, &surf_x, &surf_y, &width, &height); + + x = output->x + (output->width - width) / 2 - surf_x / 2; + y = output->y + (output->height - height) / 2 - surf_y / 2; + + weston_view_set_position(view, x, y); +} + +static void +weston_view_set_initial_position(struct weston_view *view, + struct desktop_shell *shell) +{ + struct weston_compositor *compositor = shell->compositor; + int32_t range_x, range_y; + int32_t x, y; + struct weston_output *target_output = NULL; + pixman_rectangle32_t area; + + /* As a heuristic place the new window on the same output as the + * pointer. Falling back to the output containing 0, 0. + * + * TODO: Do something clever for touch too? + */ + /* + * This code does not work well in RDP RAIL mode. Since + * pointer position outside of RAIL window in client is + * not known to RDP server side. + * + int ix = 0, iy = 0; + struct weston_seat *seat; + wl_list_for_each(seat, &compositor->seat_list, link) { + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (pointer) { + ix = wl_fixed_to_int(pointer->x); + iy = wl_fixed_to_int(pointer->y); + break; + } + } + + struct weston_output *output; + wl_list_for_each(output, &compositor->output_list, link) { + if (pixman_region32_contains_point(&output->region, ix, iy, NULL)) { + target_output = output; + break; + } + } + */ + /* Because pointer position is not known in RAIL mode, it can end up + not finding the output where pointer is, thus use default monitor + in that case rather than randomly placed (which can end up outside + of work area. And only if no default output found, place randomly */ + if (!target_output && shell->rdprail_api->get_primary_output) { + target_output = shell->rdprail_api->get_primary_output(shell->rdp_backend); + } + if (!target_output) { + target_output = get_default_output(compositor); + } + + if (!target_output) { + weston_view_set_position(view, 10 + random() % 400, + 10 + random() % 400); + return; + } + + /* Valid range within output where the surface will still be onscreen. + * If this is negative it means that the surface is bigger than + * output. + */ + get_output_work_area(shell, target_output, &area); + + x = area.x; + y = area.y; + range_x = area.width - view->surface->width; + range_y = area.height - view->surface->height; + + if (range_x > 0) + x += random() % range_x; + + if (range_y > 0) + y += random() % range_y; + + shell_rdp_debug_verbose(shell, "%s: view:%p, (%d, %d)\n", + __func__, view, x, y); + + weston_view_set_position(view, x, y); +} + +static void +force_kill_binding(struct weston_keyboard *keyboard, + const struct timespec *time, uint32_t key, void *data) +{ + struct weston_surface *focus_surface; + struct wl_client *client; + struct desktop_shell *shell = data; + struct weston_compositor *compositor = shell->compositor; + pid_t pid; + + focus_surface = keyboard->focus; + if (!focus_surface) + return; + + wl_signal_emit(&compositor->kill_signal, focus_surface); + + client = wl_resource_get_client(focus_surface->resource); + wl_client_get_credentials(client, &pid, NULL, NULL); + + /* Skip clients that we launched ourselves (the credentials of + * the socketpair is ours) */ + if (pid == getpid()) + return; + + kill(pid, SIGKILL); +} + +static void +shell_reposition_view_on_output_change(struct weston_view *view) +{ + struct weston_output *output, *first_output; + struct weston_compositor *ec = view->surface->compositor; + struct shell_surface *shsurf; + float x, y; + int visible; + + if (wl_list_empty(&ec->output_list)) + return; + + x = view->geometry.x; + y = view->geometry.y; + + /* At this point the destroyed output is not in the list anymore. + * If the view is still visible somewhere, we leave where it is, + * otherwise, move it to the first output. */ + visible = 0; + wl_list_for_each(output, &ec->output_list, link) { + if (pixman_region32_contains_point(&output->region, + x, y, NULL)) { + visible = 1; + break; + } + } + + if (!visible) { + first_output = container_of(ec->output_list.next, + struct weston_output, link); + + x = first_output->x + first_output->width / 4; + y = first_output->y + first_output->height / 4; + + weston_view_set_position(view, x, y); + } else { + weston_view_geometry_dirty(view); + } + + + shsurf = get_shell_surface(view->surface); + if (!shsurf) + return; + + shsurf->saved_position_valid = false; + //this sets window size to 0x0 when output is removed. + //set_maximized(shsurf, false); + //set_fullscreen(shsurf, false, NULL); +} + +void +shell_for_each_layer(struct desktop_shell *shell, + shell_for_each_layer_func_t func, void *data) +{ + struct workspace **ws; + + func(shell, &shell->fullscreen_layer, data); + + wl_array_for_each(ws, &shell->workspaces.array) + func(shell, &(*ws)->layer, data); +} + +static void +shell_output_changed_move_layer(struct desktop_shell *shell, + struct weston_layer *layer, + void *data) +{ + struct weston_view *view; + + wl_list_for_each(view, &layer->view_list.link, layer_link.link) + shell_reposition_view_on_output_change(view); + +} + +static void +handle_output_destroy(struct wl_listener *listener, void *data) +{ + struct shell_output *output_listener = + container_of(listener, struct shell_output, destroy_listener); + struct desktop_shell *shell = output_listener->shell; + + shell_for_each_layer(shell, shell_output_changed_move_layer, NULL); + + wl_list_remove(&output_listener->destroy_listener.link); + wl_list_remove(&output_listener->link); + free(output_listener); +} + +static void +create_shell_output(struct desktop_shell *shell, + struct weston_output *output) +{ + struct shell_output *shell_output; + + shell_output = zalloc(sizeof *shell_output); + if (shell_output == NULL) + return; + + shell_output->output = output; + shell_output->shell = shell; + shell_output->destroy_listener.notify = handle_output_destroy; + wl_signal_add(&output->destroy_signal, + &shell_output->destroy_listener); + wl_list_insert(shell->output_list.prev, &shell_output->link); + + if (wl_list_length(&shell->output_list) == 1) + shell_for_each_layer(shell, + shell_output_changed_move_layer, NULL); + + shell_output->desktop_workarea.x = output->x; + shell_output->desktop_workarea.y = output->y; + shell_output->desktop_workarea.width = output->width; + shell_output->desktop_workarea.height = output->height; +} + +static void +handle_output_create(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell = + container_of(listener, struct desktop_shell, output_create_listener); + struct weston_output *output = (struct weston_output *)data; + + create_shell_output(shell, output); +} + +static void +handle_output_move_layer(struct desktop_shell *shell, + struct weston_layer *layer, void *data) +{ + struct weston_output *output = data; + struct weston_view *view; + float x, y; + + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { + if (view->output != output) + continue; + + x = view->geometry.x + output->move_x; + y = view->geometry.y + output->move_y; + weston_view_set_position(view, x, y); + } +} + +static void +handle_output_move(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell; + + shell = container_of(listener, struct desktop_shell, + output_move_listener); + + shell_for_each_layer(shell, handle_output_move_layer, data); +} + +static void +setup_output_destroy_handler(struct weston_compositor *ec, + struct desktop_shell *shell) +{ + struct weston_output *output; + + wl_list_init(&shell->output_list); + wl_list_for_each(output, &ec->output_list, link) + create_shell_output(shell, output); + + shell->output_create_listener.notify = handle_output_create; + wl_signal_add(&ec->output_created_signal, + &shell->output_create_listener); + + shell->output_move_listener.notify = handle_output_move; + wl_signal_add(&ec->output_moved_signal, &shell->output_move_listener); +} + +static void +shell_destroy(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell = + container_of(listener, struct desktop_shell, destroy_listener); + struct workspace **ws; + struct shell_output *shell_output, *tmp; + + wl_list_remove(&shell->destroy_listener.link); + wl_list_remove(&shell->transform_listener.link); + + app_list_destroy(shell); + text_backend_destroy(shell->text_backend); + input_panel_destroy(shell); + + wl_list_for_each_safe(shell_output, tmp, &shell->output_list, link) { + wl_list_remove(&shell_output->destroy_listener.link); + wl_list_remove(&shell_output->link); + free(shell_output); + } + + wl_list_remove(&shell->output_create_listener.link); + wl_list_remove(&shell->output_move_listener.link); + + wl_array_for_each(ws, &shell->workspaces.array) + workspace_destroy(*ws); + wl_array_release(&shell->workspaces.array); + + if (shell->image_default_app_icon) + pixman_image_unref(shell->image_default_app_icon); + + if (shell->image_default_app_overlay_icon) + pixman_image_unref(shell->image_default_app_overlay_icon); + + if (shell->debug) + weston_log_scope_destroy(shell->debug); + + free(shell); +} + +static void +shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) +{ + uint32_t mod; + + if (shell->allow_zap) + weston_compositor_add_key_binding(ec, KEY_BACKSPACE, + MODIFIER_CTRL | MODIFIER_ALT, + terminate_binding, ec); + + /* fixed bindings */ + weston_compositor_add_button_binding(ec, BTN_LEFT, 0, + click_to_activate_binding, + shell); + weston_compositor_add_button_binding(ec, BTN_RIGHT, 0, + click_to_activate_binding, + shell); + weston_compositor_add_touch_binding(ec, 0, + touch_to_activate_binding, + shell); + + mod = shell->binding_modifier; + if (!mod) + return; + + /* This binding is not configurable, but is only enabled if there is a + * valid binding modifier. */ + weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, + MODIFIER_SUPER | MODIFIER_ALT, + surface_opacity_binding, NULL); + + weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, + mod, zoom_axis_binding, + NULL); + + weston_compositor_add_key_binding(ec, KEY_PAGEUP, mod, + zoom_key_binding, NULL); + weston_compositor_add_key_binding(ec, KEY_PAGEDOWN, mod, + zoom_key_binding, NULL); + weston_compositor_add_key_binding(ec, KEY_M, mod | MODIFIER_SHIFT, + maximize_binding, NULL); + weston_compositor_add_key_binding(ec, KEY_F, mod | MODIFIER_SHIFT, + fullscreen_binding, NULL); + weston_compositor_add_button_binding(ec, BTN_LEFT, mod, move_binding, + shell); + weston_compositor_add_touch_binding(ec, mod, touch_move_binding, shell); + weston_compositor_add_button_binding(ec, BTN_RIGHT, mod, + resize_binding, shell); + weston_compositor_add_button_binding(ec, BTN_LEFT, + mod | MODIFIER_SHIFT, + resize_binding, shell); + + if (ec->capabilities & WESTON_CAP_ROTATION_ANY) + weston_compositor_add_button_binding(ec, BTN_MIDDLE, mod, + rotate_binding, NULL); + + weston_compositor_add_key_binding(ec, KEY_K, mod, + force_kill_binding, shell); + + weston_install_debug_key_binding(ec, mod); +} + +static void +handle_seat_created(struct wl_listener *listener, void *data) +{ + struct weston_seat *seat = data; + + create_shell_seat(seat); +} + +struct shell_workarea_change { + struct weston_output *output; + pixman_rectangle32_t old_workarea; + pixman_rectangle32_t new_workarea; +}; + +static void +shell_reposition_view_on_workarea_change(struct weston_view *view, void *data) +{ + struct shell_surface *shsurf; + struct shell_workarea_change *workarea_change = (struct shell_workarea_change *)data; + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)view->surface->backend_state; + + weston_view_geometry_dirty(view); + + shsurf = get_shell_surface(view->surface); + if (!shsurf) + return; + + if (view->output != workarea_change->output) + return; + + shsurf->saved_position_valid = false; + if (shsurf->state.maximized) { + set_maximized(shsurf, true); + } else if (shsurf->state.fullscreen) { + set_fullscreen(shsurf, true, NULL); + } else { + bool posDirty = false; + + /* Force update window state at next window update. + When workarea changed, there is the case Windows client moves the RAIL window + but if it does, the server has no way to tell where it goes, thus here forcing + backend to resend window state to client, this force window state, especially + window position keeps in sync between server and client. */ + rail_state->forceUpdateWindowState = true; + + /* If view's upper-left is within 10% of bottom-right of workarea boundary, adjust the position. */ + int new_workarea_width = (int)workarea_change->new_workarea.width; + int x = (int)view->geometry.x - view->output->x; + if (x + (new_workarea_width / 10) > new_workarea_width) { + x += (new_workarea_width - workarea_change->old_workarea.width); + if (x < 0) + x = 0; + else if (x > workarea_change->new_workarea.x + new_workarea_width) + x = workarea_change->new_workarea.x + new_workarea_width / 2; + posDirty = true; + } + + int new_workarea_height = (int)workarea_change->new_workarea.height; + int y = (int)view->geometry.y - view->output->y; + if (y + (new_workarea_height / 10) > new_workarea_height) { + y += (new_workarea_height - workarea_change->old_workarea.height); + if (y < 0) + y = 0; + else if (y > workarea_change->new_workarea.y + new_workarea_height) + y = workarea_change->new_workarea.y + new_workarea_height / 2; + posDirty = true; + } + + if (posDirty) { + shell_rdp_debug(shsurf->shell, "shell_reposition_view_on_workarea_change(): view %p, (%d,%d) -> (%d,%d)\n", + view, (int)view->geometry.x, (int)view->geometry.y, view->output->x + x, view->output->y + y); + + weston_view_set_position(view, view->output->x + x, view->output->y + y); + } + } +} + +static void +shell_workarea_changed_layer(struct desktop_shell *shell, + struct weston_layer *layer, + void *data) +{ + struct weston_view *view; + + wl_list_for_each(view, &layer->view_list.link, layer_link.link) + shell_reposition_view_on_workarea_change(view, data); +} + +static void +shell_backend_set_desktop_workarea(struct weston_output *output, void *context, pixman_rectangle32_t *workarea) +{ + struct desktop_shell *shell = (struct desktop_shell *)context; + struct shell_output *shell_output = + find_shell_output_from_weston_output(shell, output); + if (shell_output) { + struct shell_workarea_change workarea_change; + workarea_change.output = output; + workarea_change.old_workarea = shell_output->desktop_workarea; + workarea_change.new_workarea = *workarea; + + shell_output->desktop_workarea = *workarea; + shell_for_each_layer(shell, shell_workarea_changed_layer, (void*)&workarea_change); + } +} + +static pid_t +shell_backend_get_app_id(struct weston_surface *surface, char *app_id, size_t app_id_size, char *image_name, size_t image_name_size) +{ + struct weston_desktop_surface *desktop_surface; + struct shell_surface *shsurf; + const struct weston_xwayland_surface_api *api; + pid_t pid; + const char *id; + char *class_name; + char path[32] = {}; + + assert(app_id); + assert(app_id_size); + assert(image_name); + assert(image_name_size); + + app_id[0] = '\0'; + image_name[0] = '\0'; + + desktop_surface = weston_surface_get_desktop_surface(surface); + if (!desktop_surface) + return -1; + + /* obtain application id specified via wayland interface */ + id = weston_desktop_surface_get_app_id(desktop_surface); + if (id) { + strncpy(app_id, id, app_id_size); + } else { + /* if app_id is not specified via wayland interface, + obtain class name from X server for X app, and use as app_id */ + shsurf = weston_desktop_surface_get_user_data(desktop_surface); + if (shsurf) { + api = shsurf->shell->xwayland_surface_api; + if (!api) { + api = weston_xwayland_surface_get_api(shsurf->shell->compositor); + shsurf->shell->xwayland_surface_api = api; + } + if (api && api->is_xwayland_surface(surface)) { + class_name = api->get_class_name(surface); + if (class_name) { + strncpy(app_id, class_name, app_id_size); + free(class_name); + } + } + } + } + + /* obtain pid for execuable path */ + pid = weston_desktop_surface_get_pid(desktop_surface); + if (pid > 0 && !is_system_distro()) { + sprintf(path, "/proc/%d/exe", pid); + if (readlink(path, image_name, image_name_size) < 0) + weston_log("shell_backend_get_app_id: readlink failed %s:%s\n", path, strerror(errno)); + } + + /* if app_id is not specified by above, use execuable path as app_id */ + if (app_id[0] == '\0' && image_name[0] != '\0') + strncpy(app_id, image_name, app_id_size); + + return pid; +} + +static bool +shell_backend_start_app_list_update(void *shell_context, char *clientLanguageId) +{ + struct desktop_shell *shell = (struct desktop_shell *)shell_context; + return app_list_start_backend_update(shell, clientLanguageId); +} + +static void +shell_backend_stop_app_list_update(void *shell_context) +{ + struct desktop_shell *shell = (struct desktop_shell *)shell_context; + app_list_stop_backend_update(shell); +} + +static void +shell_backend_request_window_icon(struct weston_surface *surface) +{ + struct shell_surface *shsurf = get_shell_surface(surface); + + if (!shsurf) + return; + + /* reset icon state and send to client at next surface commit */ + shsurf->icon.is_icon_set = false; + shsurf->icon.is_default_icon_used = false; +} + +static struct wl_client * +shell_backend_launch_shell_process(void *shell_context, char *exec_name) +{ + struct desktop_shell *shell = (struct desktop_shell *)shell_context; + return weston_client_start(shell->compositor, exec_name); +} + +static const struct weston_rdprail_shell_api rdprail_shell_api = { + .request_window_restore = shell_backend_request_window_restore, + .request_window_minimize = shell_backend_request_window_minimize, + .request_window_maximize = shell_backend_request_window_maximize, + .request_window_move = shell_backend_request_window_move, + .request_window_snap = shell_backend_request_window_snap, + .request_window_activate = shell_backend_request_window_activate, + .request_window_close = shell_backend_request_window_close, + .set_desktop_workarea = shell_backend_set_desktop_workarea, + .get_window_app_id = shell_backend_get_app_id, + .start_app_list_update = shell_backend_start_app_list_update, + .stop_app_list_update = shell_backend_stop_app_list_update, + .request_window_icon = shell_backend_request_window_icon, + .request_launch_shell_process = shell_backend_launch_shell_process, +}; + +WL_EXPORT int +wet_shell_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + struct weston_seat *seat; + struct desktop_shell *shell; + struct workspace **pws; + unsigned int i; + char *debug_level; + char *icon_path; + + shell = zalloc(sizeof *shell); + if (shell == NULL) + return -1; + + shell->compositor = ec; + + shell->debug = weston_log_ctx_add_log_scope(ec->weston_log_ctx, + "rdprail-shell", + "Debug messages from RDP-RAIL shell\n", + NULL, NULL, NULL); + debug_level = getenv("WESTON_RDPRAIL_SHELL_DEBUG_LEVEL"); + if (debug_level) { + shell->debugLevel = atoi(debug_level); + if (shell->debugLevel > RDPRAIL_SHELL_DEBUG_LEVEL_VERBOSE) + shell->debugLevel = RDPRAIL_SHELL_DEBUG_LEVEL_VERBOSE; + } else { + shell->debugLevel = RDPRAIL_SHELL_DEBUG_LEVEL_DEFAULT; + } + weston_log("RDPRAIL-shell: WESTON_RDPRAIL_SHELL_DEBUG_LEVEL: %d.\n", shell->debugLevel); + + + /* this make sure rdprail-shell to be used with only backend-rdp */ + shell->rdprail_api = weston_rdprail_get_api(ec); + if (!shell->rdprail_api) { + weston_log("Failed to obrain rdprail API.\n"); + free(shell); + return 0; + } + + if (!weston_compositor_add_destroy_listener_once(ec, + &shell->destroy_listener, + shell_destroy)) { + free(shell); + return 0; + } + + shell_configuration(shell); + + shell->transform_listener.notify = transform_handler; + wl_signal_add(&ec->transform_signal, &shell->transform_listener); + + weston_layer_init(&shell->fullscreen_layer, ec); + + weston_layer_set_position(&shell->fullscreen_layer, + WESTON_LAYER_POSITION_FULLSCREEN); + + wl_array_init(&shell->workspaces.array); + wl_list_init(&shell->workspaces.client_list); + + if (input_panel_setup(shell) < 0) + return -1; + + shell->text_backend = text_backend_init(ec); + if (!shell->text_backend) + return -1; + + for (i = 0; i < shell->workspaces.num; i++) { + pws = wl_array_add(&shell->workspaces.array, sizeof *pws); + if (pws == NULL) + return -1; + + *pws = workspace_create(shell); + if (*pws == NULL) + return -1; + } + activate_workspace(shell, 0); + + weston_layer_init(&shell->minimized_layer, ec); + + shell->desktop = weston_desktop_create(ec, &shell_desktop_api, shell); + if (!shell->desktop) + return -1; + + setup_output_destroy_handler(ec, shell); + + wl_list_for_each(seat, &ec->seat_list, link) + handle_seat_created(NULL, seat); + shell->seat_create_listener.notify = handle_seat_created; + wl_signal_add(&ec->seat_created_signal, &shell->seat_create_listener); + + screenshooter_create(ec); + + shell_add_bindings(ec, shell); + + clock_gettime(CLOCK_MONOTONIC, &shell->startup_time); + + shell->distroName = getenv("WSL2_DISTRO_NAME"); + if (!shell->distroName) + shell->distroName = getenv("WSL_DISTRO_NAME"); + if (shell->distroName) { + shell->distroNameLength = strlen(shell->distroName); + shell_rdp_debug(shell, "%s: distro name %s (%ld)\n", + __func__, shell->distroName, shell->distroNameLength); + } + + if (getenv("WESTON_RDPRAIL_SHELL_DISABLE_APPEND_DISTRONAME_STARTMENU")) + shell->is_appid_with_distro_name = false; + else + shell->is_appid_with_distro_name = true; + + icon_path = getenv("WSL2_DEFAULT_APP_ICON"); + if (icon_path && (strcmp(icon_path, "disabled") != 0)) + shell->image_default_app_icon = load_image(icon_path); + + icon_path = getenv("WSL2_DEFAULT_APP_OVERLAY_ICON"); + if (icon_path && (strcmp(icon_path, "disabled") != 0)) + shell->image_default_app_overlay_icon = load_image(icon_path); + + if (getenv("WESTON_RDPRAIL_SHELL_DISABLE_BLEND_OVERLAY_ICON_TASKBAR")) + shell->is_blend_overlay_icon_taskbar = false; + else + shell->is_blend_overlay_icon_taskbar = true; + + if (getenv("WESTON_RDPRAIL_SHELL_DISABLE_BLEND_OVERLAY_ICON_APPLIST")) + shell->is_blend_overlay_icon_app_list = false; + else + shell->is_blend_overlay_icon_app_list = true; + + if (shell->rdprail_api->shell_initialize_notify) + shell->rdp_backend = shell->rdprail_api->shell_initialize_notify(ec, &rdprail_shell_api, (void*)shell, shell->distroName); + + app_list_init(shell); + + return 0; +} diff --git a/rdprail-shell/shell.h b/rdprail-shell/shell.h new file mode 100644 index 000000000..f26203632 --- /dev/null +++ b/rdprail-shell/shell.h @@ -0,0 +1,187 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation + * + * 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 +#include +#include + +#include +#include +#include + +#include "weston-desktop-shell-server-protocol.h" + +#define RDPRAIL_SHELL_DEBUG_LEVEL_NONE 0 +#define RDPRAIL_SHELL_DEBUG_LEVEL_ERR 1 +#define RDPRAIL_SHELL_DEBUG_LEVEL_WARN 2 +#define RDPRAIL_SHELL_DEBUG_LEVEL_INFO 3 +#define RDPRAIL_SHELL_DEBUG_LEVEL_DEBUG 4 +#define RDPRAIL_SHELL_DEBUG_LEVEL_VERBOSE 5 + +#define RDPRAIL_SHELL_DEBUG_LEVEL_DEFAULT RDPRAIL_SHELL_DEBUG_LEVEL_INFO + +/* Ideally here should use weston_log_scope_printf() instead of weston_log() + since weston_log() requires "log" scope to be enabled, but weston_log() + added timestamp which is often helpful, thus use weston_log() here. + To enable shell_rdp_debug message, add "--logger-scopes=rdprail-shell,log" + to weston's command line, this added rdprail-shell and log scopes */ +#define shell_rdp_debug_level(s, lvl, ...) \ + if ((s) && (s)->debug && ((s)->debugLevel >= (lvl)) && weston_log_scope_is_enabled((s)->debug)) { \ + weston_log(__VA_ARGS__); \ + } + +#define shell_rdp_debug_verbose(b, ...) shell_rdp_debug_level(b, RDPRAIL_SHELL_DEBUG_LEVEL_VERBOSE, __VA_ARGS__) +#define shell_rdp_debug(b, ...) shell_rdp_debug_level(b, RDPRAIL_SHELL_DEBUG_LEVEL_INFO, __VA_ARGS__) + +#define is_system_distro() (getenv("WSL2_VM_ID") != NULL) + +struct workspace { + struct weston_layer layer; + + struct wl_list focus_list; + struct wl_listener seat_destroyed_listener; +}; + +struct shell_output { + struct desktop_shell *shell; + struct weston_output *output; + struct wl_listener destroy_listener; + struct wl_list link; + + pixman_rectangle32_t desktop_workarea; +}; + +struct weston_desktop; +struct desktop_shell { + struct weston_compositor *compositor; + struct weston_desktop *desktop; + const struct weston_xwayland_surface_api *xwayland_surface_api; + + struct wl_listener transform_listener; + struct wl_listener destroy_listener; + struct wl_listener show_input_panel_listener; + + struct weston_layer fullscreen_layer; + + struct wl_listener pointer_focus_listener; + struct weston_surface *grab_surface; + + bool prepare_event_sent; + + struct text_backend *text_backend; + + struct { + struct weston_surface *surface; + pixman_box32_t cursor_rectangle; + } text_input; + + struct { + struct wl_array array; + unsigned int current; + unsigned int num; + + struct wl_list client_list; + } workspaces; + + struct { + struct wl_resource *binding; + } input_panel; + + bool allow_zap; + uint32_t binding_modifier; + + struct weston_layer minimized_layer; + + struct wl_listener seat_create_listener; + struct wl_listener output_create_listener; + struct wl_listener output_move_listener; + struct wl_list output_list; + + struct timespec startup_time; + + bool is_localmove_supported; + bool is_localmove_pending; + + void *app_list_context; + char *distroName; + size_t distroNameLength; + bool is_appid_with_distro_name; + + pixman_image_t *image_default_app_icon; + pixman_image_t *image_default_app_overlay_icon; + + bool is_blend_overlay_icon_taskbar; + bool is_blend_overlay_icon_app_list; + + const struct weston_rdprail_api *rdprail_api; + void *rdp_backend; + + struct weston_log_scope *debug; + uint32_t debugLevel; +}; + +struct weston_output * +get_default_output(struct weston_compositor *compositor); + +struct weston_view * +get_default_view(struct weston_surface *surface); + +struct shell_surface * +get_shell_surface(struct weston_surface *surface); + +struct workspace * +get_current_workspace(struct desktop_shell *shell); + +void +lower_fullscreen_layer(struct desktop_shell *shell, + struct weston_output *lowering_output); + +void +activate(struct desktop_shell *shell, struct weston_view *view, + struct weston_seat *seat, uint32_t flags); + +int +input_panel_setup(struct desktop_shell *shell); +void +input_panel_destroy(struct desktop_shell *shell); + +typedef void (*shell_for_each_layer_func_t)(struct desktop_shell *, + struct weston_layer *, void *); + +void +shell_for_each_layer(struct desktop_shell *shell, + shell_for_each_layer_func_t func, + void *data); + +void +shell_blend_overlay_icon(struct desktop_shell *shell, + pixman_image_t *app_image, + pixman_image_t *overlay_image); + +void app_list_init(struct desktop_shell *shell); +void app_list_destroy(struct desktop_shell *shell); +pixman_image_t *app_list_load_icon_file(struct desktop_shell *shell, const char *key); +bool app_list_start_backend_update(struct desktop_shell *shell, char *clientLanguageId); +void app_list_stop_backend_update(struct desktop_shell *shell); diff --git a/shared/config-parser.c b/shared/config-parser.c index 64e482724..5f8756c3e 100644 --- a/shared/config-parser.c +++ b/shared/config-parser.c @@ -393,7 +393,7 @@ struct weston_config * weston_config_parse(const char *name) { FILE *fp; - char line[512], *p; + char line[4096], *p; struct stat filestat; struct weston_config *config; struct weston_config_section *section = NULL; diff --git a/tests/surface-screenshot-test.c b/tests/surface-screenshot-test.c index 3d93ac589..49b05eaad 100644 --- a/tests/surface-screenshot-test.c +++ b/tests/surface-screenshot-test.c @@ -177,8 +177,8 @@ trigger_binding(struct weston_keyboard *keyboard, const struct timespec *time, return; } - ret = weston_surface_copy_content(surface, pixels, sz, - 0, 0, width, height); + ret = weston_surface_copy_content(surface, pixels, sz, 0, 0, 0, + 0, 0, width, height, false, false); if (ret < 0) { weston_log("shooting surface %p failed\n", surface); goto out; diff --git a/xwayland/launcher.c b/xwayland/launcher.c index 60854c8a0..fbf288642 100644 --- a/xwayland/launcher.c +++ b/xwayland/launcher.c @@ -56,7 +56,9 @@ weston_xserver_handle_event(int listen_fd, uint32_t mask, void *data) } weston_log("Spawned Xwayland server, pid %d\n", wxs->pid); - wl_event_source_remove(wxs->abstract_source); + if (wxs->abstract_source) { + wl_event_source_remove(wxs->abstract_source); + } wl_event_source_remove(wxs->unix_source); return 1; @@ -72,10 +74,14 @@ weston_xserver_shutdown(struct weston_xserver *wxs) snprintf(path, sizeof path, "/tmp/.X11-unix/X%d", wxs->display); unlink(path); if (wxs->pid == 0) { - wl_event_source_remove(wxs->abstract_source); + if (wxs->abstract_source) { + wl_event_source_remove(wxs->abstract_source); + } wl_event_source_remove(wxs->unix_source); } - close(wxs->abstract_fd); + if (wxs->abstract_fd) { + close(wxs->abstract_fd); + } close(wxs->unix_fd); if (wxs->wm) { weston_wm_destroy(wxs->wm); @@ -260,6 +266,8 @@ weston_xwayland_listen(struct weston_xwayland *xwayland, void *user_data, wxs->user_data = user_data; wxs->spawn_func = spawn_func; + bool disable_abstract_fd = getenv("WESTON_DISABLE_ABSTRACT_FD") ? true : false; + retry: if (create_lockfile(wxs->display, lockfile, sizeof lockfile) < 0) { if (errno == EAGAIN) { @@ -273,17 +281,23 @@ weston_xwayland_listen(struct weston_xwayland *xwayland, void *user_data, } } - wxs->abstract_fd = bind_to_abstract_socket(wxs->display); - if (wxs->abstract_fd < 0 && errno == EADDRINUSE) { - wxs->display++; - unlink(lockfile); - goto retry; + if (!disable_abstract_fd) { + wxs->abstract_fd = bind_to_abstract_socket(wxs->display); + if (wxs->abstract_fd < 0 && errno == EADDRINUSE) { + wxs->display++; + unlink(lockfile); + goto retry; + } + } else { + weston_log("Not using abstract fd for Xwayland\n"); } wxs->unix_fd = bind_to_unix_socket(wxs->display); if (wxs->unix_fd < 0) { unlink(lockfile); - close(wxs->abstract_fd); + if (wxs->abstract_fd) { + close(wxs->abstract_fd); + } free(wxs); return -1; } @@ -293,10 +307,12 @@ weston_xwayland_listen(struct weston_xwayland *xwayland, void *user_data, setenv("DISPLAY", display_name, 1); wxs->loop = wl_display_get_event_loop(wxs->wl_display); - wxs->abstract_source = - wl_event_loop_add_fd(wxs->loop, wxs->abstract_fd, - WL_EVENT_READABLE, - weston_xserver_handle_event, wxs); + if (wxs->abstract_fd) { + wxs->abstract_source = + wl_event_loop_add_fd(wxs->loop, wxs->abstract_fd, + WL_EVENT_READABLE, + weston_xserver_handle_event, wxs); + } wxs->unix_source = wl_event_loop_add_fd(wxs->loop, wxs->unix_fd, WL_EVENT_READABLE, @@ -323,10 +339,12 @@ weston_xwayland_xserver_exited(struct weston_xwayland *xwayland, wxs->pid = 0; wxs->client = NULL; - wxs->abstract_source = - wl_event_loop_add_fd(wxs->loop, wxs->abstract_fd, - WL_EVENT_READABLE, - weston_xserver_handle_event, wxs); + if (wxs->abstract_fd) { + wxs->abstract_source = + wl_event_loop_add_fd(wxs->loop, wxs->abstract_fd, + WL_EVENT_READABLE, + weston_xserver_handle_event, wxs); + } wxs->unix_source = wl_event_loop_add_fd(wxs->loop, wxs->unix_fd, WL_EVENT_READABLE, diff --git a/xwayland/selection.c b/xwayland/selection.c index c4845f20a..e3cdd1233 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -513,7 +513,8 @@ weston_wm_send_data(struct weston_wm *wm, xcb_atom_t target, const char *mime_ty source = seat->selection_data_source; source->send(source, mime_type, p[1]); - close(p[1]); + //shouldn't closing pipe by done inside send()? See client_source_send() + //close(p[1]); } static void diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index de868218c..142ed1aba 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -170,11 +170,15 @@ struct weston_wm_window { int delete_window; int maximized_vert; int maximized_horz; + int take_focus; struct wm_size_hints size_hints; struct motif_wm_hints motif_hints; struct wl_list link; }; +static int +our_resource(struct weston_wm *wm, uint32_t id); + static void weston_wm_window_set_allow_commits(struct weston_wm_window *window, bool allow); @@ -346,6 +350,7 @@ xcb_cursor_library_load_cursor(struct weston_wm *wm, const char *file) xcb_cursor_t cursor; XcursorImages *images; char *v = NULL; + char *theme = NULL; int size = 0; if (!file) @@ -358,7 +363,9 @@ xcb_cursor_library_load_cursor(struct weston_wm *wm, const char *file) if (!size) size = 32; - images = XcursorLibraryLoadImages (file, NULL, size); + theme = getenv("XCURSOR_THEME"); + + images = XcursorLibraryLoadImages (file, theme, size); if (!images) return -1; @@ -548,6 +555,7 @@ weston_wm_window_read_properties(struct weston_wm_window *window) window->size_hints.flags = 0; window->motif_hints.flags = 0; window->delete_window = 0; + window->take_focus = 0; for (i = 0; i < ARRAY_LENGTH(props); i++) { reply = xcb_get_property_reply(wm->conn, cookie[i], NULL); @@ -590,7 +598,8 @@ weston_wm_window_read_properties(struct weston_wm_window *window) for (i = 0; i < reply->value_len; i++) if (atom[i] == wm->atom.wm_delete_window) { window->delete_window = 1; - break; + } else if (atom[i] == wm->atom.wm_take_focus) { + window->take_focus = 1; } break; case TYPE_WM_NORMAL_HINTS: @@ -713,6 +722,30 @@ weston_wm_window_send_configure_notify(struct weston_wm_window *window) (char *) &configure_notify); } +static void +weston_wm_window_send_event_configure_notify_window_position(struct weston_wm_window *window, int x, int y) +{ + xcb_configure_notify_event_t configure_notify; + struct weston_wm *wm = window->wm; + + configure_notify.response_type = XCB_CONFIGURE_NOTIFY; + configure_notify.pad0 = 0; + configure_notify.event = window->id; + configure_notify.window = window->id; + configure_notify.above_sibling = XCB_NONE; + configure_notify.x = x; + configure_notify.y = y; + configure_notify.width = window->width; + configure_notify.height = window->height; + configure_notify.border_width = 0; + configure_notify.override_redirect = window->override_redirect; + configure_notify.pad1 = 0; + + xcb_send_event(wm->conn, 0, window->id, + XCB_EVENT_MASK_STRUCTURE_NOTIFY, + (char *) &configure_notify); +} + static void weston_wm_configure_window(struct weston_wm *wm, xcb_window_t window_id, uint16_t mask, const uint32_t *values) @@ -781,11 +814,13 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev uint16_t mask; int x, y; int i = 0; + bool is_our_resource = our_resource(wm, configure_request->window); - wm_printf(wm, "XCB_CONFIGURE_REQUEST (window %d) %d,%d @ %dx%d\n", + wm_printf(wm, "XCB_CONFIGURE_REQUEST (window %d) %d,%d @ %dx%d%s\n", configure_request->window, configure_request->x, configure_request->y, - configure_request->width, configure_request->height); + configure_request->width, configure_request->height, + is_our_resource ? ", ours" : ""); if (!wm_lookup_window(wm, configure_request->window, &window)) return; @@ -806,14 +841,33 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev } weston_wm_window_get_child_position(window, &x, &y); - values[i++] = x; - values[i++] = y; - values[i++] = window->width; - values[i++] = window->height; - values[i++] = 0; - mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | - XCB_CONFIG_WINDOW_BORDER_WIDTH; + /* don't send x/y when frame (parent window) is not created yet, + unless this is frame itself. Since only after frame is created, + the app's window position will become relative to parent (frame). */ + if (is_our_resource || window->frame || window->override_redirect) { + if (is_our_resource) { + /* window is frame window */ + values[i++] = configure_request->x; + values[i++] = configure_request->y; + } else { + /* window is app's window with frame as parent or override */ + values[i++] = x; // relative from frame. + values[i++] = y; // relative from frame. + } + values[i++] = window->width; + values[i++] = window->height; + values[i++] = 0; + mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | + XCB_CONFIG_WINDOW_BORDER_WIDTH; + } else { + /* app's window hasn't been reparented to frame yet */ + values[i++] = window->width; + values[i++] = window->height; + values[i++] = 0; + mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | + XCB_CONFIG_WINDOW_BORDER_WIDTH; + } if (configure_request->value_mask & XCB_CONFIG_WINDOW_SIBLING) { values[i++] = configure_request->sibling; mask |= XCB_CONFIG_WINDOW_SIBLING; @@ -846,12 +900,14 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve const struct weston_desktop_xwayland_interface *xwayland_api = wm->server->compositor->xwayland_interface; struct weston_wm_window *window; + bool is_our_resource = our_resource(wm, configure_notify->window); - wm_printf(wm, "XCB_CONFIGURE_NOTIFY (window %d) %d,%d @ %dx%d%s\n", + wm_printf(wm, "XCB_CONFIGURE_NOTIFY (window %d) %d,%d @ %dx%d%s%s\n", configure_notify->window, configure_notify->x, configure_notify->y, configure_notify->width, configure_notify->height, - configure_notify->override_redirect ? ", override" : ""); + configure_notify->override_redirect ? ", override" : "", + is_our_resource ? ", ours" : ""); if (!wm_lookup_window(wm, configure_notify->window, &window)) return; @@ -924,16 +980,18 @@ weston_wm_send_focus_window(struct weston_wm *wm, if (window->override_redirect) return; - client_message.response_type = XCB_CLIENT_MESSAGE; - client_message.format = 32; - client_message.window = window->id; - client_message.type = wm->atom.wm_protocols; - client_message.data.data32[0] = wm->atom.wm_take_focus; - client_message.data.data32[1] = XCB_TIME_CURRENT_TIME; - - xcb_send_event(wm->conn, 0, window->id, - XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, - (char *) &client_message); + if (window->take_focus) { + client_message.response_type = XCB_CLIENT_MESSAGE; + client_message.format = 32; + client_message.window = window->id; + client_message.type = wm->atom.wm_protocols; + client_message.data.data32[0] = wm->atom.wm_take_focus; + client_message.data.data32[1] = XCB_TIME_CURRENT_TIME; + + xcb_send_event(wm->conn, 0, window->id, + XCB_EVENT_MASK_NO_EVENT, + (char *) &client_message); + } xcb_set_input_focus (wm->conn, XCB_INPUT_FOCUS_POINTER_ROOT, window->id, XCB_TIME_CURRENT_TIME); @@ -1079,6 +1137,9 @@ weston_wm_window_create_frame(struct weston_wm_window *window) if (window->decorate & MWM_DECOR_MAXIMIZE) buttons |= FRAME_BUTTON_MAXIMIZE; + if (window->decorate & MWM_DECOR_MINIMIZE) + buttons |= FRAME_BUTTON_MINIMIZE; + window->frame = frame_create(window->wm->theme, window->width, window->height, buttons, window->name, NULL); @@ -1299,6 +1360,9 @@ weston_wm_window_draw_decoration(struct weston_wm_window *window) how = "decorate"; frame_set_title(window->frame, window->name); frame_repaint(window->frame, cr); + } else if (window->maximized_vert && window->maximized_horz) { + how = "maximized"; + /* nothing */ } else { how = "shadow"; cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); @@ -1431,6 +1495,68 @@ weston_wm_window_schedule_repaint(struct weston_wm_window *window) weston_wm_window_do_repaint, window); } +static bool +weston_wm_window_set_icon(struct weston_wm *wm, + struct weston_wm_window *window, xcb_window_t window_id) +{ + bool is_set_window_icon_called = false; + const struct weston_desktop_xwayland_interface *xwayland_interface = + wm->server->compositor->xwayland_interface; + xcb_get_property_reply_t *reply; + xcb_get_property_cookie_t cookie; + char *data; + int length; + uint32_t *cur, *selected_bits; + uint32_t width, selected_width; + uint32_t height, selected_height; + + if (!window->shsurf) { + /* shell surface is not associated yet */ + return false; + } + + cookie = xcb_get_property(wm->conn, 0, window_id, + wm->atom.net_wm_icon, XCB_ATOM_CARDINAL, 0, 0x1fffffff); + reply = xcb_get_property_reply(wm->conn, cookie, NULL); + if (!reply) + return false; + length = xcb_get_property_value_length(reply); + if (!length) { + free(reply); + return false; + } + assert(reply->type == XCB_ATOM_CARDINAL); + data = xcb_get_property_value(reply); + wm_printf(wm, "weston_wm_window_set_icon: data:%p, length:%d\n", data, length); + + selected_bits = NULL; + selected_width = 0; + selected_height = 0; + cur = (uint32_t*)data; + while ((char *)cur < data+length) { + width = *cur++; + height = *cur++; + if (selected_width < width && selected_height < height) { + selected_width = width; + selected_height = height; + selected_bits = cur; + } + wm_printf(wm, " icon (%d x %d) at %p\n", width, height, cur); + cur += (width*height); + } + + wm_printf(wm, " selected icon (%d x %d) at %p\n", selected_width, selected_height, selected_bits); + + if (selected_width && selected_height && selected_bits) { + xwayland_interface->set_window_icon(window->shsurf, selected_width, selected_height, 32, selected_bits); + is_set_window_icon_called = true; + } + + free(reply); + + return is_set_window_icon_called; +} + static void weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *event) { @@ -1477,6 +1603,9 @@ weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *even if (property_notify->atom == wm->atom.net_wm_name || property_notify->atom == XCB_ATOM_WM_NAME) weston_wm_window_schedule_repaint(window); + + if (property_notify->atom == wm->atom.net_wm_icon) + weston_wm_window_set_icon(wm, window, property_notify->window); } static void @@ -1609,8 +1738,10 @@ weston_wm_handle_reparent_notify(struct weston_wm *wm, xcb_generic_event_t *even (xcb_reparent_notify_event_t *) event; struct weston_wm_window *window; - wm_printf(wm, "XCB_REPARENT_NOTIFY (window %d, parent %d, event %d%s)\n", + wm_printf(wm, "XCB_REPARENT_NOTIFY (window %d @ %d,%d, parent %d, event %d%s)\n", reparent_notify->window, + reparent_notify->x, + reparent_notify->y, reparent_notify->parent, reparent_notify->event, reparent_notify->override_redirect ? ", override" : ""); @@ -1803,16 +1934,37 @@ weston_wm_window_handle_state(struct weston_wm_window *window, if (weston_wm_window_is_maximized(window)) { window->saved_width = window->width; window->saved_height = window->height; - + if (window->frame) + frame_set_flag(window->frame, FRAME_FLAG_MAXIMIZED); if (window->shsurf) xwayland_interface->set_maximized(window->shsurf); } else if (window->shsurf) { + if (window->frame) + frame_unset_flag(window->frame, FRAME_FLAG_MAXIMIZED); weston_wm_window_set_toplevel(window); } } } } +static void +weston_wm_window_handle_iconic_state(struct weston_wm_window *window, + xcb_client_message_event_t *client_message) +{ + struct weston_wm *wm = window->wm; + const struct weston_desktop_xwayland_interface *xwayland_interface = + wm->server->compositor->xwayland_interface; + uint32_t iconic_state; + + iconic_state = client_message->data.data32[0]; + + if (iconic_state == ICCCM_ICONIC_STATE) { + window->saved_height = window->height; + window->saved_width = window->width; + xwayland_interface->set_minimized(window->shsurf); + } +} + static void surface_destroy(struct wl_listener *listener, void *data) { @@ -1890,6 +2042,8 @@ weston_wm_handle_client_message(struct weston_wm *wm, weston_wm_window_handle_state(window, client_message); else if (client_message->type == wm->atom.wl_surface_id) weston_wm_window_handle_surface_id(window, client_message); + else if (client_message->type == wm->atom.wm_change_state) + weston_wm_window_handle_iconic_state(window, client_message); } enum cursor_type { @@ -2166,12 +2320,21 @@ weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) if (weston_wm_window_is_maximized(window)) { window->saved_width = window->width; window->saved_height = window->height; + frame_set_flag(window->frame, FRAME_FLAG_MAXIMIZED); xwayland_interface->set_maximized(window->shsurf); } else { + frame_unset_flag(window->frame, FRAME_FLAG_MAXIMIZED); weston_wm_window_set_toplevel(window); } frame_status_clear(window->frame, FRAME_STATUS_MAXIMIZE); } + + if (frame_status(window->frame) & FRAME_STATUS_MINIMIZE) { + window->saved_width = window->width; + window->saved_height = window->height; + xwayland_interface->set_minimized(window->shsurf); + frame_status_clear(window->frame, FRAME_STATUS_MINIMIZE); + } } static void @@ -2382,6 +2545,7 @@ weston_wm_get_resources(struct weston_wm *wm) { "WM_STATE", F(atom.wm_state) }, { "WM_S0", F(atom.wm_s0) }, { "WM_CLIENT_MACHINE", F(atom.wm_client_machine) }, + { "WM_CHANGE_STATE", F(atom.wm_change_state) }, { "_NET_WM_CM_S0", F(atom.net_wm_cm_s0) }, { "_NET_WM_NAME", F(atom.net_wm_name) }, { "_NET_WM_PID", F(atom.net_wm_pid) }, @@ -2785,6 +2949,7 @@ send_position(struct weston_surface *surface, int32_t x, int32_t y) struct weston_wm *wm; uint32_t values[2]; uint16_t mask; + int dx, dy; if (!window || !window->wm) return; @@ -2799,12 +2964,114 @@ send_position(struct weston_surface *surface, int32_t x, int32_t y) values[0] = x; values[1] = y; mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; - weston_wm_configure_window(wm, window->frame_id, mask, values); + + // !!! need further investigation !!! + /* Xwayland reparents app's window with our own frame window + as new parent, so when shell moves frame window, with intent to + move app's window, the app (child) window position which is + relative to parent, doesn't change. But it seems certain application + (Qt based or PyCharm) isn't aware of this reparenting done by us at + weston_wm_window_create_frame(), thus, here sends XCB_CONFIGURE_NOTIFY + event to let application knows actual app's window position (rather + than offset from parent/frame window */ + weston_wm_window_get_child_position(window, &dx, &dy); + weston_wm_window_send_event_configure_notify_window_position(window, x + dx, y + dy); + xcb_flush(wm->conn); } } +static void +set_maximized(struct weston_surface *surface, bool is_maximized) +{ + struct weston_wm_window *window = get_wm_window(surface); + struct weston_wm *wm; + + if (!window || !window->wm) + return; + + wm = window->wm; + + if (is_maximized) { + if (!weston_wm_window_is_maximized(window)) { + window->maximized_horz = 1; + window->maximized_vert = 1; + window->saved_width = window->width; + window->saved_height = window->height; + frame_set_flag(window->frame, FRAME_FLAG_MAXIMIZED); + wm->server->compositor->xwayland_interface->set_maximized(window->shsurf); + } + } + else { + if (weston_wm_window_is_maximized(window)) { + window->maximized_horz = 0; + window->maximized_vert = 0; + frame_unset_flag(window->frame, FRAME_FLAG_MAXIMIZED); + weston_wm_window_set_toplevel(window); + } + } + weston_wm_window_set_net_wm_state(window); +} + +static char * +get_class_name(struct weston_surface *surface) +{ + struct weston_wm_window *window = get_wm_window(surface); + struct weston_wm *wm; + xcb_get_property_reply_t *reply; + xcb_get_property_cookie_t cookie; + char *data; + int length; + char *name = NULL; + + if (!window || !window->wm) + return NULL; + + wm = window->wm; + cookie = xcb_get_property(wm->conn, 0, window->id, + XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 0x1fffffff); + reply = xcb_get_property_reply(wm->conn, cookie, NULL); + if (!reply) + return NULL; + length = xcb_get_property_value_length(reply); + if (!length) { + free(reply); + return NULL; + } + assert(reply->type == XCB_ATOM_STRING); + data = xcb_get_property_value(reply); + while (*data != '\0') // class name is 2 strings, skip first one. + data++; + data++; // skip null. + wm_printf(wm, "get_class_name: name:%s, length:%d\n", data, length); + if (*data) // make sure 2nd string is not just null. + name = strdup(data); + free(reply); + + return name; +} + +static bool +trigger_set_window_icon(struct weston_surface *surface) +{ + struct weston_wm_window *window = get_wm_window(surface); + struct weston_wm *wm; + + if (!window || !window->wm) + return false; + + wm = window->wm; + return weston_wm_window_set_icon(wm, window, window->id); +} + +static void +close_window(struct weston_surface *surface) +{ + struct weston_wm_window *window = get_wm_window(surface); + weston_wm_window_close(window, XCB_TIME_CURRENT_TIME); +} + static const struct weston_xwayland_client_interface shell_client = { send_configure, }; @@ -2951,11 +3218,20 @@ xserver_map_shell_surface(struct weston_wm_window *window, window->x - parent->x, window->y - parent->y); } else { - xwayland_interface->set_toplevel(window->shsurf); + if (weston_wm_window_is_positioned(window)) { + xwayland_interface->set_toplevel_with_position(window->shsurf, + window->map_request_x, + window->map_request_y); + } else { + xwayland_interface->set_toplevel(window->shsurf); + } xwayland_interface->set_parent(window->shsurf, parent->surface); } } else if (weston_wm_window_is_maximized(window)) { + window->saved_width = window->width; + window->saved_height = window->height; + frame_set_flag(window->frame, FRAME_FLAG_MAXIMIZED); xwayland_interface->set_maximized(window->shsurf); } else { if (weston_wm_window_type_inactive(window)) { @@ -2978,9 +3254,15 @@ xserver_map_shell_surface(struct weston_wm_window *window, weston_wm_window_set_allow_commits(window, true); xcb_flush(wm->conn); } + + weston_wm_window_set_icon(wm, window, window->id); } const struct weston_xwayland_surface_api surface_api = { is_wm_window, send_position, + set_maximized, + get_class_name, + trigger_set_window_icon, + close_window, }; diff --git a/xwayland/xwayland-internal-interface.h b/xwayland/xwayland-internal-interface.h index 109644404..5c49ed901 100644 --- a/xwayland/xwayland-internal-interface.h +++ b/xwayland/xwayland-internal-interface.h @@ -58,7 +58,10 @@ struct weston_desktop_xwayland_interface { int32_t x, int32_t y, int32_t width, int32_t height); void (*set_maximized)(struct weston_desktop_xwayland_surface *shsurf); + void (*set_minimized)(struct weston_desktop_xwayland_surface *shsurf); void (*set_pid)(struct weston_desktop_xwayland_surface *shsurf, pid_t pid); + void (*set_window_icon)(struct weston_desktop_xwayland_surface *surface, + int32_t width, int32_t height, int32_t bpp, void *bits); }; #endif diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index 3ef0404fe..c430dd0b2 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -102,6 +102,7 @@ struct weston_wm { xcb_atom_t wm_state; xcb_atom_t wm_s0; xcb_atom_t wm_client_machine; + xcb_atom_t wm_change_state; xcb_atom_t net_wm_cm_s0; xcb_atom_t net_wm_name; xcb_atom_t net_wm_pid; From a9bde0542e81349da9900f1dfce8541a16209111 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 21 Apr 2021 16:06:30 -0700 Subject: [PATCH 1448/1642] revert left over temporal change (#41) Co-authored-by: Hideyuki Nagase --- libweston/input.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libweston/input.c b/libweston/input.c index dd3a1c070..d7f637135 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -1890,12 +1890,10 @@ notify_button(struct weston_seat *seat, const struct timespec *time, pointer->grab_x = pointer->x; pointer->grab_y = pointer->y; } - pointer->button_count=1; //TODO: Need to keep track of button indivually, avoid overflow/underflowing when the same button state is provided multiple time. For now just cap to 1/0. - //pointer->button_count++; + pointer->button_count++; } else { weston_compositor_idle_release(compositor); - pointer->button_count=0; //TODO: Need to keep track of button indivually, avoid overflow/underflowing when the same button state is provided multiple time. For now just cap to 1/0. - //pointer->button_count--; + pointer->button_count--; } weston_compositor_run_button_binding(compositor, pointer, time, button, From febf611b6cd4412be8df2faa18ddaf5e906f3d79 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 21 Apr 2021 19:34:29 -0700 Subject: [PATCH 1449/1642] fix keyboard inputs cause imbalance of idle_inhibit count (#42) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 19 ++++++++++++++++++- libweston/backend-rdp/rdp.h | 1 + libweston/input.c | 12 ++++++------ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 5a4abcbb7..1518a641e 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1568,7 +1568,8 @@ xf_input_synchronize_event(rdpInput *input, UINT32 flags) pixman_box32_t box; pixman_region32_t damage; - rdp_debug_verbose(b, "input_synchronize_event: ScrLk:%d, NumLk:%d, CapsLk:%d, KanaLk:%d\n", + rdp_debug_verbose(b, "RDP backend: %s ScrLk:%d, NumLk:%d, CapsLk:%d, KanaLk:%d\n", + __func__, flags & KBD_SYNC_SCROLL_LOCK ? 1 : 0, flags & KBD_SYNC_NUM_LOCK ? 1 : 0, flags & KBD_SYNC_CAPS_LOCK ? 1 : 0, @@ -1608,6 +1609,8 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) uint32_t scan_code, vk_code, full_code; enum wl_keyboard_key_state keyState; RdpPeerContext *peerContext = (RdpPeerContext *)input->context; + struct rdp_backend *b = peerContext->rdpBackend; + int notify = 0; struct timespec time; @@ -1628,6 +1631,15 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) full_code |= KBD_FLAGS_EXTENDED; vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, 4); + assert(vk_code <= 0xFF); + if (keyState == WL_KEYBOARD_KEY_STATE_RELEASED) { + /* Ignore release if key is not previously pressed. */ + if ((peerContext->key_state[vk_code>>3] & (1<<(vk_code&0x7))) == 0) + goto exit; + peerContext->key_state[vk_code>>3] &= ~(1<<(vk_code&0x7)); + } else { + peerContext->key_state[vk_code>>3] |= (1<<(vk_code&0x7)); + } if (flags & KBD_FLAGS_EXTENDED) vk_code |= KBDEXT; @@ -1638,8 +1650,13 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) weston_compositor_get_time(&time); notify_key(peerContext->item.seat, &time, scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); + + /*rdp_debug_verbose(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", + __func__, code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, + vk_code, scan_code, keyState, b->compositor->idle_inhibit);*/ } +exit: FREERDP_CB_RETURN(TRUE); } diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index ab9ed1e9f..11a200546 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -266,6 +266,7 @@ struct rdp_peer_context { struct rdp_peers_item item; bool button_state[5]; + char key_state[0xff/8]; // one bit per key. // RAIL support HANDLE vcm; diff --git a/libweston/input.c b/libweston/input.c index d7f637135..3ed1ca477 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -2194,12 +2194,6 @@ notify_key(struct weston_seat *seat, const struct timespec *time, uint32_t key, struct weston_keyboard_grab *grab = keyboard->grab; uint32_t *k, *end; - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - weston_compositor_idle_inhibit(compositor); - } else { - weston_compositor_idle_release(compositor); - } - end = keyboard->keys.data + keyboard->keys.size; for (k = keyboard->keys.data; k < end; k++) { if (*k == key) { @@ -2240,6 +2234,12 @@ notify_key(struct weston_seat *seat, const struct timespec *time, uint32_t key, keyboard->grab_time = *time; keyboard->grab_key = key; } + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + weston_compositor_idle_inhibit(compositor); + } else { + weston_compositor_idle_release(compositor); + } } WL_EXPORT void From b09ea9a25cd0cec29647b7e34e8eed9f2d88b5e6 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 22 Apr 2021 09:25:21 -0700 Subject: [PATCH 1450/1642] overactive assert with complex monitor layout with 1.0 scaling (#43) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdpdisp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 2b5c8c3fe..41b9b14bb 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -467,7 +467,7 @@ disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_mon isScalingSupported = false; } - if (isScalingSupported) { + if (isScalingUsed && isScalingSupported) { uint32_t offsetFromOriginWeston = 0; for (i = 0; i < monitorCount; i++) { monitorMode[i].rectWeston.width = monitorMode[i].monitorDef.width / monitorMode[i].scale; @@ -485,7 +485,7 @@ disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_mon } } } else { - /* monitor placement is too complex to scale in weston space, fallback to 1.0f */ + /* no scaling is used or monitor placement is too complex to scale in weston space, fallback to 1.0f */ for (i = 0; i < monitorCount; i++) { monitorMode[i].rectWeston.width = monitorMode[i].monitorDef.width; monitorMode[i].rectWeston.height = monitorMode[i].monitorDef.height; From 864f81de3abf579089e15269b6c9220a11254542 Mon Sep 17 00:00:00 2001 From: James Le Cuirot Date: Thu, 22 Apr 2021 21:00:36 +0100 Subject: [PATCH 1451/1642] Fix missing third mode argument on open() with O_CREAT This failed to build onto Ubuntu 20.04. --- libweston/backend-rdp/rdputil.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index 8c0f14fcd..2659db3f2 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -105,7 +105,7 @@ rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memor strcat(path, "/"); strcat(path, shared_memory->name); - fd = open(path, O_CREAT | O_RDWR); + fd = open(path, O_CREAT | O_RDWR, 00600); if (fd < 0) { weston_log("%s: Failed to open \"%s\" with error: %s\n", __func__, path, strerror(errno)); From d71a8cfec9bc9760b991d34578b7a2a7c0ae1763 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Sun, 25 Apr 2021 19:14:28 -0700 Subject: [PATCH 1452/1642] fix xwayland clipboard crash (#1) Co-authored-by: Hideyuki Nagase --- xwayland/selection.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/xwayland/selection.c b/xwayland/selection.c index e3cdd1233..6a807cb0d 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -414,8 +414,11 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) return 1; } - weston_log("read %d (available %d, mask 0x%x) bytes: \"%.*s\"\n", - len, available, mask, len, (char *) p); + weston_log("read %d (available %d, mask 0x%x)\n", + len, available, mask); + /*remove actual data due to privacy*/ + /*weston_log("read %d (available %d, mask 0x%x) bytes: \"%.*s\"\n", + len, available, mask, len, (char *) p);*/ wm->source_data.size = current + len; if (wm->source_data.size >= incr_chunk_size) { @@ -496,6 +499,11 @@ weston_wm_send_data(struct weston_wm *wm, xcb_atom_t target, const char *mime_ty struct weston_seat *seat = weston_wm_pick_seat(wm); int p[2]; + if (wm->property_source) { + weston_log("outstanding selection exist\n"); + return; + } + if (pipe2(p, O_CLOEXEC | O_NONBLOCK) == -1) { weston_log("pipe2 failed: %s\n", strerror(errno)); weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); From 16de531f00aa3dfd17e0de74c8f49e9fd7cec617 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Sun, 25 Apr 2021 19:17:04 -0700 Subject: [PATCH 1453/1642] make failure to watch .desktop folders non-fatal (#2) Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index e346241e9..bc9d4f5d5 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -684,6 +684,7 @@ app_list_monitor_thread(LPVOID arg) UINT error = 0; DWORD status = 0; DWORD num_events = 0; + int num_watch = 0; HANDLE events[NUM_CONTROL_EVENT + ARRAY_LENGTH(app_list_folder)] = {}; struct inotify_event *event; char buf[1024 * (sizeof *event + 16)]; @@ -727,10 +728,10 @@ app_list_monitor_thread(LPVOID arg) if (shell->rdprail_api->notify_app_list) { for (int i = 0; i < (int)ARRAY_LENGTH(app_list_folder); i++) { - fd[i] = inotify_init(); - if (fd[i] < 0) { + fd[num_watch] = inotify_init(); + if (fd[num_watch] < 0) { weston_log("app_list_monitor_thread: inotify_init[%d] failed %s\n", i, strerror(errno)); - goto Exit; + continue; } attach_app_list_namespace(shell); @@ -739,6 +740,8 @@ app_list_monitor_thread(LPVOID arg) home = getenv("HOME"); if (!home) { detach_app_list_namespace(shell); + close(fd[num_watch]); + fd[num_watch] = 0; continue; } copy_string(path, sizeof path, home); @@ -749,29 +752,39 @@ app_list_monitor_thread(LPVOID arg) if (!is_file_exist(folder)) { shell_rdp_debug(shell, "app_list_monitor_thread: %s doesn't exist, skipping.\n", folder); detach_app_list_namespace(shell); + close(fd[num_watch]); + fd[num_watch] = 0; continue; } shell_rdp_debug(shell, "app_list_monitor_thread: inotify_add_watch(%s)\n", folder); - wd[i] = inotify_add_watch(fd[i], folder, IN_CREATE|IN_DELETE|IN_MODIFY|IN_MOVED_TO|IN_MOVED_FROM); - if (wd[i] < 0) { + wd[num_watch] = inotify_add_watch(fd[num_watch], folder, IN_CREATE|IN_DELETE|IN_MODIFY|IN_MOVED_TO|IN_MOVED_FROM); + if (wd[num_watch] < 0) { weston_log("app_list_monitor_thread: inotify_add_watch failed: %s\n", strerror(errno)); detach_app_list_namespace(shell); - goto Exit; + close(fd[num_watch]); + fd[num_watch] = 0; + continue; } detach_app_list_namespace(shell); - events[num_events] = GetFileHandleForFileDescriptor(fd[i]); + events[num_events] = GetFileHandleForFileDescriptor(fd[num_watch]); if (!events[num_events]) { weston_log("app_list_monitor_thread: GetFileHandleForFileDescriptor failed\n"); - goto Exit; + inotify_rm_watch(fd[num_watch], wd[num_watch]); + wd[num_watch] = 0; + close(fd[num_watch]); + fd[num_watch] = 0; + continue; } num_events++; + num_watch++; } assert(false == context->isAppListNamespaceAttached); /* first scan folders to update all existing .desktop files */ - app_list_update_all(shell); + if (num_watch) + app_list_update_all(shell); } /* now loop as changes are made or stop event is signaled */ @@ -832,8 +845,8 @@ app_list_monitor_thread(LPVOID arg) continue; } - if (shell->rdprail_api->notify_app_list) { - /* Somethings are changed in watch folders */ + /* Somethings are changed in watch folders */ + if (shell->rdprail_api->notify_app_list && num_watch) { len = read(fd[status - WAIT_OBJECT_0 - NUM_CONTROL_EVENT], buf, sizeof buf); cur = 0; while (cur < len) { @@ -859,13 +872,13 @@ app_list_monitor_thread(LPVOID arg) assert(false == context->isAppListNamespaceAttached); for (int i = 0; i < (int)ARRAY_LENGTH(app_list_folder); i++) { + if (events[i + NUM_CONTROL_EVENT]) + CloseHandle(events[i + NUM_CONTROL_EVENT]); if (fd[i] > 0) { if (wd[i] > 0) inotify_rm_watch(fd[i], wd[i]); close(fd[i]); } - if (events[i + NUM_CONTROL_EVENT]) - CloseHandle(events[i + NUM_CONTROL_EVENT]); } if (context->weston_pidfd > 0) { From 3bef2480d4334db5d35f6cbc4d9708ad57fadd30 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 27 Apr 2021 08:51:14 -0700 Subject: [PATCH 1454/1642] configurable RDP refresh rate (#3) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 44 ++++++++++++++++++++++++++++--------- libweston/backend-rdp/rdp.h | 3 +++ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 1518a641e..32f397ee4 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -324,7 +324,7 @@ rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, &ec->primary_plane.damage, damage); } - wl_event_source_timer_update(output->finish_frame_timer, 16); + wl_event_source_timer_update(output->finish_frame_timer, b->rdp_repaint_delay_ms); return 0; } @@ -357,6 +357,7 @@ rdp_insert_new_mode(struct weston_output *output, int width, int height, int rat static struct weston_mode * ensure_matching_mode(struct weston_output *output, struct weston_mode *target) { + struct rdp_backend *b = to_rdp_backend(output->compositor); struct weston_mode *local; wl_list_for_each(local, &output->mode_list, link) { @@ -364,7 +365,7 @@ ensure_matching_mode(struct weston_output *output, struct weston_mode *target) return local; } - return rdp_insert_new_mode(output, target->width, target->height, RDP_MODE_FREQ); + return rdp_insert_new_mode(output, target->width, target->height, b->rdp_monitor_refresh_rate); } static int @@ -522,7 +523,7 @@ rdp_output_set_size(struct weston_output *base, initMode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; initMode.width = width; initMode.height = height; - initMode.refresh = RDP_MODE_FREQ; + initMode.refresh = rdpBackend->rdp_monitor_refresh_rate; currentMode = ensure_matching_mode(&output->base, &initMode); if (!currentMode) return -1; @@ -1609,7 +1610,7 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) uint32_t scan_code, vk_code, full_code; enum wl_keyboard_key_state keyState; RdpPeerContext *peerContext = (RdpPeerContext *)input->context; - struct rdp_backend *b = peerContext->rdpBackend; + /*struct rdp_backend *b = peerContext->rdpBackend;*/ int notify = 0; struct timespec time; @@ -2003,8 +2004,9 @@ rdp_backend_create(struct weston_compositor *compositor, char *fd_tail; int fd, ret; struct rdp_output *output; - char *rdp_debug_level; + char *s; int i; + struct timespec ts; b = zalloc(sizeof *b); if (b == NULL) @@ -2028,17 +2030,39 @@ rdp_backend_create(struct weston_compositor *compositor, "rdp-backend", "Debug messages from RDP backend\n", NULL, NULL, NULL); - rdp_debug_level = getenv("WESTON_RDP_DEBUG_LEVEL"); - if (rdp_debug_level) { - b->debugLevel = atoi(rdp_debug_level); - if (b->debugLevel > RDP_DEBUG_LEVEL_VERBOSE) + s = getenv("WESTON_RDP_DEBUG_LEVEL"); + if (s) { + if (!safe_strtoint(s, &b->debugLevel)) + b->debugLevel = RDP_DEBUG_LEVEL_DEFAULT; + else if (b->debugLevel > RDP_DEBUG_LEVEL_VERBOSE) b->debugLevel = RDP_DEBUG_LEVEL_VERBOSE; } else { b->debugLevel = RDP_DEBUG_LEVEL_DEFAULT; } - weston_log("RDP backend: WESTON_RDP_DEBUG_LEVEL: %d.\n", b->debugLevel); + weston_log("RDP backend: WESTON_RDP_DEBUG_LEVEL: %d\n", b->debugLevel); /* After here, rdp_debug() is ready to be used */ + s = getenv("WESTON_RDP_MONITOR_REFRESH_RATE"); + if (s) { + if (!safe_strtoint(s, &b->rdp_monitor_refresh_rate)) + b->rdp_monitor_refresh_rate = RDP_MODE_FREQ; + } else { + b->rdp_monitor_refresh_rate = RDP_MODE_FREQ; + } + rdp_debug(b, "RDP backend: WESTON_RDP_MONITOR_REFRESH_RATE: %d\n", b->rdp_monitor_refresh_rate); + + s = getenv("WESTON_RDP_REPAINT_DELAY_MS"); + if (s) { + if (!safe_strtoint(s, &b->rdp_repaint_delay_ms)) + b->rdp_repaint_delay_ms = 16; + } else { + b->rdp_repaint_delay_ms = 16; + } + rdp_debug(b, "RDP backend: WESTON_RDP_REPAINT_DELAY_MS: %d\n", b->rdp_repaint_delay_ms); + + clock_getres(CLOCK_MONOTONIC, &ts); + rdp_debug(b, "RDP backend: timer resolution tv_sec:%ld tv_nsec:%ld\n", (intmax_t)ts.tv_sec, ts.tv_nsec); + /* For diagnostics purpose, dump all enviroment to log file */ /* TODO: privacy review */ rdp_debug(b, "RDP backend: Environment dump - start\n"); diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 11a200546..644978873 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -183,6 +183,9 @@ struct rdp_backend { bool enable_fractional_hi_dpi_support; uint32_t debug_desktop_scaling_factor; /* must be between 100 to 500 */ + int rdp_monitor_refresh_rate; + int rdp_repaint_delay_ms; + #if defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) void *libFreeRDPServer; #endif // defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) From dbce0ffce6689e4bfe7dc65803e07b3fb88bf763 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 29 Apr 2021 07:43:53 -0700 Subject: [PATCH 1455/1642] configure idle time and debug protocol from .wslgconfig (#5) Co-authored-by: Hideyuki Nagase --- compositor/main.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index 503dd1f8a..54d36cd05 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -3148,11 +3148,13 @@ wet_main(int argc, char *argv[]) char *log_scopes_env = NULL; char *flight_rec_scopes = NULL; char *server_socket = NULL; + char *idle_time_env = NULL; int32_t idle_time = -1; int32_t help = 0; char *socket_name = NULL; int32_t version = 0; int32_t noconfig = 0; + char *debug_protocol_env = NULL; int32_t debug_protocol = 0; bool numlock_on; char *config_file = NULL; @@ -3325,6 +3327,10 @@ wet_main(int argc, char *argv[]) protologger = wl_display_add_protocol_logger(display, protocol_log_fn, NULL); + + debug_protocol_env = getenv("WESTON_DEBUG_PROTOCOL"); + if (debug_protocol_env && (strcmp(debug_protocol_env, "true") == 0)) + debug_protocol = 1; if (debug_protocol) weston_compositor_enable_debug_protocol(wet.compositor); @@ -3347,6 +3353,9 @@ wet_main(int argc, char *argv[]) if (wet.init_failed) goto out; + idle_time_env = getenv("WESTON_IDLE_TIME"); + if (!idle_time_env || !safe_strtoint(idle_time_env, &idle_time)) + idle_time = -1; if (idle_time < 0) weston_config_section_get_int(section, "idle-time", &idle_time, -1); if (idle_time < 0) From 2d2f2b89a290f5e02da90cf56c4842a29a60f276 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 29 Apr 2021 07:44:08 -0700 Subject: [PATCH 1456/1642] add timeline logging for performance investigation (#4) * add timeline logging for performance investigation * restore removed empty line Co-authored-by: Hideyuki Nagase --- libweston/compositor.c | 17 +++++++++++++++++ libweston/timeline.c | 23 +++++++++++++++++++++++ libweston/timeline.h | 4 ++++ 3 files changed, 44 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index a76bf9752..72c7407c1 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -2879,6 +2879,9 @@ output_repaint_timer_arm(struct weston_compositor *compositor) msec_to_this = timespec_sub_to_msec(&output->next_repaint, &now); + TL_POINT(compositor, "core_repaint_timer_arm_output", TLP_OUTPUT(output), + TLP_MSEC(&msec_to_this), + TLP_END); if (!any_should_repaint || msec_to_this < msec_to_next) msec_to_next = msec_to_this; @@ -2897,6 +2900,9 @@ output_repaint_timer_arm(struct weston_compositor *compositor) if (msec_to_next < 1) msec_to_next = 1; + TL_POINT(compositor, "core_repaint_timer_arm", + TLP_MSEC(&msec_to_next), TLP_END); + wl_event_source_timer_update(compositor->repaint_timer, msec_to_next); } @@ -2990,6 +2996,7 @@ weston_output_finish_frame(struct weston_output *output, int32_t refresh_nsec; struct timespec now; struct timespec vblank_monotonic; + struct timespec next_present_monotonic; int64_t msec_rel; assert(output->repaint_status == REPAINT_AWAITING_COMPLETION); @@ -3048,7 +3055,16 @@ weston_output_finish_frame(struct weston_output *output, } } + weston_compositor_read_presentation_clock(compositor, &now); out: + next_present_monotonic = convert_presentation_time_now(compositor, + &output->next_repaint, &now, + CLOCK_MONOTONIC); + TL_POINT(compositor, "core_repaint_finished_next_repaint", TLP_OUTPUT(output), + TLP_MSEC(&msec_rel), + TLP_NEXT_PRESENT(&next_present_monotonic), + TLP_END); + output->repaint_status = REPAINT_SCHEDULED; output_repaint_timer_arm(compositor); } @@ -3062,6 +3078,7 @@ idle_repaint(void *data) assert(output->repaint_status == REPAINT_BEGIN_FROM_IDLE); output->repaint_status = REPAINT_AWAITING_COMPLETION; output->idle_repaint_source = NULL; + TL_POINT(output->compositor, "core_repaint_start_loop", TLP_OUTPUT(output), TLP_END); ret = output->start_repaint_loop(output); if (ret != 0) weston_output_schedule_repaint_reset(output); diff --git a/libweston/timeline.c b/libweston/timeline.c index d5738f48c..d92d97081 100644 --- a/libweston/timeline.c +++ b/libweston/timeline.c @@ -341,6 +341,27 @@ emit_gpu_timestamp(struct timeline_emit_context *ctx, void *obj) return 1; } +static int +emit_msec(struct timeline_emit_context *ctx, void *obj) +{ + int64_t *i = obj; + + fprintf(ctx->cur, "\"msec\":%" PRId64 "", *i); + + return 1; +} + +static int +emit_present_timestamp(struct timeline_emit_context *ctx, void *obj) +{ + struct timespec *ts = obj; + + fprintf(ctx->cur, "\"next_present\":[%" PRId64 ", %ld]", + (int64_t)ts->tv_sec, ts->tv_nsec); + + return 1; +} + static struct weston_timeline_subscription_object * weston_timeline_get_subscription_object(struct weston_log_subscription *sub, void *object) @@ -388,6 +409,8 @@ static const type_func type_dispatch[] = { [TLT_SURFACE] = emit_weston_surface, [TLT_VBLANK] = emit_vblank_timestamp, [TLT_GPU] = emit_gpu_timestamp, + [TLT_MSEC] = emit_msec, + [TLT_PRESENT] = emit_present_timestamp, }; /** Disseminates the message to all subscriptions of the scope \c diff --git a/libweston/timeline.h b/libweston/timeline.h index aaed74314..9d5ca0078 100644 --- a/libweston/timeline.h +++ b/libweston/timeline.h @@ -39,6 +39,8 @@ enum timeline_type { TLT_SURFACE, TLT_VBLANK, TLT_GPU, + TLT_MSEC, + TLT_PRESENT, }; /** Timeline subscription created for each subscription @@ -83,6 +85,8 @@ struct weston_timeline_subscription_object { #define TLP_SURFACE(s) TLT_SURFACE, TYPEVERIFY(struct weston_surface *, (s)) #define TLP_VBLANK(t) TLT_VBLANK, TYPEVERIFY(const struct timespec *, (t)) #define TLP_GPU(t) TLT_GPU, TYPEVERIFY(const struct timespec *, (t)) +#define TLP_MSEC(i) TLT_MSEC, TYPEVERIFY(const int64_t *, (i)) +#define TLP_NEXT_PRESENT(t) TLT_PRESENT, TYPEVERIFY(const struct timespec *, (t)) /** This macro is used to add timeline points. * From 01a9381053d473d46cde03bd700ebbdc7abc8823 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 29 Apr 2021 16:46:30 -0700 Subject: [PATCH 1457/1642] support two finger scroll on precision touch pad (#6) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 31 ++++++++++++++++++++----------- libweston/backend-rdp/rdp.h | 1 + 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 32f397ee4..df34d90df 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1492,6 +1492,7 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) if (flags & PTR_FLAGS_WHEEL) { struct weston_pointer_axis_event weston_event; + int ivalue; double value; /* DEFAULT_AXIS_STEP_DISTANCE is stolen from compositor-x11.c @@ -1500,19 +1501,27 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) * * https://blogs.msdn.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value */ - value = -(flags & 0xff) / 120.0; if (flags & PTR_FLAGS_WHEEL_NEGATIVE) - value = -value; - - weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; - weston_event.value = DEFAULT_AXIS_STEP_DISTANCE * value; - weston_event.discrete = (int)value; - weston_event.has_discrete = true; - - weston_compositor_get_time(&time); + ivalue = (int)((char)(flags & 0xff)); + else + ivalue = (flags & 0xff); + peerContext->accumWheelRotation += ivalue; + if (abs(peerContext->accumWheelRotation) >= 12) { + /* multiply -1 is due to directional swap to match Windows direction of scroll. */ + value = (double)(peerContext->accumWheelRotation / 12) * -1; + + weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; + weston_event.value = DEFAULT_AXIS_STEP_DISTANCE * value; + weston_event.discrete = (int)value; + weston_event.has_discrete = true; + + weston_compositor_get_time(&time); + + notify_axis(peerContext->item.seat, &time, &weston_event); + need_frame = true; - notify_axis(peerContext->item.seat, &time, &weston_event); - need_frame = true; + peerContext->accumWheelRotation %= 12; + } } if (need_frame) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 644978873..8b18bca17 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -270,6 +270,7 @@ struct rdp_peer_context { bool button_state[5]; char key_state[0xff/8]; // one bit per key. + int accumWheelRotation; // RAIL support HANDLE vcm; From 740ec45e674a4b91bcf7d64934c135d99c36493d Mon Sep 17 00:00:00 2001 From: Steve Pronovost Date: Sun, 2 May 2021 20:56:33 -0700 Subject: [PATCH 1458/1642] Weston Performance Fix This fixes the issue where Weston wasn't presenting at the rate it was expected to. In particular, the RDP backend would always take 16ms to complete a frame after being sent to the host. Upon frame completion, Weston would calculat the next frame to present based on the monitor refresh and target 9ms ahead of that. At 64hz this ended up having Weston compositor wait for 9ms for the next frame and that 9ms would add up to the RDP monitor 16ms simulated VSYNC, resulting in an effective presentation rate of 1/25ms = 40fps. The VSYNC emulation is now improved and the RDP backend keeps the frame completion within the total expected refresh, so at 64hz the frame are kept a total of 16ms apart. Also updated the VSYNC emulation code to adapt the RDP monitor refresh to what is specified through the environment variable WESTON_RDP_MONITOR_REFRESH_RATE. --- libweston/backend-rdp/rdp.c | 50 ++++++++++++++++++++++--------------- libweston/backend-rdp/rdp.h | 9 +++---- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index df34d90df..d37cdddf7 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -292,6 +292,21 @@ rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, struct rdp_peers_item *outputPeer; struct rdp_backend *b = to_rdp_backend(ec); + /* Calculate the time we should complete this frame such that frames + are spaced out by the specified monitor refresh. */ + struct timespec now; + weston_compositor_read_presentation_clock(ec, &now); + + struct timespec target; + int refresh_nsec = millihz_to_nsec(output_base->current_mode->refresh); + int refresh_msec = refresh_nsec / 1000000; + timespec_add_nsec(&target, &output_base->frame_time, refresh_nsec); + + int next_frame_delta = (int)timespec_sub_to_msec(&target, &now); + if ( next_frame_delta < 1 || next_frame_delta > refresh_msec) { + next_frame_delta = refresh_msec; + } + if (b->rdp_peer && b->rdp_peer->settings->HiDefRemoteApp) { /* RAIL mode, repaint RAIL window */ @@ -324,7 +339,7 @@ rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, &ec->primary_plane.damage, damage); } - wl_event_source_timer_update(output->finish_frame_timer, b->rdp_repaint_delay_ms); + wl_event_source_timer_update(output->finish_frame_timer, next_frame_delta); return 0; } @@ -454,7 +469,7 @@ rdp_output_get_config(struct weston_output *base, struct rdp_head *h = to_rdp_head(head); rdp_debug(rdpBackend, "get_config: attached head [%d]: make:%s, mode:%s, name:%s, (%p)\n", - h->index, head->make, head->model, head->name, head); + h->index, head->make, head->model, head->name, head); rdp_debug(rdpBackend, "get_config: attached head [%d]: x:%d, y:%d, width:%d, height:%d\n", h->index, h->monitorMode.monitorDef.x, h->monitorMode.monitorDef.y, h->monitorMode.monitorDef.width, h->monitorMode.monitorDef.height); @@ -494,7 +509,7 @@ rdp_output_set_size(struct weston_output *base, weston_head_set_monitor_strings(head, "weston", "rdp", NULL); rdp_debug(rdpBackend, "set_size: attached head [%d]: make:%s, mode:%s, name:%s, (%p)\n", - h->index, head->make, head->model, head->name, head); + h->index, head->make, head->model, head->name, head); rdp_debug(rdpBackend, "set_size: attached head [%d]: x:%d, y:%d, width:%d, height:%d\n", h->index, h->monitorMode.monitorDef.x, h->monitorMode.monitorDef.y, h->monitorMode.monitorDef.width, h->monitorMode.monitorDef.height); @@ -710,7 +725,7 @@ rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp monitorMode->rectWeston.width, monitorMode->rectWeston.height); } else { head->monitorMode.scale = 1.0f; - head->monitorMode.clientScale = 1; + head->monitorMode.clientScale = 1; pixman_region32_init(&head->regionClient); pixman_region32_init(&head->regionWeston); } @@ -1461,7 +1476,7 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) /* Per RDP spec, the x,y position is valid on all input mouse messages, * except for PTR_FLAGS_WHEEL and PTR_FLAGS_HWHEEL event. Take the opportunity * to resample our x,y position even when PTR_FLAGS_MOVE isn't explicitly set, - * for example a button down/up only notification, to ensure proper sync with + * for example a button down/up only notification, to ensure proper sync with * the RDP client. */ if (!(flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))) { @@ -1752,11 +1767,11 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) settings->NSCodec = TRUE; settings->FrameMarkerCommandEnabled = TRUE; settings->SurfaceFrameMarkerEnabled = TRUE; - settings->RemoteApplicationMode = TRUE; + settings->RemoteApplicationMode = TRUE; settings->RemoteApplicationSupportLevel = RAIL_LEVEL_SUPPORTED | RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED | - RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED | + RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED | RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED | RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED; settings->SupportGraphicsPipeline = TRUE; @@ -1887,7 +1902,7 @@ rdp_generate_session_tls(struct rdp_backend *b) b->server_key_content = (char *)calloc(mem->length+1, 1); memcpy(b->server_key_content, mem->data, mem->length); BIO_free_all(bio); - + x509 = X509_new(); X509_set_version(x509, 2); RAND_bytes((unsigned char *)&serial, sizeof(serial)); @@ -2051,24 +2066,19 @@ rdp_backend_create(struct weston_compositor *compositor, weston_log("RDP backend: WESTON_RDP_DEBUG_LEVEL: %d\n", b->debugLevel); /* After here, rdp_debug() is ready to be used */ - s = getenv("WESTON_RDP_MONITOR_REFRESH_RATE"); + s = getenv("WESTON_RDP_MONITOR_REFRESH_RATE"); if (s) { - if (!safe_strtoint(s, &b->rdp_monitor_refresh_rate)) + if (!safe_strtoint(s, &b->rdp_monitor_refresh_rate) || + b->rdp_monitor_refresh_rate == 0) { b->rdp_monitor_refresh_rate = RDP_MODE_FREQ; + } else { + b->rdp_monitor_refresh_rate *= 1000; + } } else { b->rdp_monitor_refresh_rate = RDP_MODE_FREQ; } rdp_debug(b, "RDP backend: WESTON_RDP_MONITOR_REFRESH_RATE: %d\n", b->rdp_monitor_refresh_rate); - s = getenv("WESTON_RDP_REPAINT_DELAY_MS"); - if (s) { - if (!safe_strtoint(s, &b->rdp_repaint_delay_ms)) - b->rdp_repaint_delay_ms = 16; - } else { - b->rdp_repaint_delay_ms = 16; - } - rdp_debug(b, "RDP backend: WESTON_RDP_REPAINT_DELAY_MS: %d\n", b->rdp_repaint_delay_ms); - clock_getres(CLOCK_MONOTONIC, &ts); rdp_debug(b, "RDP backend: timer resolution tv_sec:%ld tv_nsec:%ld\n", (intmax_t)ts.tv_sec, ts.tv_nsec); @@ -2083,7 +2093,7 @@ rdp_backend_create(struct weston_compositor *compositor, compositor->backend = &b->base; fd = use_vsock_fd(config->port); - /* if we are using VSOCK to connect to the rdp backend, we don't need to enforce the TLS + /* if we are using VSOCK to connect to the rdp backend, we don't need to enforce the TLS encryption, since FreeRDP will consider AF_UNIX and AF_VSOCK as a local connection */ if (fd <= 0 || config->env_socket) { diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 8b18bca17..9e2e52fba 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -184,7 +184,6 @@ struct rdp_backend { uint32_t debug_desktop_scaling_factor; /* must be between 100 to 500 */ int rdp_monitor_refresh_rate; - int rdp_repaint_delay_ms; #if defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) void *libFreeRDPServer; @@ -222,7 +221,7 @@ struct rdp_peers_item { struct rdp_monitor_mode { rdpMonitor monitorDef; // in client coordinate. - int scale; // per monitor DPI scaling. + int scale; // per monitor DPI scaling. float clientScale; pixman_rectangle32_t rectWeston; // in weston coordinate. }; @@ -247,7 +246,7 @@ struct rdp_output { uint32_t index; struct wl_list peers; - struct wl_list link; // rdp_backend::output_list + struct wl_list link; // rdp_backend::output_list }; typedef struct _rdp_audio_block_info { @@ -527,7 +526,7 @@ to_weston_x(RdpPeerContext *peer, int32_t x) } /* TO BE REMOVED */ -static inline int32_t +static inline int32_t to_weston_y(RdpPeerContext *peer, int32_t y) { return y - peer->regionClientHeads.extents.y1; @@ -616,7 +615,7 @@ to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, to_client_scale_only(peerContext, output, scale, &sx, &sy); if (width && height) to_client_scale_only(peerContext, output, scale, width, height); - /* translate x/y to offset from this output on client space. */ + /* translate x/y to offset from this output on client space. */ sx += head->monitorMode.monitorDef.x; sy += head->monitorMode.monitorDef.y; rdp_debug_verbose(b, "%s: (x:%d, y:%d) -> (sx:%d, sy:%d) at head:%s\n", From 248dbb6abb98feed249147d687bff7fd810ecf79 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 5 May 2021 09:21:07 -0700 Subject: [PATCH 1459/1642] move clipboard debug message to own scope (#8) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 18 ++-- libweston/backend-rdp/rdp.h | 52 +++++---- libweston/backend-rdp/rdpclip.c | 186 +++++++++++++++++++------------- libweston/backend-rdp/rdputil.c | 60 +++++++++++ rdprail-shell/shell.c | 63 ++++++++++- rdprail-shell/shell.h | 22 ++-- 6 files changed, 286 insertions(+), 115 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index d37cdddf7..375b9d366 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -2054,14 +2054,16 @@ rdp_backend_create(struct weston_compositor *compositor, "rdp-backend", "Debug messages from RDP backend\n", NULL, NULL, NULL); - s = getenv("WESTON_RDP_DEBUG_LEVEL"); - if (s) { - if (!safe_strtoint(s, &b->debugLevel)) + if (b->debug) { + s = getenv("WESTON_RDP_DEBUG_LEVEL"); + if (s) { + if (!safe_strtoint(s, &b->debugLevel)) + b->debugLevel = RDP_DEBUG_LEVEL_DEFAULT; + else if (b->debugLevel > RDP_DEBUG_LEVEL_VERBOSE) + b->debugLevel = RDP_DEBUG_LEVEL_VERBOSE; + } else { b->debugLevel = RDP_DEBUG_LEVEL_DEFAULT; - else if (b->debugLevel > RDP_DEBUG_LEVEL_VERBOSE) - b->debugLevel = RDP_DEBUG_LEVEL_VERBOSE; - } else { - b->debugLevel = RDP_DEBUG_LEVEL_DEFAULT; + } } weston_log("RDP backend: WESTON_RDP_DEBUG_LEVEL: %d\n", b->debugLevel); /* After here, rdp_debug() is ready to be used */ @@ -2186,6 +2188,8 @@ rdp_backend_create(struct weston_compositor *compositor, err_compositor: weston_compositor_shutdown(compositor); err_free_strings: + if (b->debug) + weston_log_scope_destroy(b->debug); free(b->rdp_key); free(b->server_cert); free(b->server_key); diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 9e2e52fba..265742978 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -156,6 +156,8 @@ struct rdp_backend { uint32_t head_index; struct weston_log_scope *debug; uint32_t debugLevel; + struct weston_log_scope *debugClipboard; + uint32_t debugClipboardLevel; char *server_cert; char *server_key; @@ -379,26 +381,35 @@ typedef struct rdp_peer_context RdpPeerContext; #define RDP_DEBUG_LEVEL_DEFAULT RDP_DEBUG_LEVEL_INFO -/* Ideally here should use weston_log_scope_printf() instead of weston_log() - since weston_log() requires "log" scope to be enabled, but weston_log() - added timestamp which is often helpful, thus use weston_log() here. - - To enable rdp_debug message, add "--logger-scopes=rdp-backend,log" to - weston's command line, this added rdp-backend and log scopes */ -/* TODO: support debug level */ -#define rdp_debug_level(b, cont, lvl, ...) \ - if ((b) && (b)->debug && ((b)->debugLevel >= (lvl)) && weston_log_scope_is_enabled((b)->debug)) { \ - if (cont) \ - weston_log_continue(__VA_ARGS__); \ - else \ - weston_log(__VA_ARGS__); \ - } - -#define rdp_debug_verbose(b, ...) rdp_debug_level(b, FALSE, RDP_DEBUG_LEVEL_VERBOSE, __VA_ARGS__) -#define rdp_debug(b, ...) rdp_debug_level(b, FALSE, RDP_DEBUG_LEVEL_INFO, __VA_ARGS__) - -#define rdp_debug_verbose_continue(b, ...) rdp_debug_level(b, TRUE, RDP_DEBUG_LEVEL_VERBOSE, __VA_ARGS__) -#define rdp_debug_continue(b, ...) rdp_debug_level(b, TRUE, RDP_DEBUG_LEVEL_INFO, __VA_ARGS__) +/* To enable rdp_debug message, add "--logger-scopes=rdp-backend". */ +#define rdp_debug_verbose(b, ...) \ + if (b->debugLevel >= RDP_DEBUG_LEVEL_VERBOSE) \ + rdp_debug_print(b->debug, false, __VA_ARGS__) +#define rdp_debug_verbose_continue(b, ...) \ + if (b->debugLevel >= RDP_DEBUG_LEVEL_VERBOSE) \ + rdp_debug_print(b->debug, true, __VA_ARGS__) +#define rdp_debug(b, ...) \ + if (b->debugLevel >= RDP_DEBUG_LEVEL_INFO) \ + rdp_debug_print(b->debug, false, __VA_ARGS__) +#define rdp_debug_continue(b, ...) \ + if (b->debugLevel >= RDP_DEBUG_LEVEL_INFO) \ + rdp_debug_print(b->debug, true, __VA_ARGS__) + +/* To enable rdp_debug_clipboard message, add "--logger-scopes=rdp-backend-clipboard". */ +#define rdp_debug_clipboard_verbose(b, ...) \ + if (b->debugClipboardLevel >= RDP_DEBUG_LEVEL_VERBOSE) \ + rdp_debug_print(b->debugClipboard, false, __VA_ARGS__) +#define rdp_debug_clipboard_verbose_continue(b, ...) \ + if (b->debugClipboardLevel >= RDP_DEBUG_LEVEL_VERBOSE) \ + rdp_debug_print(b->debugClipboard, true, __VA_ARGS__) +#define rdp_debug_clipboard(b, ...) \ + if (b->debugClipboardLevel >= RDP_DEBUG_LEVEL_INFO) \ + rdp_debug_print(b->debugClipboard, false, __VA_ARGS__) +#define rdp_debug_clipboard_continue(b, ...) \ + if (b->debugClipboardLevel >= RDP_DEBUG_LEVEL_INFO) \ + rdp_debug_print(b->debugClipboard, true, __VA_ARGS__) + +/* To enable rdp_debug message, add "--logger-scopes=rdp-backend". */ // rdp.c void convert_rdp_keyboard_to_xkb_rule_names(UINT32 KeyboardType, UINT32 KeyboardLayout, struct xkb_rule_names *xkbRuleNames); @@ -407,6 +418,7 @@ void rdp_head_destroy(struct weston_compositor *compositor, struct rdp_head *hea // rdputil.c pid_t rdp_get_tid(void); +void rdp_debug_print(struct weston_log_scope *log_scope, bool cont, char *fmt, ...); #ifdef ENABLE_RDP_THREAD_CHECK void assert_compositor_thread(struct rdp_backend *b); void assert_not_compositor_thread(struct rdp_backend *b); diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index 9b6fd065c..6d435702b 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -129,7 +129,7 @@ clipboard_process_text(struct rdp_clipboard_data_source *source, BOOL is_send) source->is_data_processed = TRUE; } - rdp_debug_verbose(b, "RDP %s (%p): %s (%d bytes)\n", + rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s (%d bytes)\n", __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); return source->data_contents.data; @@ -222,9 +222,9 @@ clipboard_process_html(struct rdp_clipboard_data_source *source, BOOL is_send) source->is_data_processed = TRUE; } - rdp_debug_verbose(b, "RDP %s (%p): %s (%d bytes)\n", + rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s (%d bytes)\n", __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); - //rdp_debug_verbose(b, "RDP clipboard_process_html (%p): %s \n\"%s\"\n (%d bytes)\n", + //rdp_debug_clipboard_verbose(b, "RDP clipboard_process_html (%p): %s \n\"%s\"\n (%d bytes)\n", // source, is_send ? "send" : "receive", // (char *)source->data_contents.data, // (UINT32)source->data_contents.size); @@ -235,7 +235,7 @@ clipboard_process_html(struct rdp_clipboard_data_source *source, BOOL is_send) weston_log("RDP %s FAILED (%p): %s (%d bytes)\n", __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); - //rdp_debug_verbose(b, "RDP clipboard_process_html FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", + //rdp_debug_clipboard_verbose(b, "RDP clipboard_process_html FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", // source, is_send ? "send" : "receive", // (char *)source->data_contents.data, // (UINT32)source->data_contents.size); @@ -334,26 +334,26 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) assert(bmfh); assert(bmih); - rdp_debug_verbose(b, "RDP %s (%p): %s (%d bytes)\n", + rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s (%d bytes)\n", __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); - rdp_debug_verbose_continue(b, " BITMAPFILEHEADER.bfType:0x%x\n", bmfh->bfType); - rdp_debug_verbose_continue(b, " BITMAPFILEHEADER.bfSize:%d\n", bmfh->bfSize); - rdp_debug_verbose_continue(b, " BITMAPFILEHEADER.bfOffBits:%d\n", bmfh->bfOffBits); - rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biSize:%d\n", bmih->biSize); - rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biWidth:%d\n", bmih->biWidth); - rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biHeight:%d, y-Up:%s\n", abs(bmih->biHeight), bmih->biHeight < 0 ? "yes" : "no"); - rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biPlanes:%d\n", bmih->biPlanes); - rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biBitCount:%d\n", bmih->biBitCount); - rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biCompression:%d\n", bmih->biCompression); - rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biSizeImage:%d\n", bmih->biSizeImage); - rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biXPelsPerMeter:%d\n", bmih->biXPelsPerMeter); - rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biYPelsPerMeter:%d\n", bmih->biYPelsPerMeter); - rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biClrUsed:%d\n", bmih->biClrUsed); - rdp_debug_verbose_continue(b, " BITMAPINFOHEADER.biClrImportant:%d\n", bmih->biClrImportant); + rdp_debug_clipboard_verbose_continue(b, " BITMAPFILEHEADER.bfType:0x%x\n", bmfh->bfType); + rdp_debug_clipboard_verbose_continue(b, " BITMAPFILEHEADER.bfSize:%d\n", bmfh->bfSize); + rdp_debug_clipboard_verbose_continue(b, " BITMAPFILEHEADER.bfOffBits:%d\n", bmfh->bfOffBits); + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biSize:%d\n", bmih->biSize); + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biWidth:%d\n", bmih->biWidth); + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biHeight:%d, y-Up:%s\n", abs(bmih->biHeight), bmih->biHeight < 0 ? "yes" : "no"); + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biPlanes:%d\n", bmih->biPlanes); + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biBitCount:%d\n", bmih->biBitCount); + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biCompression:%d\n", bmih->biCompression); + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biSizeImage:%d\n", bmih->biSizeImage); + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biXPelsPerMeter:%d\n", bmih->biXPelsPerMeter); + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biYPelsPerMeter:%d\n", bmih->biYPelsPerMeter); + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biClrUsed:%d\n", bmih->biClrUsed); + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biClrImportant:%d\n", bmih->biClrImportant); BITMAPINFO *bmi = (BITMAPINFO *)bmih; for (UINT32 i = 0; i < color_table_size / sizeof(RGBQUAD); i++) { - rdp_debug_verbose_continue(b, " BITMAPINFO.bmiColors[%d]:%02x:%02x:%02x:%02x\n", + rdp_debug_clipboard_verbose_continue(b, " BITMAPINFO.bmiColors[%d]:%02x:%02x:%02x:%02x\n", i, (UINT32)bmi->bmiColors[i].rgbReserved, (UINT32)bmi->bmiColors[i].rgbRed, @@ -364,7 +364,7 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) DWORD *bits = (DWORD*)((char*)bmfh + bmfh->bfOffBits); assert(bits == (DWORD*)(&bmi->bmiColors[color_table_size/sizeof(RGBQUAD)])); //for (UINT32 i = 0; i < 4; i++) { - // rdp_debug_verbose_continue(b, " %08x %08x %08x %08x %08x %08x %08x %08x\n", + // rdp_debug_clipboard_verbose_continue(b, " %08x %08x %08x %08x %08x %08x %08x %08x\n", // bits[0],bits[1],bits[2],bits[3],bits[4],bits[5],bits[6],bits[7]); // bits += 8; //} @@ -372,17 +372,17 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) BYTE *bits = (BYTE*)bmfh + bmfh->bfOffBits; assert(bits == (BYTE*)(&bmi->bmiColors[color_table_size/sizeof(RGBQUAD)])); //for (UINT32 i = 0; i < 4; i++) { - // rdp_debug_verbose_continue(b, " %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x\n", + // rdp_debug_clipboard_verbose_continue(b, " %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x\n", // bits[ 0],bits[ 1],bits[ 2], bits[ 3],bits[ 4],bits[ 5], bits[ 6],bits[ 7],bits[ 8], bits[ 9],bits[10],bits[11], // bits[12],bits[13],bits[14], bits[15],bits[16],bits[17], bits[18],bits[19],bits[20], bits[21],bits[22],bits[23]); // bits += 24; //} } - rdp_debug_verbose_continue(b, " sizeof(BITMAPFILEHEADER):%d\n", (UINT32) sizeof(BITMAPFILEHEADER)); - rdp_debug_verbose_continue(b, " sizeof(BITMAPINFOHEADER):%d\n", (UINT32) sizeof(BITMAPINFOHEADER)); - rdp_debug_verbose_continue(b, " original_data_size:%d\n", (UINT32) original_data_size); - rdp_debug_verbose_continue(b, " new_data_size:%d\n", (UINT32) source->data_contents.size); - rdp_debug_verbose_continue(b, " data_processed:%d -> %d\n", was_data_processed, source->is_data_processed); + rdp_debug_clipboard_verbose_continue(b, " sizeof(BITMAPFILEHEADER):%d\n", (UINT32) sizeof(BITMAPFILEHEADER)); + rdp_debug_clipboard_verbose_continue(b, " sizeof(BITMAPINFOHEADER):%d\n", (UINT32) sizeof(BITMAPINFOHEADER)); + rdp_debug_clipboard_verbose_continue(b, " original_data_size:%d\n", (UINT32) original_data_size); + rdp_debug_clipboard_verbose_continue(b, " new_data_size:%d\n", (UINT32) source->data_contents.size); + rdp_debug_clipboard_verbose_continue(b, " data_processed:%d -> %d\n", was_data_processed, source->is_data_processed); return ret; @@ -530,7 +530,7 @@ clipboard_data_source_unref(struct rdp_clipboard_data_source *source) assert(source->refcount); source->refcount--; - rdp_debug(b, "RDP %s (%p): refcount:%d\n", __func__, source, source->refcount); + rdp_debug_clipboard(b, "RDP %s (%p): refcount:%d\n", __func__, source, source->refcount); if (source->refcount > 0) return; @@ -565,7 +565,7 @@ clipboard_client_send_format_data_response(RdpPeerContext *peerCtx, struct rdp_c struct rdp_backend *b = peerCtx->rdpBackend; CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; - rdp_debug(b, "Client: %s (%p) format_index:%d %s (%d bytes)\n", + rdp_debug_clipboard(b, "Client: %s (%p) format_index:%d %s (%d bytes)\n", __func__, source, source->format_index, clipboard_supported_formats[source->format_index].mime_type, size); @@ -589,7 +589,7 @@ clipboard_client_send_format_data_response_fail(RdpPeerContext *peerCtx, struct struct rdp_backend *b = peerCtx->rdpBackend; CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; - rdp_debug(b, "Client: %s (%p)\n", __func__, source); + rdp_debug_clipboard(b, "Client: %s (%p)\n", __func__, source); formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; formatDataResponse.msgFlags = CB_RESPONSE_FAIL; @@ -620,7 +620,7 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) int len, size; void *data_to_send; - rdp_debug_verbose(b, "RDP %s (%p) fd:%d\n", __func__, source, fd); + rdp_debug_clipboard_verbose(b, "RDP %s (%p) fd:%d\n", __func__, source, fd); ASSERT_COMPOSITOR_THREAD(b); @@ -645,7 +645,7 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) len = read(fd, data, size); if (len == 0) { /* all data from source is read, so completed. */ - rdp_debug(b, "RDP %s (%p): read completed (%ld bytes)\n", + rdp_debug_clipboard(b, "RDP %s (%p): read completed (%ld bytes)\n", __func__, source, source->data_contents.size); if (!source->data_contents.size) goto error_exit; @@ -668,7 +668,7 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) } else { source->data_contents.size += len; ((char*)source->data_contents.data)[source->data_contents.size] = '\0'; - rdp_debug_verbose(b, "RDP %s (%p): read (%zu bytes)\n", + rdp_debug_clipboard_verbose(b, "RDP %s (%p): read (%zu bytes)\n", __func__, source, source->data_contents.size); } return 0; @@ -695,7 +695,7 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) size_t data_size; ssize_t size; - rdp_debug_verbose(b, "RDP %s (%p) fd:%d\n", __func__, source, fd); + rdp_debug_clipboard_verbose(b, "RDP %s (%p) fd:%d\n", __func__, source, fd); ASSERT_COMPOSITOR_THREAD(b); @@ -709,7 +709,7 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) if (source->is_canceled == FALSE && source->data_contents.data && source->data_contents.size) { if (source->inflight_data_to_write) { assert(source->inflight_data_size); - rdp_debug_verbose(b, "RDP %s: retry write retry count:%d (%p)\n", + rdp_debug_clipboard_verbose(b, "RDP %s: retry write retry count:%d (%p)\n", __func__, source->inflight_write_count, source); data_to_write = source->inflight_data_to_write; data_size = source->inflight_data_size; @@ -745,10 +745,10 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) assert(data_size >= (size_t)size); data_size -= size; data_to_write = (char *)data_to_write + size; - rdp_debug_verbose(b, "RDP %s (%p) wrote %ld bytes, remaining %ld bytes\n", + rdp_debug_clipboard_verbose(b, "RDP %s (%p) wrote %ld bytes, remaining %ld bytes\n", __func__, source, size, data_size); if (!data_size) - rdp_debug(b, "RDP %s (%p) write completed (%ld bytes)\n", + rdp_debug_clipboard(b, "RDP %s (%p) write completed (%ld bytes)\n", __func__, source, source->data_contents.size); } } @@ -791,7 +791,7 @@ clipboard_data_source_send(struct weston_data_source *base, CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = {}; int index; - rdp_debug(b, "RDP %s (%p) fd:%d, mime-type:\"%s\"\n", __func__, source, fd, mime_type); + rdp_debug_clipboard(b, "RDP %s (%p) fd:%d, mime-type:\"%s\"\n", __func__, source, fd, mime_type); ASSERT_COMPOSITOR_THREAD(b); @@ -834,7 +834,7 @@ clipboard_data_source_send(struct weston_data_source *base, formatDataRequest.msgType = CB_FORMAT_DATA_REQUEST; formatDataRequest.dataLen = 4; formatDataRequest.requestedFormatId = source->client_format_id_table[index]; - rdp_debug(b, "RDP %s (%p) request index:%d formatId:%d %s\n", + rdp_debug_clipboard(b, "RDP %s (%p) request index:%d formatId:%d %s\n", __func__, source, index, formatDataRequest.requestedFormatId, clipboard_format_id_to_string(formatDataRequest.requestedFormatId, false)); @@ -873,7 +873,7 @@ clipboard_data_source_cancel(struct weston_data_source *base) RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - rdp_debug(b, "RDP %s (%p)\n", __func__, source); + rdp_debug_clipboard(b, "RDP %s (%p)\n", __func__, source); ASSERT_COMPOSITOR_THREAD(b); @@ -913,7 +913,7 @@ clipboard_data_source_publish(void *arg) struct rdp_backend *b = peerCtx->rdpBackend; struct rdp_clipboard_data_source *source_prev; - rdp_debug(b, "RDP %s (%p)\n", __func__, source); + rdp_debug_clipboard(b, "RDP %s (%p)\n", __func__, source); ASSERT_COMPOSITOR_THREAD(b); @@ -958,12 +958,12 @@ clipboard_data_source_request(void *arg) index = peerCtx->clipboard_last_requested_format_index; assert(index >= 0 && index < (int)RDP_NUM_CLIPBOARD_FORMATS); requested_mime_type = clipboard_supported_formats[index].mime_type; - rdp_debug(b, "RDP %s (base:%p) requested mime type:\"%s\"\n", + rdp_debug_clipboard(b, "RDP %s (base:%p) requested mime type:\"%s\"\n", __func__, selection_data_source, requested_mime_type); found_requested_format = FALSE; wl_array_for_each(mime_type, &selection_data_source->mime_types) { - rdp_debug(b, "RDP %s (base:%p) available formats: %s\n", + rdp_debug_clipboard(b, "RDP %s (base:%p) available formats: %s\n", __func__, selection_data_source, *mime_type); if (strcmp(requested_mime_type, *mime_type) == 0) { found_requested_format = TRUE; @@ -971,7 +971,7 @@ clipboard_data_source_request(void *arg) } } if (!found_requested_format) { - rdp_debug(b, "RDP %s (base:%p) requested format not found format:\"%s\"\n", + rdp_debug_clipboard(b, "RDP %s (base:%p) requested format not found format:\"%s\"\n", __func__, selection_data_source, requested_mime_type); goto error_exit_response_fail; } @@ -980,7 +980,7 @@ clipboard_data_source_request(void *arg) if (!source) goto error_exit_response_fail; - rdp_debug(b, "RDP %s (%p) allocated\n", __func__, source); + rdp_debug_clipboard(b, "RDP %s (%p) allocated\n", __func__, source); wl_signal_init(&source->base.destroy_signal); wl_array_init(&source->base.mime_types); wl_array_init(&source->data_contents); @@ -1037,7 +1037,7 @@ clipboard_set_selection(struct wl_listener *listener, void *data) const char **mime_type; int index, num_supported_format = 0, num_avail_format = 0; - rdp_debug(b, "RDP %s (base:%p}\n", __func__, selection_data_source); + rdp_debug_clipboard(b, "RDP %s (base:%p}\n", __func__, selection_data_source); ASSERT_COMPOSITOR_THREAD(b); @@ -1056,7 +1056,7 @@ clipboard_set_selection(struct wl_listener *listener, void *data) } wl_array_for_each(mime_type, &selection_data_source->mime_types) { - rdp_debug(b, "RDP %s (base:%p) available formats[%d]: %s\n", + rdp_debug_clipboard(b, "RDP %s (base:%p) available formats[%d]: %s\n", __func__, selection_data_source, num_avail_format, *mime_type); num_avail_format++; } @@ -1067,7 +1067,7 @@ clipboard_set_selection(struct wl_listener *listener, void *data) if (index >= 0) { format[num_supported_format].formatId = clipboard_supported_formats[index].format_id; format[num_supported_format].formatName = clipboard_supported_formats[index].format_name; - rdp_debug(b, "RDP %s (base:%p) supported formats[%d]: %d: %s\n", + rdp_debug_clipboard(b, "RDP %s (base:%p) supported formats[%d]: %d: %s\n", __func__, selection_data_source, num_supported_format, @@ -1086,7 +1086,7 @@ clipboard_set_selection(struct wl_listener *listener, void *data) formatList.formats = &format[0]; peerCtx->clipboard_server_context->ServerFormatList(peerCtx->clipboard_server_context, &formatList); } else { - rdp_debug(b, "RDP %s (base:%p) no supported formats\n", __func__, selection_data_source); + rdp_debug_clipboard(b, "RDP %s (base:%p) no supported formats\n", __func__, selection_data_source); } return; @@ -1103,7 +1103,7 @@ clipboard_client_temp_directory(CliprdrServerContext* context, const CLIPRDR_TEM freerdp_peer *client = (freerdp_peer*)context->custom; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - rdp_debug(b, "Client: %s %s\n", __func__, tempDirectory->szTempDir); + rdp_debug_clipboard(b, "Client: %s %s\n", __func__, tempDirectory->szTempDir); return 0; } @@ -1114,24 +1114,24 @@ clipboard_client_capabilities(CliprdrServerContext* context, const CLIPRDR_CAPAB freerdp_peer *client = (freerdp_peer*)context->custom; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - rdp_debug(b, "Client: clipboard capabilities: cCapabilitiesSet:%d\n", capabilities->cCapabilitiesSets); + rdp_debug_clipboard(b, "Client: clipboard capabilities: cCapabilitiesSet:%d\n", capabilities->cCapabilitiesSets); for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++) { CLIPRDR_CAPABILITY_SET* capabilitySets = &capabilities->capabilitySets[i]; switch (capabilitySets->capabilitySetType) { case CB_CAPSTYPE_GENERAL: { CLIPRDR_GENERAL_CAPABILITY_SET *generalCapabilitySet = (CLIPRDR_GENERAL_CAPABILITY_SET *)capabilitySets; - rdp_debug(b, "Client: clipboard capabilities[%d]: General\n", i); - rdp_debug(b, " Version:%d\n", generalCapabilitySet->version); - rdp_debug(b, " GeneralFlags:0x%x\n", generalCapabilitySet->generalFlags); + rdp_debug_clipboard(b, "Client: clipboard capabilities[%d]: General\n", i); + rdp_debug_clipboard(b, " Version:%d\n", generalCapabilitySet->version); + rdp_debug_clipboard(b, " GeneralFlags:0x%x\n", generalCapabilitySet->generalFlags); if (generalCapabilitySet->generalFlags & CB_USE_LONG_FORMAT_NAMES) - rdp_debug(b, " CB_USE_LONG_FORMAT_NAMES\n"); + rdp_debug_clipboard(b, " CB_USE_LONG_FORMAT_NAMES\n"); if (generalCapabilitySet->generalFlags & CB_STREAM_FILECLIP_ENABLED) - rdp_debug(b, " CB_STREAM_FILECLIP_ENABLED\n"); + rdp_debug_clipboard(b, " CB_STREAM_FILECLIP_ENABLED\n"); if (generalCapabilitySet->generalFlags & CB_FILECLIP_NO_FILE_PATHS) - rdp_debug(b, " CB_FILECLIP_NO_FILE_PATHS\n"); + rdp_debug_clipboard(b, " CB_FILECLIP_NO_FILE_PATHS\n"); if (generalCapabilitySet->generalFlags & CB_CAN_LOCK_CLIPDATA) - rdp_debug(b, " CB_CAN_LOCK_CLIPDATA\n"); + rdp_debug_clipboard(b, " CB_CAN_LOCK_CLIPDATA\n"); break; } default: @@ -1154,17 +1154,17 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT ASSERT_NOT_COMPOSITOR_THREAD(b); - rdp_debug(b, "Client: %s clipboard format list: numFormats:%d\n", __func__, formatList->numFormats); + rdp_debug_clipboard(b, "Client: %s clipboard format list: numFormats:%d\n", __func__, formatList->numFormats); for (UINT32 i = 0; i < formatList->numFormats; i++) { CLIPRDR_FORMAT* format = &formatList->formats[i]; - rdp_debug(b, "Client: %s clipboard formats[%d]: formatId:%d, formatName:%s\n", + rdp_debug_clipboard(b, "Client: %s clipboard formats[%d]: formatId:%d, formatName:%s\n", __func__, i, format->formatId, format->formatName ? format->formatName : clipboard_format_id_to_string(format->formatId, false)); } source = zalloc(sizeof *source); if (source) { - rdp_debug(b, "Client: %s (%p) allocated\n", __func__, source); + rdp_debug_clipboard(b, "Client: %s (%p) allocated\n", __func__, source); wl_signal_init(&source->base.destroy_signal); wl_array_init(&source->base.mime_types); wl_array_init(&source->data_contents); @@ -1183,14 +1183,14 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT if (s) { p = wl_array_add(&source->base.mime_types, sizeof *p); if (p) { - rdp_debug(b, "Client: %s (%p) mine_type:\"%s\"\n", __func__, source, s); + rdp_debug_clipboard(b, "Client: %s (%p) mine_type:\"%s\"\n", __func__, source, s); *p = s; } else { - rdp_debug(b, "Client: %s (%p) wl_array_add failed\n", __func__, source); + rdp_debug_clipboard(b, "Client: %s (%p) wl_array_add failed\n", __func__, source); free(s); } } else { - rdp_debug(b, "Client: %s (%p) strdup failed\n", __func__, source); + rdp_debug_clipboard(b, "Client: %s (%p) strdup failed\n", __func__, source); } } } @@ -1205,7 +1205,7 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT else weston_log("Client: %s (%p) rdp_defer_rdp_task_to_display_loop failed\n", __func__, source); } else { - rdp_debug(b, "Client: %s (%p) no formats are supported\n", __func__, source); + rdp_debug_clipboard(b, "Client: %s (%p) no formats are supported\n", __func__, source); } } @@ -1235,7 +1235,7 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR struct rdp_clipboard_data_source *source = peerCtx->clipboard_inflight_client_data_source; BOOL Success = FALSE; - rdp_debug(b, "Client: %s (%p) flags:%d, dataLen:%d\n", + rdp_debug_clipboard(b, "Client: %s (%p) flags:%d, dataLen:%d\n", __func__, source, formatDataResponse->msgFlags, formatDataResponse->dataLen); ASSERT_NOT_COMPOSITOR_THREAD(b); @@ -1278,7 +1278,7 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR peerCtx->clipboard_inflight_client_data_source = NULL; } } else { - rdp_debug(b, "Client: %s client send data without server asking. protocol error.\n", __func__); + rdp_debug_clipboard(b, "Client: %s client send data without server asking. protocol error.\n", __func__); return -1; } @@ -1292,7 +1292,7 @@ clipboard_client_format_list_response(CliprdrServerContext* context, const CLIPR freerdp_peer *client = (freerdp_peer*)context->custom; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - rdp_debug(b, "Client: %s msgFlags:0x%x\n", __func__, formatListResponse->msgFlags); + rdp_debug_clipboard(b, "Client: %s msgFlags:0x%x\n", __func__, formatListResponse->msgFlags); return 0; } @@ -1305,7 +1305,7 @@ clipboard_client_format_data_request(CliprdrServerContext* context, const CLIPRD struct rdp_backend *b = peerCtx->rdpBackend; int index; - rdp_debug(b, "Client: %s requestedFormatId:%d - %s\n", + rdp_debug_clipboard(b, "Client: %s requestedFormatId:%d - %s\n", __func__, formatDataRequest->requestedFormatId, clipboard_format_id_to_string(formatDataRequest->requestedFormatId, true)); @@ -1351,14 +1351,32 @@ rdp_clipboard_init(freerdp_peer* client) RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; struct weston_seat *seat = peerCtx->item.seat; + char *s; assert(seat); ASSERT_COMPOSITOR_THREAD(b); + b->debugClipboard = weston_log_ctx_add_log_scope(b->compositor->weston_log_ctx, + "rdp-backend-clipboard", + "Debug messages from RDP backend clipboard\n", + NULL, NULL, NULL); + if (b->debugClipboard) { + s = getenv("WESTON_RDP_DEBUG_CLIPBOARD_LEVEL"); + if (s) { + if (!safe_strtoint(s, &b->debugClipboardLevel)) + b->debugClipboardLevel = RDP_DEBUG_LEVEL_DEFAULT; + else if (b->debugClipboardLevel > RDP_DEBUG_LEVEL_VERBOSE) + b->debugClipboardLevel = RDP_DEBUG_LEVEL_VERBOSE; + } else { + b->debugClipboardLevel = RDP_DEBUG_LEVEL_DEFAULT; + } + } + weston_log("RDP backend: WESTON_RDP_DEBUG_CLIPBOARD_LEVEL: %d\n", b->debugClipboardLevel); + peerCtx->clipboard_server_context = cliprdr_server_context_new(peerCtx->vcm); - if (!peerCtx->clipboard_server_context) - return -1; + if (!peerCtx->clipboard_server_context) + goto error; peerCtx->clipboard_server_context->custom = (void *)client; peerCtx->clipboard_server_context->TempDirectory = clipboard_client_temp_directory; @@ -1375,21 +1393,34 @@ rdp_clipboard_init(freerdp_peer* client) peerCtx->clipboard_server_context->streamFileClipEnabled = FALSE; peerCtx->clipboard_server_context->fileClipNoFilePaths = FALSE; peerCtx->clipboard_server_context->canLockClipData = TRUE; - if (peerCtx->clipboard_server_context->Start(peerCtx->clipboard_server_context) != 0) { - cliprdr_server_context_free(peerCtx->clipboard_server_context); - return -1; - } + if (peerCtx->clipboard_server_context->Start(peerCtx->clipboard_server_context) != 0) + goto error; peerCtx->clipboard_selection_listener.notify = clipboard_set_selection; wl_signal_add(&seat->selection_signal, &peerCtx->clipboard_selection_listener); return 0; + +error: + if (peerCtx->clipboard_server_context) { + cliprdr_server_context_free(peerCtx->clipboard_server_context); + peerCtx->clipboard_server_context = NULL; + } + + if (b->debugClipboard) { + weston_log_scope_destroy(b->debugClipboard); + b->debugClipboard = NULL; + } + + return -1; } void rdp_clipboard_destroy(RdpPeerContext *peerCtx) { + struct rdp_backend *b = peerCtx->rdpBackend; + if (peerCtx->clipboard_selection_listener.notify) { wl_list_remove(&peerCtx->clipboard_selection_listener.link); peerCtx->clipboard_selection_listener.notify = NULL; @@ -1414,4 +1445,9 @@ rdp_clipboard_destroy(RdpPeerContext *peerCtx) cliprdr_server_context_free(peerCtx->clipboard_server_context); peerCtx->clipboard_server_context = NULL; } + + if (b->debugClipboard) { + weston_log_scope_destroy(b->debugClipboard); + b->debugClipboard = NULL; + } } diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index 2659db3f2..1a126871d 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -48,6 +49,65 @@ pid_t rdp_get_tid() #endif } +static int cached_tm_mday = -1; + +static char * +rdp_log_timestamp(char *buf, size_t len) +{ + struct timeval tv; + struct tm *brokendown_time; + char datestr[128]; + char timestr[128]; + + gettimeofday(&tv, NULL); + + brokendown_time = localtime(&tv.tv_sec); + if (brokendown_time == NULL) { + snprintf(buf, len, "%s", "[(NULL)localtime] "); + return buf; + } + + memset(datestr, 0, sizeof(datestr)); + if (brokendown_time->tm_mday != cached_tm_mday) { + strftime(datestr, sizeof(datestr), "Date: %Y-%m-%d %Z\n", + brokendown_time); + cached_tm_mday = brokendown_time->tm_mday; + } + + strftime(timestr, sizeof(timestr), "%H:%M:%S", brokendown_time); + /* if datestr is empty it prints only timestr*/ + snprintf(buf, len, "%s[%s.%03li]", datestr, + timestr, (tv.tv_usec / 1000)); + + return buf; +} + +void rdp_debug_print(struct weston_log_scope *log_scope, bool cont, char *fmt, ...) +{ + if (log_scope && weston_log_scope_is_enabled(log_scope)) { + va_list ap; + va_start(ap, fmt); + if (cont) { + weston_log_scope_vprintf(log_scope, fmt, ap); + } else { + char timestr[128]; + int len_va; + char *str; + rdp_log_timestamp(timestr, sizeof(timestr)); + len_va = vasprintf(&str, fmt, ap); + if (len_va >= 0) { + weston_log_scope_printf(log_scope, "%s %s", + timestr, str); + free(str); + } else { + const char *oom = "Out of memory"; + weston_log_scope_printf(log_scope, "%s %s", + timestr, oom); + } + } + } +} + #ifdef ENABLE_RDP_THREAD_CHECK void assert_compositor_thread(struct rdp_backend *b) { diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 1626f5e78..a136a395b 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "shell.h" @@ -232,6 +233,66 @@ shell_surface_update_child_surface_layers(struct shell_surface *shsurf); #define ICON_STRIDE( W, BPP ) ((((W) * (BPP) + 31) / 32) * 4) +static int cached_tm_mday = -1; + +static char * +shell_rdp_log_timestamp(char *buf, size_t len) +{ + struct timeval tv; + struct tm *brokendown_time; + char datestr[128]; + char timestr[128]; + + gettimeofday(&tv, NULL); + + brokendown_time = localtime(&tv.tv_sec); + if (brokendown_time == NULL) { + snprintf(buf, len, "%s", "[(NULL)localtime] "); + return buf; + } + + memset(datestr, 0, sizeof(datestr)); + if (brokendown_time->tm_mday != cached_tm_mday) { + strftime(datestr, sizeof(datestr), "Date: %Y-%m-%d %Z\n", + brokendown_time); + cached_tm_mday = brokendown_time->tm_mday; + } + + strftime(timestr, sizeof(timestr), "%H:%M:%S", brokendown_time); + /* if datestr is empty it prints only timestr*/ + snprintf(buf, len, "%s[%s.%03li]", datestr, + timestr, (tv.tv_usec / 1000)); + + return buf; +} + +void +shell_rdp_debug_print(struct weston_log_scope *scope, bool cont, char *fmt, ...) +{ + if (scope && weston_log_scope_is_enabled(scope)) { + va_list ap; + va_start(ap, fmt); + if (cont) { + weston_log_scope_vprintf(scope, fmt, ap); + } else { + char timestr[128]; + int len_va; + char *str; + shell_rdp_log_timestamp(timestr, sizeof(timestr)); + len_va = vasprintf(&str, fmt, ap); + if (len_va >= 0) { + weston_log_scope_printf(scope, "%s %s", + timestr, str); + free(str); + } else { + const char *oom = "Out of memory"; + weston_log_scope_printf(scope, "%s %s", + timestr, oom); + } + } + } +} + void shell_blend_overlay_icon(struct desktop_shell *shell, pixman_image_t *app_image, pixman_image_t *overlay_image) { @@ -258,7 +319,7 @@ shell_blend_overlay_icon(struct desktop_shell *shell, pixman_image_t *app_image, overlay_scale_width = 1.0f / (((double)app_width / overlay_width) / 1.75f); overlay_scale_height = 1.0f / (((double)app_height / overlay_height) / 1.75f); - shell_rdp_debug(shell, "%s: app %dx%d; overlay %dx%d; scale %4.2fx%4.2f\n", + shell_rdp_debug_verbose(shell, "%s: app %dx%d; overlay %dx%d; scale %4.2fx%4.2f\n", __func__, app_width, app_height, overlay_width, overlay_height, overlay_scale_width, overlay_scale_height); diff --git a/rdprail-shell/shell.h b/rdprail-shell/shell.h index f26203632..366ed76dc 100644 --- a/rdprail-shell/shell.h +++ b/rdprail-shell/shell.h @@ -42,18 +42,13 @@ #define RDPRAIL_SHELL_DEBUG_LEVEL_DEFAULT RDPRAIL_SHELL_DEBUG_LEVEL_INFO -/* Ideally here should use weston_log_scope_printf() instead of weston_log() - since weston_log() requires "log" scope to be enabled, but weston_log() - added timestamp which is often helpful, thus use weston_log() here. - To enable shell_rdp_debug message, add "--logger-scopes=rdprail-shell,log" - to weston's command line, this added rdprail-shell and log scopes */ -#define shell_rdp_debug_level(s, lvl, ...) \ - if ((s) && (s)->debug && ((s)->debugLevel >= (lvl)) && weston_log_scope_is_enabled((s)->debug)) { \ - weston_log(__VA_ARGS__); \ - } - -#define shell_rdp_debug_verbose(b, ...) shell_rdp_debug_level(b, RDPRAIL_SHELL_DEBUG_LEVEL_VERBOSE, __VA_ARGS__) -#define shell_rdp_debug(b, ...) shell_rdp_debug_level(b, RDPRAIL_SHELL_DEBUG_LEVEL_INFO, __VA_ARGS__) +/* To enable shell_rdp_debug message, add "--logger-scopes=rdprail-shell" */ +#define shell_rdp_debug_verbose(b, ...) \ + if (b->debugLevel >= RDPRAIL_SHELL_DEBUG_LEVEL_VERBOSE) \ + shell_rdp_debug_print(b->debug, false, __VA_ARGS__) +#define shell_rdp_debug(b, ...) \ + if (b->debugLevel >= RDPRAIL_SHELL_DEBUG_LEVEL_INFO) \ + shell_rdp_debug_print(b->debug, false, __VA_ARGS__) #define is_system_distro() (getenv("WSL2_VM_ID") != NULL) @@ -180,6 +175,9 @@ shell_blend_overlay_icon(struct desktop_shell *shell, pixman_image_t *app_image, pixman_image_t *overlay_image); +void +shell_rdp_debug_print(struct weston_log_scope *scope, bool cont, char *fmt, ...); + void app_list_init(struct desktop_shell *shell); void app_list_destroy(struct desktop_shell *shell); pixman_image_t *app_list_load_icon_file(struct desktop_shell *shell, const char *key); From 43741db009c7f5af1ae2fe61dd1eb7a0508b7f3d Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Sun, 9 May 2021 18:33:43 -0700 Subject: [PATCH 1460/1642] support window z order sync with client (#9) Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 7 +- libweston/backend-rdp/rdp.c | 9 +- libweston/backend-rdp/rdp.h | 5 + libweston/backend-rdp/rdpclip.c | 12 +- libweston/backend-rdp/rdprail.c | 235 ++++++++++++++++++++++++-------- rdprail-shell/shell.c | 23 +++- 6 files changed, 219 insertions(+), 72 deletions(-) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index 447746f68..c4dbab57e 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -83,7 +83,7 @@ struct weston_rdprail_shell_api { /** Activate a window. */ - void (*request_window_activate)(struct weston_surface *surface, struct weston_seat *seat); + void (*request_window_activate)(void *shell_context, struct weston_seat *seat, struct weston_surface *surface); /** Close a window. */ @@ -160,6 +160,11 @@ struct weston_rdprail_api { /** Get primary output */ struct weston_output *(*get_primary_output)(void *rdp_backend); + + /** Update window zorder + */ + void (*notify_window_zorder_change)(struct weston_compositor *compositor, + struct weston_surface *surface); }; static inline const struct weston_rdprail_api * diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 375b9d366..4029021ca 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1453,7 +1453,7 @@ rdp_validate_button_state(RdpPeerContext *peerContext, bool pressed, uint32_t* b uint32_t index = *button - BTN_LEFT; assert(index < ARRAY_LENGTH(peerContext->button_state)); if (pressed == peerContext->button_state[index]) { - rdp_debug(b, "%s: inconsistent button state button:%d (index:%d) pressed:%d\n", + rdp_debug_verbose(b, "%s: inconsistent button state button:%d (index:%d) pressed:%d\n", __func__, *button, index, pressed); /* ignore button input */ *button = 0; @@ -1634,7 +1634,7 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) uint32_t scan_code, vk_code, full_code; enum wl_keyboard_key_state keyState; RdpPeerContext *peerContext = (RdpPeerContext *)input->context; - /*struct rdp_backend *b = peerContext->rdpBackend;*/ + struct rdp_backend *b = peerContext->rdpBackend; int notify = 0; struct timespec time; @@ -1659,8 +1659,11 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) assert(vk_code <= 0xFF); if (keyState == WL_KEYBOARD_KEY_STATE_RELEASED) { /* Ignore release if key is not previously pressed. */ - if ((peerContext->key_state[vk_code>>3] & (1<<(vk_code&0x7))) == 0) + if ((peerContext->key_state[vk_code>>3] & (1<<(vk_code&0x7))) == 0) { + rdp_debug_verbose(b, "%s: inconsistent key state vk_code:%x\n", + __func__, vk_code); goto exit; + } peerContext->key_state[vk_code>>3] &= ~(1<<(vk_code&0x7)); } else { peerContext->key_state[vk_code>>3] |= (1<<(vk_code&0x7)); diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 265742978..8763d9588 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -181,6 +181,8 @@ struct rdp_backend { struct wl_listener create_window_listener; + bool enable_window_zorder_sync; + bool enable_hi_dpi_support; bool enable_fractional_hi_dpi_support; uint32_t debug_desktop_scaling_factor; /* must be between 100 to 500 */ @@ -309,6 +311,9 @@ struct rdp_peer_context { struct wl_listener idle_listener; struct wl_listener wake_listener; + bool is_window_zorder_dirty; + struct weston_surface *active_surface; + // Multiple monitor support (monitor topology) pixman_region32_t regionClientHeads; pixman_region32_t regionWestonHeads; diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index 6d435702b..5d70ebd95 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -834,15 +834,15 @@ clipboard_data_source_send(struct weston_data_source *base, formatDataRequest.msgType = CB_FORMAT_DATA_REQUEST; formatDataRequest.dataLen = 4; formatDataRequest.requestedFormatId = source->client_format_id_table[index]; - rdp_debug_clipboard(b, "RDP %s (%p) request index:%d formatId:%d %s\n", - __func__, source, index, + rdp_debug_clipboard(b, "RDP %s (%p) request \"%s\" index:%d formatId:%d %s\n", + __func__, source, mime_type, index, formatDataRequest.requestedFormatId, clipboard_format_id_to_string(formatDataRequest.requestedFormatId, false)); if (peerCtx->clipboard_server_context->ServerFormatDataRequest(peerCtx->clipboard_server_context, &formatDataRequest) != 0) goto error_return_unref_source; } } else { - weston_log("RDP %s (%p) specified format (%s.%d.%d) is not supported by client\n", + weston_log("RDP %s (%p) specified format \"%s\" index:%d formatId:%d is not supported by client\n", __func__, source, mime_type, index, source->client_format_id_table[index]); goto error_return_close_fd; } @@ -878,11 +878,12 @@ clipboard_data_source_cancel(struct weston_data_source *base) ASSERT_COMPOSITOR_THREAD(b); if (source == peerCtx->clipboard_inflight_client_data_source) { - weston_log("RDP %s (%p): still inflight\n", __func__, source); + rdp_debug_clipboard(b, "RDP %s (%p): still inflight\n", __func__, source); assert(source->refcount > 1); source->is_canceled = TRUE; } else { /* everything outside of the base has to be cleaned up */ + rdp_debug_clipboard_verbose(b, "RDP %s (%p): cancelled\n", __func__, source); assert(source->event_source == NULL); wl_array_release(&source->data_contents); wl_array_init(&source->data_contents); @@ -1183,7 +1184,8 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT if (s) { p = wl_array_add(&source->base.mime_types, sizeof *p); if (p) { - rdp_debug_clipboard(b, "Client: %s (%p) mine_type:\"%s\"\n", __func__, source, s); + rdp_debug_clipboard(b, "Client: %s (%p) mine_type:\"%s\" index:%d formatId:%d\n", + __func__, source, s, index, format->formatId); *p = s; } else { rdp_debug_clipboard(b, "Client: %s (%p) wl_array_add failed\n", __func__, source); diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 4845c0d6d..dc155ac07 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -50,6 +50,7 @@ extern PWtsApiFunctionTable FreeRDP_InitWtsApi(void); static void rdp_rail_destroy_window(struct wl_listener *listener, void *data); static void rdp_rail_schedule_update_window(struct wl_listener *listener, void *data); +static void rdp_rail_dump_window_label(struct weston_surface *surface, char *label, uint32_t label_size); struct rdp_dispatch_data { struct rdp_loop_event_source _base; @@ -293,19 +294,21 @@ rail_client_Activate_callback(void *arg) freerdp_peer *client = data->client; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - struct weston_surface *surface; + struct weston_surface *surface = NULL; rdp_debug_verbose(b, "Client: ClientActivate: WindowId:0x%x, enabled:%d\n", activate->windowId, activate->enabled); ASSERT_COMPOSITOR_THREAD(b); if (b->rdprail_shell_api && - b->rdprail_shell_api->request_window_activate) { - surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, activate->windowId); - if (surface) - b->rdprail_shell_api->request_window_activate(surface, peerCtx->item.seat); - else - weston_log("Client: ClientActivate: WindowId:0x%x is not found.\n", activate->windowId); + b->rdprail_shell_api->request_window_activate && + b->rdprail_shell_context) { + if (activate->windowId && activate->enabled) { + surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, activate->windowId); + if (!surface) + weston_log("Client: ClientActivate: WindowId:0x%x is not found.\n", activate->windowId); + } + b->rdprail_shell_api->request_window_activate(b->rdprail_shell_context, peerCtx->item.seat, surface); } RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); @@ -314,18 +317,7 @@ rail_client_Activate_callback(void *arg) static UINT rail_client_Activate(RailServerContext* context, const RAIL_ACTIVATE_ORDER* arg) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - - if (arg->windowId == RDP_RAIL_MARKER_WINDOW_ID) { - rdp_debug_verbose(b, "Client: ClientActivate: marker window is %s.\n", arg->enabled ? "enabled" : "disabled"); - } else if (arg->enabled) { - RDP_DISPATCH_TO_DISPLAY_LOOP(context, activate, arg, rail_client_Activate_callback); - } else { - /* no need to handle deactivate from client, just */ - /* keep current focus until new focus reported. */ - } + RDP_DISPATCH_TO_DISPLAY_LOOP(context, activate, arg, rail_client_Activate_callback); return CHANNEL_RC_OK; } @@ -1448,6 +1440,12 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) pixman_region32_init_rect(&rail_state->damage, 0, 0, surface->width_from_buffer, surface->height_from_buffer); + /* as new window created, mark z order dirty */ + /* TODO: ideally this better be triggered from shell, but shell isn't notified + creation/destruction of certain type of window, such as dropdown menu + (popup in Wayland, override_redirect in X), thus do it here. */ + peerCtx->is_window_zorder_dirty = true; + Exit: /* once window is successfully created, start listening repaint update */ if (!rail_state->error) { @@ -1580,6 +1578,15 @@ rdp_rail_destroy_window(struct wl_listener *listener, void *data) rdp_id_manager_free_id(&peerCtx->windowId, window_id); rail_state->window_id = 0; + /* as window destroyed, mark z order dirty and if this is active window, clear it */ + /* TODO: ideally this better be triggered from shell, but shell isn't notified + creation/destruction of certain type of window, such as dropdown menu + (popup in Wayland, override_redirect in X), thus do it here. */ + peerCtx->is_window_zorder_dirty = true; + if (peerCtx->active_surface == surface) { + peerCtx->active_surface = NULL; + } + if (rail_state->repaint_listener.notify) { wl_list_remove(&rail_state->repaint_listener.link); rail_state->repaint_listener.notify = NULL; @@ -1638,8 +1645,15 @@ rdp_rail_schedule_update_window(struct wl_listener *listener, void *data) return; } +struct update_window_iter_data { + uint32_t output_id; + UINT32 startedFrameId; + BOOL needEndFrame; + BOOL isUpdatePending; +}; + static int -rdp_rail_update_window(struct weston_surface *surface, BOOL *needEndFrame, UINT32 *startedFrameId, BOOL *isUpdatePending) +rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter_data *iter_data) { struct weston_compositor *compositor = surface->compositor; struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; @@ -2218,7 +2232,7 @@ rdp_rail_update_window(struct weston_surface *surface, BOOL *needEndFrame, UINT3 if (peerCtx->gfxredir_server_context->PresentBuffer(peerCtx->gfxredir_server_context, &presentBuffer) == 0) { rail_state->isUpdatePending = TRUE; - *isUpdatePending = TRUE; + iter_data->isUpdatePending = TRUE; } else { weston_log("PresentBuffer failed for windowId:0x%x\n",window_id); } @@ -2300,15 +2314,15 @@ rdp_rail_update_window(struct weston_surface *surface, BOOL *needEndFrame, UINT3 } } - if (*needEndFrame == FALSE) { + if (iter_data->needEndFrame == FALSE) { /* if frame is not started yet, send StartFrame first before sendng surface command. */ RDPGFX_START_FRAME_PDU startFrame = {}; startFrame.frameId = ++peerCtx->currentFrameId; rdp_debug_verbose(b, "StartFrame(frameId:0x%x, windowId:0x%x)\n", startFrame.frameId, window_id); peerCtx->rail_grfx_server_context->StartFrame(peerCtx->rail_grfx_server_context, &startFrame); - *startedFrameId = startFrame.frameId; - *needEndFrame = TRUE; - *isUpdatePending = TRUE; + iter_data->startedFrameId = startFrame.frameId; + iter_data->needEndFrame = TRUE; + iter_data->isUpdatePending = TRUE; } surfaceCommand.surfaceId = rail_state->surface_id; @@ -2326,14 +2340,16 @@ rdp_rail_update_window(struct weston_surface *surface, BOOL *needEndFrame, UINT3 surfaceCommand.codecId = RDPGFX_CODECID_ALPHA; surfaceCommand.length = alphaSize; surfaceCommand.data = &alpha[0]; - rdp_debug_verbose(b, "SurfaceCommand(frameId:0x%x, windowId:0x%x) for alpha\n", *startedFrameId, window_id); + rdp_debug_verbose(b, "SurfaceCommand(frameId:0x%x, windowId:0x%x) for alpha\n", + iter_data->startedFrameId, window_id); peerCtx->rail_grfx_server_context->SurfaceCommand(peerCtx->rail_grfx_server_context, &surfaceCommand); /* send bitmap data */ surfaceCommand.codecId = RDPGFX_CODECID_UNCOMPRESSED; surfaceCommand.length = damageSize; surfaceCommand.data = &data[0]; - rdp_debug_verbose(b, "SurfaceCommand(frameId:0x%x, windowId:0x%x) for bitmap\n", *startedFrameId, window_id); + rdp_debug_verbose(b, "SurfaceCommand(frameId:0x%x, windowId:0x%x) for bitmap\n", + iter_data->startedFrameId, window_id); peerCtx->rail_grfx_server_context->SurfaceCommand(peerCtx->rail_grfx_server_context, &surfaceCommand); free(data); @@ -2384,13 +2400,6 @@ rdp_rail_update_window(struct weston_surface *surface, BOOL *needEndFrame, UINT3 return 0; } -struct update_window_iter_data { - uint32_t output_id; - UINT32 startedFrameId; - BOOL needEndFrame; - BOOL isUpdatePending; -}; - static void rdp_rail_update_window_iter(void *element, void *data) { @@ -2401,18 +2410,95 @@ rdp_rail_update_window_iter(void *element, void *data) struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; assert(rail_state); // this iter is looping from window hash table, thus it must have rail_state initialized. if (surface->output_mask & (1u << iter_data->output_id)) { - if (rail_state->isCursor) { + if (rail_state->isCursor) rdp_rail_update_cursor(surface); - } else if (rail_state->isUpdatePending == FALSE) { - rdp_rail_update_window(surface, - &iter_data->needEndFrame, - &iter_data->startedFrameId, - &iter_data->isUpdatePending); - } else { + else if (rail_state->isUpdatePending == FALSE) + rdp_rail_update_window(surface, iter_data); + else rdp_debug_verbose(b, "window update is skipped for windowId:0x%x, isUpdatePending = %d\n", - rail_state->window_id, rail_state->isUpdatePending); + rail_state->window_id, rail_state->isUpdatePending); + } +} + +static void +rdp_rail_sync_window_zorder(struct weston_compositor *compositor) +{ + struct rdp_backend *b = to_rdp_backend(compositor); + freerdp_peer* client = b->rdp_peer; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + UINT32 numWindowId = 0; + UINT32 *windowIdArray = NULL; + WINDOW_ORDER_INFO window_order_info = {}; + MONITORED_DESKTOP_ORDER monitored_desktop_order = {}; + char label[256]; + UINT32 i = 0; + + ASSERT_COMPOSITOR_THREAD(b); + + if (!b->enable_window_zorder_sync) + return; + + numWindowId = peerCtx->windowId.id_used + 1; // +1 for marker window. + windowIdArray = zalloc(numWindowId * sizeof(UINT32)); + if (!windowIdArray) { + weston_log("%s: zalloc(%ld bytes) failed\n", __func__, numWindowId * sizeof(UINT32)); + return; + } + + rdp_debug_verbose(b, "Dump Window Z order\n"); + if (!peerCtx->active_surface) { + /* if no active window, put marker window top as client window has focus. */ + rdp_debug_verbose(b, " window[%d]: %x: %s\n", i, RDP_RAIL_MARKER_WINDOW_ID, "marker window"); + windowIdArray[i++] = RDP_RAIL_MARKER_WINDOW_ID; + } + /* walk windows in z-order */ + struct weston_layer *layer; + wl_list_for_each(layer, &compositor->layer_list, link) { + struct weston_view *view; + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)view->surface->backend_state; + if (rail_state->isWindowCreated && + !rail_state->is_minimized && + !rail_state->is_minimized_requested) { + if (i >= numWindowId) { + weston_log("%s: more windows in tree than ID manager tracking (%d vs %d)\n", + __func__, i, numWindowId); + goto Exit; + } + if (b->debugLevel >= RDP_DEBUG_LEVEL_VERBOSE) { + rdp_rail_dump_window_label(view->surface, label, sizeof(label)); + rdp_debug_verbose(b, " window[%d]: %x: %s\n", i, rail_state->window_id, label); + } + windowIdArray[i++] = rail_state->window_id; + } } } + if (peerCtx->active_surface) { + /* TODO: marker window better be placed correct place relative to client window, not always bottom */ + /* In order to do that, dummpy window to be created to track where is the highest client window. */ + rdp_debug_verbose(b, " window[%d]: %x: %s\n", i, RDP_RAIL_MARKER_WINDOW_ID, "marker window"); + windowIdArray[i++] = RDP_RAIL_MARKER_WINDOW_ID; + } + assert(i <= numWindowId); + assert(i > 0); + rdp_debug_verbose(b, " send Window Z order: numWindowIds:%d\n", i); + + window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | + WINDOW_ORDER_FIELD_DESKTOP_ZORDER | + WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND; + monitored_desktop_order.activeWindowId = windowIdArray[0]; + monitored_desktop_order.numWindowIds = i; + monitored_desktop_order.windowIds = windowIdArray; + + client->update->window->MonitoredDesktop(client->context, &window_order_info, &monitored_desktop_order); + client->DrainOutputBuffer(client); + +Exit: + if (windowIdArray) + free(windowIdArray); + + return; } void @@ -2435,6 +2521,11 @@ rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage) rdp_debug_verbose(b, "EndFrame(frameId:0x%x)\n", endFrame.frameId); peerCtx->rail_grfx_server_context->EndFrame(peerCtx->rail_grfx_server_context, &endFrame); } + if (peerCtx->is_window_zorder_dirty) { + /* notify window z order to client */ + rdp_rail_sync_window_zorder(b->compositor); + peerCtx->is_window_zorder_dirty = false; + } if (iter_data.isUpdatePending) { /* By default, compositor won't update idle timer by screen activity, thus, here manually call wake function to postpone idle timer when @@ -2672,6 +2763,8 @@ rdp_rail_idle_handler(struct wl_listener *listener, void *data) container_of(listener, RdpPeerContext, idle_listener); struct rdp_backend *b = peerCtx->rdpBackend; + ASSERT_COMPOSITOR_THREAD(b); + rdp_debug(b, "%s is called on peerCtx:%p\n", __func__, peerCtx); displayRequest.active = FALSE; @@ -2687,6 +2780,8 @@ rdp_rail_wake_handler(struct wl_listener *listener, void *data) container_of(listener, RdpPeerContext, wake_listener); struct rdp_backend *b = peerCtx->rdpBackend; + ASSERT_COMPOSITOR_THREAD(b); + rdp_debug(b, "%s is called on peerCtx:%p\n", __func__, peerCtx); displayRequest.active = TRUE; @@ -2694,6 +2789,21 @@ rdp_rail_wake_handler(struct wl_listener *listener, void *data) peerCtx->rail_server_context, &displayRequest); } +static void +rdp_rail_notify_window_zorder_change(struct weston_compositor *compositor, struct weston_surface *active_surface) +{ + struct rdp_backend *b = to_rdp_backend(compositor); + freerdp_peer* client = b->rdp_peer; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + + ASSERT_COMPOSITOR_THREAD(b); + + /* active_surface is NULL while client window has focus */ + peerCtx->active_surface = active_surface; + /* z order will be sent to client at next repaint */ + peerCtx->is_window_zorder_dirty = true; +} + void rdp_rail_sync_window_status(freerdp_peer* client) { @@ -2790,8 +2900,6 @@ rdp_rail_sync_window_status(freerdp_peer* client) } } - /* TODO: Z-order sync */ - /* this assume repaint to be scheduled on idle loop, not directly from here */ weston_compositor_damage_all(b->compositor); } @@ -3229,6 +3337,19 @@ struct rdp_rail_dump_window_context { RdpPeerContext *peerCtx; }; +static void +rdp_rail_dump_window_label(struct weston_surface *surface, char *label, uint32_t label_size) +{ + if (surface->get_label) { + strcpy(label, "Label: "); // 7 chars + surface->get_label(surface, label + 7, label_size - 7); + } else if (surface->role_name) { + snprintf(label, label_size, "RoleName: %s", surface->role_name); + } else { + strcpy(label, "(No Label, No Role name)"); + } +} + static void rdp_rail_dump_window_iter(void *element, void *data) { @@ -3242,17 +3363,8 @@ rdp_rail_dump_window_iter(void *element, void *data) int contentBufferWidth, contentBufferHeight; weston_surface_get_content_size(surface, &contentBufferWidth, &contentBufferHeight); - if (surface->role_name || surface->get_label) { - if (surface->role_name) - fprintf(fp," RoleName: %s\n", surface->role_name); - if (surface->get_label) { - label[0] = '\0'; - surface->get_label(surface, label, sizeof(label)); - fprintf(fp," Label: %s\n", label); - } - } else { - fprintf(fp," (No Label, No Role name)\n"); - } + rdp_rail_dump_window_label(surface, label, sizeof(label)); + fprintf(fp," %s\n", label); fprintf(fp," WindowId:0x%x, SurfaceId:0x%x\n", rail_state->window_id, rail_state->surface_id); fprintf(fp," PoolId:0x%x, BufferId:0x%x\n", @@ -3442,7 +3554,7 @@ rdp_rail_set_window_icon(struct weston_surface *surface, pixman_image_t *icon) if (width == 0 || height == 0) return; - rdp_debug(b, "rdp_rail_set_window_icon: original icon width:%d height:%d format:%d\n", + rdp_debug_verbose(b, "rdp_rail_set_window_icon: original icon width:%d height:%d format:%d\n", width, height, format); /* TS_RAIL_CLIENTSTATUS_HIGH_DPI_ICONS_SUPPORTED @@ -3506,7 +3618,7 @@ rdp_rail_set_window_icon(struct weston_surface *surface, pixman_image_t *icon) assert(height == targetIconHeight); assert(format == PIXMAN_a8r8g8b8); - rdp_debug(b, "rdp_rail_set_window_icon: converted icon width:%d height:%d format:%d\n", + rdp_debug_verbose(b, "rdp_rail_set_window_icon: converted icon width:%d height:%d format:%d\n", width, height, format); /* color bitmap is 32 bits */ @@ -3742,6 +3854,7 @@ struct weston_rdprail_api rdprail_api = { .notify_app_list = NULL, #endif // HAVE_FREERDP_RDPAPPLIST_H .get_primary_output = rdp_rail_get_primary_output, + .notify_window_zorder_change = rdp_rail_notify_window_zorder_change, }; int @@ -3867,6 +3980,14 @@ rdp_rail_backend_create(struct rdp_backend *b) } rdp_debug(b, "RDP backend: debug_desktop_scaling_factor = %d\n", b->debug_desktop_scaling_factor); + b->enable_window_zorder_sync = true; + s = getenv("WESTON_RDP_DISABLE_WINDOW_ZORDER_SYNC"); + if (s) { + if (strcmp(s, "true") == 0) + b->enable_window_zorder_sync = false; + } + rdp_debug(b, "RDP backend: enable_window_zorder_sync = %d\n", b->enable_window_zorder_sync); + b->rdprail_shell_name = NULL; b->enable_distro_name_title = true; diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index a136a395b..3aed8e73d 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -3357,6 +3357,9 @@ activate(struct desktop_shell *shell, struct weston_view *view, /* Update the surface’s layer. This brings it to the top of the stacking * order as appropriate. */ shell_surface_update_layer(shsurf); + + if (shell->rdprail_api->notify_window_zorder_change) + shsurf->shell->rdprail_api->notify_window_zorder_change(shell->compositor, es); } /* no-op func for checking black surface */ @@ -3430,11 +3433,23 @@ touch_to_activate_binding(struct weston_touch *touch, } static void -shell_backend_request_window_activate(struct weston_surface *surface, struct weston_seat *seat) +shell_backend_request_window_activate(void *shell_context, struct weston_seat *seat, struct weston_surface *surface) { + struct desktop_shell *shell = (struct desktop_shell *)shell_context; struct weston_view *view; struct shell_surface *shsurf; - struct desktop_shell *shell; + + if (!surface) { + /* Here, focus is moving to a window in client side, thus none of Linux app has focus. */ + /* TODO: move focus to dummy marker window, thus Linux app can correctly show as + 'not focused' state (such as title bar) while client application has focus. */ + if (shell->rdprail_api->notify_window_zorder_change) { + shell->rdprail_api->notify_window_zorder_change(shell->compositor, NULL); + /* schedule repaint to force send z order */ + weston_compositor_schedule_repaint(shell->compositor); + } + return; + } view = NULL; wl_list_for_each(view, &surface->views, surface_link) @@ -3446,10 +3461,6 @@ shell_backend_request_window_activate(struct weston_surface *surface, struct wes if (!shsurf) return; - shell = shell_surface_get_shell(shsurf); - if (!shell) - return; - activate_binding(seat, shell, view, WESTON_ACTIVATE_FLAG_CLICKED | WESTON_ACTIVATE_FLAG_CONFIGURE); From 8c6298d9ba3c7aa6aeac7594ab78c6da81df4984 Mon Sep 17 00:00:00 2001 From: Steve Pronovost Date: Mon, 10 May 2021 11:41:00 -0700 Subject: [PATCH 1461/1642] Fix scroll rotation to work both with standard and precision wheels Fix wheel increments such that we send a movement update to our Wayland clients every 12 wheel increments for smmooth scrolling on high precision wheels or pads, but only bump the discrete wheel tick every 120 wheel increments so it aligns properly with standard wheel which always do +120/-120 increment at every wheel notch. --- libweston/backend-rdp/rdp.c | 32 ++++++++++++++++++++++++-------- libweston/backend-rdp/rdp.h | 4 ++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 4029021ca..099125390 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1467,6 +1467,7 @@ static FREERDP_CB_RET_TYPE xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) { RdpPeerContext *peerContext = (RdpPeerContext *)input->context; + struct rdp_backend *b = peerContext->rdpBackend; uint32_t button = 0; bool need_frame = false; struct timespec time; @@ -1510,7 +1511,7 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) int ivalue; double value; - /* DEFAULT_AXIS_STEP_DISTANCE is stolen from compositor-x11.c + /* * The RDP specs says the lower bits of flags contains the "the number of rotation * units the mouse wheel was rotated". * @@ -1520,22 +1521,37 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) ivalue = (int)((char)(flags & 0xff)); else ivalue = (flags & 0xff); - peerContext->accumWheelRotation += ivalue; - if (abs(peerContext->accumWheelRotation) >= 12) { - /* multiply -1 is due to directional swap to match Windows direction of scroll. */ - value = (double)(peerContext->accumWheelRotation / 12) * -1; + + /* Flip the scroll direction as the RDP direction is inverse of X/Wayland */ + ivalue *= -1; + + /* + * Accumulate the wheel increments. + * + * Every 12 wheel increments, we will send an update to our Wayland + * clients with an updated value for the wheel for smooth scrolling. + * + * Every 120 wheel increments, we tick one discrete wheel click. + */ + peerContext->accumWheelRotationPrecise += ivalue; + peerContext->accumWheelRotationDiscrete += ivalue; + if (abs(peerContext->accumWheelRotationPrecise) >= 12) { + value = (double)(peerContext->accumWheelRotationPrecise / 12); weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; - weston_event.value = DEFAULT_AXIS_STEP_DISTANCE * value; - weston_event.discrete = (int)value; + weston_event.value = value; + weston_event.discrete = peerContext->accumWheelRotationDiscrete / 120; weston_event.has_discrete = true; + rdp_debug_verbose(b, "wheel: value:%f discrete:%d\n", weston_event.value, weston_event.discrete); + weston_compositor_get_time(&time); notify_axis(peerContext->item.seat, &time, &weston_event); need_frame = true; - peerContext->accumWheelRotation %= 12; + peerContext->accumWheelRotationPrecise %= 12; + peerContext->accumWheelRotationDiscrete %= 120; } } diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 8763d9588..d798e580d 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -110,7 +110,6 @@ #include "shared/timespec-util.h" #define MAX_FREERDP_FDS 32 -#define DEFAULT_AXIS_STEP_DISTANCE 10 #define RDP_MODE_FREQ 60 * 1000 #define RDP_MAX_MONITOR 16 // RDP max monitors. @@ -273,7 +272,8 @@ struct rdp_peer_context { bool button_state[5]; char key_state[0xff/8]; // one bit per key. - int accumWheelRotation; + int accumWheelRotationPrecise; + int accumWheelRotationDiscrete; // RAIL support HANDLE vcm; From 68924541acc379af586d8f4191877c54d0708d93 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 11 May 2021 05:00:08 -0700 Subject: [PATCH 1462/1642] fix shell crash with android studio 4.2 (#11) Co-authored-by: Hideyuki Nagase --- desktop-shell/shell.c | 7 +++++-- rdprail-shell/shell.c | 22 ++++++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index c1c126e81..cf85cbc6f 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2780,8 +2780,11 @@ desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, if (parent) { shsurf_parent = weston_desktop_surface_get_user_data(parent); - wl_list_insert(shsurf_parent->children_list.prev, - &shsurf->children_link); + if (shsurf_parent) + wl_list_insert(shsurf_parent->children_list.prev, + &shsurf->children_link); + else + wl_list_init(&shsurf->children_link); } else { wl_list_init(&shsurf->children_link); } diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 3aed8e73d..46168a15c 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -2427,14 +2427,20 @@ desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, if (parent) { shsurf_parent = weston_desktop_surface_get_user_data(parent); - wl_list_insert(shsurf_parent->children_list.prev, - &shsurf->children_link); - /* libweston-desktop doesn't establish parent/child relationship - with weston_desktop_api shell_desktop_api.set_parent call, - thus calling weston_desktop_surface_get_parent won't work, - so shell need to track by itself. This also means child's - geometry won't be adjusted to relative to parent. */ - shsurf->parent = shsurf_parent; + if (shsurf_parent) { + wl_list_insert(shsurf_parent->children_list.prev, + &shsurf->children_link); + /* libweston-desktop doesn't establish parent/child relationship + with weston_desktop_api shell_desktop_api.set_parent call, + thus calling weston_desktop_surface_get_parent won't work, + so shell need to track by itself. This also means child's + geometry won't be adjusted to relative to parent. */ + shsurf->parent = shsurf_parent; + } else { + weston_log("RDP shell: parent is not toplevel surface\n"); + wl_list_init(&shsurf->children_link); + shsurf->parent = NULL; + } } else { wl_list_init(&shsurf->children_link); shsurf->parent = NULL; From 994868c82b3aa051e851f7d15dd92746ee6dfda1 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 13 May 2021 09:08:16 -0700 Subject: [PATCH 1463/1642] add subsurfaces to z order list (#12) Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 1 + libweston/backend-rdp/rdprail.c | 95 +++++++++++++++++++++++---------- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index c4dbab57e..bc63d808d 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -217,6 +217,7 @@ struct weston_surface_rail_state { bool forceUpdateWindowState; bool error; bool isUpdatePending; + bool isFirstUpdateDone; void *get_label; int taskbarButton; diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index dc155ac07..7435003d0 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2357,6 +2357,16 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter } pixman_region32_clear(&rail_state->damage); + + /* TODO: this is temporary workaround, some window is not visible to shell + (such as subsurfaces, override_redirect), so z order update is + not done by activate callback, thus trigger it at first update. + solution would make those surface visible to shell or hook signal on + when view_list is changed on libweston/compositor.c */ + if (!rail_state->isFirstUpdateDone) { + peerCtx->is_window_zorder_dirty = true; + rail_state->isFirstUpdateDone = true; + } } #ifdef HAVE_FREERDP_GFXREDIR_H @@ -2420,6 +2430,49 @@ rdp_rail_update_window_iter(void *element, void *data) } } +static UINT32 +rdp_insert_window_zorder_array(struct weston_view *view, UINT32 *windowIdArray, UINT32 WindowIdArraySize, UINT32 iCurrent) +{ + struct weston_surface *surface = view->surface; + struct weston_compositor *compositor = surface->compositor; + struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; + + /* insert subsurface first to zorder list */ + struct weston_subsurface *sub; + wl_list_for_each(sub, &surface->subsurface_list, parent_link) { + struct weston_view *sub_view; + wl_list_for_each(sub_view, &sub->surface->views, surface_link) { + if (sub_view->parent_view != view) + continue; + + iCurrent = rdp_insert_window_zorder_array(sub_view, windowIdArray, WindowIdArraySize, iCurrent); + if (iCurrent == UINT_MAX) + return iCurrent; + } + } + + /* insert itself as parent (which is below sub-surfaces in z order) */ + if (rail_state->isWindowCreated && + !rail_state->is_minimized && + !rail_state->is_minimized_requested) { + if (iCurrent >= WindowIdArraySize) { + weston_log("%s: more windows in tree than ID manager tracking (%d vs %d)\n", + __func__, iCurrent, WindowIdArraySize); + return UINT_MAX; + } + if (b->debugLevel >= RDP_DEBUG_LEVEL_VERBOSE) { + char label[256]; + rdp_rail_dump_window_label(surface, label, sizeof(label)); + rdp_debug_verbose(b, " window[%d]: %x: %s\n", iCurrent, rail_state->window_id, label); + } + windowIdArray[iCurrent++] = rail_state->window_id; + } + + return iCurrent; +} + static void rdp_rail_sync_window_zorder(struct weston_compositor *compositor) { @@ -2430,8 +2483,7 @@ rdp_rail_sync_window_zorder(struct weston_compositor *compositor) UINT32 *windowIdArray = NULL; WINDOW_ORDER_INFO window_order_info = {}; MONITORED_DESKTOP_ORDER monitored_desktop_order = {}; - char label[256]; - UINT32 i = 0; + UINT32 iCurrent = 0; ASSERT_COMPOSITOR_THREAD(b); @@ -2448,47 +2500,34 @@ rdp_rail_sync_window_zorder(struct weston_compositor *compositor) rdp_debug_verbose(b, "Dump Window Z order\n"); if (!peerCtx->active_surface) { /* if no active window, put marker window top as client window has focus. */ - rdp_debug_verbose(b, " window[%d]: %x: %s\n", i, RDP_RAIL_MARKER_WINDOW_ID, "marker window"); - windowIdArray[i++] = RDP_RAIL_MARKER_WINDOW_ID; + rdp_debug_verbose(b, " window[%d]: %x: %s\n", iCurrent, RDP_RAIL_MARKER_WINDOW_ID, "marker window"); + windowIdArray[iCurrent++] = RDP_RAIL_MARKER_WINDOW_ID; } /* walk windows in z-order */ struct weston_layer *layer; wl_list_for_each(layer, &compositor->layer_list, link) { struct weston_view *view; wl_list_for_each(view, &layer->view_list.link, layer_link.link) { - struct weston_surface_rail_state *rail_state = - (struct weston_surface_rail_state *)view->surface->backend_state; - if (rail_state->isWindowCreated && - !rail_state->is_minimized && - !rail_state->is_minimized_requested) { - if (i >= numWindowId) { - weston_log("%s: more windows in tree than ID manager tracking (%d vs %d)\n", - __func__, i, numWindowId); - goto Exit; - } - if (b->debugLevel >= RDP_DEBUG_LEVEL_VERBOSE) { - rdp_rail_dump_window_label(view->surface, label, sizeof(label)); - rdp_debug_verbose(b, " window[%d]: %x: %s\n", i, rail_state->window_id, label); - } - windowIdArray[i++] = rail_state->window_id; - } + iCurrent = rdp_insert_window_zorder_array(view, windowIdArray, numWindowId, iCurrent); + if (iCurrent == UINT_MAX) + goto Exit; } } if (peerCtx->active_surface) { /* TODO: marker window better be placed correct place relative to client window, not always bottom */ /* In order to do that, dummpy window to be created to track where is the highest client window. */ - rdp_debug_verbose(b, " window[%d]: %x: %s\n", i, RDP_RAIL_MARKER_WINDOW_ID, "marker window"); - windowIdArray[i++] = RDP_RAIL_MARKER_WINDOW_ID; + rdp_debug_verbose(b, " window[%d]: %x: %s\n", iCurrent, RDP_RAIL_MARKER_WINDOW_ID, "marker window"); + windowIdArray[iCurrent++] = RDP_RAIL_MARKER_WINDOW_ID; } - assert(i <= numWindowId); - assert(i > 0); - rdp_debug_verbose(b, " send Window Z order: numWindowIds:%d\n", i); + assert(iCurrent <= numWindowId); + assert(iCurrent > 0); + rdp_debug_verbose(b, " send Window Z order: numWindowIds:%d\n", iCurrent); window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_ZORDER | WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND; monitored_desktop_order.activeWindowId = windowIdArray[0]; - monitored_desktop_order.numWindowIds = i; + monitored_desktop_order.numWindowIds = iCurrent; monitored_desktop_order.windowIds = windowIdArray; client->update->window->MonitoredDesktop(client->context, &window_order_info, &monitored_desktop_order); @@ -3403,8 +3442,8 @@ rdp_rail_dump_window_iter(void *element, void *data) rail_state->is_maximized, rail_state->is_maximized_requested); fprintf(fp," forceRecreateSurface:%d, error:%d\n", rail_state->forceRecreateSurface, rail_state->error); - fprintf(fp," isUdatePending:%d\n", - rail_state->isUpdatePending); + fprintf(fp," isUdatePending:%d, isFirstUpdateDone:%d\n", + rail_state->isUpdatePending, rail_state->isFirstUpdateDone); fprintf(fp," surface:0x%p\n", surface); wl_list_for_each(view, &surface->views, surface_link) { fprintf(fp," view: %p\n", view); From 9e402088aa2cd9316851ea20b9befbcf3b9c9564 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 14 May 2021 11:50:04 -0700 Subject: [PATCH 1464/1642] fix crash at walking window for z order list (#13) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdprail.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 7435003d0..a3e3a6e83 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2454,7 +2454,12 @@ rdp_insert_window_zorder_array(struct weston_view *view, UINT32 *windowIdArray, } /* insert itself as parent (which is below sub-surfaces in z order) */ - if (rail_state->isWindowCreated && + /* because z order is taken from compositor's scene-graph, it's possible + there is surface hasn't been associated with rail_state, so check it. + and if window is not remoted to client side, or minimized (or going to be + minimized), those won't included in z order list. */ + if (rail_state && + rail_state->isWindowCreated && !rail_state->is_minimized && !rail_state->is_minimized_requested) { if (iCurrent >= WindowIdArraySize) { From 1ae66ade65d55510b4d5ad04b1d3811f8c47d7d2 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 19 May 2021 18:25:17 -0700 Subject: [PATCH 1465/1642] fix android emulator window doesn't move and crash at minimize (#14) Co-authored-by: Hideyuki Nagase --- libweston-desktop/xwayland.c | 24 +++++ xwayland/window-manager.c | 140 +++++++++++++++++++++++-- xwayland/xwayland-internal-interface.h | 2 + xwayland/xwayland.h | 1 + 4 files changed, 158 insertions(+), 9 deletions(-) diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index d0b6896dd..cc93fc05b 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -375,6 +375,29 @@ set_window_geometry(struct weston_desktop_xwayland_surface *surface, } } +static void +set_position(struct weston_desktop_xwayland_surface *surface, + int x, int y, int width, int height) +{ + if (surface->state == XWAYLAND) { + /* For XWAYLAND surface, here directly set view position, + just like set_xwayland() when view is associated. */ + if (surface->view) + weston_view_set_position(surface->view, x, y); + } else { + /* TODO what to do these? need a way to move shell surface! */ + } +#ifdef WM_DEBUG + weston_log("%s: %s window (%p) move to (%d,%d)\n", + __func__, + (surface->state == XWAYLAND) ? "XWAYLAND" : \ + (surface->state == TOPLEVEL) ? "TOPLEVEL" : \ + (surface->state == MAXIMIZED) ? "MAXIMIZED" : \ + (surface->state == FULLSCREEN) ? "FULLSCREEN" : "UNKNOWN", + surface, x, y); +#endif +} + static void set_maximized(struct weston_desktop_xwayland_surface *surface) { @@ -413,6 +436,7 @@ static const struct weston_desktop_xwayland_interface weston_desktop_xwayland_in .set_transient = set_transient, .set_fullscreen = set_fullscreen, .set_xwayland = set_xwayland, + .set_position = set_position, .move = move, .resize = resize, .set_title = set_title, diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 142ed1aba..eb82144f2 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -171,6 +171,7 @@ struct weston_wm_window { int maximized_vert; int maximized_horz; int take_focus; + int no_shadow; struct wm_size_hints size_hints; struct motif_wm_hints motif_hints; struct wl_list link; @@ -498,6 +499,43 @@ read_and_dump_property(FILE *fp, struct weston_wm *wm, free(reply); } +static char * +window_type_atom_to_string(struct weston_wm *wm, xcb_atom_t atom) +{ + if (atom == wm->atom.net_wm_window_type_normal) + return "_NET_WM_WINDOW_TYPE_NORMAL"; + else if (atom == wm->atom.net_wm_window_type_desktop) + return "_NET_WM_WINDOW_TYPE_DESKTOP"; + else if (atom == wm->atom.net_wm_window_type_dock) + return "_NET_WM_WINDOW_TYPE_DOCK"; + else if (atom == wm->atom.net_wm_window_type_toolbar) + return "_NET_WM_WINDOW_TYPE_TOOLBAR"; + else if (atom == wm->atom.net_wm_window_type_menu) + return "_NET_WM_WINDOW_TYPE_MENU"; + else if (atom == wm->atom.net_wm_window_type_utility) + return "_WM_WINDOW_TYPE_UTILITY"; + else if (atom == wm->atom.net_wm_window_type_splash) + return "_NET_WM_WINDOW_TYPE_SPLASH"; + else if (atom == wm->atom.net_wm_window_type_dialog) + return "_NET_WM_WINDOW_TYPE_DIALOG"; + else if (atom == wm->atom.net_wm_window_type_dropdown) + return "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"; + else if (atom == wm->atom.net_wm_window_type_popup) + return "_NET_WM_WINDOW_TYPE_POPUP_MENU"; + else if (atom == wm->atom.net_wm_window_type_tooltip) + return "_NET_WM_WINDOW_TYPE_TOOLTIP"; + else if (atom == wm->atom.net_wm_window_type_notification) + return "_NET_WM_WINDOW_TYPE_NOTIFICATION"; + else if (atom == wm->atom.net_wm_window_type_combo) + return "_NET_WM_WINDOW_TYPE_COMBO"; + else if (atom == wm->atom.net_wm_window_type_dnd) + return "_NET_WM_WINDOW_TYPE_DND"; + else if (atom == wm->atom.kde_net_wm_window_type_override) + return "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"; + else + return "UNKNOWN"; +} + /* We reuse some predefined, but otherwise useles atoms * as local type placeholders that never touch the X11 server, * to make weston_wm_window_read_properties() less exceptional. @@ -506,6 +544,7 @@ read_and_dump_property(FILE *fp, struct weston_wm *wm, #define TYPE_MOTIF_WM_HINTS XCB_ATOM_CUT_BUFFER1 #define TYPE_NET_WM_STATE XCB_ATOM_CUT_BUFFER2 #define TYPE_WM_NORMAL_HINTS XCB_ATOM_CUT_BUFFER3 +#define TYPE_WM_WINDOW_TYPE XCB_ATOM_CUT_BUFFER4 static void weston_wm_window_read_properties(struct weston_wm_window *window) @@ -524,7 +563,7 @@ weston_wm_window_read_properties(struct weston_wm_window *window) { wm->atom.wm_protocols, TYPE_WM_PROTOCOLS, NULL }, { wm->atom.wm_normal_hints, TYPE_WM_NORMAL_HINTS, NULL }, { wm->atom.net_wm_state, TYPE_NET_WM_STATE, NULL }, - { wm->atom.net_wm_window_type, XCB_ATOM_ATOM, F(type) }, + { wm->atom.net_wm_window_type, TYPE_WM_WINDOW_TYPE, F(type) }, { wm->atom.net_wm_name, XCB_ATOM_STRING, F(name) }, { wm->atom.net_wm_pid, XCB_ATOM_CARDINAL, F(pid) }, { wm->atom.motif_wm_hints, TYPE_MOTIF_WM_HINTS, NULL }, @@ -593,6 +632,23 @@ weston_wm_window_read_properties(struct weston_wm_window *window) atom = xcb_get_property_value(reply); *(xcb_atom_t *) p = *atom; break; + case TYPE_WM_WINDOW_TYPE: + atom = xcb_get_property_value(reply); + /* pick first one as type */ + *(xcb_atom_t *) p = *atom; + /* scan all atoms */ + for (i = 0; i < reply->value_len; i++) { + /* while there is a lot of discussion on this KDE property, but + commonly mentioned there should be no window decoration at all + including window shadow for _KDE_NET_WM_WINDOW_TYPE_OVERRIDE. */ + if (atom[i] == wm->atom.kde_net_wm_window_type_override) { + window->no_shadow = 1; + window->decorate = 0; + } + wm_printf(wm, "wm_window_read_properties (window %d) window type: %s\n", + window->id, window_type_atom_to_string(wm, atom[i])); + } + break; case TYPE_WM_PROTOCOLS: atom = xcb_get_property_value(reply); for (i = 0; i < reply->value_len; i++) @@ -766,6 +822,7 @@ weston_wm_configure_window(struct weston_wm *wm, xcb_window_t window_id, size_t sz = 0; FILE *fp; unsigned i, v; + bool is_our_resource = our_resource(wm, window_id); xcb_configure_window(wm->conn, window_id, mask, values); @@ -781,6 +838,7 @@ weston_wm_configure_window(struct weston_wm *wm, xcb_window_t window_id, if (mask & names[i].bitmask) fprintf(fp, " %s=%d", names[i].name, values[v++]); } + fprintf(fp, "%s", is_our_resource ? ", ours" : ""); fclose(fp); wm_printf(wm, "%s\n", buf); @@ -804,6 +862,28 @@ weston_wm_window_configure_frame(struct weston_wm_window *window) weston_wm_configure_window(window->wm, window->frame_id, mask, values); } +static void +weston_wm_window_configure_frame_with_position(struct weston_wm_window *window, int x, int y) +{ + uint16_t mask; + uint32_t values[4]; + int width, height; + + if (!window->frame_id) + return; + + weston_wm_window_get_frame_size(window, &width, &height); + values[0] = x; // x = position of frame, not child + values[1] = y; // y = position of frame, not child + values[2] = width; + values[3] = height; + mask = XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT; + weston_wm_configure_window(window->wm, window->frame_id, mask, values); +} + static void weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *event) { @@ -812,19 +892,27 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev struct weston_wm_window *window; uint32_t values[16]; uint16_t mask; - int x, y; + int x, y, configure_x, configure_y; int i = 0; bool is_our_resource = our_resource(wm, configure_request->window); + bool configure_frame_position = false; - wm_printf(wm, "XCB_CONFIGURE_REQUEST (window %d) %d,%d @ %dx%d%s\n", + wm_printf(wm, "XCB_CONFIGURE_REQUEST (window %d) %d,%d @ %dx%d mask 0x%x%s\n", configure_request->window, configure_request->x, configure_request->y, configure_request->width, configure_request->height, + configure_request->value_mask, is_our_resource ? ", ours" : ""); if (!wm_lookup_window(wm, configure_request->window, &window)) return; + wm_printf(wm, "XCB_CONFIGURE_REQUEST (window %d) frame %d%s%s\n", + configure_request->window, + window->frame_id, + window->fullscreen ? ", fullscreen" : "", + window->override_redirect ? ", override" : ""); + if (window->fullscreen) { weston_wm_window_send_configure_notify(window); return; @@ -835,6 +923,15 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev if (configure_request->value_mask & XCB_CONFIG_WINDOW_HEIGHT) window->height = configure_request->height; + if (configure_request->value_mask & XCB_CONFIG_WINDOW_X) + configure_x = configure_request->x; + else + configure_x = window->x; + if (configure_request->value_mask & XCB_CONFIG_WINDOW_Y) + configure_y = configure_request->y; + else + configure_y = window->y; + if (window->frame) { weston_wm_window_set_allow_commits(window, false); frame_resize_inside(window->frame, window->width, window->height); @@ -845,14 +942,18 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev unless this is frame itself. Since only after frame is created, the app's window position will become relative to parent (frame). */ if (is_our_resource || window->frame || window->override_redirect) { - if (is_our_resource) { - /* window is frame window */ - values[i++] = configure_request->x; - values[i++] = configure_request->y; + if (is_our_resource || (window->frame == NULL)) { + /* window is frame window or no frame */ + values[i++] = configure_x; + values[i++] = configure_y; } else { /* window is app's window with frame as parent or override */ values[i++] = x; // relative from frame. values[i++] = y; // relative from frame. + /* and configure has differnt position than current position, + move frame, so app contents will move, too */ + if ((configure_x != window->x) || (configure_y != window->y)) + configure_frame_position = true; } values[i++] = window->width; values[i++] = window->height; @@ -878,7 +979,12 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev } weston_wm_configure_window(wm, window->id, mask, values); - weston_wm_window_configure_frame(window); + if (configure_frame_position) + weston_wm_window_configure_frame_with_position(window, + configure_x - x, + configure_y - y); + else + weston_wm_window_configure_frame(window); weston_wm_window_schedule_repaint(window); } @@ -929,6 +1035,11 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve if (window->shsurf) xwayland_api->set_xwayland(window->shsurf, window->x, window->y); + } else if (is_our_resource) { + if (window->shsurf) + xwayland_api->set_position(window->shsurf, + window->x, window->y, + window->width, window->height); } } @@ -1363,6 +1474,9 @@ weston_wm_window_draw_decoration(struct weston_wm_window *window) } else if (window->maximized_vert && window->maximized_horz) { how = "maximized"; /* nothing */ + } else if (window->no_shadow) { + how = "no shadow"; + /* nothing */ } else { how = "shadow"; cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); @@ -1510,6 +1624,9 @@ weston_wm_window_set_icon(struct weston_wm *wm, uint32_t width, selected_width; uint32_t height, selected_height; + if (!xwayland_interface->set_window_icon) + return false; + if (!window->shsurf) { /* shell surface is not associated yet */ return false; @@ -1956,6 +2073,9 @@ weston_wm_window_handle_iconic_state(struct weston_wm_window *window, wm->server->compositor->xwayland_interface; uint32_t iconic_state; + if (!window->shsurf) + return; + iconic_state = client_message->data.data32[0]; if (iconic_state == ICCCM_ICONIC_STATE) { @@ -2573,6 +2693,7 @@ weston_wm_get_resources(struct weston_wm *wm) { "_NET_WM_WINDOW_TYPE_COMBO", F(atom.net_wm_window_type_combo) }, { "_NET_WM_WINDOW_TYPE_DND", F(atom.net_wm_window_type_dnd) }, { "_NET_WM_WINDOW_TYPE_NORMAL", F(atom.net_wm_window_type_normal) }, + { "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE", F(atom.kde_net_wm_window_type_override) }, { "_NET_WM_MOVERESIZE", F(atom.net_wm_moveresize) }, { "_NET_SUPPORTING_WM_CHECK", @@ -3255,7 +3376,8 @@ xserver_map_shell_surface(struct weston_wm_window *window, xcb_flush(wm->conn); } - weston_wm_window_set_icon(wm, window, window->id); + if (!window->override_redirect) + weston_wm_window_set_icon(wm, window, window->id); } const struct weston_xwayland_surface_api surface_api = { diff --git a/xwayland/xwayland-internal-interface.h b/xwayland/xwayland-internal-interface.h index 5c49ed901..bf7ed7705 100644 --- a/xwayland/xwayland-internal-interface.h +++ b/xwayland/xwayland-internal-interface.h @@ -48,6 +48,8 @@ struct weston_desktop_xwayland_interface { struct weston_output *output); void (*set_xwayland)(struct weston_desktop_xwayland_surface *shsurf, int x, int y); + void (*set_position)(struct weston_desktop_xwayland_surface *shsurf, + int x, int y, int width, int height); int (*move)(struct weston_desktop_xwayland_surface *shsurf, struct weston_pointer *pointer); int (*resize)(struct weston_desktop_xwayland_surface *shsurf, diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index c430dd0b2..801ede3e5 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -129,6 +129,7 @@ struct weston_wm { xcb_atom_t net_wm_window_type_combo; xcb_atom_t net_wm_window_type_dnd; xcb_atom_t net_wm_window_type_normal; + xcb_atom_t kde_net_wm_window_type_override; xcb_atom_t net_wm_moveresize; xcb_atom_t net_supporting_wm_check; xcb_atom_t net_supported; From b84745b311b4a3135b8c8c06d307d4df6a3fd829 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Sat, 22 May 2021 18:35:57 -0700 Subject: [PATCH 1466/1642] rdp-backend/rdprail-shell debug message rearrangement (#15) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 80 ++++---- libweston/backend-rdp/rdp.h | 21 ++- libweston/backend-rdp/rdpaudio.c | 57 +++--- libweston/backend-rdp/rdpaudioin.c | 36 ++-- libweston/backend-rdp/rdpclip.c | 292 +++++++++++++++++++++-------- libweston/backend-rdp/rdpdisp.c | 12 +- libweston/backend-rdp/rdprail.c | 98 +++++----- libweston/backend-rdp/rdputil.c | 22 ++- rdprail-shell/app-list.c | 21 ++- rdprail-shell/shell.c | 158 ++++++---------- rdprail-shell/shell.h | 3 + 11 files changed, 456 insertions(+), 344 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 099125390..f87815dad 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -400,7 +400,7 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) local_mode = ensure_matching_mode(output, target_mode); if (!local_mode) { - weston_log("mode %dx%d not available\n", target_mode->width, target_mode->height); + rdp_debug_error(rdpBackend, "mode %dx%d not available\n", target_mode->width, target_mode->height); return -ENOENT; } @@ -444,7 +444,7 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) if (!settings->DesktopResize) { /* too bad this peer does not support desktop resize */ - weston_log("%s: desktop resize is not allowed\n", __func__); + rdp_debug_error(rdpBackend, "%s: desktop resize is not allowed\n", __func__); rdpPeer->peer->Close(rdpPeer->peer); } else { settings->DesktopWidth = target_mode->width; @@ -595,7 +595,7 @@ rdp_output_enable(struct weston_output *base) NULL, output->base.current_mode->width * 4); if (output->shadow_surface == NULL) { - weston_log("Failed to create surface for frame buffer.\n"); + rdp_debug_error(b, "Failed to create surface for frame buffer.\n"); return -1; } @@ -653,7 +653,7 @@ rdp_output_attach_head(struct weston_output *output_base, rdp_debug(b, "Head attaching: %s, index:%d, is_primary: %d\n", head_base->name, h->index, h->monitorMode.monitorDef.is_primary); if (!wl_list_empty(&output_base->head_list)) { - weston_log("attaching more than 1 head to single output (= clone) is not supported\n"); + rdp_debug_error(b, "attaching more than 1 head to single output (= clone) is not supported\n"); return -1; } o->index = h->index; @@ -952,16 +952,17 @@ rdp_client_activity(int fd, uint32_t mask, void *data) { freerdp_peer* client = (freerdp_peer *)data; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *rdpBackend = peerCtx->rdpBackend; if (!client->CheckFileDescriptor(client)) { - weston_log("unable to checkDescriptor for %p\n", client); + rdp_debug_error(rdpBackend, "unable to checkDescriptor for %p\n", client); goto out_clean; } if (peerCtx && peerCtx->vcm) { if (!WTSVirtualChannelManagerCheckFileDescriptor(peerCtx->vcm)) { - weston_log("failed to check FreeRDP WTS VC file descriptor for %p\n", client); + rdp_debug_error(rdpBackend, "failed to check FreeRDP WTS VC file descriptor for %p\n", client); goto out_clean; } } @@ -1159,31 +1160,31 @@ xf_peer_activate(freerdp_peer* client) settings = client->settings; if (!settings->SurfaceCommandsEnabled) { - weston_log("client doesn't support required SurfaceCommands\n"); + rdp_debug_error(b, "client doesn't support required SurfaceCommands\n"); return FALSE; } if (b->force_no_compression && settings->CompressionEnabled) { - weston_log("Forcing compression off\n"); + rdp_debug_error(b, "Forcing compression off\n"); settings->CompressionEnabled = FALSE; } /* in RAIL mode, only one peer per backend can be activated */ if (settings->RemoteApplicationMode) { if (b->rdp_peer != client) { - weston_log("Another RAIL connection active, only one connection is allowed.\n"); + rdp_debug_error(b, "Another RAIL connection active, only one connection is allowed.\n"); return FALSE; } if (!settings->HiDefRemoteApp) { /* HiDef is required for RAIL mode. Cookie-cutter window remoting is not supported. */ - weston_log("HiDef-RAIL is required for RAIL.\n"); + rdp_debug_error(b, "HiDef-RAIL is required for RAIL.\n"); return FALSE; } /* in HiDef RAIL mode, RAIL-shell must be used */ if (b->rdprail_shell_api == NULL) { - weston_log("HiDef-RAIL is requested from client, but RAIL-shell is not used\n"); + rdp_debug_error(b, "HiDef-RAIL is requested from client, but RAIL-shell is not used\n"); return FALSE; } } @@ -1213,7 +1214,7 @@ xf_peer_activate(freerdp_peer* client) settings->AudioCapture) { if (!peerCtx->vcm) { - weston_log("Virtual channel is required for RAIL, clipboard, audio playback/capture\n"); + rdp_debug_error(b, "Virtual channel is required for RAIL, clipboard, audio playback/capture\n"); goto error_exit; } @@ -1243,7 +1244,7 @@ xf_peer_activate(freerdp_peer* client) /* multiple monitor is not supported in non-HiDef */ assert(b->output_default); output = b->output_default; - weston_log("%s: DesktopWidth:%d, DesktopHeigh:%d, DesktopScaleFactor:%d\n", __FUNCTION__, + rdp_debug_error(b, "%s: DesktopWidth:%d, DesktopHeigh:%d, DesktopScaleFactor:%d\n", __FUNCTION__, settings->DesktopWidth, settings->DesktopHeight, settings->DesktopScaleFactor); if (output->base.width != (int)settings->DesktopWidth || output->base.height != (int)settings->DesktopHeight) { @@ -1251,7 +1252,7 @@ xf_peer_activate(freerdp_peer* client) /* RDP peers don't dictate their resolution to weston */ if (!settings->DesktopResize) { /* peer does not support desktop resize */ - weston_log("%s: client doesn't support resizing, closing connection\n", __FUNCTION__); + rdp_debug_error(b, "%s: client doesn't support resizing, closing connection\n", __FUNCTION__); goto error_exit; } else { settings->DesktopWidth = output->base.width; @@ -1266,7 +1267,7 @@ xf_peer_activate(freerdp_peer* client) new_mode.height = (int)settings->DesktopHeight; target_mode = ensure_matching_mode(&output->base, &new_mode); if (!target_mode) { - weston_log("client mode not found\n"); + rdp_debug_error(b, "client mode not found\n"); goto error_exit; } weston_output_mode_set_native(&output->base, target_mode, @@ -1286,7 +1287,7 @@ xf_peer_activate(freerdp_peer* client) weston_output = &output->base; - weston_log("%s: OutputWidth:%d, OutputHeight:%d, OutputScaleFactor:%d\n", __FUNCTION__, + rdp_debug(b, "%s: OutputWidth:%d, OutputHeight:%d, OutputScaleFactor:%d\n", __FUNCTION__, weston_output->width, weston_output->height, weston_output->scale); pixman_region32_clear(&peerCtx->regionWestonHeads); @@ -1330,7 +1331,7 @@ xf_peer_activate(freerdp_peer* client) peersItem->seat = zalloc(sizeof(*peersItem->seat)); if (!peersItem->seat) { xkb_keymap_unref(keymap); - weston_log("unable to create a weston_seat\n"); + rdp_debug_error(b, "unable to create a weston_seat\n"); goto error_exit; } @@ -1707,7 +1708,11 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) static FREERDP_CB_RET_TYPE xf_input_unicode_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) { - weston_log("Client sent a unicode keyboard event (flags:0x%X code:0x%X)\n", flags, code); + RdpPeerContext *peerContext = (RdpPeerContext *)input->context; + struct rdp_backend *b = peerContext->rdpBackend; + + rdp_debug_error(b, "Client sent a unicode keyboard event (flags:0x%X code:0x%X)\n", flags, code); + FREERDP_CB_RETURN(TRUE); } @@ -1774,7 +1779,7 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) settings->NlaSecurity = FALSE; if (!client->Initialize(client)) { - weston_log("peer initialization failed\n"); + rdp_debug_error(b, "peer initialization failed\n"); goto error_initialize; } @@ -1813,7 +1818,7 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) input->UnicodeKeyboardEvent = xf_input_unicode_keyboard_event; if (!client->GetFileDescriptor(client, rfds, &rcount)) { - weston_log("unable to retrieve client fds\n"); + rdp_debug_error(b, "unable to retrieve client fds\n"); goto error_initialize; } @@ -1826,7 +1831,7 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) /* event must be valid when server is successfully opened */ assert(peerCtx->eventVcm); } else { - weston_log("WTSOpenServer is failed! continue without virtual channel.\n"); + rdp_debug_error(b, "WTSOpenServer is failed! continue without virtual channel.\n"); } loop = wl_display_get_event_loop(b->compositor->wl_display); @@ -1878,7 +1883,7 @@ rdp_incoming_peer(freerdp_listener *instance, freerdp_peer *client) { struct rdp_backend *b = (struct rdp_backend *)instance->param4; if (rdp_peer_init(client, b) < 0) { - weston_log("error when treating incoming peer\n"); + rdp_debug_error(b, "error when treating incoming peer\n"); FREERDP_CB_RETURN(FALSE); } @@ -1968,22 +1973,19 @@ static int create_vsock_fd(int port) int socket_fd = socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC, 0); - if (socket_fd < 0) - { + if (socket_fd < 0) { weston_log("Fail to create vsocket"); return -1; } const int bufferSize = 65536; - if (setsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)) < 0) - { + if (setsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)) < 0) { weston_log("Fail to setsockopt SO_SNDBUF"); return -1; } - if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)) < 0) - { + if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)) < 0) { weston_log("Fail to setsockopt SO_RCVBUF"); return -1; } @@ -1996,8 +1998,7 @@ static int create_vsock_fd(int port) socklen_t socket_addr_size = sizeof(socket_address); - if (bind(socket_fd, (const struct sockaddr *)&socket_address, socket_addr_size) < 0) - { + if (bind(socket_fd, (const struct sockaddr *)&socket_address, socket_addr_size) < 0) { weston_log("Fail to bind socket to address socket"); close(socket_fd); return -2; @@ -2005,8 +2006,7 @@ static int create_vsock_fd(int port) int status = listen(socket_fd, 1); - if (status != 0) - { + if (status != 0) { weston_log("Fail to listen on socket"); close(socket_fd); return -4; @@ -2084,7 +2084,7 @@ rdp_backend_create(struct weston_compositor *compositor, b->debugLevel = RDP_DEBUG_LEVEL_DEFAULT; } } - weston_log("RDP backend: WESTON_RDP_DEBUG_LEVEL: %d\n", b->debugLevel); + rdp_debug(b, "RDP backend: WESTON_RDP_DEBUG_LEVEL: %d\n", b->debugLevel); /* After here, rdp_debug() is ready to be used */ s = getenv("WESTON_RDP_MONITOR_REFRESH_RATE"); @@ -2122,7 +2122,7 @@ rdp_backend_create(struct weston_compositor *compositor, #if HAVE_OPENSSL rdp_generate_session_tls(b); #else - weston_log("the RDP compositor requires keys and an optional certificate for RDP or TLS security (" + rdp_debug_error(b, "the RDP compositor requires keys and an optional certificate for RDP or TLS security (" "--rdp4-key or --rdp-tls-cert/--rdp-tls-key)\n"); goto err_free_strings; #endif @@ -2130,7 +2130,7 @@ rdp_backend_create(struct weston_compositor *compositor, /* activate TLS only if certificate/key are available */ if (is_tls_enabled(b)) { - weston_log("TLS support activated\n"); + rdp_debug_error(b, "TLS support activated\n"); } else if (!b->rdp_key) { goto err_free_strings; } @@ -2155,15 +2155,15 @@ rdp_backend_create(struct weston_compositor *compositor, b->listener->PeerAccepted = rdp_incoming_peer; b->listener->param4 = b; if (fd > 0) { - weston_log("Using VSOCK for incoming connections: %d\n", fd); + rdp_debug_error(b, "Using VSOCK for incoming connections: %d\n", fd); if (!b->listener->OpenFromSocket(b->listener, fd)) { - weston_log("unable opem from socket fd: %d\n", fd); + rdp_debug_error(b, "unable opem from socket fd: %d\n", fd); goto err_listener; } } else { if (!b->listener->Open(b->listener, config->bind_address, config->port)) { - weston_log("unable to bind rdp socket\n"); + rdp_debug_error(b, "unable to bind rdp socket\n"); goto err_listener; } } @@ -2174,7 +2174,7 @@ rdp_backend_create(struct weston_compositor *compositor, /* get the socket from RDP_FD var */ fd_str = getenv("RDP_FD"); if (!fd_str) { - weston_log("RDP_FD env variable not set\n"); + rdp_debug_error(b, "RDP_FD env variable not set\n"); goto err_output; } @@ -2188,7 +2188,7 @@ rdp_backend_create(struct weston_compositor *compositor, &api, sizeof(api)); if (ret < 0) { - weston_log("Failed to register output API.\n"); + rdp_debug_error(b, "Failed to register output API.\n"); goto err_output; } diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index d798e580d..a04ff4613 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -125,8 +125,10 @@ struct rdp_output; struct rdp_clipboard_data_source; +struct rdp_backend; struct rdp_id_manager { + struct rdp_backend *rdp_backend; UINT32 id; UINT32 id_low_limit; UINT32 id_high_limit; @@ -384,9 +386,9 @@ typedef struct rdp_peer_context RdpPeerContext; #define RDP_DEBUG_LEVEL_DEBUG 4 #define RDP_DEBUG_LEVEL_VERBOSE 5 +/* To enable rdp_debug message, add "--logger-scopes=rdp-backend". */ #define RDP_DEBUG_LEVEL_DEFAULT RDP_DEBUG_LEVEL_INFO -/* To enable rdp_debug message, add "--logger-scopes=rdp-backend". */ #define rdp_debug_verbose(b, ...) \ if (b->debugLevel >= RDP_DEBUG_LEVEL_VERBOSE) \ rdp_debug_print(b->debug, false, __VA_ARGS__) @@ -399,8 +401,13 @@ typedef struct rdp_peer_context RdpPeerContext; #define rdp_debug_continue(b, ...) \ if (b->debugLevel >= RDP_DEBUG_LEVEL_INFO) \ rdp_debug_print(b->debug, true, __VA_ARGS__) +#define rdp_debug_error(b, ...) \ + if (b->debugLevel >= RDP_DEBUG_LEVEL_ERR) \ + rdp_debug_print(b->debug, false, __VA_ARGS__) /* To enable rdp_debug_clipboard message, add "--logger-scopes=rdp-backend-clipboard". */ +#define RDP_DEBUG_CLIPBOARD_LEVEL_DEFAULT RDP_DEBUG_LEVEL_ERR + #define rdp_debug_clipboard_verbose(b, ...) \ if (b->debugClipboardLevel >= RDP_DEBUG_LEVEL_VERBOSE) \ rdp_debug_print(b->debugClipboard, false, __VA_ARGS__) @@ -413,6 +420,9 @@ typedef struct rdp_peer_context RdpPeerContext; #define rdp_debug_clipboard_continue(b, ...) \ if (b->debugClipboardLevel >= RDP_DEBUG_LEVEL_INFO) \ rdp_debug_print(b->debugClipboard, true, __VA_ARGS__) +#define rdp_debug_clipboard_error(b, ...) \ + if (b->debugClipboardLevel >= RDP_DEBUG_LEVEL_ERR) \ + rdp_debug_print(b->debugClipboard, false, __VA_ARGS__) /* To enable rdp_debug message, add "--logger-scopes=rdp-backend". */ @@ -430,7 +440,7 @@ void assert_not_compositor_thread(struct rdp_backend *b); #endif // ENABLE_RDP_THREAD_CHECK BOOL rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory); void rdp_free_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory); -BOOL rdp_id_manager_init(struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 high_limit); +BOOL rdp_id_manager_init(struct rdp_backend *rdp_backend, struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 high_limit); void rdp_id_manager_free(struct rdp_id_manager *id_manager); BOOL rdp_id_manager_allocate_id(struct rdp_id_manager *id_manager, void *object, UINT32 *new_id); void rdp_id_manager_free_id(struct rdp_id_manager *id_manager, UINT32 id); @@ -475,10 +485,11 @@ rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_idle_f ASSERT_NOT_COMPOSITOR_THREAD(b); struct wl_event_loop *loop = wl_display_get_event_loop(b->compositor->wl_display); struct wl_event_source *event_source = wl_event_loop_add_idle(loop, func, data); - if (event_source) + if (event_source) { SetEvent(peerCtx->eventVcm); - else - weston_log("%s: wl_event_loop_add_idle failed\n", __func__); + } else { + rdp_debug_error(b, "%s: wl_event_loop_add_idle failed\n", __func__); + } return event_source; } else { /* RDP server is not opened, this must not be used */ diff --git a/libweston/backend-rdp/rdpaudio.c b/libweston/backend-rdp/rdpaudio.c index 96bf37c2f..34ef926cb 100644 --- a/libweston/backend-rdp/rdpaudio.c +++ b/libweston/backend-rdp/rdpaudio.c @@ -298,14 +298,14 @@ rdp_audio_setup_listener(RdpPeerContext *peerCtx) fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) { - weston_log("Couldn't create listener socket.\n"); + rdp_debug_error(b, "Couldn't create listener socket.\n"); return -1; } sink_socket_path = getenv("PULSE_AUDIO_RDP_SINK"); if (sink_socket_path == NULL || sink_socket_path[0] == '\0') { close(fd); - weston_log("Environment variable PULSE_AUDIO_RDP_SINK not set.\n"); + rdp_debug_error(b, "Environment variable PULSE_AUDIO_RDP_SINK not set.\n"); return -1; } @@ -320,7 +320,7 @@ rdp_audio_setup_listener(RdpPeerContext *peerCtx) error = bind(fd, (struct sockaddr *)&s, sizeof(struct sockaddr_un)); if (error != 0) { close(fd); - weston_log("Failed to bind to listener socket (%d).\n", error); + rdp_debug_error(b, "Failed to bind to listener socket (%d).\n", error); return -1; } @@ -343,6 +343,7 @@ rdp_audio_client_confirm_block( UINT16 wtimestamp) { RdpPeerContext *peerCtx = (RdpPeerContext*)context->data; + struct rdp_backend *b = peerCtx->rdpBackend; if (peerCtx->blockInfo[confirmBlockNum].ackReceivedTime != 0) { assert(peerCtx->blockInfo[confirmBlockNum].ackPlayedTime == 0); @@ -363,7 +364,7 @@ rdp_audio_client_confirm_block( uint64_t one = 1; if (write(peerCtx->audioInSem, &one, sizeof(one)) != sizeof(uint64_t)) { - weston_log("RDP Audio error at confirm_block while writing to audioInSem (%s)\n", strerror(errno)); + rdp_debug_error(b, "RDP Audio error at confirm_block while writing to audioInSem (%s)\n", strerror(errno)); return ERROR_INTERNAL_ERROR; } @@ -395,7 +396,7 @@ rdp_audio_handle_version( sizeSent = send(peerCtx->pulseAudioSinkFd, &version, sizeof(version), MSG_DONTWAIT); if (sizeSent != sizeof(version)) { - weston_log("RDP audio error responding to version request sent:%ld. %s\n", + rdp_debug_error(b, "RDP audio error responding to version request sent:%ld. %s\n", sizeSent, strerror(errno)); return -1; } @@ -409,6 +410,7 @@ rdp_audio_handle_transfer( UINT bytesLeft, UINT64 timestamp) { + struct rdp_backend *b = peerCtx->rdpBackend; int nbFrames = bytesLeft / peerCtx->bytesPerFrame; UINT bytesRead = 0; ssize_t sizeRead = 0; @@ -419,7 +421,7 @@ rdp_audio_handle_transfer( peerCtx->audioBuffer = zalloc(bytesLeft); if (!peerCtx->audioBuffer) { - weston_log("RDP Audio error zalloc(%d) failed.\n", bytesLeft); + rdp_debug_error(b, "RDP Audio error zalloc(%d) failed.\n", bytesLeft); return -1; } peerCtx->audioBufferSize = bytesLeft; @@ -434,7 +436,7 @@ rdp_audio_handle_transfer( sizeRead = read(peerCtx->pulseAudioSinkFd, peerCtx->audioBuffer + bytesRead, bytesLeft); if (sizeRead <= 0) { - weston_log("RDP Audio error while reading data from sink socket sizeRead:%ld. %s\n", sizeRead, strerror(errno)); + rdp_debug_error(b, "RDP Audio error while reading data from sink socket sizeRead:%ld. %s\n", sizeRead, strerror(errno)); return -1; } bytesRead += sizeRead; @@ -453,7 +455,7 @@ rdp_audio_handle_transfer( */ uint64_t dummy; if (read(peerCtx->audioInSem, &dummy, sizeof(dummy)) != sizeof(uint64_t)) { - weston_log("RDP Audio error at handle_transfer while reading from audioInSem (%s)\n", strerror(errno)); + rdp_debug_error(b, "RDP Audio error at handle_transfer while reading from audioInSem (%s)\n", strerror(errno)); return -1; } @@ -471,7 +473,7 @@ rdp_audio_handle_transfer( audioBuffer, MIN(nbFrames, AUDIO_FRAMES_PER_RDP_PACKET), 0) != 0) { - weston_log("RDP Audio error while SendSamples\n"); + rdp_debug_error(b, "RDP Audio error while SendSamples\n"); return -1; } @@ -481,7 +483,7 @@ rdp_audio_handle_transfer( */ uint64_t one = 1; if (write(peerCtx->audioInSem, &one, sizeof(one)) != sizeof(uint64_t)) { - weston_log("RDP Audio error at handle_transfer while writing to audioInSem (%s)\n", strerror(errno)); + rdp_debug_error(b, "RDP Audio error at handle_transfer while writing to audioInSem (%s)\n", strerror(errno)); return -1; } } else { @@ -503,6 +505,7 @@ static int rdp_audio_handle_get_latency( RdpPeerContext *peerCtx) { + struct rdp_backend *b = peerCtx->rdpBackend; UINT networkLatency; UINT renderedLatency; ssize_t sizeSent; @@ -531,7 +534,7 @@ rdp_audio_handle_get_latency( sizeSent = send(peerCtx->pulseAudioSinkFd, &renderedLatency, sizeof(renderedLatency), MSG_DONTWAIT); if (sizeSent != sizeof(renderedLatency)) { - weston_log("RDP audio error responding to latency request sent:%ld. %s\n", + rdp_debug_error(b, "RDP audio error responding to latency request sent:%ld. %s\n", sizeSent, strerror(errno)); return -1; } @@ -554,18 +557,18 @@ rdp_audio_pulse_audio_sink_thread(void *context) sigemptyset(&set); if (sigaddset(&set, SIGUSR2) == -1) { - weston_log("Audio sink thread: sigaddset(SIGUSR2) failed.\n"); + rdp_debug_error(b, "Audio sink thread: sigaddset(SIGUSR2) failed.\n"); return NULL; } if (pthread_sigmask(SIG_UNBLOCK, &set, NULL) != 0) { - weston_log("Audio sink thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); + rdp_debug_error(b, "Audio sink thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); return NULL; } act.sa_flags = 0; act.sa_mask = set; act.sa_handler = &signalhandler; if (sigaction(SIGUSR2, &act, NULL) == -1) { - weston_log("Audio sink thread: sigaction(SIGUSR2) failed.\n"); + rdp_debug_error(b, "Audio sink thread: sigaction(SIGUSR2) failed.\n"); return NULL; } @@ -586,7 +589,7 @@ rdp_audio_pulse_audio_sink_thread(void *context) peerCtx->pulseAudioSinkFd = accept(peerCtx->pulseAudioSinkListenerFd, NULL, NULL); if (peerCtx->pulseAudioSinkFd < 0) { - weston_log("Audio sink thread: Listener connection error (%s)\n", strerror(errno)); + rdp_debug_error(b, "Audio sink thread: Listener connection error (%s)\n", strerror(errno)); continue; } else { rdp_debug(b, "Audio sink thread: connection successful on socket (%d).\n", @@ -603,7 +606,7 @@ rdp_audio_pulse_audio_sink_thread(void *context) sizeRead = read(peerCtx->pulseAudioSinkFd, &header, sizeof(header)); /* pulseaudio RDP sink always send sizeof(header) regardless command type. */ if (sizeRead != sizeof(header)) { - weston_log("Audio sink thread: error while reading from sink socket sizeRead:%ld. %s\n", + rdp_debug_error(b, "Audio sink thread: error while reading from sink socket sizeRead:%ld. %s\n", sizeRead, strerror(errno)); break; } else if (header.cmd == RDP_AUDIO_CMD_VERSION) { @@ -628,7 +631,7 @@ rdp_audio_pulse_audio_sink_thread(void *context) peerCtx->accumulatedRenderedLatency = 0; peerCtx->accumulatedRenderedLatencyCount = 0; } else { - weston_log("Audio sink thread: unknown command from sink.\n"); + rdp_debug_error(b, "Audio sink thread: unknown command from sink.\n"); break; } } @@ -684,23 +687,25 @@ rdp_audio_client_activated(RdpsndServerContext* context) context->SetVolume(context, 0x7FFF, 0x7FFF); peerCtx->pulseAudioSinkListenerFd = rdp_audio_setup_listener(peerCtx); - if (peerCtx->pulseAudioSinkListenerFd < 0) - weston_log("RDPAudio - Failed to create listener socket\n"); - else if (pthread_create(&peerCtx->pulseAudioSinkThread, NULL, rdp_audio_pulse_audio_sink_thread, (void*)peerCtx) < 0) - weston_log("RDPAudio - Failed to start Pulse Audio Sink Thread. No audio will be available.\n"); + if (peerCtx->pulseAudioSinkListenerFd < 0) { + rdp_debug_error(b, "RDPAudio - Failed to create listener socket\n"); + } else if (pthread_create(&peerCtx->pulseAudioSinkThread, NULL, rdp_audio_pulse_audio_sink_thread, (void*)peerCtx) < 0) { + rdp_debug_error(b, "RDPAudio - Failed to start Pulse Audio Sink Thread. No audio will be available.\n"); + } } else { - weston_log("RDPAudio - No agreeded format.\n"); + rdp_debug_error(b, "RDPAudio - No agreeded format.\n"); } } int rdp_audio_init(RdpPeerContext *peerCtx) { + struct rdp_backend *b = peerCtx->rdpBackend; char *s; peerCtx->rdpsnd_server_context = rdpsnd_server_context_new(peerCtx->vcm); if (!peerCtx->rdpsnd_server_context) { - weston_log("RDPAudio - Couldn't initialize audio virtual channel.\n"); + rdp_debug_error(b, "RDPAudio - Couldn't initialize audio virtual channel.\n"); return 0; // Continue without audio } @@ -712,14 +717,14 @@ rdp_audio_init(RdpPeerContext *peerCtx) peerCtx->audioInSem = eventfd(256, EFD_SEMAPHORE | EFD_CLOEXEC); if (!peerCtx->audioInSem) { - weston_log("RDPAudio - Couldn't initialize event semaphore.\n"); + rdp_debug_error(b, "RDPAudio - Couldn't initialize event semaphore.\n"); goto Error_Exit; } /* this will be freed by FreeRDP at rdpsnd_server_context_free. */ AUDIO_FORMAT *audio_formats = malloc(sizeof rdp_audio_supported_audio_formats); if (!audio_formats) { - weston_log("RDPAudio - Couldn't allocate memory for audio formats.\n"); + rdp_debug_error(b, "RDPAudio - Couldn't allocate memory for audio formats.\n"); goto Error_Exit; } memcpy(audio_formats, rdp_audio_supported_audio_formats, sizeof rdp_audio_supported_audio_formats); @@ -736,7 +741,7 @@ rdp_audio_init(RdpPeerContext *peerCtx) if (s) { if (strcmp(s, "true") == 0) { peerCtx->rdpsnd_server_context->use_dynamic_virtual_channel = FALSE; - weston_log("RDPAudio - force static channel.\n"); + rdp_debug_error(b, "RDPAudio - force static channel.\n"); } } #endif // HAVE_RDPAUDIO_DYNAMIC_VIRTUAL_CHANNEL diff --git a/libweston/backend-rdp/rdpaudioin.c b/libweston/backend-rdp/rdpaudioin.c index f49a2c112..2b654aba5 100644 --- a/libweston/backend-rdp/rdpaudioin.c +++ b/libweston/backend-rdp/rdpaudioin.c @@ -277,14 +277,14 @@ rdp_audioin_setup_listener(RdpPeerContext *peerCtx) fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) { - weston_log("Couldn't create audioin listener socket.\n"); + rdp_debug_error(b, "Couldn't create audioin listener socket.\n"); return -1; } source_socket_path = getenv("PULSE_AUDIO_RDP_SOURCE"); if (source_socket_path == NULL || source_socket_path[0] == '\0') { close(fd); - weston_log("Environment variable PULSE_AUDIO_RDP_SOURCE not set.\n"); + rdp_debug_error(b, "Environment variable PULSE_AUDIO_RDP_SOURCE not set.\n"); return -1; } @@ -299,7 +299,7 @@ rdp_audioin_setup_listener(RdpPeerContext *peerCtx) error = bind(fd, (struct sockaddr *)&s, sizeof(struct sockaddr_un)); if (error != 0) { close(fd); - weston_log("Failed to bind to listener socket for audioin (%d).\n", error); + rdp_debug_error(b, "Failed to bind to listener socket for audioin (%d).\n", error); return -1; } @@ -338,7 +338,7 @@ rdp_audioin_client_opening(audin_server_context* context) } if (format == -1) { - weston_log("RDPAudioIn - No agreeded format.\n"); + rdp_debug_error(b, "RDPAudioIn - No agreeded format.\n"); return ERROR_INVALID_DATA; } @@ -371,7 +371,7 @@ rdp_audioin_client_receive_samples( struct rdp_backend *b = peerCtx->rdpBackend; if (!peerCtx->isAudioInStreamOpened || peerCtx->pulseAudioSourceFd == -1) { - weston_log("RDPAudioIn - audio stream is not opened.\n"); + rdp_debug_error(b, "RDPAudioIn - audio stream is not opened.\n"); return 0; } @@ -391,7 +391,7 @@ rdp_audioin_client_receive_samples( /* Unblock worker thread to close pipe to pulseaudio */ uint64_t one=1; if (write(peerCtx->closeAudioSourceFd, &one, sizeof(one)) != sizeof(uint64_t)) { - weston_log("RDP AudioIn error at receive_samples while writing to closeAudioSourceFd (%s)\n", strerror(errno)); + rdp_debug_error(b, "RDP AudioIn error at receive_samples while writing to closeAudioSourceFd (%s)\n", strerror(errno)); return ERROR_INTERNAL_ERROR; } @@ -420,18 +420,18 @@ rdp_audioin_source_thread(void *context) sigemptyset(&set); if (sigaddset(&set, SIGUSR2) == -1) { - weston_log("AudioIn source thread: sigaddset(SIGUSR2) failed.\n"); + rdp_debug_error(b, "AudioIn source thread: sigaddset(SIGUSR2) failed.\n"); return NULL; } if (pthread_sigmask(SIG_UNBLOCK, &set, NULL) != 0) { - weston_log("AudioIn source thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); + rdp_debug_error(b, "AudioIn source thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); return NULL; } act.sa_flags = 0; act.sa_mask = set; act.sa_handler = &signalhandler; if (sigaction(SIGUSR2, &act, NULL) == -1) { - weston_log("AudioIn source thread: sigaction(SIGUSR2) failed.\n"); + rdp_debug_error(b, "AudioIn source thread: sigaction(SIGUSR2) failed.\n"); return NULL; } @@ -451,7 +451,7 @@ rdp_audioin_source_thread(void *context) */ peerCtx->pulseAudioSourceFd = accept(peerCtx->pulseAudioSourceListenerFd, NULL, NULL); if (peerCtx->pulseAudioSourceFd < 0) { - weston_log("AudioIn source thread: Listener connection error (%s)\n", strerror(errno)); + rdp_debug_error(b, "AudioIn source thread: Listener connection error (%s)\n", strerror(errno)); continue; } else { rdp_debug(b, "AudioIn connection successful on socket (%d).\n", peerCtx->pulseAudioSourceFd); @@ -462,13 +462,13 @@ rdp_audioin_source_thread(void *context) */ uint64_t dummy; if (read(peerCtx->closeAudioSourceFd, &dummy, sizeof(dummy)) != sizeof(uint64_t)) { - weston_log("RDP AudioIn wait on eventfd failed. thread exiting. %s\n", strerror(errno)); + rdp_debug_error(b, "RDP AudioIn wait on eventfd failed. thread exiting. %s\n", strerror(errno)); break; } peerCtx->audin_server_context->Close(peerCtx->audin_server_context); rdp_debug(b, "RDP AudioIn closed.\n"); } else { - weston_log("Failed to open audio in connection with RDP client.\n"); + rdp_debug_error(b, "Failed to open audio in connection with RDP client.\n"); } close(peerCtx->pulseAudioSourceFd); @@ -490,9 +490,11 @@ rdp_audioin_source_thread(void *context) int rdp_audioin_init(RdpPeerContext *peerCtx) { + struct rdp_backend *b = peerCtx->rdpBackend; + peerCtx->audin_server_context = audin_server_context_new(peerCtx->vcm); if (!peerCtx->audin_server_context) { - weston_log("RDPAudioIn - Couldn't initialize audio virtual channel.\n"); + rdp_debug_error(b, "RDPAudioIn - Couldn't initialize audio virtual channel.\n"); return 0; // Continue without audio } @@ -505,7 +507,7 @@ rdp_audioin_init(RdpPeerContext *peerCtx) // this will be freed by FreeRDP at audin_server_context_free. AUDIO_FORMAT *audio_formats = malloc(sizeof rdp_audioin_supported_audio_formats); if (!audio_formats) { - weston_log("RDPAudioIn - Couldn't allocate memory for audio formats.\n"); + rdp_debug_error(b, "RDPAudioIn - Couldn't allocate memory for audio formats.\n"); goto Error_Exit; } memcpy(audio_formats, rdp_audioin_supported_audio_formats, sizeof rdp_audioin_supported_audio_formats); @@ -521,18 +523,18 @@ rdp_audioin_init(RdpPeerContext *peerCtx) peerCtx->closeAudioSourceFd = eventfd(0, EFD_CLOEXEC); if (peerCtx->closeAudioSourceFd < 0) { - weston_log("RDPAudioIn - Couldn't initialize eventfd.\n"); + rdp_debug_error(b, "RDPAudioIn - Couldn't initialize eventfd.\n"); goto Error_Exit; } peerCtx->pulseAudioSourceListenerFd = rdp_audioin_setup_listener(peerCtx); if (peerCtx->pulseAudioSourceListenerFd < 0) { - weston_log("RDPAudioIn - rdp_audioin_setup_listener failed.\n"); + rdp_debug_error(b, "RDPAudioIn - rdp_audioin_setup_listener failed.\n"); goto Error_Exit; } if (pthread_create(&peerCtx->pulseAudioSourceThread, NULL, rdp_audioin_source_thread, (void*)peerCtx) < 0) { - weston_log("RDPAudioIn - Failed to start Pulse Audio Source Thread. No audio in will be available.\n"); + rdp_debug_error(b, "RDPAudioIn - Failed to start Pulse Audio Source Thread. No audio in will be available.\n"); goto Error_Exit; } diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index 5d70ebd95..bad7e5511 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -86,6 +86,49 @@ struct rdp_clipboard_supported_format clipboard_supported_formats[] = { }; #define RDP_NUM_CLIPBOARD_FORMATS ARRAY_LENGTH(clipboard_supported_formats) +enum rdp_clipboard_data_source_state { + RDP_CLIPBOARD_SOURCE_ALLOCATED = 0, + RDP_CLIPBOARD_SOURCE_FORMATLIST_READY, /* format list is obtained from provider */ + RDP_CLIPBOARD_SOURCE_PUBLISHED, /* availablity of some or none clipboard data is notified to comsumer */ + RDP_CLIPBOARD_SOURCE_REQUEST_DATA, /* data request is sent to provider */ + RDP_CLIPBOARD_SOURCE_RECEIVED_DATA, /* data is received from provider, waiting data to be dispatched to consumer */ + RDP_CLIPBOARD_SOURCE_TRANSFERING, /* transfering data to consumer */ + RDP_CLIPBOARD_SOURCE_TRANSFERRED, /* complete transfering data to comsumer */ + RDP_CLIPBOARD_SOURCE_CANCEL_PENDING, /* data transfer cancel is requested */ + RDP_CLIPBOARD_SOURCE_CANCELED, /* data transfer is canceled */ + RDP_CLIPBOARD_SOURCE_FAILED, /* failure occured */ +}; + +static char * +clipboard_data_source_state_to_string(enum rdp_clipboard_data_source_state state) +{ + switch (state) + { + case RDP_CLIPBOARD_SOURCE_ALLOCATED: + return "allocated"; + case RDP_CLIPBOARD_SOURCE_FORMATLIST_READY: + return "format list ready"; + case RDP_CLIPBOARD_SOURCE_PUBLISHED: + return "published"; + case RDP_CLIPBOARD_SOURCE_REQUEST_DATA: + return "request data"; + case RDP_CLIPBOARD_SOURCE_RECEIVED_DATA: + return "received data"; + case RDP_CLIPBOARD_SOURCE_TRANSFERING: + return "transferring"; + case RDP_CLIPBOARD_SOURCE_TRANSFERRED: + return "transferred"; + case RDP_CLIPBOARD_SOURCE_CANCEL_PENDING: + return "cancel pending"; + case RDP_CLIPBOARD_SOURCE_CANCELED: + return "cenceled"; + case RDP_CLIPBOARD_SOURCE_FAILED: + return "failed"; + } + assert(false); + return "unknown"; +} + struct rdp_clipboard_data_source { struct weston_data_source base; struct wl_event_source *event_source; @@ -94,6 +137,7 @@ struct rdp_clipboard_data_source { int refcount; int data_source_fd; int format_index; + enum rdp_clipboard_data_source_state state; UINT32 inflight_write_count; void *inflight_data_to_write; size_t inflight_data_size; @@ -129,8 +173,9 @@ clipboard_process_text(struct rdp_clipboard_data_source *source, BOOL is_send) source->is_data_processed = TRUE; } - rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s (%d bytes)\n", - __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%d bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source->state), + is_send ? "send" : "receive", (UINT32)source->data_contents.size); return source->data_contents.data; } @@ -222,8 +267,9 @@ clipboard_process_html(struct rdp_clipboard_data_source *source, BOOL is_send) source->is_data_processed = TRUE; } - rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s (%d bytes)\n", - __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%d bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source->state), + is_send ? "send" : "receive", (UINT32)source->data_contents.size); //rdp_debug_clipboard_verbose(b, "RDP clipboard_process_html (%p): %s \n\"%s\"\n (%d bytes)\n", // source, is_send ? "send" : "receive", // (char *)source->data_contents.data, @@ -233,8 +279,10 @@ clipboard_process_html(struct rdp_clipboard_data_source *source, BOOL is_send) error_return: - weston_log("RDP %s FAILED (%p): %s (%d bytes)\n", - __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "RDP %s FAILED (%p:%s): %s (%d bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source->state), + is_send ? "send" : "receive", (UINT32)source->data_contents.size); //rdp_debug_clipboard_verbose(b, "RDP clipboard_process_html FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", // source, is_send ? "send" : "receive", // (char *)source->data_contents.data, @@ -334,8 +382,9 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) assert(bmfh); assert(bmih); - rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s (%d bytes)\n", - __func__, source, is_send ? "send" : "receive", + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%d bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source->state), + is_send ? "send" : "receive", (UINT32)source->data_contents.size); rdp_debug_clipboard_verbose_continue(b, " BITMAPFILEHEADER.bfType:0x%x\n", bmfh->bfType); rdp_debug_clipboard_verbose_continue(b, " BITMAPFILEHEADER.bfSize:%d\n", bmfh->bfSize); @@ -388,8 +437,10 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) error_return: - weston_log("RDP %s FAILED (%p): %s (%d bytes)\n", - __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "RDP %s FAILED (%p:%s): %s (%d bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source->state), + is_send ? "send" : "receive", (UINT32)source->data_contents.size); wl_array_release(&data_contents); @@ -530,7 +581,8 @@ clipboard_data_source_unref(struct rdp_clipboard_data_source *source) assert(source->refcount); source->refcount--; - rdp_debug_clipboard(b, "RDP %s (%p): refcount:%d\n", __func__, source, source->refcount); + rdp_debug_clipboard(b, "RDP %s (%p:%s): refcount:%d\n", + __func__, source, clipboard_data_source_state_to_string(source->state), source->refcount); if (source->refcount > 0) return; @@ -554,7 +606,7 @@ clipboard_data_source_unref(struct rdp_clipboard_data_source *source) free(source); } -/*****************************************\ +/******************************************\ * FreeRDP format data response functions * \******************************************/ @@ -565,8 +617,8 @@ clipboard_client_send_format_data_response(RdpPeerContext *peerCtx, struct rdp_c struct rdp_backend *b = peerCtx->rdpBackend; CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; - rdp_debug_clipboard(b, "Client: %s (%p) format_index:%d %s (%d bytes)\n", - __func__, source, source->format_index, + rdp_debug_clipboard(b, "Client: %s (%p:%s) format_index:%d %s (%d bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source->state), source->format_index, clipboard_supported_formats[source->format_index].mime_type, size); formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; @@ -589,7 +641,8 @@ clipboard_client_send_format_data_response_fail(RdpPeerContext *peerCtx, struct struct rdp_backend *b = peerCtx->rdpBackend; CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; - rdp_debug_clipboard(b, "Client: %s (%p)\n", __func__, source); + rdp_debug_clipboard(b, "Client: %s (%p:%s)\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; formatDataResponse.msgFlags = CB_RESPONSE_FAIL; @@ -620,7 +673,8 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) int len, size; void *data_to_send; - rdp_debug_clipboard_verbose(b, "RDP %s (%p) fd:%d\n", __func__, source, fd); + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", + __func__, source, clipboard_data_source_state_to_string(source->state), fd); ASSERT_COMPOSITOR_THREAD(b); @@ -640,13 +694,15 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) source->data_contents.size -= 1024; } + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERING; data = (char*)source->data_contents.data + source->data_contents.size; size = source->data_contents.alloc - source->data_contents.size - 1; // -1 leave space for NULL-terminate. len = read(fd, data, size); if (len == 0) { /* all data from source is read, so completed. */ - rdp_debug_clipboard(b, "RDP %s (%p): read completed (%ld bytes)\n", - __func__, source, source->data_contents.size); + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; + rdp_debug_clipboard(b, "RDP %s (%p:%s): read completed (%ld bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source->state), source->data_contents.size); if (!source->data_contents.size) goto error_exit; /* process data before sending to client */ @@ -663,13 +719,15 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) assert(source->refcount == 1); clipboard_data_source_unref(source); } else if (len < 0) { - weston_log("RDP %s (%p) read failed (%s)\n", __func__, source, strerror(errno)); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "RDP %s (%p:%s) read failed (%s)\n", + __func__, source, clipboard_data_source_state_to_string(source->state), strerror(errno)); goto error_exit; } else { source->data_contents.size += len; ((char*)source->data_contents.data)[source->data_contents.size] = '\0'; - rdp_debug_clipboard_verbose(b, "RDP %s (%p): read (%zu bytes)\n", - __func__, source, source->data_contents.size); + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) read (%zu bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source->state), source->data_contents.size); } return 0; @@ -695,7 +753,8 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) size_t data_size; ssize_t size; - rdp_debug_clipboard_verbose(b, "RDP %s (%p) fd:%d\n", __func__, source, fd); + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", __func__, + source, clipboard_data_source_state_to_string(source->state), fd); ASSERT_COMPOSITOR_THREAD(b); @@ -709,8 +768,8 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) if (source->is_canceled == FALSE && source->data_contents.data && source->data_contents.size) { if (source->inflight_data_to_write) { assert(source->inflight_data_size); - rdp_debug_clipboard_verbose(b, "RDP %s: retry write retry count:%d (%p)\n", - __func__, source->inflight_write_count, source); + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) retry write retry count:%d\n", + __func__, source, clipboard_data_source_state_to_string(source->state), source->inflight_write_count); data_to_write = source->inflight_data_to_write; data_size = source->inflight_data_size; } else { @@ -722,11 +781,13 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) data_size = source->data_contents.size; } while (data_to_write && data_size) { + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERING; size = write(fd, data_to_write, data_size); if (size <= 0) { if (errno != EAGAIN) { - weston_log("RDP %s (%p) write failed %s\n", - __func__, source, strerror(errno)); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "RDP %s (%p:%s) write failed %s\n", + __func__, source, clipboard_data_source_state_to_string(source->state), strerror(errno)); break; } source->inflight_data_to_write = data_to_write; @@ -736,8 +797,9 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, clipboard_data_source_write, source); if (!source->event_source) { - weston_log("RDP %s (%p) wl_event_loop_add_fd failed\n", - __func__, source); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); break; } return 0; @@ -745,13 +807,19 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) assert(data_size >= (size_t)size); data_size -= size; data_to_write = (char *)data_to_write + size; - rdp_debug_clipboard_verbose(b, "RDP %s (%p) wrote %ld bytes, remaining %ld bytes\n", - __func__, source, size, data_size); - if (!data_size) - rdp_debug_clipboard(b, "RDP %s (%p) write completed (%ld bytes)\n", - __func__, source, source->data_contents.size); + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) wrote %ld bytes, remaining %ld bytes\n", + __func__, source, clipboard_data_source_state_to_string(source->state), size, data_size); + if (!data_size) { + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; + rdp_debug_clipboard(b, "RDP %s (%p:%s) write completed (%ld bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source->state), source->data_contents.size); + } } } + } else if (source->is_canceled) { + source->state = RDP_CLIPBOARD_SOURCE_CANCELED; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s)\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); } close(source->data_source_fd); @@ -774,7 +842,12 @@ static void clipboard_data_source_accept(struct weston_data_source *base, uint32_t time, const char *mime_type) { - weston_log("RDP %s (base:%p) mime-type:\"%s\"\n", __func__, base, mime_type); + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)base; + freerdp_peer *client = (freerdp_peer*)source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug(b, "RDP %s (base:%p) mime-type:\"%s\"\n", __func__, base, mime_type); } /* data-device informs the application requested the specified format data in given data_source (= client's clipboard) */ @@ -791,7 +864,8 @@ clipboard_data_source_send(struct weston_data_source *base, CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = {}; int index; - rdp_debug_clipboard(b, "RDP %s (%p) fd:%d, mime-type:\"%s\"\n", __func__, source, fd, mime_type); + rdp_debug_clipboard(b, "RDP %s (%p:%s) fd:%d, mime-type:\"%s\"\n", + __func__, source, clipboard_data_source_state_to_string(source->state), fd, mime_type); ASSERT_COMPOSITOR_THREAD(b); @@ -799,8 +873,16 @@ clipboard_data_source_send(struct weston_data_source *base, /* Here means server side (Linux application) request clipboard data, but server hasn't completed with previous request yet. If this happens, punt to idle loop and reattempt. */ - weston_log("\n\n\nRDP %s (%p) vs (%p): outstanding RDP data request (client to server)\n\n\n", - __func__, source, peerCtx->clipboard_inflight_client_data_source); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "\n\n\nRDP %s (%p:%s) vs (%p): outstanding RDP data request (client to server)\n\n\n", + __func__, source, clipboard_data_source_state_to_string(source->state), peerCtx->clipboard_inflight_client_data_source); + goto error_return_close_fd; + } + + if (source->base.mime_types.size == 0) { + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; + rdp_debug_clipboard(b, "RDP %s (%p:%s) source has no data\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); goto error_return_close_fd; } @@ -816,11 +898,14 @@ clipboard_data_source_send(struct weston_data_source *base, if (index == source->format_index) { /* data is already in data_contents, no need to pull from client */ assert(source->event_source == NULL); + source->state = RDP_CLIPBOARD_SOURCE_RECEIVED_DATA; source->event_source = wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, clipboard_data_source_write, source); if (!source->event_source) { - weston_log("RDP %s (%p) wl_event_loop_add_fd failed\n", __func__, source); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); goto error_return_unref_source; } } else { @@ -834,16 +919,19 @@ clipboard_data_source_send(struct weston_data_source *base, formatDataRequest.msgType = CB_FORMAT_DATA_REQUEST; formatDataRequest.dataLen = 4; formatDataRequest.requestedFormatId = source->client_format_id_table[index]; - rdp_debug_clipboard(b, "RDP %s (%p) request \"%s\" index:%d formatId:%d %s\n", - __func__, source, mime_type, index, + source->state = RDP_CLIPBOARD_SOURCE_REQUEST_DATA; + rdp_debug_clipboard(b, "RDP %s (%p:%s) request \"%s\" index:%d formatId:%d %s\n", + __func__, source, clipboard_data_source_state_to_string(source->state), mime_type, index, formatDataRequest.requestedFormatId, clipboard_format_id_to_string(formatDataRequest.requestedFormatId, false)); if (peerCtx->clipboard_server_context->ServerFormatDataRequest(peerCtx->clipboard_server_context, &formatDataRequest) != 0) goto error_return_unref_source; } } else { - weston_log("RDP %s (%p) specified format \"%s\" index:%d formatId:%d is not supported by client\n", - __func__, source, mime_type, index, source->client_format_id_table[index]); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "RDP %s (%p:%s) specified format \"%s\" index:%d formatId:%d is not supported by client\n", + __func__, source, clipboard_data_source_state_to_string(source->state), + mime_type, index, source->client_format_id_table[index]); goto error_return_close_fd; } @@ -873,17 +961,22 @@ clipboard_data_source_cancel(struct weston_data_source *base) RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - rdp_debug_clipboard(b, "RDP %s (%p)\n", __func__, source); + rdp_debug_clipboard(b, "RDP %s (%p:%s)\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); ASSERT_COMPOSITOR_THREAD(b); if (source == peerCtx->clipboard_inflight_client_data_source) { - rdp_debug_clipboard(b, "RDP %s (%p): still inflight\n", __func__, source); - assert(source->refcount > 1); source->is_canceled = TRUE; + source->state = RDP_CLIPBOARD_SOURCE_CANCEL_PENDING; + rdp_debug_clipboard(b, "RDP %s (%p:%s): still inflight\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); + assert(source->refcount > 1); } else { /* everything outside of the base has to be cleaned up */ - rdp_debug_clipboard_verbose(b, "RDP %s (%p): cancelled\n", __func__, source); + source->state = RDP_CLIPBOARD_SOURCE_CANCELED; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s)\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); assert(source->event_source == NULL); wl_array_release(&source->data_contents); wl_array_init(&source->data_contents); @@ -914,7 +1007,8 @@ clipboard_data_source_publish(void *arg) struct rdp_backend *b = peerCtx->rdpBackend; struct rdp_clipboard_data_source *source_prev; - rdp_debug_clipboard(b, "RDP %s (%p)\n", __func__, source); + rdp_debug_clipboard(b, "RDP %s (%p:%s)\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); ASSERT_COMPOSITOR_THREAD(b); @@ -927,6 +1021,7 @@ clipboard_data_source_publish(void *arg) source->base.accept = clipboard_data_source_accept; source->base.send = clipboard_data_source_send; source->base.cancel = clipboard_data_source_cancel; + source->state = RDP_CLIPBOARD_SOURCE_PUBLISHED; weston_seat_set_selection(peerCtx->item.seat, &source->base, wl_display_next_serial(b->compositor->wl_display)); @@ -981,7 +1076,11 @@ clipboard_data_source_request(void *arg) if (!source) goto error_exit_response_fail; - rdp_debug_clipboard(b, "RDP %s (%p) allocated\n", __func__, source); + /* By now, the server side data availablity is already notified + to client by clipboard_set_selection(). */ + source->state = RDP_CLIPBOARD_SOURCE_PUBLISHED; + rdp_debug_clipboard(b, "RDP %s (%p:%s)\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); wl_signal_init(&source->base.destroy_signal); wl_array_init(&source->base.mime_types); wl_array_init(&source->data_contents); @@ -997,6 +1096,7 @@ clipboard_data_source_request(void *arg) source->data_source_fd = p[0]; /* Request data from data source */ + source->state = RDP_CLIPBOARD_SOURCE_REQUEST_DATA; selection_data_source->send(selection_data_source, requested_mime_type, p[1]); /* p[1] should be closed by data source */ @@ -1005,7 +1105,9 @@ clipboard_data_source_request(void *arg) wl_event_loop_add_fd(loop, p[0], WL_EVENT_READABLE, clipboard_data_source_read, source); if (!source->event_source) { - weston_log("RDP %s: wl_event_loop_add_fd failed (%p)\n", __func__, source); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed.\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); goto error_exit_free_source; } @@ -1038,7 +1140,7 @@ clipboard_set_selection(struct wl_listener *listener, void *data) const char **mime_type; int index, num_supported_format = 0, num_avail_format = 0; - rdp_debug_clipboard(b, "RDP %s (base:%p}\n", __func__, selection_data_source); + rdp_debug_clipboard(b, "RDP %s (base:%p)\n", __func__, selection_data_source); ASSERT_COMPOSITOR_THREAD(b); @@ -1165,7 +1267,9 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT source = zalloc(sizeof *source); if (source) { - rdp_debug_clipboard(b, "Client: %s (%p) allocated\n", __func__, source); + source->state = RDP_CLIPBOARD_SOURCE_ALLOCATED; + rdp_debug_clipboard(b, "Client: %s (%p:%s) allocated\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); wl_signal_init(&source->base.destroy_signal); wl_array_init(&source->base.mime_types); wl_array_init(&source->data_contents); @@ -1184,30 +1288,39 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT if (s) { p = wl_array_add(&source->base.mime_types, sizeof *p); if (p) { - rdp_debug_clipboard(b, "Client: %s (%p) mine_type:\"%s\" index:%d formatId:%d\n", - __func__, source, s, index, format->formatId); + rdp_debug_clipboard(b, "Client: %s (%p:%s) mine_type:\"%s\" index:%d formatId:%d\n", + __func__, source, clipboard_data_source_state_to_string(source->state), + s, index, format->formatId); *p = s; } else { - rdp_debug_clipboard(b, "Client: %s (%p) wl_array_add failed\n", __func__, source); + rdp_debug_clipboard(b, "Client: %s (%p:%s) wl_array_add failed\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); free(s); } } else { - rdp_debug_clipboard(b, "Client: %s (%p) strdup failed\n", __func__, source); + rdp_debug_clipboard(b, "Client: %s (%p:%s) strdup failed\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); } } } - if (source->base.mime_types.size) { - source->event_source = - rdp_defer_rdp_task_to_display_loop(peerCtx, - clipboard_data_source_publish, - source); - if (source->event_source) - isPublished = TRUE; - else - weston_log("Client: %s (%p) rdp_defer_rdp_task_to_display_loop failed\n", __func__, source); + if (formatList->numFormats != 0 && + source->base.mime_types.size == 0) { + rdp_debug_clipboard(b, "Client: %s (%p:%s) no formats are supported\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); + } + + source->state = RDP_CLIPBOARD_SOURCE_FORMATLIST_READY; + source->event_source = + rdp_defer_rdp_task_to_display_loop(peerCtx, + clipboard_data_source_publish, + source); + if (source->event_source) { + isPublished = TRUE; } else { - rdp_debug_clipboard(b, "Client: %s (%p) no formats are supported\n", __func__, source); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "Client: %s (%p:%s) rdp_defer_rdp_task_to_display_loop failed\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); } } @@ -1216,7 +1329,9 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT formatListResponse.msgFlags = source ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; formatListResponse.dataLen = 0; if (peerCtx->clipboard_server_context->ServerFormatListResponse(peerCtx->clipboard_server_context, &formatListResponse) != 0) { - weston_log("Client: %s (%p) ServerFormatListResponse failed\n", __func__, source); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "Client: %s (%p:%s) ServerFormatListResponse failed\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); return -1; } @@ -1237,12 +1352,22 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR struct rdp_clipboard_data_source *source = peerCtx->clipboard_inflight_client_data_source; BOOL Success = FALSE; - rdp_debug_clipboard(b, "Client: %s (%p) flags:%d, dataLen:%d\n", - __func__, source, formatDataResponse->msgFlags, formatDataResponse->dataLen); + rdp_debug_clipboard(b, "Client: %s (%p:%s) flags:%d, dataLen:%d\n", + __func__, source, clipboard_data_source_state_to_string(source->state), + formatDataResponse->msgFlags, formatDataResponse->dataLen); ASSERT_NOT_COMPOSITOR_THREAD(b); if (source) { + if (source->event_source || (source->inflight_write_count != 0)) { + /* here means client responded more than once for single data request */ + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "Client: %s (%p:%s) middle of write loop:%p, %d\n", + __func__, source, clipboard_data_source_state_to_string(source->state), + source->event_source, source->inflight_write_count); + return -1; + } + if (formatDataResponse->msgFlags == CB_RESPONSE_OK) { /* Recieved data from client, cache to data source */ if (wl_array_add(&source->data_contents, formatDataResponse->dataLen+1)) { @@ -1252,17 +1377,28 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR source->data_contents.size = formatDataResponse->dataLen; /* regardless data type, make sure it ends with NULL */ ((char*)source->data_contents.data)[source->data_contents.size] = '\0'; + /* data is ready, waiting to be written to destination */ + source->state = RDP_CLIPBOARD_SOURCE_RECEIVED_DATA; Success = TRUE; + } else { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; } + } else { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; } + rdp_debug_clipboard_verbose(b, "Client: %s (%p:%s)\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); if (Success) { assert(source->event_source == NULL); source->event_source = wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, clipboard_data_source_write, source); - if (!source->event_source) - weston_log("Client: %s: wl_event_loop_add_fd failed (%p)\n", __func__, source); + if (!source->event_source) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "Client: %s (%p:%s) wl_event_loop_add_fd failed\n", + __func__, source, clipboard_data_source_state_to_string(source->state)); + } } if (!source->event_source) { @@ -1280,7 +1416,7 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR peerCtx->clipboard_inflight_client_data_source = NULL; } } else { - rdp_debug_clipboard(b, "Client: %s client send data without server asking. protocol error.\n", __func__); + rdp_debug_clipboard(b, "Client: %s client send data without server asking. protocol error", __func__); return -1; } @@ -1314,7 +1450,7 @@ clipboard_client_format_data_request(CliprdrServerContext* context, const CLIPRD ASSERT_NOT_COMPOSITOR_THREAD(b); if (peerCtx->clipboard_data_request_event_source) { - weston_log("Client: %s (%p) client requests data while server hasn't responded previous request yet. protocol error.\n", + rdp_debug_clipboard_error(b, "Client: %s (outstanding event:%p) client requests data while server hasn't responded previous request yet. protocol error.\n", __func__, peerCtx->clipboard_data_request_event_source); return -1; } @@ -1326,11 +1462,11 @@ clipboard_client_format_data_request(CliprdrServerContext* context, const CLIPRD peerCtx->clipboard_data_request_event_source = rdp_defer_rdp_task_to_display_loop(peerCtx, clipboard_data_source_request, peerCtx); if (!peerCtx->clipboard_data_request_event_source) { - weston_log("Client: %s rdp_defer_rdp_task_to_display_loop failed\n", __func__); + rdp_debug_clipboard_error(b, "Client: %s rdp_defer_rdp_task_to_display_loop failed\n", __func__); goto error_return; } } else { - weston_log("Client: %s client requests data format the server never reported in format list response. protocol error.\n", __func__); + rdp_debug_clipboard_error(b, "Client: %s client requests data format the server never reported in format list response. protocol error.\n", __func__); return -1; } @@ -1367,14 +1503,16 @@ rdp_clipboard_init(freerdp_peer* client) s = getenv("WESTON_RDP_DEBUG_CLIPBOARD_LEVEL"); if (s) { if (!safe_strtoint(s, &b->debugClipboardLevel)) - b->debugClipboardLevel = RDP_DEBUG_LEVEL_DEFAULT; + b->debugClipboardLevel = RDP_DEBUG_CLIPBOARD_LEVEL_DEFAULT; else if (b->debugClipboardLevel > RDP_DEBUG_LEVEL_VERBOSE) b->debugClipboardLevel = RDP_DEBUG_LEVEL_VERBOSE; } else { - b->debugClipboardLevel = RDP_DEBUG_LEVEL_DEFAULT; + /* by default, clipboard scope is disabled, so when it's enabled, + log with verbose mode to assist debugging */ + b->debugClipboardLevel = RDP_DEBUG_LEVEL_VERBOSE; // RDP_DEBUG_CLIPBOARD_LEVEL_DEFAULT; } } - weston_log("RDP backend: WESTON_RDP_DEBUG_CLIPBOARD_LEVEL: %d\n", b->debugClipboardLevel); + rdp_debug_clipboard(b, "RDP backend: WESTON_RDP_DEBUG_CLIPBOARD_LEVEL: %d\n", b->debugClipboardLevel); peerCtx->clipboard_server_context = cliprdr_server_context_new(peerCtx->vcm); if (!peerCtx->clipboard_server_context) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 41b9b14bb..a4fffd15b 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -377,12 +377,12 @@ disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_mon if (monitorMode[i].monitorDef.is_primary) { /* count number of primary */ if (++primaryCount > 1) { - weston_log("%s: RDP client reported unexpected primary count (%d)\n",__func__, primaryCount); + rdp_debug_error(b, "%s: RDP client reported unexpected primary count (%d)\n",__func__, primaryCount); return FALSE; } /* primary must be at (0,0) in client space */ if (monitorMode[i].monitorDef.x != 0 || monitorMode[i].monitorDef.y != 0) { - weston_log("%s: RDP client reported primary is not at (0,0) but (%d,%d).\n", + rdp_debug_error(b, "%s: RDP client reported primary is not at (0,0) but (%d,%d).\n", __func__, monitorMode[i].monitorDef.x, monitorMode[i].monitorDef.y); return FALSE; } @@ -463,7 +463,7 @@ disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_mon if (isScalingUsed && (!isConnected_H && !isConnected_V)) { /* scaling can't be supported in complex monitor placement */ - weston_log("\nWARNING\nWARNING\nWARNING: Scaling is used, but can't be supported in complex monitor placement\nWARNING\nWARNING\n"); + rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: Scaling is used, but can't be supported in complex monitor placement\nWARNING\nWARNING\n"); isScalingSupported = false; } @@ -537,7 +537,7 @@ disp_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MON assert(settings->HiDefRemoteApp); if (displayControl->NumMonitors > RDP_MAX_MONITOR) { - weston_log("\nWARNING\nWARNING\nWARNING: client reports more monitors then expected:(%d)\nWARNING\nWARNING\n", + rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: client reports more monitors then expected:(%d)\nWARNING\nWARNING\n", displayControl->NumMonitors); return; } @@ -677,12 +677,12 @@ xf_peer_adjust_monitor_layout(freerdp_peer* client) /* If not in RAIL mode, or RAIL-shell is not used, only signle mon is allowed */ if (!settings->HiDefRemoteApp || b->rdprail_shell_api == NULL) { if (settings->MonitorCount > 1) { - weston_log("\nWARNING\nWARNING\nWARNING: multiple monitor is not supported in non HiDef RAIL mode\nWARNING\nWARNING\n"); + rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: multiple monitor is not supported in non HiDef RAIL mode\nWARNING\nWARNING\n"); return FALSE; } } if (settings->MonitorCount > RDP_MAX_MONITOR) { - weston_log("\nWARNING\nWARNING\nWARNING: client reports more monitors then expected:(%d)\nWARNING\nWARNING\n", + rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: client reports more monitors then expected:(%d)\nWARNING\nWARNING\n", settings->MonitorCount); return FALSE; } diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index a3e3a6e83..cfad94c2c 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -72,12 +72,12 @@ struct rdp_dispatch_data { #define RDP_DISPATCH_TO_DISPLAY_LOOP(context, arg_type, arg, callback) \ { \ + freerdp_peer *client = (freerdp_peer*)(context)->custom; \ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; \ + struct rdp_backend *b = peerCtx->rdpBackend; \ struct rdp_dispatch_data *dispatch_data; \ dispatch_data = (struct rdp_dispatch_data *)malloc(sizeof(*dispatch_data)); \ if (dispatch_data) { \ - freerdp_peer *client = (freerdp_peer*)(context)->custom; \ - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; \ - struct rdp_backend *b = peerCtx->rdpBackend; \ ASSERT_NOT_COMPOSITOR_THREAD(b); \ dispatch_data->client = client; \ dispatch_data->u_##arg_type = *(arg); \ @@ -87,14 +87,14 @@ struct rdp_dispatch_data { dispatch_data->_base.event_source = \ rdp_defer_rdp_task_to_display_loop(peerCtx, callback, dispatch_data); \ if (!dispatch_data->_base.event_source) { \ - weston_log("%s: rdp_queue_deferred_task failed\n", __func__); \ + rdp_debug_error(b, "%s: rdp_queue_deferred_task failed\n", __func__); \ pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ wl_list_remove(&dispatch_data->_base.link); \ pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ free(dispatch_data); \ } \ } else { \ - weston_log("%s: malloc failed\n", __func__); \ + rdp_debug_error(b, "%s: malloc failed\n", __func__); \ } \ } @@ -225,7 +225,7 @@ rail_client_Exec_callback(void *arg) &peerCtx->clientExec_destroy_listener); result = RAIL_EXEC_S_OK; } else { - weston_log("%s: fail to launch shell process %s\n", + rdp_debug_error(b, "%s: fail to launch shell process %s\n", __func__, remoteProgramAndArgs); } } @@ -306,7 +306,7 @@ rail_client_Activate_callback(void *arg) if (activate->windowId && activate->enabled) { surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, activate->windowId); if (!surface) - weston_log("Client: ClientActivate: WindowId:0x%x is not found.\n", activate->windowId); + rdp_debug_error(b, "Client: ClientActivate: WindowId:0x%x is not found.\n", activate->windowId); } b->rdprail_shell_api->request_window_activate(b->rdprail_shell_context, peerCtx->item.seat, surface); } @@ -424,7 +424,7 @@ rail_client_Syscommand_callback(void *arg) surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, syscommand->windowId); if (!surface) { - weston_log("Client: ClientSyscommand: WindowId:0x%x is not found.\n", syscommand->windowId); + rdp_debug_error(b, "Client: ClientSyscommand: WindowId:0x%x is not found.\n", syscommand->windowId); goto Exit; } @@ -588,7 +588,7 @@ rail_client_ClientSysparam_callback(void *arg) to_rdp_head(base_head_iter)->workareaClient = workareaRectClient; } } else { - weston_log("Client: ClientSysparam: workArea isn't belonging to an output\n"); + rdp_debug_error(b, "Client: ClientSysparam: workArea isn't belonging to an output\n"); } } } @@ -627,14 +627,14 @@ rail_client_ClientGetAppidReq_callback(void *arg) surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, getAppidReq->windowId); if (!surface) { - weston_log("Client: ClientGetAppidReq: WindowId:0x%x is not found.\n", getAppidReq->windowId); + rdp_debug_error(b, "Client: ClientGetAppidReq: WindowId:0x%x is not found.\n", getAppidReq->windowId); goto Exit; } pid = b->rdprail_shell_api->get_window_app_id( surface, &appId[0], sizeof(appId)-1, &imageName[0], sizeof(imageName)-1); if (appId[0] == '\0') { - weston_log("Client: ClientGetAppidReq: WindowId:0x%x does not have appId, or not top level window.\n", getAppidReq->windowId); + rdp_debug_error(b, "Client: ClientGetAppidReq: WindowId:0x%x does not have appId, or not top level window.\n", getAppidReq->windowId); goto Exit; } @@ -934,7 +934,7 @@ rail_grfx_client_caps_advertise(RdpgfxServerContext* context, const RDPGFX_CAPS_ break; } default: - weston_log(" Version : UNKNOWN(%d)\n", capsSet->version); + rdp_debug_error(b, " Version : UNKNOWN(%d)\n", capsSet->version); } } @@ -982,12 +982,12 @@ gfxredir_client_graphics_redirection_legacy_caps(GfxRedirServerContext* context, rdp_debug(b, "Client: gfxredir_caps: version:%d\n", redirectionCaps->version); /* This is legacy caps callback, version must be 1 */ if (redirectionCaps->version != GFXREDIR_CHANNEL_VERSION_LEGACY) { - weston_log("Client: gfxredir_caps: invalid version:%d\n", redirectionCaps->version); + rdp_debug_error(b, "Client: gfxredir_caps: invalid version:%d\n", redirectionCaps->version); return ERROR_INTERNAL_ERROR; } /* Legacy version 1 client is not supported, so don't set 'activationGraphicsRedirectionCompleted'. */ - weston_log("Client: gfxredir_caps: version 1 is not supported.\n"); + rdp_debug_error(b, "Client: gfxredir_caps: version 1 is not supported.\n"); return CHANNEL_RC_OK; } @@ -1074,7 +1074,7 @@ gfxredir_client_present_buffer_ack(GfxRedirServerContext* context, const GFXREDI rail_state = (struct weston_surface_rail_state *)surface->backend_state; rail_state->isUpdatePending = FALSE; } else { - weston_log("Client: PresentBufferAck: WindowId:0x%lx is not found.\n", presentAck->windowId); + rdp_debug_error(b, "Client: PresentBufferAck: WindowId:0x%lx is not found.\n", presentAck->windowId); } return CHANNEL_RC_OK; @@ -1091,7 +1091,7 @@ rdp_rail_create_cursor(struct weston_surface *surface) ASSERT_COMPOSITOR_THREAD(b); if (peerCtx->cursorSurface) - weston_log("cursor surface already exists old %p vs new %p\n", peerCtx->cursorSurface, surface); + rdp_debug_error(b, "cursor surface already exists old %p vs new %p\n", peerCtx->cursorSurface, surface); peerCtx->cursorSurface = surface; return 0; } @@ -1169,7 +1169,7 @@ rdp_rail_update_cursor(struct weston_surface *surface) int pointerBitsSize = newClientPos.width*cursorBpp*newClientPos.height; BYTE *pointerBits = malloc(pointerBitsSize); if (!pointerBits) { - weston_log("malloc failed for cursor shape\n"); + rdp_debug_error(b, "malloc failed for cursor shape\n"); return -1; } @@ -1179,7 +1179,7 @@ rdp_rail_update_cursor(struct weston_surface *surface) newClientPos.width, newClientPos.height, 0, 0, contentBufferWidth, contentBufferHeight, true /* y-flip */, true /* is_argb */) < 0) { - weston_log("weston_surface_copy_content failed for cursor shape\n"); + rdp_debug_error(b, "weston_surface_copy_content failed for cursor shape\n"); free(pointerBits); return -1; } @@ -1226,12 +1226,12 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) /* negative width/height is not allowed, allow window to be created with zeros */ if (surface->width < 0 || surface->height < 0) { - weston_log("surface width and height are negative\n"); + rdp_debug_error(b, "surface width and height are negative\n"); return; } if (!b || !b->rdp_peer) { - weston_log("CreateWndow(): rdp_peer is not initalized\n"); + rdp_debug_error(b, "CreateWndow(): rdp_peer is not initalized\n"); return; } @@ -1272,7 +1272,7 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) /* windowId can be assigned only after activation completed */ if (!rdp_id_manager_allocate_id(&peerCtx->windowId, (void*)surface, &window_id)) { rail_state->error = true; - weston_log("CreateWindow(): fail to insert windowId.hash_table (windowId:%d surface:%p.\n", + rdp_debug_error(b, "CreateWindow(): fail to insert windowId.hash_table (windowId:%d surface:%p.\n", window_id, surface); return; } @@ -1536,7 +1536,7 @@ rdp_rail_destroy_window(struct wl_listener *listener, void *data) (peerCtx->currentFrameId != peerCtx->acknowledgedFrameId && !peerCtx->isAcknowledgedSuspended)) { if (++waitRetry > 1000) { // timeout after 10 sec. - weston_log("%s: update is still pending in client side (windowId:0x%x)\n", + rdp_debug_error(b, "%s: update is still pending in client side (windowId:0x%x)\n", __func__, window_id); break; } @@ -1624,7 +1624,7 @@ rdp_rail_schedule_update_window(struct wl_listener *listener, void *data) /* negative width/height is not allowed */ if (surface->width < 0 || surface->height < 0) { - weston_log("surface width and height are negative\n"); + rdp_debug_error(b, "surface width and height are negative\n"); return; } @@ -1693,7 +1693,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter } if (!rail_state->isCursor) { if (strncmp(surface->role_name, "wl_pointer-cursor", sizeof("wl_pointer-cursor")) == 0) { - weston_log("!!!cursor role is added after creation - WindowId:0x%x\n", window_id); + rdp_debug_error(b, "!!!cursor role is added after creation - WindowId:0x%x\n", window_id); /* convert to RDP cursor */ rdp_rail_destroy_window(NULL, (void *)surface); @@ -1702,7 +1702,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rdp_rail_create_window(NULL, (void *)surface); rail_state = (struct weston_surface_rail_state *)surface->backend_state; if (!rail_state || rail_state->window_id == 0) { - weston_log("Fail to convert to RDP cursor - surface:0x%p\n", surface); + rdp_debug_error(b, "Fail to convert to RDP cursor - surface:0x%p\n", surface); return 0; } assert(rail_state->isCursor); @@ -2196,7 +2196,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter copyDamageWidth, copyDamageHeight, damageBox.x1, damageBox.y1, damageWidth, damageHeight, false /* y-flip */, true /* is_argb */) < 0) { - weston_log("weston_surface_copy_content failed for windowId:0x%x\n",window_id); + rdp_debug_error(b, "weston_surface_copy_content failed for windowId:0x%x\n",window_id); return -1; } @@ -2234,7 +2234,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rail_state->isUpdatePending = TRUE; iter_data->isUpdatePending = TRUE; } else { - weston_log("PresentBuffer failed for windowId:0x%x\n",window_id); + rdp_debug_error(b, "PresentBuffer failed for windowId:0x%x\n",window_id); } } else #endif // HAVE_FREERDP_GFXREDIR_H @@ -2251,7 +2251,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter data = malloc(damageSize); if (!data) { // need better handling to avoid leaking surface on host. - weston_log("Couldn't allocate memory for bitmap update.\n"); + rdp_debug_error(b, "Couldn't allocate memory for bitmap update.\n"); return -1; } @@ -2264,7 +2264,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter if (!alpha) { free(data); // need better handling to avoid leaking surface on host. - weston_log("Couldn't allocate memory for alpha update.\n"); + rdp_debug_error(b, "Couldn't allocate memory for alpha update.\n"); return -1; } @@ -2272,7 +2272,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter data, damageSize, 0, 0, 0, damageBox.x1, damageBox.y1, damageWidth, damageHeight, false /* y-flip */, true /* is_argb */) < 0) { - weston_log("weston_surface_copy_content failed for cursor shape\n"); + rdp_debug_error(b, "weston_surface_copy_content failed for cursor shape\n"); free(data); free(alpha); return -1; @@ -2463,7 +2463,7 @@ rdp_insert_window_zorder_array(struct weston_view *view, UINT32 *windowIdArray, !rail_state->is_minimized && !rail_state->is_minimized_requested) { if (iCurrent >= WindowIdArraySize) { - weston_log("%s: more windows in tree than ID manager tracking (%d vs %d)\n", + rdp_debug_error(b, "%s: more windows in tree than ID manager tracking (%d vs %d)\n", __func__, iCurrent, WindowIdArraySize); return UINT_MAX; } @@ -2498,7 +2498,7 @@ rdp_rail_sync_window_zorder(struct weston_compositor *compositor) numWindowId = peerCtx->windowId.id_used + 1; // +1 for marker window. windowIdArray = zalloc(numWindowId * sizeof(UINT32)); if (!windowIdArray) { - weston_log("%s: zalloc(%ld bytes) failed\n", __func__, numWindowId * sizeof(UINT32)); + rdp_debug_error(b, "%s: zalloc(%ld bytes) failed\n", __func__, numWindowId * sizeof(UINT32)); return; } @@ -2611,7 +2611,7 @@ rdp_rail_peer_activate(freerdp_peer* client) /* HiDef requires graphics pipeline to be supported */ if (settings->SupportGraphicsPipeline == FALSE) { if (settings->HiDefRemoteApp) { - weston_log("HiDef remoting is going to be disabled because client doesn't support graphics pipeline\n"); + rdp_debug_error(b, "HiDef remoting is going to be disabled because client doesn't support graphics pipeline\n"); settings->HiDefRemoteApp = FALSE; } } @@ -3227,25 +3227,27 @@ rdp_drdynvc_destroy(RdpPeerContext* context) BOOL rdp_rail_peer_init(freerdp_peer *client, RdpPeerContext *peerCtx) { + struct rdp_backend *b = peerCtx->rdpBackend; + /* RDP window ID must be within 31 bits range. MSB is reserved and exclude 0. */ - if (!rdp_id_manager_init(&peerCtx->windowId, 0x1, 0x7FFFFFFF)) { - weston_log("unable to create windowId.\n"); + if (!rdp_id_manager_init(b, &peerCtx->windowId, 0x1, 0x7FFFFFFF)) { + rdp_debug_error(b, "unable to create windowId.\n"); goto error_return; } /* RDP surface ID must be within 16 bits range, exclude 0. */ - if (!rdp_id_manager_init(&peerCtx->surfaceId, 0x1, 0xFFFF)) { - weston_log("unable to create windowId.\n"); + if (!rdp_id_manager_init(b, &peerCtx->surfaceId, 0x1, 0xFFFF)) { + rdp_debug_error(b, "unable to create windowId.\n"); goto error_return; } #ifdef HAVE_FREERDP_GFXREDIR_H /* RDP pool ID must be within 32 bits range, exclude 0. */ - if (!rdp_id_manager_init(&peerCtx->poolId, 0x1, 0xFFFFFFFF)) { - weston_log("unable to create windowId.\n"); + if (!rdp_id_manager_init(b, &peerCtx->poolId, 0x1, 0xFFFFFFFF)) { + rdp_debug_error(b, "unable to create windowId.\n"); goto error_return; } /* RDP buffer ID must be within 32 bits range, exclude 0. */ - if (!rdp_id_manager_init(&peerCtx->bufferId, 0x1, 0xFFFFFFFF)) { - weston_log("unable to create windowId.\n"); + if (!rdp_id_manager_init(b, &peerCtx->bufferId, 0x1, 0xFFFFFFFF)) { + rdp_debug_error(b, "unable to create windowId.\n"); goto error_return; } #endif // HAVE_FREERDP_GFXREDIR_H @@ -3371,7 +3373,7 @@ rdp_rail_dump_monitor_binding(struct weston_keyboard *keyboard, } err = fclose(fp); assert(err == 0); - weston_log("%s", str); + rdp_debug_error(b, "%s", str); free(str); } } @@ -3520,12 +3522,12 @@ rdp_rail_dump_window_binding(struct weston_keyboard *keyboard, hash_table_for_each(peerCtx->windowId.hash_table, rdp_rail_dump_window_iter, (void*)&context); err = fclose(fp); assert(err == 0); - weston_log("%s", str); + rdp_debug_error(b, "%s", str); free(str); /* print out compositor's scene graph */ str = weston_compositor_print_scene_graph(b->compositor); - weston_log("%s", str); + rdp_debug_error(b, "%s", str); free(str); } } @@ -3572,7 +3574,7 @@ rdp_rail_set_window_icon(struct weston_surface *surface, pixman_image_t *icon) int targetIconHeight; if (!b || !b->rdp_peer) { - weston_log("set_window_icon(): rdp_peer is not initalized\n"); + rdp_debug_error(b, "set_window_icon(): rdp_peer is not initalized\n"); return; } @@ -3744,7 +3746,7 @@ rdp_rail_notify_app_list(void *rdp_backend, struct weston_rdprail_app_list_data RdpPeerContext *peerCtx; if (!b || !b->rdp_peer) { - weston_log("rdp_rail_notify_app_list(): rdp_peer is not initalized\n"); + rdp_debug_error(b, "rdp_rail_notify_app_list(): rdp_peer is not initalized\n"); return false; // return false only when peer is not ready for possible re-send. } @@ -3908,7 +3910,7 @@ rdp_rail_backend_create(struct rdp_backend *b) int ret = weston_plugin_api_register(b->compositor, WESTON_RDPRAIL_API_NAME, &rdprail_api, sizeof(rdprail_api)); if (ret < 0) { - weston_log("Failed to register rdprail API.\n"); + rdp_debug_error(b, "Failed to register rdprail API.\n"); return -1; } @@ -3916,7 +3918,7 @@ rdp_rail_backend_create(struct rdp_backend *b) dlerror(); /* clear error */ b->libFreeRDPServer = dlopen("libfreerdp-server2.so", RTLD_NOW); if (!b->libFreeRDPServer) - weston_log("dlopen(libfreerdp-server2.so) failed with %s\n", dlerror()); + rdp_debug_error(b, "dlopen(libfreerdp-server2.so) failed with %s\n", dlerror()); #endif // defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H)o #ifdef HAVE_FREERDP_RDPAPPLIST_H diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index 1a126871d..931366884 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -128,7 +128,7 @@ rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memor char path[b->shared_memory_mount_path_size + 1 + RDP_SHARED_MEMORY_NAME_SIZE + 1]; if (shared_memory->size <= 0) { - weston_log("%s: invalid size %ld\n", + rdp_debug_error(b, "%s: invalid size %ld\n", __func__, shared_memory->size); goto error_exit; } @@ -139,12 +139,12 @@ rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memor if (shared_memory->name[0] == '\0') { int fd_uuid = open("/proc/sys/kernel/random/uuid", O_RDONLY); if (fd_uuid < 0) { - weston_log("%s: open uuid failed with error %s\n", + rdp_debug_error(b, "%s: open uuid failed with error %s\n", __func__, strerror(errno)); goto error_exit; } if (read(fd_uuid, &shared_memory->name[1], 32 + 4) < 0) { - weston_log("%s: read uuid failed with error %s\n", + rdp_debug_error(b, "%s: read uuid failed with error %s\n", __func__, strerror(errno)); goto error_exit; } @@ -156,7 +156,7 @@ rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memor (shared_memory->name[0] != '{') || (shared_memory->name[RDP_SHARED_MEMORY_NAME_SIZE - 1] != '}') || (shared_memory->name[RDP_SHARED_MEMORY_NAME_SIZE] != '\0')) { - weston_log("%s: name is not in GUID form \"%s\"\n", + rdp_debug_error(b, "%s: name is not in GUID form \"%s\"\n", __func__, shared_memory->name); goto error_exit; } @@ -167,20 +167,20 @@ rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memor fd = open(path, O_CREAT | O_RDWR, 00600); if (fd < 0) { - weston_log("%s: Failed to open \"%s\" with error: %s\n", + rdp_debug_error(b, "%s: Failed to open \"%s\" with error: %s\n", __func__, path, strerror(errno)); goto error_exit; } if (fallocate(fd, 0, 0, shared_memory->size) < 0) { - weston_log("%s: Failed to allocate %d: \"%s\" %ld bytes with error: %s\n", + rdp_debug_error(b, "%s: Failed to allocate %d: \"%s\" %ld bytes with error: %s\n", __func__, fd, path, shared_memory->size, strerror(errno)); goto error_exit; } addr = mmap(NULL, shared_memory->size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) { - weston_log("%s: Failed to mmmap %d: \"%s\" %ld bytes with error: %s\n", + rdp_debug_error(b, "%s: Failed to mmmap %d: \"%s\" %ld bytes with error: %s\n", __func__, fd, path, shared_memory->size, strerror(errno)); goto error_exit; } @@ -222,9 +222,10 @@ rdp_free_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *s } BOOL -rdp_id_manager_init(struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 high_limit) +rdp_id_manager_init(struct rdp_backend *rdp_backend, struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 high_limit) { assert(low_limit < high_limit); + id_manager->rdp_backend = rdp_backend; id_manager->id_total = high_limit - low_limit; id_manager->id_used = 0; id_manager->id_low_limit = low_limit; @@ -232,7 +233,7 @@ rdp_id_manager_init(struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 id_manager->id = low_limit; id_manager->hash_table = hash_table_create(); if (!id_manager->hash_table) - weston_log("%s: unable to create hash_table.\n", __func__); + rdp_debug_error(rdp_backend, "%s: unable to create hash_table.\n", __func__); return id_manager->hash_table != NULL; } @@ -240,7 +241,7 @@ void rdp_id_manager_free(struct rdp_id_manager *id_manager) { if (id_manager->id_used != 0) - weston_log("%s: possible id leak: %d\n", __func__, id_manager->id_used); + rdp_debug_error(id_manager->rdp_backend, "%s: possible id leak: %d\n", __func__, id_manager->id_used); if (id_manager->hash_table) { hash_table_destroy(id_manager->hash_table); id_manager->hash_table = NULL; @@ -250,6 +251,7 @@ rdp_id_manager_free(struct rdp_id_manager *id_manager) id_manager->id_high_limit = 0; id_manager->id_total = 0; id_manager->id_used = 0; + id_manager->rdp_backend = NULL; } BOOL diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index bc9d4f5d5..ada1e8e0b 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -180,10 +180,11 @@ attach_app_list_namespace(struct desktop_shell *shell) assert(false == context->isAppListNamespaceAttached); if (context && context->app_list_pidfd > 0) { assert(context->weston_pidfd > 0); - if (setns(context->app_list_pidfd, 0) == -1) - weston_log("attach_app_list_namespace failed %s\n", strerror(errno)); - else + if (setns(context->app_list_pidfd, 0) == -1) { + shell_rdp_debug_error(shell, "attach_app_list_namespace failed %s\n", strerror(errno)); + } else { context->isAppListNamespaceAttached = true; + } } } @@ -194,7 +195,7 @@ detach_app_list_namespace(struct desktop_shell *shell) if (context && context->weston_pidfd > 0 && context->isAppListNamespaceAttached) { if (setns(context->weston_pidfd, 0) == -1) { /* TODO: when failed to go back, this is fatal, should terminate weston and restart? */ - weston_log("detach_app_list_namespace failed %s\n", strerror(errno)); + shell_rdp_debug_error(shell, "detach_app_list_namespace failed %s\n", strerror(errno)); } else { context->isAppListNamespaceAttached = false; } @@ -696,14 +697,14 @@ app_list_monitor_thread(LPVOID arg) shell_rdp_debug(shell, "app_list_monitor_thread: running in system-distro with user-distro: %s\n", shell->distroName); if (unshare(CLONE_FS) < 0) - weston_log("app_list_monitor_thread: unshare(CLONE_FS) failed %s\n", strerror(errno)); + shell_rdp_debug_error(shell, "app_list_monitor_thread: unshare(CLONE_FS) failed %s\n", strerror(errno)); /* obtain pidfd for current process */ pidfd_path = "/proc/self/ns/mnt"; shell_rdp_debug(shell, "app_list_monitor_thread: open(%s)\n", pidfd_path); context->weston_pidfd = open(pidfd_path, O_RDONLY | O_CLOEXEC); if (context->weston_pidfd < 0) { - weston_log("app_list_monitor_thread: open(%s) failed %s\n", pidfd_path, strerror(errno)); + shell_rdp_debug_error(shell, "app_list_monitor_thread: open(%s) failed %s\n", pidfd_path, strerror(errno)); goto Exit; } @@ -712,7 +713,7 @@ app_list_monitor_thread(LPVOID arg) shell_rdp_debug(shell, "app_list_monitor_thread: open(%s)\n", pidfd_path); context->app_list_pidfd = open(pidfd_path, O_RDONLY | O_CLOEXEC); if (context->app_list_pidfd < 0) { - weston_log("app_list_monitor_thread: open(%s) failed %s\n", pidfd_path, strerror(errno)); + shell_rdp_debug_error(shell, "app_list_monitor_thread: open(%s) failed %s\n", pidfd_path, strerror(errno)); goto Exit; } } else { @@ -730,7 +731,7 @@ app_list_monitor_thread(LPVOID arg) for (int i = 0; i < (int)ARRAY_LENGTH(app_list_folder); i++) { fd[num_watch] = inotify_init(); if (fd[num_watch] < 0) { - weston_log("app_list_monitor_thread: inotify_init[%d] failed %s\n", i, strerror(errno)); + shell_rdp_debug_error(shell, "app_list_monitor_thread: inotify_init[%d] failed %s\n", i, strerror(errno)); continue; } @@ -760,7 +761,7 @@ app_list_monitor_thread(LPVOID arg) shell_rdp_debug(shell, "app_list_monitor_thread: inotify_add_watch(%s)\n", folder); wd[num_watch] = inotify_add_watch(fd[num_watch], folder, IN_CREATE|IN_DELETE|IN_MODIFY|IN_MOVED_TO|IN_MOVED_FROM); if (wd[num_watch] < 0) { - weston_log("app_list_monitor_thread: inotify_add_watch failed: %s\n", strerror(errno)); + shell_rdp_debug_error(shell, "app_list_monitor_thread: inotify_add_watch failed: %s\n", strerror(errno)); detach_app_list_namespace(shell); close(fd[num_watch]); fd[num_watch] = 0; @@ -770,7 +771,7 @@ app_list_monitor_thread(LPVOID arg) events[num_events] = GetFileHandleForFileDescriptor(fd[num_watch]); if (!events[num_events]) { - weston_log("app_list_monitor_thread: GetFileHandleForFileDescriptor failed\n"); + shell_rdp_debug_error(shell, "app_list_monitor_thread: GetFileHandleForFileDescriptor failed\n"); inotify_rm_watch(fd[num_watch], wd[num_watch]); wd[num_watch] = 0; close(fd[num_watch]); diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 46168a15c..c059701be 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -371,7 +371,7 @@ shell_surface_set_window_icon(struct weston_desktop_surface *desktop_surface, format = PIXMAN_a8r8g8b8; break; default: - weston_log("shell_surface_set_window_icon(): unsupported bpp: %d\n", bpp); + shell_rdp_debug_error(shsurf->shell, "shell_surface_set_window_icon(): unsupported bpp: %d\n", bpp); return; } image = pixman_image_create_bits_no_clear(format, @@ -634,7 +634,7 @@ static enum weston_keyboard_modifier get_modifier(char *modifier) { if (!modifier) - return MODIFIER_SUPER; + return 0; // default to no binding-modifier. if (!strcmp("ctrl", modifier)) return MODIFIER_CTRL; @@ -645,7 +645,7 @@ get_modifier(char *modifier) else if (!strcmp("none", modifier)) return 0; else - return MODIFIER_SUPER; + return 0; // default to no binding-modifier. } static void @@ -659,12 +659,14 @@ shell_configuration(struct desktop_shell *shell) section = weston_config_get_section(wet_get_config(shell->compositor), "shell", NULL, NULL); + /* default to not allow zap */ weston_config_section_get_bool(section, - "allow-zap", &allow_zap, true); + "allow-zap", &allow_zap, false); shell->allow_zap = allow_zap; + /* set "none" to default to disable optional key-bindings */ weston_config_section_get_string(section, - "binding-modifier", &s, "super"); + "binding-modifier", &s, "none"); shell->binding_modifier = get_modifier(s); free(s); @@ -2014,7 +2016,8 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, if (wl_client) wl_client_post_no_memory(wl_client); else - weston_log("%s: no memory to allocate shell surface\n", __func__); + shell_rdp_debug(((struct desktop_shell *)shell), + "%s: no memory to allocate shell surface\n", __func__); return; } @@ -2437,7 +2440,7 @@ desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, geometry won't be adjusted to relative to parent. */ shsurf->parent = shsurf_parent; } else { - weston_log("RDP shell: parent is not toplevel surface\n"); + shell_rdp_debug_error(shsurf->shell, "RDP shell: parent is not toplevel surface\n"); wl_list_init(&shsurf->children_link); shsurf->parent = NULL; } @@ -2863,6 +2866,7 @@ move_binding(struct weston_pointer *pointer, const struct timespec *time, weston_desktop_surface_get_maximized(shsurf->desktop_surface)) return; + shell_rdp_debug_verbose(shsurf->shell, "%s\n", __func__); surface_move(shsurf, pointer, false); } @@ -2882,6 +2886,7 @@ maximize_binding(struct weston_keyboard *keyboard, const struct timespec *time, if (shsurf == NULL) return; + shell_rdp_debug_verbose(shsurf->shell, "%s\n", __func__); set_maximized(shsurf, !weston_desktop_surface_get_maximized(shsurf->desktop_surface)); } @@ -2905,6 +2910,7 @@ fullscreen_binding(struct weston_keyboard *keyboard, fullscreen = weston_desktop_surface_get_fullscreen(shsurf->desktop_surface); + shell_rdp_debug_verbose(shsurf->shell, "%s: fullscreen:%d\n", __func__, !fullscreen); set_fullscreen(shsurf, !fullscreen, NULL); } @@ -2976,6 +2982,7 @@ resize_binding(struct weston_pointer *pointer, const struct timespec *time, else edges |= WL_SHELL_SURFACE_RESIZE_BOTTOM; + shell_rdp_debug_verbose(shsurf->shell, "%s edges:%x\n", __func__, edges); surface_resize(shsurf, pointer, edges); } @@ -3005,76 +3012,12 @@ surface_opacity_binding(struct weston_pointer *pointer, shsurf->view->alpha = 1.0; if (shsurf->view->alpha < step) shsurf->view->alpha = step; + shell_rdp_debug_verbose(shsurf->shell, "%s alpha:%f\n", __func__, shsurf->view->alpha); weston_view_geometry_dirty(shsurf->view); weston_surface_damage(surface); } -static void -do_zoom(struct weston_seat *seat, const struct timespec *time, uint32_t key, - uint32_t axis, double value) -{ - struct weston_compositor *compositor = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_output *output; - float increment; - - if (!pointer) { - weston_log("Zoom hotkey pressed but seat '%s' contains no pointer.\n", seat->seat_name); - return; - } - - wl_list_for_each(output, &compositor->output_list, link) { - if (pixman_region32_contains_point(&output->region, - wl_fixed_to_double(pointer->x), - wl_fixed_to_double(pointer->y), - NULL)) { - if (key == KEY_PAGEUP) - increment = output->zoom.increment; - else if (key == KEY_PAGEDOWN) - increment = -output->zoom.increment; - else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) - /* For every pixel zoom 20th of a step */ - increment = output->zoom.increment * - -value / 20.0; - else - increment = 0; - - output->zoom.level += increment; - - if (output->zoom.level < 0.0) - output->zoom.level = 0.0; - else if (output->zoom.level > output->zoom.max_level) - output->zoom.level = output->zoom.max_level; - - if (!output->zoom.active) { - if (output->zoom.level <= 0.0) - continue; - weston_output_activate_zoom(output, seat); - } - - output->zoom.spring_z.target = output->zoom.level; - - weston_output_update_zoom(output); - } - } -} - -static void -zoom_axis_binding(struct weston_pointer *pointer, const struct timespec *time, - struct weston_pointer_axis_event *event, - void *data) -{ - do_zoom(pointer->seat, time, 0, event->axis, event->value); -} - -static void -zoom_key_binding(struct weston_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - do_zoom(keyboard->seat, time, key, 0, 0); -} - static void terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) @@ -3233,6 +3176,9 @@ surface_rotate(struct shell_surface *shsurf, struct weston_pointer *pointer) pointer, WESTON_DESKTOP_SHELL_CURSOR_ARROW); } +/* +//TODO: while RAIL can't do arbirary rotation, but can do 0,90,180,270 degree rotation +// maybe it can have a new cap for that ? static void rotate_binding(struct weston_pointer *pointer, const struct timespec *time, uint32_t button, void *data) @@ -3256,8 +3202,10 @@ rotate_binding(struct weston_pointer *pointer, const struct timespec *time, weston_desktop_surface_get_maximized(surface->desktop_surface)) return; + shell_rdp_debug(surface->shell, "%s\n", __func__); surface_rotate(surface, pointer); } +*/ /* Move all fullscreen layers down to the current workspace and hide their * black views. The surfaces' state is set to both fullscreen and lowered, @@ -3886,20 +3834,10 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) if (!mod) return; - /* This binding is not configurable, but is only enabled if there is a - * valid binding modifier. */ weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, - MODIFIER_SUPER | MODIFIER_ALT, + mod | MODIFIER_ALT, surface_opacity_binding, NULL); - weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, - mod, zoom_axis_binding, - NULL); - - weston_compositor_add_key_binding(ec, KEY_PAGEUP, mod, - zoom_key_binding, NULL); - weston_compositor_add_key_binding(ec, KEY_PAGEDOWN, mod, - zoom_key_binding, NULL); weston_compositor_add_key_binding(ec, KEY_M, mod | MODIFIER_SHIFT, maximize_binding, NULL); weston_compositor_add_key_binding(ec, KEY_F, mod | MODIFIER_SHIFT, @@ -3913,12 +3851,14 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) mod | MODIFIER_SHIFT, resize_binding, shell); - if (ec->capabilities & WESTON_CAP_ROTATION_ANY) - weston_compositor_add_button_binding(ec, BTN_MIDDLE, mod, - rotate_binding, NULL); + //TODO: while RAIL can't do arbirary rotation, but can do 0,90,180,270 degree rotation + // maybe it can have a new cap for that ? + //if (ec->capabilities & WESTON_CAP_ROTATION_ANY) + // weston_compositor_add_button_binding(ec, BTN_MIDDLE, mod, + // rotate_binding, NULL); weston_compositor_add_key_binding(ec, KEY_K, mod, - force_kill_binding, shell); + force_kill_binding, shell); weston_install_debug_key_binding(ec, mod); } @@ -4158,34 +4098,34 @@ wet_shell_init(struct weston_compositor *ec, shell->compositor = ec; + if (!weston_compositor_add_destroy_listener_once(ec, + &shell->destroy_listener, + shell_destroy)) { + free(shell); + return 0; + } + shell->debug = weston_log_ctx_add_log_scope(ec->weston_log_ctx, "rdprail-shell", "Debug messages from RDP-RAIL shell\n", NULL, NULL, NULL); - debug_level = getenv("WESTON_RDPRAIL_SHELL_DEBUG_LEVEL"); - if (debug_level) { - shell->debugLevel = atoi(debug_level); - if (shell->debugLevel > RDPRAIL_SHELL_DEBUG_LEVEL_VERBOSE) - shell->debugLevel = RDPRAIL_SHELL_DEBUG_LEVEL_VERBOSE; - } else { - shell->debugLevel = RDPRAIL_SHELL_DEBUG_LEVEL_DEFAULT; + if (shell->debug) { + debug_level = getenv("WESTON_RDPRAIL_SHELL_DEBUG_LEVEL"); + if (debug_level) { + shell->debugLevel = atoi(debug_level); + if (shell->debugLevel > RDPRAIL_SHELL_DEBUG_LEVEL_VERBOSE) + shell->debugLevel = RDPRAIL_SHELL_DEBUG_LEVEL_VERBOSE; + } else { + shell->debugLevel = RDPRAIL_SHELL_DEBUG_LEVEL_DEFAULT; + } } weston_log("RDPRAIL-shell: WESTON_RDPRAIL_SHELL_DEBUG_LEVEL: %d.\n", shell->debugLevel); - /* this make sure rdprail-shell to be used with only backend-rdp */ shell->rdprail_api = weston_rdprail_get_api(ec); if (!shell->rdprail_api) { - weston_log("Failed to obrain rdprail API.\n"); - free(shell); - return 0; - } - - if (!weston_compositor_add_destroy_listener_once(ec, - &shell->destroy_listener, - shell_destroy)) { - free(shell); - return 0; + shell_rdp_debug_error(shell, "Failed to obrain rdprail API.\n"); + return -1; } shell_configuration(shell); @@ -4251,24 +4191,32 @@ wet_shell_init(struct weston_compositor *ec, shell->is_appid_with_distro_name = false; else shell->is_appid_with_distro_name = true; + shell_rdp_debug(shell, "WESTON_RDPRAIL_SHELL_DISABLE_APPEND_DISTRONAME_STARTMEN:%d\n", + shell->is_appid_with_distro_name); icon_path = getenv("WSL2_DEFAULT_APP_ICON"); if (icon_path && (strcmp(icon_path, "disabled") != 0)) shell->image_default_app_icon = load_image(icon_path); + shell_rdp_debug(shell, "WSL2_DEFAULT_APP_ICON:%s\n", icon_path); icon_path = getenv("WSL2_DEFAULT_APP_OVERLAY_ICON"); if (icon_path && (strcmp(icon_path, "disabled") != 0)) shell->image_default_app_overlay_icon = load_image(icon_path); + shell_rdp_debug(shell, "WSL2_DEFAULT_APP_OVERLAY_ICON:%s\n", icon_path); if (getenv("WESTON_RDPRAIL_SHELL_DISABLE_BLEND_OVERLAY_ICON_TASKBAR")) shell->is_blend_overlay_icon_taskbar = false; else shell->is_blend_overlay_icon_taskbar = true; + shell_rdp_debug(shell, "WESTON_RDPRAIL_SHELL_DISABLE_BLEND_OVERLAY_ICON_TASKBAR:%d\n", + shell->is_blend_overlay_icon_taskbar); if (getenv("WESTON_RDPRAIL_SHELL_DISABLE_BLEND_OVERLAY_ICON_APPLIST")) shell->is_blend_overlay_icon_app_list = false; else shell->is_blend_overlay_icon_app_list = true; + shell_rdp_debug(shell, "WESTON_RDPRAIL_SHELL_DISABLE_BLEND_OVERLAY_ICON_APPLIST:%d\n", + shell->is_blend_overlay_icon_app_list); if (shell->rdprail_api->shell_initialize_notify) shell->rdp_backend = shell->rdprail_api->shell_initialize_notify(ec, &rdprail_shell_api, (void*)shell, shell->distroName); diff --git a/rdprail-shell/shell.h b/rdprail-shell/shell.h index 366ed76dc..e8508a837 100644 --- a/rdprail-shell/shell.h +++ b/rdprail-shell/shell.h @@ -49,6 +49,9 @@ #define shell_rdp_debug(b, ...) \ if (b->debugLevel >= RDPRAIL_SHELL_DEBUG_LEVEL_INFO) \ shell_rdp_debug_print(b->debug, false, __VA_ARGS__) +#define shell_rdp_debug_error(b, ...) \ + if (b->debugLevel >= RDPRAIL_SHELL_DEBUG_LEVEL_ERR) \ + shell_rdp_debug_print(b->debug, false, __VA_ARGS__) #define is_system_distro() (getenv("WSL2_VM_ID") != NULL) From 40ea46493b1fdb5a3696752442ee23cd1c8b42e2 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 25 May 2021 06:24:58 -0700 Subject: [PATCH 1467/1642] fix multiple monitor coordinate translation in WSLg (#16) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdpdisp.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index a4fffd15b..de78b26e2 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -483,14 +483,18 @@ disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_mon monitorMode[i].rectWeston.y = offsetFromOriginWeston; offsetFromOriginWeston += monitorMode[i].rectWeston.height; } + assert(monitorMode[i].rectWeston.x >= 0); + assert(monitorMode[i].rectWeston.y >= 0); } } else { /* no scaling is used or monitor placement is too complex to scale in weston space, fallback to 1.0f */ for (i = 0; i < monitorCount; i++) { monitorMode[i].rectWeston.width = monitorMode[i].monitorDef.width; monitorMode[i].rectWeston.height = monitorMode[i].monitorDef.height; - monitorMode[i].rectWeston.x = monitorMode[i].monitorDef.x + abs(monitorMode[0].monitorDef.x); - monitorMode[i].rectWeston.y = monitorMode[i].monitorDef.y + abs(monitorMode[0].monitorDef.y); + monitorMode[i].rectWeston.x = monitorMode[i].monitorDef.x + abs(upperLeftX); + monitorMode[i].rectWeston.y = monitorMode[i].monitorDef.y + abs(upperLeftY); + assert(monitorMode[i].rectWeston.x >= 0); + assert(monitorMode[i].rectWeston.y >= 0); monitorMode[i].scale = 1; monitorMode[i].clientScale = 1.0f; } From ddc6979cd9d4518aa7a265ee8ad21a112621f73a Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 25 May 2021 14:06:18 -0700 Subject: [PATCH 1468/1642] fix android emulator window is not movable when no frame (#17) Co-authored-by: Hideyuki Nagase --- include/libweston-desktop/libweston-desktop.h | 6 +++ libweston-desktop/internal.h | 5 ++ libweston-desktop/libweston-desktop.c | 10 ++++ libweston-desktop/xwayland.c | 48 ++++++++--------- rdprail-shell/shell.c | 40 ++++++++++++++- xwayland/window-manager.c | 51 ++++++++++++++----- xwayland/xwayland-internal-interface.h | 4 +- 7 files changed, 122 insertions(+), 42 deletions(-) diff --git a/include/libweston-desktop/libweston-desktop.h b/include/libweston-desktop/libweston-desktop.h index a63249372..47295cf16 100644 --- a/include/libweston-desktop/libweston-desktop.h +++ b/include/libweston-desktop/libweston-desktop.h @@ -117,6 +117,12 @@ struct weston_desktop_api { */ void (*set_xwayland_position)(struct weston_desktop_surface *surface, int32_t x, int32_t y, void *user_data); + /* + * In contrast to above set_xwayland_position(), move_xwayland_position() + * to be used to move window after mapped. + */ + void (*move_xwayland_position)(struct weston_desktop_surface *surface, + int32_t x, int32_t y, void *user_data); }; void diff --git a/libweston-desktop/internal.h b/libweston-desktop/internal.h index 67172766c..26ff30c93 100644 --- a/libweston-desktop/internal.h +++ b/libweston-desktop/internal.h @@ -91,6 +91,11 @@ weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, struct weston_desktop_surface *surface, int32_t x, int32_t y); +void +weston_desktop_api_move_xwayland_position(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t x, int32_t y); + struct weston_desktop_seat * weston_desktop_seat_from_seat(struct weston_seat *wseat); diff --git a/libweston-desktop/libweston-desktop.c b/libweston-desktop/libweston-desktop.c index d1d0f2011..c72039211 100644 --- a/libweston-desktop/libweston-desktop.c +++ b/libweston-desktop/libweston-desktop.c @@ -262,3 +262,13 @@ weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, desktop->api.set_xwayland_position(surface, x, y, desktop->user_data); } + +void +weston_desktop_api_move_xwayland_position(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t x, int32_t y) +{ + if (desktop->api.move_xwayland_position != NULL) + desktop->api.move_xwayland_position(surface, x, y, + desktop->user_data); +} diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index cc93fc05b..14b883ea0 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -328,6 +328,29 @@ set_xwayland(struct weston_desktop_xwayland_surface *surface, int x, int y) weston_view_set_position(surface->view, x, y); } +static void +move_position(struct weston_desktop_xwayland_surface *surface, int x, int y) +{ + if (surface->state == XWAYLAND) { + /* For XWAYLAND surface, here directly set view position, + just like set_xwayland() when view is associated. */ + if (surface->view) + weston_view_set_position(surface->view, x, y); + } else if (surface->state == TOPLEVEL) { + weston_desktop_api_move_xwayland_position(surface->desktop, + surface->surface, x, y); + } +#ifdef WM_DEBUG + weston_log("%s: %s window (%p) move to (%d,%d)\n", + __func__, + (surface->state == XWAYLAND) ? "XWAYLAND" : \ + (surface->state == TOPLEVEL) ? "TOPLEVEL" : \ + (surface->state == MAXIMIZED) ? "MAXIMIZED" : \ + (surface->state == FULLSCREEN) ? "FULLSCREEN" : "UNKNOWN", + surface, x, y); +#endif +} + static int move(struct weston_desktop_xwayland_surface *surface, struct weston_pointer *pointer) @@ -375,29 +398,6 @@ set_window_geometry(struct weston_desktop_xwayland_surface *surface, } } -static void -set_position(struct weston_desktop_xwayland_surface *surface, - int x, int y, int width, int height) -{ - if (surface->state == XWAYLAND) { - /* For XWAYLAND surface, here directly set view position, - just like set_xwayland() when view is associated. */ - if (surface->view) - weston_view_set_position(surface->view, x, y); - } else { - /* TODO what to do these? need a way to move shell surface! */ - } -#ifdef WM_DEBUG - weston_log("%s: %s window (%p) move to (%d,%d)\n", - __func__, - (surface->state == XWAYLAND) ? "XWAYLAND" : \ - (surface->state == TOPLEVEL) ? "TOPLEVEL" : \ - (surface->state == MAXIMIZED) ? "MAXIMIZED" : \ - (surface->state == FULLSCREEN) ? "FULLSCREEN" : "UNKNOWN", - surface, x, y); -#endif -} - static void set_maximized(struct weston_desktop_xwayland_surface *surface) { @@ -436,7 +436,7 @@ static const struct weston_desktop_xwayland_interface weston_desktop_xwayland_in .set_transient = set_transient, .set_fullscreen = set_fullscreen, .set_xwayland = set_xwayland, - .set_position = set_position, + .move_position = move_position, .move = move, .resize = resize, .set_title = set_title, diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index c059701be..6f1db1bab 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -2138,8 +2138,8 @@ set_position_from_xwayland(struct shell_surface *shsurf) /* Use xwayland position as this is the X app's origin of client area */ if (shsurf->xwayland.x >= area.x && shsurf->xwayland.y >= area.y && - shsurf->xwayland.x <= (int32_t)(area.x + area.width - (area.width / 10)) && - shsurf->xwayland.y <= (int32_t)(area.y + area.width - (area.width / 10))) { + shsurf->xwayland.x < (int32_t)(area.x + area.width - (area.width / 10)) && + shsurf->xwayland.y < (int32_t)(area.y + area.height - (area.height / 10))) { weston_view_set_position(shsurf->view, x, y); @@ -2808,6 +2808,41 @@ desktop_surface_set_xwayland_position(struct weston_desktop_surface *surface, shsurf->xwayland.is_set = true; } +static void +desktop_surface_move_xwayland_position(struct weston_desktop_surface *desktop_surface, + int32_t x, int32_t y, void *shell_) +{ + struct weston_surface *surface = + weston_desktop_surface_get_surface(desktop_surface); + struct shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct desktop_shell *shell = shsurf->shell; + const struct weston_xwayland_surface_api *api; + + assert(shell == shell_); + + api = shell->xwayland_surface_api; + if (!api) { + api = weston_xwayland_surface_get_api(shell->compositor); + shell->xwayland_surface_api = api; + } + if (api && api->is_xwayland_surface(surface)) { + /* TODO: Make sure the position given from xwayland is a part of workarea, + But this is not simple, for example, app can have accompanying + window which move along with other main window, in such case, + often, it's totally fine the accompanying goes out of workarea. */ + + weston_view_set_position(shsurf->view, x, y); + weston_compositor_schedule_repaint(shell->compositor); + + shell_rdp_debug_verbose(shell, "%s: surface:%p, position (%d,%d)\n", + __func__, surface, x, y); + } else { + shell_rdp_debug_error(shell, "%s: surface:%p is not from xwayland\n", + __func__, surface); + } +} + static const struct weston_desktop_api shell_desktop_api = { .struct_size = sizeof(struct weston_desktop_api), .surface_added = desktop_surface_added, @@ -2822,6 +2857,7 @@ static const struct weston_desktop_api shell_desktop_api = { .ping_timeout = desktop_surface_ping_timeout, .pong = desktop_surface_pong, .set_xwayland_position = desktop_surface_set_xwayland_position, + .move_xwayland_position = desktop_surface_move_xwayland_position, .set_window_icon = desktop_surface_set_window_icon, }; diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index eb82144f2..407404288 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -157,6 +157,8 @@ struct weston_wm_window { int x; int y; bool pos_dirty; + int frame_x; + int frame_y; int map_request_x; int map_request_y; struct weston_output_weak_ref legacy_fullscreen_output; @@ -757,6 +759,7 @@ weston_wm_window_send_configure_notify(struct weston_wm_window *window) { xcb_configure_notify_event_t configure_notify; struct weston_wm *wm = window->wm; + bool is_our_resource = our_resource(wm, window->id); int x, y; weston_wm_window_get_child_position(window, &x, &y); @@ -776,13 +779,18 @@ weston_wm_window_send_configure_notify(struct weston_wm_window *window) xcb_send_event(wm->conn, 0, window->id, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *) &configure_notify); + + wm_printf(wm, "XWM: send_configure_notify (window %d) %d,%d @ %dx%d%s\n", + window->id, x, y, window->width, window->height, + is_our_resource ? ", ours" : ""); } static void -weston_wm_window_send_event_configure_notify_window_position(struct weston_wm_window *window, int x, int y) +weston_wm_window_send_event_configure_notify_with_position(struct weston_wm_window *window, int x, int y) { xcb_configure_notify_event_t configure_notify; struct weston_wm *wm = window->wm; + bool is_our_resource = our_resource(wm, window->id); configure_notify.response_type = XCB_CONFIGURE_NOTIFY; configure_notify.pad0 = 0; @@ -800,6 +808,10 @@ weston_wm_window_send_event_configure_notify_window_position(struct weston_wm_wi xcb_send_event(wm->conn, 0, window->id, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *) &configure_notify); + + wm_printf(wm, "XWM: send_event_configure_notify_window_position (window %d) %d,%d @ %dx%d%s\n", + window->id, x, y, window->width, window->height, + is_our_resource ? ", ours" : ""); } static void @@ -1018,8 +1030,16 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve if (!wm_lookup_window(wm, configure_notify->window, &window)) return; - window->x = configure_notify->x; - window->y = configure_notify->y; + if (window->override_redirect || is_our_resource) { + /* override or frame */ + window->frame_x = configure_notify->x; + window->frame_y = configure_notify->y; + } + if (window->override_redirect || !is_our_resource) { + /* override or not frame */ + window->x = configure_notify->x; + window->y = configure_notify->y; + } window->pos_dirty = false; if (window->override_redirect) { @@ -1037,9 +1057,8 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve window->x, window->y); } else if (is_our_resource) { if (window->shsurf) - xwayland_api->set_position(window->shsurf, - window->x, window->y, - window->width, window->height); + xwayland_api->move_position(window->shsurf, + window->frame_x, window->frame_y); } } @@ -1755,6 +1774,8 @@ weston_wm_window_create(struct weston_wm *wm, window->x = x; window->y = y; window->pos_dirty = false; + window->frame_x = INT_MIN; + window->frame_y = INT_MIN; window->map_request_x = INT_MIN; /* out of range for valid positions */ window->map_request_y = INT_MIN; /* out of range for valid positions */ weston_output_weak_ref_init(&window->legacy_fullscreen_output); @@ -3068,24 +3089,26 @@ send_position(struct weston_surface *surface, int32_t x, int32_t y) { struct weston_wm_window *window = get_wm_window(surface); struct weston_wm *wm; - uint32_t values[2]; - uint16_t mask; int dx, dy; if (!window || !window->wm) return; wm = window->wm; + + wm_printf(wm, "XWM: send_position (window %d) input %d,%d frame %d,%d window %d,%d%s\n", + window->id, x, y, + window->frame_x, window->frame_y, + window->x, window->y, + window->override_redirect ? ", override" : ""); + /* We use pos_dirty to tell whether a configure message is in flight. * This is needed in case we send two configure events in a very * short time, since window->x/y is set in after a roundtrip, hence * we cannot just check if the current x and y are different. */ - if (window->x != x || window->y != y || window->pos_dirty) { + if (window->frame_x != x || window->frame_y != y || window->pos_dirty) { window->pos_dirty = true; - values[0] = x; - values[1] = y; - mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; - weston_wm_configure_window(wm, window->frame_id, mask, values); + weston_wm_window_configure_frame_with_position(window, x, y); // !!! need further investigation !!! /* Xwayland reparents app's window with our own frame window @@ -3097,7 +3120,7 @@ send_position(struct weston_surface *surface, int32_t x, int32_t y) event to let application knows actual app's window position (rather than offset from parent/frame window */ weston_wm_window_get_child_position(window, &dx, &dy); - weston_wm_window_send_event_configure_notify_window_position(window, x + dx, y + dy); + weston_wm_window_send_event_configure_notify_with_position(window, x + dx, y + dy); xcb_flush(wm->conn); } diff --git a/xwayland/xwayland-internal-interface.h b/xwayland/xwayland-internal-interface.h index bf7ed7705..88d153fd5 100644 --- a/xwayland/xwayland-internal-interface.h +++ b/xwayland/xwayland-internal-interface.h @@ -48,8 +48,8 @@ struct weston_desktop_xwayland_interface { struct weston_output *output); void (*set_xwayland)(struct weston_desktop_xwayland_surface *shsurf, int x, int y); - void (*set_position)(struct weston_desktop_xwayland_surface *shsurf, - int x, int y, int width, int height); + void (*move_position)(struct weston_desktop_xwayland_surface *shsurf, + int x, int y); int (*move)(struct weston_desktop_xwayland_surface *shsurf, struct weston_pointer *pointer); int (*resize)(struct weston_desktop_xwayland_surface *shsurf, From d91a6e3f92d40f22ece7194e45af85029d558b12 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 28 May 2021 09:15:12 -0700 Subject: [PATCH 1469/1642] support non-ascii text in RDP clipboard (#18) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdpclip.c | 85 +++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index bad7e5511..25cbd5844 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -79,7 +79,7 @@ static void *clipboard_process_bmp(struct rdp_clipboard_data_source *, BOOL); static void *clipboard_process_html(struct rdp_clipboard_data_source *, BOOL); struct rdp_clipboard_supported_format clipboard_supported_formats[] = { - { 0, CF_TEXT, NULL, "text/plain;charset=utf-8", clipboard_process_text }, + { 0, CF_UNICODETEXT, NULL, "text/plain;charset=utf-8", clipboard_process_text }, { 1, CF_DIB, NULL, "image/bmp", clipboard_process_bmp }, { 2, CF_PRIVATE_RTF, "Rich Text Format", "text/rtf", clipboard_process_text }, // same as text { 3, CF_PRIVATE_HTML, "HTML Format", "text/html", clipboard_process_html }, @@ -152,24 +152,74 @@ clipboard_process_text(struct rdp_clipboard_data_source *source, BOOL is_send) freerdp_peer *client = (freerdp_peer*)source->context; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; + struct wl_array data_contents; + + wl_array_init(&data_contents); if (!source->is_data_processed) { if (is_send) { - /* Linux to Windows */ - assert(source->data_contents.size <= source->data_contents.alloc); - assert(((char*)source->data_contents.data)[source->data_contents.size] == '\0'); + /* Linux to Windows (convert utf-8 to UNICODE) */ /* Include terminating NULL in size */ + assert((source->data_contents.size + 1) <= source->data_contents.alloc); + assert(((char*)source->data_contents.data)[source->data_contents.size] == '\0'); source->data_contents.size++; + + /* obtain size in UNICODE */ + size_t data_size = MultiByteToWideChar(CP_UTF8, 0, + (char*)source->data_contents.data, + source->data_contents.size, + NULL, 0); + if (data_size < 1) + goto error_return; + + data_size *= 2; // convert to size in bytes. + if (!wl_array_add(&data_contents, data_size)) + goto error_return; + + /* convert to UNICODE */ + size_t data_size_in_char = MultiByteToWideChar(CP_UTF8, 0, + (char*)source->data_contents.data, + source->data_contents.size, + (LPWSTR)data_contents.data, + data_size); + assert(data_contents.size == (data_size_in_char * 2)); } else { - /* Windows to Linux */ - char *data = (char*)source->data_contents.data; - size_t data_size = source->data_contents.size; + /* Windows to Linux (UNICODE to utf-8) */ + LPWSTR data = (LPWSTR)source->data_contents.data; + size_t data_size_in_char = source->data_contents.size / 2; /* Windows's data has trailing chars, which Linux doesn't expect. */ - while(data_size && ((data[data_size-1] == '\0') || (data[data_size-1] == '\n'))) - data_size -= 1; - source->data_contents.size = data_size; + while(data_size_in_char && + ((data[data_size_in_char-1] == L'\0') || (data[data_size_in_char-1] == L'\n'))) + data_size_in_char -= 1; + if (!data_size_in_char) + goto error_return; + + /* obtain size in utf-8 */ + size_t data_size = WideCharToMultiByte(CP_UTF8, 0, + (LPCWSTR)source->data_contents.data, + data_size_in_char, + NULL, 0, + NULL, NULL); + if (data_size < 1) + goto error_return; + + if (!wl_array_add(&data_contents, data_size)) + goto error_return; + + /* convert to utf-8 */ + data_size = WideCharToMultiByte(CP_UTF8, 0, + (LPCWSTR)source->data_contents.data, + data_size_in_char, + (char*)data_contents.data, + data_size, + NULL, NULL); + assert(data_contents.size == data_size); } + + /* swap the data_contents with new one */ + wl_array_release(&source->data_contents); + source->data_contents = data_contents; source->is_data_processed = TRUE; } @@ -178,6 +228,21 @@ clipboard_process_text(struct rdp_clipboard_data_source *source, BOOL is_send) is_send ? "send" : "receive", (UINT32)source->data_contents.size); return source->data_contents.data; + +error_return: + + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "RDP %s FAILED (%p:%s): %s (%d bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source->state), + is_send ? "send" : "receive", (UINT32)source->data_contents.size); + //rdp_debug_clipboard_verbose(b, "RDP clipboard_process_html FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", + // source, is_send ? "send" : "receive", + // (char *)source->data_contents.data, + // (UINT32)source->data_contents.size); + + wl_array_release(&data_contents); + + return NULL; } /* based off sample code at https://docs.microsoft.com/en-us/troubleshoot/cpp/add-html-code-clipboard From 2731cc96b4a6521d6ee0b58947b23b8df474ed60 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 28 May 2021 12:17:11 -0700 Subject: [PATCH 1470/1642] fix initial window position by shell is over-written by configure_notify (#19) Co-authored-by: Hideyuki Nagase --- xwayland/window-manager.c | 65 +++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 407404288..759ec8bf1 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -903,7 +903,7 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev (xcb_configure_request_event_t *) event; struct weston_wm_window *window; uint32_t values[16]; - uint16_t mask; + uint16_t mask = 0; int x, y, configure_x, configure_y; int i = 0; bool is_our_resource = our_resource(wm, configure_request->window); @@ -953,34 +953,21 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev /* don't send x/y when frame (parent window) is not created yet, unless this is frame itself. Since only after frame is created, the app's window position will become relative to parent (frame). */ - if (is_our_resource || window->frame || window->override_redirect) { - if (is_our_resource || (window->frame == NULL)) { - /* window is frame window or no frame */ - values[i++] = configure_x; - values[i++] = configure_y; - } else { - /* window is app's window with frame as parent or override */ - values[i++] = x; // relative from frame. - values[i++] = y; // relative from frame. - /* and configure has differnt position than current position, - move frame, so app contents will move, too */ - if ((configure_x != window->x) || (configure_y != window->y)) - configure_frame_position = true; - } - values[i++] = window->width; - values[i++] = window->height; - values[i++] = 0; - mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | - XCB_CONFIG_WINDOW_BORDER_WIDTH; - } else { - /* app's window hasn't been reparented to frame yet */ - values[i++] = window->width; - values[i++] = window->height; - values[i++] = 0; - mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | - XCB_CONFIG_WINDOW_BORDER_WIDTH; - } + if (window->frame || window->override_redirect) { + /* window is app's window has frame as parent, or override. */ + values[i++] = x; // relative from frame. + values[i++] = y; // relative from frame. + mask |= XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; + /* and if configure has differnt position than current position, + move its frame (if it has frame), so app contents will move, too */ + if (window->frame && ((configure_x != window->x) || (configure_y != window->y))) + configure_frame_position = true; + } + values[i++] = window->width; + values[i++] = window->height; + values[i++] = 0; + mask |= XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | + XCB_CONFIG_WINDOW_BORDER_WIDTH; if (configure_request->value_mask & XCB_CONFIG_WINDOW_SIBLING) { values[i++] = configure_request->sibling; mask |= XCB_CONFIG_WINDOW_SIBLING; @@ -1027,6 +1014,24 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve configure_notify->override_redirect ? ", override" : "", is_our_resource ? ", ours" : ""); + /* Certain application (such as nedit) sends configure_notify with the + window position where window is created (see weston_wm_window_create_frame). + But there is race condition between initial position set by shell, + the initial position set by shell is over-writen by with position + from xcb_create_window (for frame). + Previously, toplevel window was not movable by configure_notify, but + with move_position API, it can be moved. Thus, xcb_create_window is + for frame is now called with SHRT_MIN (previously it was 0, now changed to + something explicit, SHRT_SHORT), and configure with that value for frame + is ignored here. */ + if (is_our_resource && + configure_notify->x == SHRT_MIN && + configure_notify->y == SHRT_MIN) { + wm_printf(wm, "XCB_CONFIGURE_NOTIFY (window %d) is ignored\n", + configure_notify->window); + return; + } + if (!wm_lookup_window(wm, configure_notify->window, &window)) return; @@ -1300,7 +1305,7 @@ weston_wm_window_create_frame(struct weston_wm_window *window) 32, window->frame_id, wm->screen->root, - 0, 0, + SHRT_MIN, SHRT_MIN, /* see XCB_CONFIGURE_NOTIFY */ width, height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, From c3a84bbd7fe60150ba0d8274c3eca4434f71c6e5 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 1 Jun 2021 10:57:48 -0700 Subject: [PATCH 1471/1642] fix xterm geometry option doesn't work (#20) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdprail.c | 6 ++++++ xwayland/window-manager.c | 17 ++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index cfad94c2c..0eb72615e 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -582,6 +582,12 @@ rail_client_ClientSysparam_callback(void *arg) &workareaRect.x, &workareaRect.y, &workareaRect.width, &workareaRect.height); if (base_output) { + rdp_debug(b, "Translated workarea:(%d,%d)-(%d,%d) at %s:(%d,%d)-(%d,%d)\n", + workareaRect.x, workareaRect.y, + workareaRect.x + workareaRect.width, workareaRect.y + workareaRect.height, + base_output->name, + base_output->x, base_output->y, + base_output->x + base_output->width, base_output->y + base_output->height); b->rdprail_shell_api->set_desktop_workarea(base_output, b->rdprail_shell_context, &workareaRect); wl_list_for_each(base_head_iter, &base_output->head_list, output_link) { to_rdp_head(base_head_iter)->workarea = workareaRect; diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 759ec8bf1..43652f39a 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -978,12 +978,23 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev } weston_wm_configure_window(wm, window->id, mask, values); - if (configure_frame_position) + if (window->frame == NULL && !window->override_redirect) { + /* must not mapped yet */ + assert(!window->shsurf); + /* if frame is not created yet, or not override window, + save window position, so it's captured at map, + weston_wm_handle_map_request() for map_request_x/y. + This over-writes the position given at create_notify. */ + window->x = configure_x; + window->y = configure_y; + } else if (configure_frame_position) { weston_wm_window_configure_frame_with_position(window, configure_x - x, configure_y - y); - else + } else { weston_wm_window_configure_frame(window); + } + weston_wm_window_schedule_repaint(window); } @@ -1022,7 +1033,7 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve Previously, toplevel window was not movable by configure_notify, but with move_position API, it can be moved. Thus, xcb_create_window is for frame is now called with SHRT_MIN (previously it was 0, now changed to - something explicit, SHRT_SHORT), and configure with that value for frame + something explicit, SHRT_MIN), and configure with that value for frame is ignored here. */ if (is_our_resource && configure_notify->x == SHRT_MIN && From 55a9ddd082d7e906f2bad49c2c61581c5ee4f932 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 3 Jun 2021 14:11:37 -0700 Subject: [PATCH 1472/1642] move rdpapplist to WSLg project from FreeRDP (#21) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/meson.build | 10 ++-- libweston/backend-rdp/rdp.h | 12 +++-- libweston/backend-rdp/rdprail.c | 76 +++++++++++++++++++++++-------- 3 files changed, 70 insertions(+), 28 deletions(-) diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index 43ef3bf10..59e9db6a0 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -19,6 +19,11 @@ if not dep_wpr.found() error('RDP-backend requires winpr2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') endif +dep_rdpapplist = dependency('rdpapplist', version: '>= 1.0.0', required: false) +if dep_rdpapplist.found() + config_h.set('HAVE_FREERDP_RDPAPPLIST_H', '1') +endif + if cc.has_header('freerdp/version.h', dependencies: dep_frdp) config_h.set('HAVE_FREERDP_VERSION_H', '1') endif @@ -27,10 +32,6 @@ if cc.has_header('freerdp/channels/gfxredir.h', dependencies: dep_frdp) config_h.set('HAVE_FREERDP_GFXREDIR_H', '1') endif -if cc.has_header('freerdp/channels/rdpapplist.h', dependencies: dep_frdp) - config_h.set('HAVE_FREERDP_RDPAPPLIST_H', '1') -endif - if cc.has_member( 'SURFACE_BITS_COMMAND', 'bmp', dependencies : dep_frdp, @@ -70,6 +71,7 @@ deps_rdp = [ dep_frdp, dep_frdp_server, dep_wpr, + dep_rdpapplist, ] dep_openssl = dependency('openssl', version: '>= 1.1.1', required: false) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index a04ff4613..c7320f3d6 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -95,7 +95,9 @@ #include #endif // HAVE_FREERDP_GFXREDIR_H #ifdef HAVE_FREERDP_RDPAPPLIST_H -#include +#include +#include +#include #endif // HAVE_FREERDP_RDPAPPLIST_H #include @@ -190,14 +192,13 @@ struct rdp_backend { int rdp_monitor_refresh_rate; -#if defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) - void *libFreeRDPServer; -#endif // defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) - #ifdef HAVE_FREERDP_RDPAPPLIST_H /* import from libfreerdp-server2.so */ RdpAppListServerContext* (*rdpapplist_server_context_new)(HANDLE vcm); void (*rdpapplist_server_context_free)(RdpAppListServerContext* context); + + void *libRDPApplistServer; + bool use_rdpapplist; #endif // HAVE_FREERDP_RDPAPPLIST_H #ifdef HAVE_FREERDP_GFXREDIR_H @@ -205,6 +206,7 @@ struct rdp_backend { GfxRedirServerContext* (*gfxredir_server_context_new)(HANDLE vcm); void (*gfxredir_server_context_free)(GfxRedirServerContext* context); + void *libFreeRDPServer; bool use_gfxredir; char *shared_memory_mount_path; size_t shared_memory_mount_path_size; diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 0eb72615e..634b305ac 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2720,8 +2720,7 @@ rdp_rail_peer_activate(freerdp_peer* client) /* open Application List channel. */ if (b->rdprail_shell_api && b->rdprail_shell_name && - b->rdpapplist_server_context_new && - b->rdpapplist_server_context_free) { + b->use_rdpapplist) { peerCtx->applist_server_context = b->rdpapplist_server_context_new(peerCtx->vcm); if (!peerCtx->applist_server_context) goto error_exit; @@ -3920,20 +3919,46 @@ rdp_rail_backend_create(struct rdp_backend *b) return -1; } -#if defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) - dlerror(); /* clear error */ - b->libFreeRDPServer = dlopen("libfreerdp-server2.so", RTLD_NOW); - if (!b->libFreeRDPServer) - rdp_debug_error(b, "dlopen(libfreerdp-server2.so) failed with %s\n", dlerror()); -#endif // defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H)o - #ifdef HAVE_FREERDP_RDPAPPLIST_H - if (b->libFreeRDPServer) { - *(void **)(&b->rdpapplist_server_context_new) = dlsym(b->libFreeRDPServer, "rdpapplist_server_context_new"); - *(void **)(&b->rdpapplist_server_context_free) = dlsym(b->libFreeRDPServer, "rdpapplist_server_context_free"); - if (!b->rdpapplist_server_context_new || !b->rdpapplist_server_context_free) - rdp_debug(b, "libfreerdp-server2.so doesn't support applist API.\n"); + bool use_rdpapplist = true; + + s = getenv("WESTON_RDP_DISABLE_APPLIST"); + if (s) { + rdp_debug(b, "WESTON_RDP_DISABLE_APPLIST is set to %s.\n", s); + if (strcmp(s, "true") == 0) + use_rdpapplist = false; } + + if (use_rdpapplist) { + use_rdpapplist = false; + + rdp_debug(b, "RDPAPPLIST_MODULEDIR is set to %s\n", RDPAPPLIST_MODULEDIR); + + dlerror(); /* clear error */ + b->libRDPApplistServer = dlopen(RDPAPPLIST_MODULEDIR "/" "librdpapplist-server.so", RTLD_NOW); + if (!b->libRDPApplistServer) { + rdp_debug_error(b, "dlopen(%s/librdpapplist-server.so) failed with %s\n", RDPAPPLIST_MODULEDIR, dlerror()); + b->libRDPApplistServer = dlopen("librdpapplist-server.so", RTLD_NOW); + if (!b->libRDPApplistServer) { + rdp_debug_error(b, "dlopen(librdpapplist-server.so) failed with %s\n", dlerror()); + } + } + + if (b->libRDPApplistServer) { + *(void **)(&b->rdpapplist_server_context_new) = dlsym(b->libRDPApplistServer, "rdpapplist_server_context_new"); + *(void **)(&b->rdpapplist_server_context_free) = dlsym(b->libRDPApplistServer, "rdpapplist_server_context_free"); + if (b->rdpapplist_server_context_new && b->rdpapplist_server_context_free) { + use_rdpapplist = true; + } else { + rdp_debug(b, "librdpapplist-server.so doesn't have required applist entry.\n"); + dlclose(b->libRDPApplistServer); + b->libRDPApplistServer = NULL; + } + } + } + + b->use_rdpapplist = use_rdpapplist; + rdp_debug(b, "RDP backend: use_rdpapplist = %d\n", b->use_rdpapplist); #endif // HAVE_FREERDP_RDPAPPLIST_H #ifdef HAVE_FREERDP_GFXREDIR_H @@ -3962,13 +3987,21 @@ rdp_rail_backend_create(struct rdp_backend *b) /* check if FreeRDP server lib supports graphics redirection channel API */ if (use_gfxredir) { use_gfxredir = false; - if (b->libFreeRDPServer) { + + dlerror(); /* clear error */ + b->libFreeRDPServer = dlopen("libfreerdp-server2.so", RTLD_NOW); + if (!b->libFreeRDPServer) { + rdp_debug_error(b, "dlopen(libfreerdp-server2.so) failed with %s\n", dlerror()); + } else { *(void **)(&b->gfxredir_server_context_new) = dlsym(b->libFreeRDPServer, "gfxredir_server_context_new"); *(void **)(&b->gfxredir_server_context_free) = dlsym(b->libFreeRDPServer, "gfxredir_server_context_free"); - if (b->gfxredir_server_context_new && b->gfxredir_server_context_new) + if (b->gfxredir_server_context_new && b->gfxredir_server_context_new) { use_gfxredir = true; - else + } else { rdp_debug(b, "libfreerdp-server2.so doesn't support graphics redirection API.\n"); + dlclose(b->libFreeRDPServer); + b->libFreeRDPServer = NULL; + } } } @@ -4095,8 +4128,13 @@ rdp_rail_destroy(struct rdp_backend *b) if (b->debug_binding_W) weston_binding_destroy(b->debug_binding_W); -#if defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) +#if defined(HAVE_FREERDP_RDPAPPLIST_H) + if (b->libRDPApplistServer) + dlclose(b->libRDPApplistServer); +#endif // defined(HAVE_FREERDP_RDPAPPLIST_H) + +#if defined(HAVE_FREERDP_GFXREDIR_H) if (b->libFreeRDPServer) dlclose(b->libFreeRDPServer); -#endif // defined(HAVE_FREERDP_RDPAPPLIST_H) || defined(HAVE_FREERDP_GFXREDIR_H) +#endif // defined(HAVE_FREERDP_GFXREDIR_H) } From ed4f3bbbf5efbd5bde880704bc03497f2446c3f2 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Sun, 6 Jun 2021 19:15:14 -0700 Subject: [PATCH 1473/1642] enable font attribute fallback for international text (#22) Co-authored-by: Hideyuki Nagase --- shared/cairo-util.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/shared/cairo-util.c b/shared/cairo-util.c index f558e1d0c..5d1387aed 100644 --- a/shared/cairo-util.c +++ b/shared/cairo-util.c @@ -461,6 +461,7 @@ create_layout(cairo_t *cr, const char *title) { PangoLayout *layout; PangoFontDescription *desc; + PangoAttrList *attrs; layout = pango_cairo_create_layout(cr); if (title) { @@ -468,6 +469,13 @@ create_layout(cairo_t *cr, const char *title) desc = pango_font_description_from_string("Sans Bold 10"); pango_layout_set_font_description(layout, desc); pango_font_description_free(desc); + /* enable fallback attribute */ + attrs = pango_attr_list_new(); + if (attrs) { + pango_attr_list_insert(attrs, pango_attr_fallback_new(true)); + pango_layout_set_attributes(layout, attrs); + pango_attr_list_unref(attrs); + } } pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); From e9d79f15ea6760be64bcd1f204eab5145a738f73 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 11 Jun 2021 13:47:54 -0700 Subject: [PATCH 1474/1642] fix selection notifier never gets initialized if no seat at window manager initialization (#23) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdpclip.c | 170 +++++++++++++++++++------------- xwayland/selection.c | 24 ++++- xwayland/xwayland.h | 1 + 3 files changed, 120 insertions(+), 75 deletions(-) diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index 25cbd5844..6eb3beba2 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -99,10 +99,31 @@ enum rdp_clipboard_data_source_state { RDP_CLIPBOARD_SOURCE_FAILED, /* failure occured */ }; +struct rdp_clipboard_data_source { + struct weston_data_source base; + struct wl_event_source *event_source; + struct wl_array data_contents; + void *context; + int refcount; + int data_source_fd; + int format_index; + enum rdp_clipboard_data_source_state state; + UINT32 data_response_fail_count; + UINT32 inflight_write_count; + void *inflight_data_to_write; + size_t inflight_data_size; + BOOL is_data_processed; + BOOL is_canceled; + UINT32 client_format_id_table[RDP_NUM_CLIPBOARD_FORMATS]; +}; + static char * -clipboard_data_source_state_to_string(enum rdp_clipboard_data_source_state state) +clipboard_data_source_state_to_string(struct rdp_clipboard_data_source *source) { - switch (state) + if (!source) + return "null"; + + switch (source->state) { case RDP_CLIPBOARD_SOURCE_ALLOCATED: return "allocated"; @@ -129,23 +150,6 @@ clipboard_data_source_state_to_string(enum rdp_clipboard_data_source_state state return "unknown"; } -struct rdp_clipboard_data_source { - struct weston_data_source base; - struct wl_event_source *event_source; - struct wl_array data_contents; - void *context; - int refcount; - int data_source_fd; - int format_index; - enum rdp_clipboard_data_source_state state; - UINT32 inflight_write_count; - void *inflight_data_to_write; - size_t inflight_data_size; - BOOL is_data_processed; - BOOL is_canceled; - UINT32 client_format_id_table[RDP_NUM_CLIPBOARD_FORMATS]; -}; - static void * clipboard_process_text(struct rdp_clipboard_data_source *source, BOOL is_send) { @@ -224,7 +228,7 @@ clipboard_process_text(struct rdp_clipboard_data_source *source, BOOL is_send) } rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source->state), + __func__, source, clipboard_data_source_state_to_string(source), is_send ? "send" : "receive", (UINT32)source->data_contents.size); return source->data_contents.data; @@ -233,7 +237,7 @@ clipboard_process_text(struct rdp_clipboard_data_source *source, BOOL is_send) source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s FAILED (%p:%s): %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source->state), + __func__, source, clipboard_data_source_state_to_string(source), is_send ? "send" : "receive", (UINT32)source->data_contents.size); //rdp_debug_clipboard_verbose(b, "RDP clipboard_process_html FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", // source, is_send ? "send" : "receive", @@ -333,7 +337,7 @@ clipboard_process_html(struct rdp_clipboard_data_source *source, BOOL is_send) } rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source->state), + __func__, source, clipboard_data_source_state_to_string(source), is_send ? "send" : "receive", (UINT32)source->data_contents.size); //rdp_debug_clipboard_verbose(b, "RDP clipboard_process_html (%p): %s \n\"%s\"\n (%d bytes)\n", // source, is_send ? "send" : "receive", @@ -346,7 +350,7 @@ clipboard_process_html(struct rdp_clipboard_data_source *source, BOOL is_send) source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s FAILED (%p:%s): %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source->state), + __func__, source, clipboard_data_source_state_to_string(source), is_send ? "send" : "receive", (UINT32)source->data_contents.size); //rdp_debug_clipboard_verbose(b, "RDP clipboard_process_html FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", // source, is_send ? "send" : "receive", @@ -448,7 +452,7 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) assert(bmih); rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source->state), + __func__, source, clipboard_data_source_state_to_string(source), is_send ? "send" : "receive", (UINT32)source->data_contents.size); rdp_debug_clipboard_verbose_continue(b, " BITMAPFILEHEADER.bfType:0x%x\n", bmfh->bfType); @@ -504,7 +508,7 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s FAILED (%p:%s): %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source->state), + __func__, source, clipboard_data_source_state_to_string(source), is_send ? "send" : "receive", (UINT32)source->data_contents.size); wl_array_release(&data_contents); @@ -647,7 +651,7 @@ clipboard_data_source_unref(struct rdp_clipboard_data_source *source) source->refcount--; rdp_debug_clipboard(b, "RDP %s (%p:%s): refcount:%d\n", - __func__, source, clipboard_data_source_state_to_string(source->state), source->refcount); + __func__, source, clipboard_data_source_state_to_string(source), source->refcount); if (source->refcount > 0) return; @@ -683,7 +687,7 @@ clipboard_client_send_format_data_response(RdpPeerContext *peerCtx, struct rdp_c CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; rdp_debug_clipboard(b, "Client: %s (%p:%s) format_index:%d %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source->state), source->format_index, + __func__, source, clipboard_data_source_state_to_string(source), source->format_index, clipboard_supported_formats[source->format_index].mime_type, size); formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; @@ -707,7 +711,12 @@ clipboard_client_send_format_data_response_fail(RdpPeerContext *peerCtx, struct CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; rdp_debug_clipboard(b, "Client: %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); + + if (source) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + source->data_response_fail_count++; + } formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; formatDataResponse.msgFlags = CB_RESPONSE_FAIL; @@ -739,7 +748,7 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) void *data_to_send; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", - __func__, source, clipboard_data_source_state_to_string(source->state), fd); + __func__, source, clipboard_data_source_state_to_string(source), fd); ASSERT_COMPOSITOR_THREAD(b); @@ -767,7 +776,7 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) /* all data from source is read, so completed. */ source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; rdp_debug_clipboard(b, "RDP %s (%p:%s): read completed (%ld bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source->state), source->data_contents.size); + __func__, source, clipboard_data_source_state_to_string(source), source->data_contents.size); if (!source->data_contents.size) goto error_exit; /* process data before sending to client */ @@ -786,13 +795,13 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) } else if (len < 0) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s (%p:%s) read failed (%s)\n", - __func__, source, clipboard_data_source_state_to_string(source->state), strerror(errno)); + __func__, source, clipboard_data_source_state_to_string(source), strerror(errno)); goto error_exit; } else { source->data_contents.size += len; ((char*)source->data_contents.data)[source->data_contents.size] = '\0'; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) read (%zu bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source->state), source->data_contents.size); + __func__, source, clipboard_data_source_state_to_string(source), source->data_contents.size); } return 0; @@ -819,7 +828,7 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) ssize_t size; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", __func__, - source, clipboard_data_source_state_to_string(source->state), fd); + source, clipboard_data_source_state_to_string(source), fd); ASSERT_COMPOSITOR_THREAD(b); @@ -834,7 +843,7 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) if (source->inflight_data_to_write) { assert(source->inflight_data_size); rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) retry write retry count:%d\n", - __func__, source, clipboard_data_source_state_to_string(source->state), source->inflight_write_count); + __func__, source, clipboard_data_source_state_to_string(source), source->inflight_write_count); data_to_write = source->inflight_data_to_write; data_size = source->inflight_data_size; } else { @@ -852,7 +861,7 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) if (errno != EAGAIN) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s (%p:%s) write failed %s\n", - __func__, source, clipboard_data_source_state_to_string(source->state), strerror(errno)); + __func__, source, clipboard_data_source_state_to_string(source), strerror(errno)); break; } source->inflight_data_to_write = data_to_write; @@ -864,7 +873,7 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) if (!source->event_source) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); break; } return 0; @@ -873,18 +882,18 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) data_size -= size; data_to_write = (char *)data_to_write + size; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) wrote %ld bytes, remaining %ld bytes\n", - __func__, source, clipboard_data_source_state_to_string(source->state), size, data_size); + __func__, source, clipboard_data_source_state_to_string(source), size, data_size); if (!data_size) { source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; rdp_debug_clipboard(b, "RDP %s (%p:%s) write completed (%ld bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source->state), source->data_contents.size); + __func__, source, clipboard_data_source_state_to_string(source), source->data_contents.size); } } } } else if (source->is_canceled) { source->state = RDP_CLIPBOARD_SOURCE_CANCELED; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); } close(source->data_source_fd); @@ -930,7 +939,7 @@ clipboard_data_source_send(struct weston_data_source *base, int index; rdp_debug_clipboard(b, "RDP %s (%p:%s) fd:%d, mime-type:\"%s\"\n", - __func__, source, clipboard_data_source_state_to_string(source->state), fd, mime_type); + __func__, source, clipboard_data_source_state_to_string(source), fd, mime_type); ASSERT_COMPOSITOR_THREAD(b); @@ -938,16 +947,18 @@ clipboard_data_source_send(struct weston_data_source *base, /* Here means server side (Linux application) request clipboard data, but server hasn't completed with previous request yet. If this happens, punt to idle loop and reattempt. */ + rdp_debug_clipboard_error(b, "\n\n\nRDP %s new (%p:%s) vs prev (%p:%s): outstanding RDP data request (client to server)\n\n\n", + __func__, source, clipboard_data_source_state_to_string(source), + peerCtx->clipboard_inflight_client_data_source, + clipboard_data_source_state_to_string(peerCtx->clipboard_inflight_client_data_source)); source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "\n\n\nRDP %s (%p:%s) vs (%p): outstanding RDP data request (client to server)\n\n\n", - __func__, source, clipboard_data_source_state_to_string(source->state), peerCtx->clipboard_inflight_client_data_source); goto error_return_close_fd; } if (source->base.mime_types.size == 0) { source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; rdp_debug_clipboard(b, "RDP %s (%p:%s) source has no data\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); goto error_return_close_fd; } @@ -970,7 +981,7 @@ clipboard_data_source_send(struct weston_data_source *base, if (!source->event_source) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); goto error_return_unref_source; } } else { @@ -986,7 +997,7 @@ clipboard_data_source_send(struct weston_data_source *base, formatDataRequest.requestedFormatId = source->client_format_id_table[index]; source->state = RDP_CLIPBOARD_SOURCE_REQUEST_DATA; rdp_debug_clipboard(b, "RDP %s (%p:%s) request \"%s\" index:%d formatId:%d %s\n", - __func__, source, clipboard_data_source_state_to_string(source->state), mime_type, index, + __func__, source, clipboard_data_source_state_to_string(source), mime_type, index, formatDataRequest.requestedFormatId, clipboard_format_id_to_string(formatDataRequest.requestedFormatId, false)); if (peerCtx->clipboard_server_context->ServerFormatDataRequest(peerCtx->clipboard_server_context, &formatDataRequest) != 0) @@ -995,7 +1006,7 @@ clipboard_data_source_send(struct weston_data_source *base, } else { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s (%p:%s) specified format \"%s\" index:%d formatId:%d is not supported by client\n", - __func__, source, clipboard_data_source_state_to_string(source->state), + __func__, source, clipboard_data_source_state_to_string(source), mime_type, index, source->client_format_id_table[index]); goto error_return_close_fd; } @@ -1027,7 +1038,7 @@ clipboard_data_source_cancel(struct weston_data_source *base) struct rdp_backend *b = peerCtx->rdpBackend; rdp_debug_clipboard(b, "RDP %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); ASSERT_COMPOSITOR_THREAD(b); @@ -1035,13 +1046,13 @@ clipboard_data_source_cancel(struct weston_data_source *base) source->is_canceled = TRUE; source->state = RDP_CLIPBOARD_SOURCE_CANCEL_PENDING; rdp_debug_clipboard(b, "RDP %s (%p:%s): still inflight\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); assert(source->refcount > 1); } else { /* everything outside of the base has to be cleaned up */ source->state = RDP_CLIPBOARD_SOURCE_CANCELED; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); assert(source->event_source == NULL); wl_array_release(&source->data_contents); wl_array_init(&source->data_contents); @@ -1073,7 +1084,7 @@ clipboard_data_source_publish(void *arg) struct rdp_clipboard_data_source *source_prev; rdp_debug_clipboard(b, "RDP %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); ASSERT_COMPOSITOR_THREAD(b); @@ -1145,7 +1156,7 @@ clipboard_data_source_request(void *arg) to client by clipboard_set_selection(). */ source->state = RDP_CLIPBOARD_SOURCE_PUBLISHED; rdp_debug_clipboard(b, "RDP %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); wl_signal_init(&source->base.destroy_signal); wl_array_init(&source->base.mime_types); wl_array_init(&source->data_contents); @@ -1172,7 +1183,7 @@ clipboard_data_source_request(void *arg) if (!source->event_source) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed.\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); goto error_exit_free_source; } @@ -1334,7 +1345,7 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT if (source) { source->state = RDP_CLIPBOARD_SOURCE_ALLOCATED; rdp_debug_clipboard(b, "Client: %s (%p:%s) allocated\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); wl_signal_init(&source->base.destroy_signal); wl_array_init(&source->base.mime_types); wl_array_init(&source->data_contents); @@ -1354,17 +1365,17 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT p = wl_array_add(&source->base.mime_types, sizeof *p); if (p) { rdp_debug_clipboard(b, "Client: %s (%p:%s) mine_type:\"%s\" index:%d formatId:%d\n", - __func__, source, clipboard_data_source_state_to_string(source->state), + __func__, source, clipboard_data_source_state_to_string(source), s, index, format->formatId); *p = s; } else { rdp_debug_clipboard(b, "Client: %s (%p:%s) wl_array_add failed\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); free(s); } } else { rdp_debug_clipboard(b, "Client: %s (%p:%s) strdup failed\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); } } } @@ -1372,7 +1383,7 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT if (formatList->numFormats != 0 && source->base.mime_types.size == 0) { rdp_debug_clipboard(b, "Client: %s (%p:%s) no formats are supported\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); } source->state = RDP_CLIPBOARD_SOURCE_FORMATLIST_READY; @@ -1385,7 +1396,7 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT } else { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "Client: %s (%p:%s) rdp_defer_rdp_task_to_display_loop failed\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); } } @@ -1396,7 +1407,7 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT if (peerCtx->clipboard_server_context->ServerFormatListResponse(peerCtx->clipboard_server_context, &formatListResponse) != 0) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "Client: %s (%p:%s) ServerFormatListResponse failed\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); return -1; } @@ -1417,8 +1428,8 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR struct rdp_clipboard_data_source *source = peerCtx->clipboard_inflight_client_data_source; BOOL Success = FALSE; - rdp_debug_clipboard(b, "Client: %s (%p:%s) flags:%d, dataLen:%d\n", - __func__, source, clipboard_data_source_state_to_string(source->state), + rdp_debug_clipboard(b, "Client: %s (%p:%s) flags:%d dataLen:%d\n", + __func__, source, clipboard_data_source_state_to_string(source), formatDataResponse->msgFlags, formatDataResponse->dataLen); ASSERT_NOT_COMPOSITOR_THREAD(b); @@ -1428,7 +1439,7 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR /* here means client responded more than once for single data request */ source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "Client: %s (%p:%s) middle of write loop:%p, %d\n", - __func__, source, clipboard_data_source_state_to_string(source->state), + __func__, source, clipboard_data_source_state_to_string(source), source->event_source, source->inflight_write_count); return -1; } @@ -1450,9 +1461,12 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR } } else { source->state = RDP_CLIPBOARD_SOURCE_FAILED; + source->data_response_fail_count++; } - rdp_debug_clipboard_verbose(b, "Client: %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + rdp_debug_clipboard_verbose(b, "Client: %s (%p:%s:%d)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->data_response_fail_count); if (Success) { assert(source->event_source == NULL); @@ -1462,22 +1476,38 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR if (!source->event_source) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "Client: %s (%p:%s) wl_event_loop_add_fd failed\n", - __func__, source, clipboard_data_source_state_to_string(source->state)); + __func__, source, clipboard_data_source_state_to_string(source)); } } if (!source->event_source) { - wl_array_release(&source->data_contents); - wl_array_init(&source->data_contents); - source->is_data_processed = FALSE; - source->format_index = -1; - memset(source->client_format_id_table, 0, sizeof(source->client_format_id_table)); + if (formatDataResponse->msgFlags == CB_RESPONSE_OK) { + /* if data is recieved, but failed to sent to write(), + then keep data and format index for future request, + otherwise data is purged at last reference release. */ + /* wl_array_release(&source->data_contents); */ + /* wl_array_init(&source->data_contents); */ + } else { + /* data has been never recieved, thus must be empty. */ + assert(source->data_contents.size == 0); + assert(source->data_contents.alloc == 0); + assert(source->data_contents.data == NULL); + /* clear previous requested format so it can be requested later again. */ + source->format_index = -1; + } + /* don't clear format id table, so it allows to retry to get data from client. */ + /* memset(source->client_format_id_table, 0, sizeof(source->client_format_id_table)); */ + /* data has never been sent to write(), thus must be no inflight write. */ assert(source->inflight_write_count == 0); assert(source->inflight_data_to_write == NULL); assert(source->inflight_data_size == 0); + /* data never has been sent to write(), so must not be processed. */ + assert(source->is_data_processed == FALSE); + /* close fd to server clipboard stop pulling data. */ close(source->data_source_fd); source->data_source_fd = -1; clipboard_data_source_unref(source); + /* clear inflight data source from client to server. */ peerCtx->clipboard_inflight_client_data_source = NULL; } } else { diff --git a/xwayland/selection.c b/xwayland/selection.c index 6a807cb0d..d40c0b47b 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -726,6 +726,18 @@ weston_wm_set_selection(struct wl_listener *listener, void *data) XCB_TIME_CURRENT_TIME); } +static void +weston_wm_seat_created(struct wl_listener *listener, void *data) +{ + struct weston_seat *seat = data; + struct weston_wm *wm = + container_of(listener, struct weston_wm, seat_create_listener); + + wl_signal_add(&seat->selection_signal, &wm->selection_listener); + + weston_wm_set_selection(&wm->selection_listener, seat); +} + void weston_wm_selection_init(struct weston_wm *wm) { @@ -761,11 +773,13 @@ weston_wm_selection_init(struct weston_wm *wm) xcb_xfixes_select_selection_input(wm->conn, wm->selection_window, wm->atom.clipboard, mask); - seat = weston_wm_pick_seat(wm); - if (seat == NULL) - return; wm->selection_listener.notify = weston_wm_set_selection; - wl_signal_add(&seat->selection_signal, &wm->selection_listener); - weston_wm_set_selection(&wm->selection_listener, seat); + wm->seat_create_listener.notify = weston_wm_seat_created; + wl_signal_add(&wm->server->compositor->seat_created_signal, + &wm->seat_create_listener); + + seat = weston_wm_pick_seat(wm); + if (seat) + weston_wm_seat_created(&wm->seat_create_listener, seat); } diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index 801ede3e5..2203cfcab 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -90,6 +90,7 @@ struct weston_wm { int selection_property_set; int flush_property_on_delete; struct wl_listener selection_listener; + struct wl_listener seat_create_listener; xcb_window_t dnd_window; xcb_window_t dnd_owner; From 9c4dead7d5adac8d09f5daffaeadd933c64e6603 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 15 Jun 2021 14:40:40 -0700 Subject: [PATCH 1475/1642] fix display loop keep awake (#24) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 18 +++++-- libweston/backend-rdp/rdp.h | 28 ++--------- libweston/backend-rdp/rdpclip.c | 86 ++++++++++++++++++++------------- libweston/backend-rdp/rdpdisp.c | 42 ++++++++++------ libweston/backend-rdp/rdprail.c | 52 +++++++++++--------- libweston/backend-rdp/rdputil.c | 34 +++++++++++++ 6 files changed, 161 insertions(+), 99 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index f87815dad..486694dae 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -856,6 +857,8 @@ rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) context->item.peer = client; context->item.flags = RDP_PEER_OUTPUT_ENABLED; + context->loop_event_source_fd = -1; + #if FREERDP_VERSION_MAJOR == 1 && FREERDP_VERSION_MINOR == 1 context->rfx_context = rfx_context_new(); #else @@ -901,6 +904,9 @@ rdp_peer_context_free(freerdp_peer* client, RdpPeerContext* context) wl_list_remove(&context->item.link); + if (context->loop_event_source_fd != -1) + close(context->loop_event_source_fd); + for (i = 0; i < ARRAY_LENGTH(context->events); i++) { if (context->events[i]) wl_event_source_remove(context->events[i]); @@ -1827,9 +1833,6 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) peerCtx->vcm = WTSOpenServerA((LPSTR)peerCtx); if (peerCtx->vcm) { WTSVirtualChannelManagerGetFileDescriptor(peerCtx->vcm, rfds, &rcount); - peerCtx->eventVcm = WTSVirtualChannelManagerGetEventHandle(peerCtx->vcm); - /* event must be valid when server is successfully opened */ - assert(peerCtx->eventVcm); } else { rdp_debug_error(b, "WTSOpenServer is failed! continue without virtual channel.\n"); } @@ -1844,6 +1847,10 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) for ( ; i < ARRAY_LENGTH(peerCtx->events); i++) peerCtx->events[i] = 0; + peerCtx->loop_event_source_fd = eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC); + if (peerCtx->loop_event_source_fd == -1) + goto error_peer_initialize; + if (!rdp_rail_peer_init(client, peerCtx)) goto error_peer_initialize; @@ -1860,6 +1867,10 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) return 0; error_peer_initialize: + if (peerCtx->loop_event_source_fd != -1) { + close(peerCtx->loop_event_source_fd); + peerCtx->loop_event_source_fd = -1; + } for (i = 0; i < ARRAY_LENGTH(peerCtx->events); i++) { if (peerCtx->events[i]) { wl_event_source_remove(peerCtx->events[i]); @@ -1868,7 +1879,6 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) } if (peerCtx->vcm) { WTSCloseServer(peerCtx->vcm); - peerCtx->eventVcm = NULL; peerCtx->vcm = NULL; } diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index c7320f3d6..b05473b42 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -281,7 +281,6 @@ struct rdp_peer_context { // RAIL support HANDLE vcm; - HANDLE eventVcm; RailServerContext* rail_server_context; DrdynvcServerContext* drdynvc_server_context; DispServerContext* disp_server_context; @@ -310,8 +309,10 @@ struct rdp_peer_context { struct wl_listener clientExec_destroy_listener; struct weston_surface *cursorSurface; // list of outstanding event_source sent from FreeRDP thread to display loop. + int loop_event_source_fd; pthread_mutex_t loop_event_source_list_mutex; struct wl_list loop_event_source_list; + // RAIL power management. struct wl_listener idle_listener; struct wl_listener wake_listener; @@ -447,6 +448,8 @@ void rdp_id_manager_free(struct rdp_id_manager *id_manager); BOOL rdp_id_manager_allocate_id(struct rdp_id_manager *id_manager, void *object, UINT32 *new_id); void rdp_id_manager_free_id(struct rdp_id_manager *id_manager, UINT32 id); void dump_id_manager_state(FILE *fp, struct rdp_id_manager *id_manager, char* title); +struct wl_event_source *rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_fd_func_t func, void *data); +void rdp_defer_rdp_task_done(RdpPeerContext *peerCtx); // rdprail.c int rdp_rail_backend_create(struct rdp_backend *b); @@ -477,29 +480,6 @@ void rdp_audioin_destroy(RdpPeerContext *peerCtx); int rdp_clipboard_init(freerdp_peer* client); void rdp_clipboard_destroy(RdpPeerContext *peerCtx); -/* this function is ONLY used to defer the task from RDP thread, - to be performed at wayland display loop thread */ -static inline struct wl_event_source * -rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_idle_func_t func, void *data) -{ - if (peerCtx->vcm) { - struct rdp_backend *b = peerCtx->rdpBackend; - ASSERT_NOT_COMPOSITOR_THREAD(b); - struct wl_event_loop *loop = wl_display_get_event_loop(b->compositor->wl_display); - struct wl_event_source *event_source = wl_event_loop_add_idle(loop, func, data); - if (event_source) { - SetEvent(peerCtx->eventVcm); - } else { - rdp_debug_error(b, "%s: wl_event_loop_add_idle failed\n", __func__); - } - return event_source; - } else { - /* RDP server is not opened, this must not be used */ - assert(false); - return NULL; - } -} - static inline struct rdp_head * to_rdp_head(struct weston_head *base) { diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index 6eb3beba2..f1304855a 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -101,7 +101,8 @@ enum rdp_clipboard_data_source_state { struct rdp_clipboard_data_source { struct weston_data_source base; - struct wl_event_source *event_source; + struct wl_event_source *transfer_event_source; /* used for read/write with pipe */ + struct wl_event_source *defer_event_source; /* used for defer task to display loop */ struct wl_array data_contents; void *context; int refcount; @@ -656,8 +657,13 @@ clipboard_data_source_unref(struct rdp_clipboard_data_source *source) if (source->refcount > 0) return; - if (source->event_source) - wl_event_source_remove(source->event_source); + if (source->transfer_event_source) + wl_event_source_remove(source->transfer_event_source); + + if (source->defer_event_source) { + rdp_defer_rdp_task_done(peerCtx); + wl_event_source_remove(source->defer_event_source); + } if (source->data_source_fd != -1) close(source->data_source_fd); @@ -698,6 +704,7 @@ clipboard_client_send_format_data_response(RdpPeerContext *peerCtx, struct rdp_c /* if here failed to send response, what can we do ? */ /* now client can send new data request */ + assert(peerCtx->clipboard_data_request_event_source == RDP_INVALID_EVENT_SOURCE); peerCtx->clipboard_data_request_event_source = NULL; return 0; @@ -726,6 +733,7 @@ clipboard_client_send_format_data_response_fail(RdpPeerContext *peerCtx, struct /* if here failed to send response, what can we do ? */ /* now client can send new data request */ + assert(peerCtx->clipboard_data_request_event_source == RDP_INVALID_EVENT_SOURCE); peerCtx->clipboard_data_request_event_source = NULL; return 0; @@ -757,7 +765,7 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) /* event source is not removed here, but it will be removed when read is completed, until it's completed this function will be called whenever next chunk of data is available for read in pipe. */ - assert(source->event_source); + assert(source->transfer_event_source); /* if buffer is less than 1024 bytes remaining, request another 1024 bytes minimum */ /* but actual reallocated buffer size will be increased by ^2 */ @@ -836,13 +844,13 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) assert(source == peerCtx->clipboard_inflight_client_data_source); /* remove event source now, and if write is failed with EAGAIN, queue back to display loop. */ - wl_event_source_remove(source->event_source); - source->event_source = NULL; + wl_event_source_remove(source->transfer_event_source); + source->transfer_event_source = NULL; if (source->is_canceled == FALSE && source->data_contents.data && source->data_contents.size) { if (source->inflight_data_to_write) { assert(source->inflight_data_size); - rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) retry write retry count:%d\n", + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) transfer in chunck, count:%d\n", __func__, source, clipboard_data_source_state_to_string(source), source->inflight_write_count); data_to_write = source->inflight_data_to_write; data_size = source->inflight_data_size; @@ -864,13 +872,14 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) __func__, source, clipboard_data_source_state_to_string(source), strerror(errno)); break; } + /* buffer is full, schedule write for next chunk. */ source->inflight_data_to_write = data_to_write; source->inflight_data_size = data_size; source->inflight_write_count++; - source->event_source = + source->transfer_event_source = wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, clipboard_data_source_write, source); - if (!source->event_source) { + if (!source->transfer_event_source) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed\n", __func__, source, clipboard_data_source_state_to_string(source)); @@ -951,6 +960,7 @@ clipboard_data_source_send(struct weston_data_source *base, __func__, source, clipboard_data_source_state_to_string(source), peerCtx->clipboard_inflight_client_data_source, clipboard_data_source_state_to_string(peerCtx->clipboard_inflight_client_data_source)); + /* Here set error state after logging error, so log can show previous state. */ source->state = RDP_CLIPBOARD_SOURCE_FAILED; goto error_return_close_fd; } @@ -973,12 +983,12 @@ clipboard_data_source_send(struct weston_data_source *base, assert(source->inflight_data_size == 0); if (index == source->format_index) { /* data is already in data_contents, no need to pull from client */ - assert(source->event_source == NULL); + assert(source->transfer_event_source == NULL); source->state = RDP_CLIPBOARD_SOURCE_RECEIVED_DATA; - source->event_source = + source->transfer_event_source = wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, clipboard_data_source_write, source); - if (!source->event_source) { + if (!source->transfer_event_source) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed\n", __func__, source, clipboard_data_source_state_to_string(source)); @@ -1053,7 +1063,7 @@ clipboard_data_source_cancel(struct weston_data_source *base) source->state = RDP_CLIPBOARD_SOURCE_CANCELED; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s)\n", __func__, source, clipboard_data_source_state_to_string(source)); - assert(source->event_source == NULL); + assert(source->transfer_event_source == NULL); wl_array_release(&source->data_contents); wl_array_init(&source->data_contents); source->is_data_processed = FALSE; @@ -1074,8 +1084,8 @@ clipboard_data_source_cancel(struct weston_data_source *base) \**********************************/ /* Publish client's available clipboard formats to compositor (make them visible to applications in server) */ -static void -clipboard_data_source_publish(void *arg) +static int +clipboard_data_source_publish(int fd, uint32_t mask, void *arg) { struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; freerdp_peer *client = (freerdp_peer*)source->context; @@ -1088,12 +1098,17 @@ clipboard_data_source_publish(void *arg) ASSERT_COMPOSITOR_THREAD(b); - /* here is going to publish new data, if previous data from us is still referenced, + rdp_defer_rdp_task_done(peerCtx); + assert(source->defer_event_source); + wl_event_source_remove(source->defer_event_source); + source->defer_event_source = NULL; + + /* here is going to publish new data, if previous data from client is still referenced, unref it after selection */ source_prev = peerCtx->clipboard_client_data_source; peerCtx->clipboard_client_data_source = source; - source->event_source = NULL; + source->transfer_event_source = NULL; source->base.accept = clipboard_data_source_accept; source->base.send = clipboard_data_source_send; source->base.cancel = clipboard_data_source_cancel; @@ -1104,12 +1119,12 @@ clipboard_data_source_publish(void *arg) if (source_prev) clipboard_data_source_unref(source_prev); - return; + return 0; } /* Request the specified clipboard data from data-device at server side */ -static void -clipboard_data_source_request(void *arg) +static int +clipboard_data_source_request(int fd, uint32_t mask, void *arg) { RdpPeerContext *peerCtx = (RdpPeerContext *)arg; struct rdp_backend *b = peerCtx->rdpBackend; @@ -1124,6 +1139,10 @@ clipboard_data_source_request(void *arg) ASSERT_COMPOSITOR_THREAD(b); + rdp_defer_rdp_task_done(peerCtx); + assert(peerCtx->clipboard_data_request_event_source); + assert(peerCtx->clipboard_data_request_event_source != RDP_INVALID_EVENT_SOURCE); + wl_event_source_remove(peerCtx->clipboard_data_request_event_source); /* set to invalid, so it still validate incoming request, but won't free event source at error. */ peerCtx->clipboard_data_request_event_source = RDP_INVALID_EVENT_SOURCE; @@ -1177,17 +1196,17 @@ clipboard_data_source_request(void *arg) /* p[1] should be closed by data source */ /* wait until data is ready on pipe */ - source->event_source = + source->transfer_event_source = wl_event_loop_add_fd(loop, p[0], WL_EVENT_READABLE, clipboard_data_source_read, source); - if (!source->event_source) { + if (!source->transfer_event_source) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed.\n", __func__, source, clipboard_data_source_state_to_string(source)); goto error_exit_free_source; } - return; + return 0; error_exit_free_source: clipboard_data_source_unref(source); @@ -1195,7 +1214,7 @@ clipboard_data_source_request(void *arg) error_exit_response_fail: clipboard_client_send_format_data_response_fail(peerCtx, NULL); - return; + return 0; } /*************************************\ @@ -1228,7 +1247,7 @@ clipboard_set_selection(struct wl_listener *listener, void *data) } /* another data source (from server side) gets selected, - no longer need previous data from us */ + no longer need previous data from client. */ if (peerCtx->clipboard_client_data_source) { clipboard_data_source_unref(peerCtx->clipboard_client_data_source); peerCtx->clipboard_client_data_source = NULL; @@ -1387,11 +1406,11 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT } source->state = RDP_CLIPBOARD_SOURCE_FORMATLIST_READY; - source->event_source = + source->defer_event_source = rdp_defer_rdp_task_to_display_loop(peerCtx, clipboard_data_source_publish, source); - if (source->event_source) { + if (source->defer_event_source) { isPublished = TRUE; } else { source->state = RDP_CLIPBOARD_SOURCE_FAILED; @@ -1435,12 +1454,12 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR ASSERT_NOT_COMPOSITOR_THREAD(b); if (source) { - if (source->event_source || (source->inflight_write_count != 0)) { + if (source->transfer_event_source || (source->inflight_write_count != 0)) { /* here means client responded more than once for single data request */ source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "Client: %s (%p:%s) middle of write loop:%p, %d\n", __func__, source, clipboard_data_source_state_to_string(source), - source->event_source, source->inflight_write_count); + source->transfer_event_source, source->inflight_write_count); return -1; } @@ -1469,18 +1488,18 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR source->data_response_fail_count); if (Success) { - assert(source->event_source == NULL); - source->event_source = + assert(source->transfer_event_source == NULL); + source->transfer_event_source = wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, clipboard_data_source_write, source); - if (!source->event_source) { + if (!source->transfer_event_source) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "Client: %s (%p:%s) wl_event_loop_add_fd failed\n", __func__, source, clipboard_data_source_state_to_string(source)); } } - if (!source->event_source) { + if (!source->transfer_event_source) { if (formatDataResponse->msgFlags == CB_RESPONSE_OK) { /* if data is recieved, but failed to sent to write(), then keep data and format index for future request, @@ -1662,6 +1681,7 @@ rdp_clipboard_destroy(RdpPeerContext *peerCtx) } if (peerCtx->clipboard_data_request_event_source && peerCtx->clipboard_data_request_event_source != RDP_INVALID_EVENT_SOURCE) { + rdp_defer_rdp_task_done(peerCtx); wl_event_source_remove(peerCtx->clipboard_data_request_event_source); peerCtx->clipboard_data_request_event_source = NULL; } diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index de78b26e2..3a3600826 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -603,19 +603,32 @@ disp_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MON } struct disp_schedule_monitor_layout_change_data { - struct rdp_loop_event_source _base; + struct rdp_loop_event_source _base_event_source; DispServerContext* context; DISPLAY_CONTROL_MONITOR_LAYOUT_PDU displayControl; }; -static void -disp_monitor_layout_change_callback(void* dataIn) +static int +disp_monitor_layout_change_callback(int fd, uint32_t mask, void* dataIn) { struct disp_schedule_monitor_layout_change_data *data = (struct disp_schedule_monitor_layout_change_data *)dataIn; DispServerContext* context = data->context; - wl_list_remove(&data->_base.link); + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + + ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); + + rdp_defer_rdp_task_done(peerCtx); + assert(data->_base_event_source.event_source); + wl_event_source_remove(data->_base_event_source.event_source); + pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); + wl_list_remove(&data->_base_event_source.link); + pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); + disp_monitor_layout_change(context, &data->displayControl); free(dataIn); + + return 0; } UINT @@ -643,16 +656,17 @@ disp_client_monitor_layout_change(DispServerContext* context, const DISPLAY_CONT memcpy(data->displayControl.Monitors, displayControl->Monitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT) * displayControl->NumMonitors); - pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ - wl_list_insert(&peerCtx->loop_event_source_list, &data->_base.link); - pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ - data->_base.event_source = rdp_defer_rdp_task_to_display_loop(peerCtx, - disp_monitor_layout_change_callback, - data); - if (!data->_base.event_source) { - pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ - wl_list_remove(&data->_base.link); - pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ + pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); + wl_list_insert(&peerCtx->loop_event_source_list, &data->_base_event_source.link); + pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); + data->_base_event_source.event_source = + rdp_defer_rdp_task_to_display_loop(peerCtx, + disp_monitor_layout_change_callback, + data); + if (!data->_base_event_source.event_source) { + pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); + wl_list_remove(&data->_base_event_source.link); + pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); free(data); return ERROR_INTERNAL_ERROR; } diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 634b305ac..255840d17 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -53,7 +53,7 @@ static void rdp_rail_schedule_update_window(struct wl_listener *listener, void * static void rdp_rail_dump_window_label(struct weston_surface *surface, char *label, uint32_t label_size); struct rdp_dispatch_data { - struct rdp_loop_event_source _base; + struct rdp_loop_event_source _base_event_source; freerdp_peer *client; union { RAIL_SYSPARAM_ORDER u_sysParam; @@ -82,14 +82,14 @@ struct rdp_dispatch_data { dispatch_data->client = client; \ dispatch_data->u_##arg_type = *(arg); \ pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ - wl_list_insert(&peerCtx->loop_event_source_list, &dispatch_data->_base.link); \ + wl_list_insert(&peerCtx->loop_event_source_list, &dispatch_data->_base_event_source.link); \ pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ - dispatch_data->_base.event_source = \ + dispatch_data->_base_event_source.event_source = \ rdp_defer_rdp_task_to_display_loop(peerCtx, callback, dispatch_data); \ - if (!dispatch_data->_base.event_source) { \ + if (!dispatch_data->_base_event_source.event_source) { \ rdp_debug_error(b, "%s: rdp_queue_deferred_task failed\n", __func__); \ pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ - wl_list_remove(&dispatch_data->_base.link); \ + wl_list_remove(&dispatch_data->_base_event_source.link); \ pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ free(dispatch_data); \ } \ @@ -101,15 +101,19 @@ struct rdp_dispatch_data { #define RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, dispatch_data) \ { \ ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); \ + rdp_defer_rdp_task_done(peerCtx); \ + assert(dispatch_data->_base_event_source.event_source); \ + wl_event_source_remove(dispatch_data->_base_event_source.event_source); \ pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ - wl_list_remove(&dispatch_data->_base.link); \ + wl_list_remove(&dispatch_data->_base_event_source.link); \ pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ free(dispatch_data); \ + return 0; \ } #ifdef HAVE_FREERDP_RDPAPPLIST_H -static void -applist_client_Caps_callback(void *arg) +static int +applist_client_Caps_callback(int fd, uint32_t mask, void *arg) { struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; const RDPAPPLIST_CLIENT_CAPS_PDU* caps = &data->u_appListCaps; @@ -172,8 +176,8 @@ rail_ClientExec_destroy(struct wl_listener *listener, void *data) peerCtx->clientExec = NULL; } -static void -rail_client_Exec_callback(void *arg) +static int +rail_client_Exec_callback(int fd, uint32_t mask, void *arg) { struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; const RAIL_EXEC_ORDER* exec = &data->u_exec; @@ -286,8 +290,8 @@ rail_client_Exec(RailServerContext* context, const RAIL_EXEC_ORDER* arg) return CHANNEL_RC_NO_BUFFER; } -static void -rail_client_Activate_callback(void *arg) +static int +rail_client_Activate_callback(int fd, uint32_t mask, void *arg) { struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; const RAIL_ACTIVATE_ORDER* activate = &data->u_activate; @@ -321,8 +325,8 @@ rail_client_Activate(RailServerContext* context, const RAIL_ACTIVATE_ORDER* arg) return CHANNEL_RC_OK; } -static void -rail_client_SnapArrange_callback(void *arg) +static int +rail_client_SnapArrange_callback(int fd, uint32_t mask, void *arg) { struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; const RAIL_SNAP_ARRANGE* snap = &data->u_snapArrange; @@ -368,8 +372,8 @@ rail_client_SnapArrange(RailServerContext* context, const RAIL_SNAP_ARRANGE* arg return CHANNEL_RC_OK; } -static void -rail_client_WindowMove_callback(void *arg) +static int +rail_client_WindowMove_callback(int fd, uint32_t mask, void *arg) { struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; const RAIL_WINDOW_MOVE_ORDER* windowMove = &data->u_windowMove; @@ -410,8 +414,8 @@ rail_client_WindowMove(RailServerContext* context, const RAIL_WINDOW_MOVE_ORDER* return CHANNEL_RC_OK; } -static void -rail_client_Syscommand_callback(void *arg) +static int +rail_client_Syscommand_callback(int fd, uint32_t mask, void *arg) { struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; const RAIL_SYSCOMMAND_ORDER* syscommand = &data->u_sysCommand; @@ -485,8 +489,8 @@ rail_client_Syscommand(RailServerContext* context, const RAIL_SYSCOMMAND_ORDER* return CHANNEL_RC_OK; } -static void -rail_client_ClientSysparam_callback(void *arg) +static int +rail_client_ClientSysparam_callback(int fd, uint32_t mask, void *arg) { struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; const RAIL_SYSPARAM_ORDER* sysparam = &data->u_sysParam; @@ -609,8 +613,8 @@ rail_client_ClientSysparam(RailServerContext* context, const RAIL_SYSPARAM_ORDER return CHANNEL_RC_OK; } -static void -rail_client_ClientGetAppidReq_callback(void *arg) +static int +rail_client_ClientGetAppidReq_callback(int fd, uint32_t mask, void *arg) { struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; const RAIL_GET_APPID_REQ_ORDER* getAppidReq = &data->u_getAppidReq; @@ -787,8 +791,8 @@ languageGuid_to_string(const GUID *guid) return "Unknown GUID"; } -static void -rail_client_LanguageImeInfo_callback(void *arg) +static int +rail_client_LanguageImeInfo_callback(int fd, uint32_t mask, void *arg) { struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; const RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo = &data->u_languageImeInfo; diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index 931366884..e0163cb82 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -294,4 +295,37 @@ dump_id_manager_state(FILE *fp, struct rdp_id_manager *id_manager, char* title) fprintf(fp,"\n"); } +/* this function is ONLY used to defer the task from RDP thread, + to be performed at wayland display loop thread */ +struct wl_event_source * +rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_fd_func_t func, void *data) +{ + if (peerCtx->vcm) { + struct rdp_backend *b = peerCtx->rdpBackend; + ASSERT_NOT_COMPOSITOR_THREAD(b); + struct wl_event_loop *loop = wl_display_get_event_loop(b->compositor->wl_display); + struct wl_event_source *event_source = + wl_event_loop_add_fd(loop, + peerCtx->loop_event_source_fd, + WL_EVENT_READABLE, + func, data); + if (event_source) { + eventfd_write(peerCtx->loop_event_source_fd, 1); + } else { + rdp_debug_error(b, "%s: wl_event_loop_add_idle failed\n", __func__); + } + return event_source; + } else { + /* RDP server is not opened, this must not be used */ + assert(false); + return NULL; + } +} + +void +rdp_defer_rdp_task_done(RdpPeerContext *peerCtx) +{ + eventfd_t dummy; + eventfd_read(peerCtx->loop_event_source_fd, &dummy); +} From af24155f564be138d00d2b329498499177f41621 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 17 Jun 2021 14:41:02 -0700 Subject: [PATCH 1476/1642] support Korean 101a and 103 keyboard (#25) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 69 +++++++++++++++++++++++++++++---- libweston/backend-rdp/rdp.h | 2 +- libweston/backend-rdp/rdprail.c | 8 +++- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 486694dae..91a171056 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1118,28 +1118,43 @@ static const char *rdp_keyboard_types[] = { "pc102",/* 4: IBM enhanced (101- or 102-key) keyboard */ "", /* 5: Nokia 1050 and similar keyboards */ "", /* 6: Nokia 9140 and similar keyboards */ - "jp106" /* 7: Japanese keyboard */ /* alternative ja106 */ + "jp106",/* 7: Japanese keyboard */ /* alternative ja106 */ + "pc102" /* 8: Korean keyboard which is based on pc101, + 2 special Korean keys */ }; void convert_rdp_keyboard_to_xkb_rule_names( UINT32 KeyboardType, + UINT32 KeyboardSubType, UINT32 KeyboardLayout, struct xkb_rule_names *xkbRuleNames) { int i; memset(xkbRuleNames, 0, sizeof(*xkbRuleNames)); - if (KeyboardType <= 7) + if (KeyboardType <= ARRAY_LENGTH(rdp_keyboard_types)) xkbRuleNames->model = rdp_keyboard_types[KeyboardType]; for (i = 0; rdp_keyboards[i].rdpLayoutCode; i++) { if (rdp_keyboards[i].rdpLayoutCode == KeyboardLayout) { xkbRuleNames->layout = rdp_keyboards[i].xkbLayout; xkbRuleNames->variant = rdp_keyboards[i].xkbVariant; - weston_log("%s: matching layout=%s variant=%s\n", __FUNCTION__, - xkbRuleNames->layout, xkbRuleNames->variant); break; } } + /* Korean keyboard support (KeyboardType 8, LangID 0x412) */ + if (KeyboardType == 8 && ((KeyboardLayout & 0xFFFF) == 0x412)) { + /* TODO: PC/AT 101 Enhanced Korean Keyboard (Type B) and (Type C) is not supported yet + because default Xkb settings for Korean layout doesn't have corresponding + configuration. + (Type B): KeyboardSubType:4: rctrl_hangul/ratl_hanja + (Type C): KeyboardSubType:5: shift_space_hangul/crtl_space_hanja */ + if (KeyboardSubType == 0 || + KeyboardSubType == 3) // PC/AT 101 Enhanced Korean Keyboard (Type A) + xkbRuleNames->variant = "kr104"; // kr(ralt_hangul)/kr(rctrl_hanja) + else if (KeyboardSubType == 6) // PC/AT 103 Enhanced Korean Keyboard + xkbRuleNames->variant = "kr106"; // kr(hw_keys) + } + weston_log("%s: matching layout=%s variant=%s options=%s\n", __FUNCTION__, + xkbRuleNames->layout, xkbRuleNames->variant, xkbRuleNames->options); } static BOOL @@ -1320,6 +1335,7 @@ xf_peer_activate(freerdp_peer* client) settings->KeyboardFunctionKey); convert_rdp_keyboard_to_xkb_rule_names(settings->KeyboardType, + settings->KeyboardSubType, settings->KeyboardLayout, &xkbRuleNames); @@ -1656,8 +1672,10 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) { uint32_t scan_code, vk_code, full_code; enum wl_keyboard_key_state keyState; + freerdp_peer *client = input->context->peer; RdpPeerContext *peerContext = (RdpPeerContext *)input->context; struct rdp_backend *b = peerContext->rdpBackend; + bool need_release = false; int notify = 0; struct timespec time; @@ -1678,7 +1696,28 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) if (flags & KBD_FLAGS_EXTENDED) full_code |= KBD_FLAGS_EXTENDED; - vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, 4); + /* Korean keyboard support */ + /* WinPR's GetVirtualKeyCodeFromVirtualScanCode() can't handle hangul/hanja keys */ + /* 0x1f1 and 0x1f2 keys are only exists on Korean 103 keyboard (Type 8:SubType 6) */ + if (client->settings->KeyboardType == 8 && + client->settings->KeyboardSubType == 6 && + ((full_code == 0x1f1) || (full_code == 0x1f2))) { + if (full_code == 0x1f1) + vk_code = VK_HANGUL; + else if (full_code == 0x1f2) + vk_code = VK_HANJA; + /* From Linux's keyboard driver at drivers/input/keyboard/atkbd.c */ + /* + * HANGEUL and HANJA keys do not send release events so we need to + * generate such events ourselves + */ + /* RDP works same, there is no release for those 2 Korean keys, + * thus generate release right after press. */ + assert(keyState == WL_KEYBOARD_KEY_STATE_PRESSED); + need_release = true; + } else { + vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, client->settings->KeyboardType); + } assert(vk_code <= 0xFF); if (keyState == WL_KEYBOARD_KEY_STATE_RELEASED) { /* Ignore release if key is not previously pressed. */ @@ -1688,11 +1727,14 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) goto exit; } peerContext->key_state[vk_code>>3] &= ~(1<<(vk_code&0x7)); - } else { + } else if (!need_release /* when release is issued right after, no need to save state */) { peerContext->key_state[vk_code>>3] |= (1<<(vk_code&0x7)); } - if (flags & KBD_FLAGS_EXTENDED) - vk_code |= KBDEXT; + /* Korean keyboard support */ + /* WinPR's GetKeycodeFromVirtualKeyCode() expects no extended bit for VK_HANGUL and VK_HANJA */ + if (vk_code != VK_HANGUL && vk_code != VK_HANJA) + if (flags & KBD_FLAGS_EXTENDED) + vk_code |= KBDEXT; scan_code = GetKeycodeFromVirtualKeyCode(vk_code, KEYCODE_TYPE_EVDEV); @@ -1705,6 +1747,17 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) /*rdp_debug_verbose(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", __func__, code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, vk_code, scan_code, keyState, b->compositor->idle_inhibit);*/ + + if (need_release) { + /* send release of same key */ + weston_compositor_get_time(&time); + notify_key(peerContext->item.seat, &time, + scan_code - 8, WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_AUTOMATIC); + + /*rdp_debug_verbose(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", + __func__, code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, + vk_code, scan_code, keyState, b->compositor->idle_inhibit);*/ + } } exit: diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index b05473b42..3244629d5 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -430,7 +430,7 @@ typedef struct rdp_peer_context RdpPeerContext; /* To enable rdp_debug message, add "--logger-scopes=rdp-backend". */ // rdp.c -void convert_rdp_keyboard_to_xkb_rule_names(UINT32 KeyboardType, UINT32 KeyboardLayout, struct xkb_rule_names *xkbRuleNames); +void convert_rdp_keyboard_to_xkb_rule_names(UINT32 KeyboardType, UINT32 KeyboardSubType, UINT32 KeyboardLayout, struct xkb_rule_names *xkbRuleNames); struct rdp_head * rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp_monitor_mode *monitorMode); void rdp_head_destroy(struct weston_compositor *compositor, struct rdp_head *head); diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 255840d17..6163d6b6d 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -827,8 +827,10 @@ rail_client_LanguageImeInfo_callback(int fd, uint32_t mask, void *arg) if (languageImeInfo->ProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT) { struct xkb_rule_names xkbRuleNames; struct xkb_keymap *keymap = NULL; + settings->KeyboardLayout = languageImeInfo->KeyboardLayout; convert_rdp_keyboard_to_xkb_rule_names(settings->KeyboardType, - languageImeInfo->KeyboardLayout, + settings->KeyboardSubType, + settings->KeyboardLayout, &xkbRuleNames); if (xkbRuleNames.layout) { keymap = xkb_keymap_new_from_names(b->compositor->xkb_context, @@ -838,6 +840,10 @@ rail_client_LanguageImeInfo_callback(int fd, uint32_t mask, void *arg) xkb_keymap_unref(keymap); } } + if (!keymap) { + rdp_debug_error(b, "%s: Failed to switch to kbd_layout:0x%x kbd_type:0x%x kbd_subType:0x%x\n", + __func__, settings->KeyboardLayout, settings->KeyboardType, settings->KeyboardSubType); + } } RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); From 6f3a57b397ccb44ad3b569435b83921ba31daa55 Mon Sep 17 00:00:00 2001 From: brdegeer <75335209+brdegeer@users.noreply.github.com> Date: Mon, 21 Jun 2021 18:55:13 -0700 Subject: [PATCH 1477/1642] Horizontal scroll support (#26) * Debugging implementation * Notify FreeRDP about horizontal scroll support * Fix scroll direction --- libweston/backend-rdp/rdp.c | 117 ++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 46 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 91a171056..fcacabe9c 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1454,6 +1454,8 @@ dump_mouseinput(RdpPeerContext *peerContext, UINT16 flags, UINT16 x, UINT16 y, b rdp_debug_verbose_continue(b, "WHEEL "); if (flags & PTR_FLAGS_WHEEL_NEGATIVE) rdp_debug_verbose_continue(b, "WHEEL_NEGATIVE "); + if (flags & PTR_FLAGS_HWHEEL) + rdp_debug_verbose_continue(b, "HWHEEL "); if (flags & PTR_FLAGS_MOVE) rdp_debug_verbose_continue(b, "MOVE "); if (flags & PTR_FLAGS_DOWN) @@ -1486,11 +1488,71 @@ rdp_validate_button_state(RdpPeerContext *peerContext, bool pressed, uint32_t* b return; } +static bool +rdp_notify_wheel_scroll(RdpPeerContext *peerContext, UINT16 flags, uint32_t axis) +{ + struct weston_pointer_axis_event weston_event; + struct rdp_backend *b = peerContext->rdpBackend; + int ivalue; + double value; + struct timespec time; + + /* + * The RDP specs says the lower bits of flags contains the "the number of rotation + * units the mouse wheel was rotated". + * + * https://blogs.msdn.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value + */ + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + ivalue = (int)((char)(flags & 0xff)); + else + ivalue = (flags & 0xff); + + /* + * Flip the scroll direction as the RDP direction is inverse of X/Wayland + * for vertical scroll + */ + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) + ivalue *= -1; + + /* + * Accumulate the wheel increments. + * + * Every 12 wheel increments, we will send an update to our Wayland + * clients with an updated value for the wheel for smooth scrolling. + * + * Every 120 wheel increments, we tick one discrete wheel click. + */ + peerContext->accumWheelRotationPrecise += ivalue; + peerContext->accumWheelRotationDiscrete += ivalue; + if (abs(peerContext->accumWheelRotationPrecise) >= 12) { + value = (double)(peerContext->accumWheelRotationPrecise / 12); + + weston_event.axis = axis; + weston_event.value = value; + weston_event.discrete = peerContext->accumWheelRotationDiscrete / 120; + weston_event.has_discrete = true; + + rdp_debug_verbose(b, "wheel: value:%f discrete:%d\n", + weston_event.value, weston_event.discrete); + + weston_compositor_get_time(&time); + + notify_axis(peerContext->item.seat, &time, &weston_event); + + peerContext->accumWheelRotationPrecise %= 12; + peerContext->accumWheelRotationDiscrete %= 120; + + return true; + } + + return false; +} + static FREERDP_CB_RET_TYPE xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) { RdpPeerContext *peerContext = (RdpPeerContext *)input->context; - struct rdp_backend *b = peerContext->rdpBackend; uint32_t button = 0; bool need_frame = false; struct timespec time; @@ -1529,53 +1591,15 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) need_frame = true; } + /* Per RDP spec, if both PTRFLAGS_WHEEL and PTRFLAGS_HWHEEL are specified + * then PTRFLAGS_WHEEL takes precedent + */ if (flags & PTR_FLAGS_WHEEL) { - struct weston_pointer_axis_event weston_event; - int ivalue; - double value; - - /* - * The RDP specs says the lower bits of flags contains the "the number of rotation - * units the mouse wheel was rotated". - * - * https://blogs.msdn.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value - */ - if (flags & PTR_FLAGS_WHEEL_NEGATIVE) - ivalue = (int)((char)(flags & 0xff)); - else - ivalue = (flags & 0xff); - - /* Flip the scroll direction as the RDP direction is inverse of X/Wayland */ - ivalue *= -1; - - /* - * Accumulate the wheel increments. - * - * Every 12 wheel increments, we will send an update to our Wayland - * clients with an updated value for the wheel for smooth scrolling. - * - * Every 120 wheel increments, we tick one discrete wheel click. - */ - peerContext->accumWheelRotationPrecise += ivalue; - peerContext->accumWheelRotationDiscrete += ivalue; - if (abs(peerContext->accumWheelRotationPrecise) >= 12) { - value = (double)(peerContext->accumWheelRotationPrecise / 12); - - weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; - weston_event.value = value; - weston_event.discrete = peerContext->accumWheelRotationDiscrete / 120; - weston_event.has_discrete = true; - - rdp_debug_verbose(b, "wheel: value:%f discrete:%d\n", weston_event.value, weston_event.discrete); - - weston_compositor_get_time(&time); - - notify_axis(peerContext->item.seat, &time, &weston_event); + if (rdp_notify_wheel_scroll(peerContext, flags, WL_POINTER_AXIS_VERTICAL_SCROLL)) + need_frame = true; + } else if (flags & PTR_FLAGS_HWHEEL) { + if (rdp_notify_wheel_scroll(peerContext, flags, WL_POINTER_AXIS_HORIZONTAL_SCROLL)) need_frame = true; - - peerContext->accumWheelRotationPrecise %= 12; - peerContext->accumWheelRotationDiscrete %= 120; - } } if (need_frame) @@ -1861,6 +1885,7 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) settings->SupportMonitorLayoutPdu = TRUE; settings->RedirectClipboard = TRUE; settings->HasExtendedMouseEvent = TRUE; + settings->HasHorizontalWheel = TRUE; client->Capabilities = xf_peer_capabilities; client->PostConnect = xf_peer_post_connect; From 1ebdc9881f3ec3fcf166b7e7039ff9770518b4a5 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 22 Jun 2021 11:49:07 -0700 Subject: [PATCH 1478/1642] let open fail if shared memory file already exists (#27) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdputil.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index e0163cb82..f59c81182 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -166,7 +166,7 @@ rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memor strcat(path, "/"); strcat(path, shared_memory->name); - fd = open(path, O_CREAT | O_RDWR, 00600); + fd = open(path, O_CREAT | O_RDWR | O_EXCL, S_IWUSR | S_IRUSR); if (fd < 0) { rdp_debug_error(b, "%s: Failed to open \"%s\" with error: %s\n", __func__, path, strerror(errno)); From 01786e76d9a2c9208cbadf6614042fc35fb5c4f1 Mon Sep 17 00:00:00 2001 From: brdegeer <75335209+brdegeer@users.noreply.github.com> Date: Wed, 23 Jun 2021 11:39:17 -0700 Subject: [PATCH 1479/1642] Fix tabs and separate scroll axis accumulated ticks (#28) --- libweston/backend-rdp/rdp.c | 56 ++++++++++++++++++++++--------------- libweston/backend-rdp/rdp.h | 6 ++-- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index fcacabe9c..364ad49b7 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1496,41 +1496,51 @@ rdp_notify_wheel_scroll(RdpPeerContext *peerContext, UINT16 flags, uint32_t axis int ivalue; double value; struct timespec time; + int *accumWheelRotationPrecise; + int *accumWheelRotationDiscrete; /* - * The RDP specs says the lower bits of flags contains the "the number of rotation - * units the mouse wheel was rotated". - * - * https://blogs.msdn.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value - */ + * The RDP specs says the lower bits of flags contains the "the number of rotation + * units the mouse wheel was rotated". + * + * https://blogs.msdn.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value + */ if (flags & PTR_FLAGS_WHEEL_NEGATIVE) ivalue = (int)((char)(flags & 0xff)); else ivalue = (flags & 0xff); /* - * Flip the scroll direction as the RDP direction is inverse of X/Wayland - * for vertical scroll - */ - if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) + * Flip the scroll direction as the RDP direction is inverse of X/Wayland + * for vertical scroll + */ + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { ivalue *= -1; + accumWheelRotationPrecise = &peerContext->verticalAccumWheelRotationPrecise; + accumWheelRotationDiscrete = &peerContext->verticalAccumWheelRotationDiscrete; + } + else { + accumWheelRotationPrecise = &peerContext->horizontalAccumWheelRotationPrecise; + accumWheelRotationDiscrete = &peerContext->horizontalAccumWheelRotationDiscrete; + } + /* - * Accumulate the wheel increments. - * - * Every 12 wheel increments, we will send an update to our Wayland - * clients with an updated value for the wheel for smooth scrolling. - * - * Every 120 wheel increments, we tick one discrete wheel click. - */ - peerContext->accumWheelRotationPrecise += ivalue; - peerContext->accumWheelRotationDiscrete += ivalue; - if (abs(peerContext->accumWheelRotationPrecise) >= 12) { - value = (double)(peerContext->accumWheelRotationPrecise / 12); + * Accumulate the wheel increments. + * + * Every 12 wheel increments, we will send an update to our Wayland + * clients with an updated value for the wheel for smooth scrolling. + * + * Every 120 wheel increments, we tick one discrete wheel click. + */ + *accumWheelRotationPrecise += ivalue; + *accumWheelRotationDiscrete += ivalue; + if (abs(*accumWheelRotationPrecise) >= 12) { + value = (double)(*accumWheelRotationPrecise / 12); weston_event.axis = axis; weston_event.value = value; - weston_event.discrete = peerContext->accumWheelRotationDiscrete / 120; + weston_event.discrete = *accumWheelRotationDiscrete / 120; weston_event.has_discrete = true; rdp_debug_verbose(b, "wheel: value:%f discrete:%d\n", @@ -1540,8 +1550,8 @@ rdp_notify_wheel_scroll(RdpPeerContext *peerContext, UINT16 flags, uint32_t axis notify_axis(peerContext->item.seat, &time, &weston_event); - peerContext->accumWheelRotationPrecise %= 12; - peerContext->accumWheelRotationDiscrete %= 120; + *accumWheelRotationPrecise %= 12; + *accumWheelRotationDiscrete %= 120; return true; } diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 3244629d5..b6a8be4b4 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -276,8 +276,10 @@ struct rdp_peer_context { bool button_state[5]; char key_state[0xff/8]; // one bit per key. - int accumWheelRotationPrecise; - int accumWheelRotationDiscrete; + int verticalAccumWheelRotationPrecise; + int verticalAccumWheelRotationDiscrete; + int horizontalAccumWheelRotationPrecise; + int horizontalAccumWheelRotationDiscrete; // RAIL support HANDLE vcm; From e8bc2d9a9b7ac7176683a162618c8a30d519a69b Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 25 Jun 2021 09:58:03 -0700 Subject: [PATCH 1480/1642] support svg format icon (#29) Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 9 ++- rdprail-shell/img-load.c | 140 ++++++++++++++++++++++++++++++++++++++ rdprail-shell/meson.build | 6 ++ rdprail-shell/shell.c | 5 +- rdprail-shell/shell.h | 3 + 5 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 rdprail-shell/img-load.c diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index ada1e8e0b..b699ea646 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -49,7 +49,6 @@ #include "shell.h" #include "shared/helpers.h" -#include "shared/image-loader.h" #ifdef HAVE_WINPR2 #include @@ -317,7 +316,7 @@ send_app_entry(struct desktop_shell *shell, char *key, struct app_entry *entry, if (!entry->icon_file) entry->icon_file = find_icon_file(entry->icon); if (entry->icon_file) - app_list_data.appIcon = load_image(entry->icon_file); + app_list_data.appIcon = load_icon_image(shell, entry->icon_file); detach_app_list_namespace(shell); } if (!app_list_data.appIcon) { @@ -837,7 +836,7 @@ app_list_monitor_thread(LPVOID arg) if (!entry->icon_file) entry->icon_file = find_icon_file(entry->icon); if (entry->icon_file) - context->load_icon.image = load_image(entry->icon_file); + context->load_icon.image = load_icon_image(shell, entry->icon_file); detach_app_list_namespace(shell); } shell_rdp_debug(shell, "app_list_monitor_thread: entry %p, image %p\n", entry, context->load_icon.image); @@ -1112,11 +1111,11 @@ void app_list_init(struct desktop_shell *shell) /* load default icon */ iconpath = getenv("WSL2_DEFAULT_APP_ICON"); if (iconpath && (strcmp(iconpath, "disabled") != 0)) - context->default_icon = load_image(iconpath); + context->default_icon = load_icon_image(shell, iconpath); iconpath = getenv("WSL2_DEFAULT_APP_OVERLAY_ICON"); if (iconpath && (strcmp(iconpath, "disabled") != 0)) - context->default_overlay_icon = load_image(iconpath); + context->default_overlay_icon = load_icon_image(shell, iconpath); /* set default language as "en_US". this will be updated once client connected */ strcpy(context->lang_info.requestedClientLanguageId, "en_US"); diff --git a/rdprail-shell/img-load.c b/rdprail-shell/img-load.c new file mode 100644 index 000000000..359779667 --- /dev/null +++ b/rdprail-shell/img-load.c @@ -0,0 +1,140 @@ +/* + * 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 + +#ifdef HAVE_LIBRSVG2 +#include +#endif // HAVE_LIBRSVG2 + +#include "shell.h" + +#include "shared/image-loader.h" + +#ifdef HAVE_LIBRSVG2 +static pixman_image_t * +load_svg(struct desktop_shell *shell, const char *filename) +{ + GError *error = NULL; + RsvgHandle *rsvg = NULL; + cairo_surface_t *surface = NULL; + cairo_t *cr = NULL; + pixman_image_t *image = NULL; + + /* DEPRECATED: g_type_init(); rsvg_init(); */ + /* rsvg_init has been deprecated since version 2.36 and should not be used + in newly-written code. Use g_type_init() */ + /* g_type_init has been deprecated since version 2.36 and should not be used + in newly-written code. the type system is now initialised automatically. */ + rsvg = rsvg_handle_new_from_file(filename, &error); + if (!rsvg) { + shell_rdp_debug(shell, "%s: rsvg_handle_new_from_file failed %s\n", + __func__, filename); + goto Exit; + } + + RsvgDimensionData dimensionData; + rsvg_handle_get_dimensions(rsvg, &dimensionData); + + image = pixman_image_create_bits(PIXMAN_a8r8g8b8, + dimensionData.width, + dimensionData.height, + NULL, + dimensionData.width * 4); + if (!image) { + shell_rdp_debug(shell, "%s: pixman_image_create_bits(%dx%d) failed %s\n", + __func__, dimensionData.width, dimensionData.height, filename); + goto Exit; + } + + surface = cairo_image_surface_create_for_data( + (unsigned char *)pixman_image_get_data(image), + CAIRO_FORMAT_ARGB32, + dimensionData.width, + dimensionData.height, + dimensionData.width * 4); + if (!surface) { + shell_rdp_debug(shell, "%s: cairo_image_surface_create(%dx%d) failed %s\n", + __func__, dimensionData.width, dimensionData.height, filename); + goto Exit; + } + + cr = cairo_create(surface); + if (!cr) { + shell_rdp_debug(shell, "%s: cairo_create failed %s\n", + __func__, filename); + goto Exit; + } + + if (!rsvg_handle_render_cairo(rsvg, cr)) { + shell_rdp_debug(shell, "%s: rsvg_handle_render_cairo failed %s\n", + __func__, filename); + goto Exit; + } + + pixman_image_ref(image); + +Exit: + if (cr) + cairo_destroy(cr); + + if (surface) + cairo_surface_destroy(surface); + + if (image) { + if (pixman_image_unref(image)) + image = NULL; + } + + /* DEPRECATED: rsvg_handle_free(rsvg); */ + /* rsvg_handle_free is deprecated and should not be used in + newly-written code. Use g_object_unref() instead. */ + if (rsvg) + g_object_unref(rsvg); + + /* DEPRECATED: rsvg_term(); */ + /* rsvg_term has been deprecated since version 2.36 and should not be used + in newly-written code. There is no need to de-initialize librsvg. */ + + return image; +} +#endif // HAVE_LIBRSVG2 + +pixman_image_t * +load_icon_image(struct desktop_shell *shell, const char *filename) +{ + pixman_image_t *image; + image = load_image(filename); +#ifdef HAVE_LIBRSVG2 + if (!image) + image = load_svg(shell, filename); +#endif // HAVE_LIBRSVG2 + return image; +} diff --git a/rdprail-shell/meson.build b/rdprail-shell/meson.build index 34a6486b0..c442b2b54 100644 --- a/rdprail-shell/meson.build +++ b/rdprail-shell/meson.build @@ -3,10 +3,15 @@ if get_option('shell-rdprail') if dep_winpr.found() config_h.set('HAVE_WINPR2', '1') endif + dep_librsvg = dependency('librsvg-2.0', version: '>= 2.36.0', required: false) + if dep_librsvg.found() + config_h.set('HAVE_LIBRSVG2', '1') + endif srcs_shell_rdprail = [ 'shell.c', 'input-panel.c', 'app-list.c', + 'img-load.c', weston_desktop_shell_server_protocol_h, weston_desktop_shell_protocol_c, input_method_unstable_v1_server_protocol_h, @@ -18,6 +23,7 @@ if get_option('shell-rdprail') dep_libshared, dep_lib_desktop, dep_libweston_public, + dep_librsvg, dep_winpr, ] plugin_shell_rdprail = shared_library( diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 6f1db1bab..95d8fafd2 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -44,7 +44,6 @@ #include #include "shared/helpers.h" #include "shared/timespec-util.h" -#include "shared/image-loader.h" #include #include #include @@ -4232,12 +4231,12 @@ wet_shell_init(struct weston_compositor *ec, icon_path = getenv("WSL2_DEFAULT_APP_ICON"); if (icon_path && (strcmp(icon_path, "disabled") != 0)) - shell->image_default_app_icon = load_image(icon_path); + shell->image_default_app_icon = load_icon_image(shell, icon_path); shell_rdp_debug(shell, "WSL2_DEFAULT_APP_ICON:%s\n", icon_path); icon_path = getenv("WSL2_DEFAULT_APP_OVERLAY_ICON"); if (icon_path && (strcmp(icon_path, "disabled") != 0)) - shell->image_default_app_overlay_icon = load_image(icon_path); + shell->image_default_app_overlay_icon = load_icon_image(shell, icon_path); shell_rdp_debug(shell, "WSL2_DEFAULT_APP_OVERLAY_ICON:%s\n", icon_path); if (getenv("WESTON_RDPRAIL_SHELL_DISABLE_BLEND_OVERLAY_ICON_TASKBAR")) diff --git a/rdprail-shell/shell.h b/rdprail-shell/shell.h index e8508a837..2cb24b8c1 100644 --- a/rdprail-shell/shell.h +++ b/rdprail-shell/shell.h @@ -181,8 +181,11 @@ shell_blend_overlay_icon(struct desktop_shell *shell, void shell_rdp_debug_print(struct weston_log_scope *scope, bool cont, char *fmt, ...); +// app-list.c void app_list_init(struct desktop_shell *shell); void app_list_destroy(struct desktop_shell *shell); pixman_image_t *app_list_load_icon_file(struct desktop_shell *shell, const char *key); bool app_list_start_backend_update(struct desktop_shell *shell, char *clientLanguageId); void app_list_stop_backend_update(struct desktop_shell *shell); +// img-load.c +pixman_image_t *load_icon_image(struct desktop_shell *shell, const char *filename); From 46756d0e77e5c01b5995fbbee6f3ab0db9b30612 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 28 Jun 2021 13:09:07 -0700 Subject: [PATCH 1481/1642] add CF_TEXT and STRING mapping for clipboard format conversion (#30) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdpclip.c | 72 +++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index f1304855a..150b5ebbb 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -74,15 +74,19 @@ struct rdp_clipboard_supported_format { pfn_process_data pfn; }; -static void *clipboard_process_text(struct rdp_clipboard_data_source *, BOOL); +static void *clipboard_process_text_utf8(struct rdp_clipboard_data_source *, BOOL); +static void *clipboard_process_text_raw(struct rdp_clipboard_data_source *, BOOL); static void *clipboard_process_bmp(struct rdp_clipboard_data_source *, BOOL); static void *clipboard_process_html(struct rdp_clipboard_data_source *, BOOL); +//TODO: need to support to 1:n or m:n format conversion. +//For example, CF_UNICODETEXT to "UTF8_STRING" as well as "text/plain;charset=utf-8". struct rdp_clipboard_supported_format clipboard_supported_formats[] = { - { 0, CF_UNICODETEXT, NULL, "text/plain;charset=utf-8", clipboard_process_text }, - { 1, CF_DIB, NULL, "image/bmp", clipboard_process_bmp }, - { 2, CF_PRIVATE_RTF, "Rich Text Format", "text/rtf", clipboard_process_text }, // same as text - { 3, CF_PRIVATE_HTML, "HTML Format", "text/html", clipboard_process_html }, + { 0, CF_UNICODETEXT, NULL, "text/plain;charset=utf-8", clipboard_process_text_utf8 }, + { 1, CF_TEXT, NULL, "STRING", clipboard_process_text_raw }, + { 2, CF_DIB, NULL, "image/bmp", clipboard_process_bmp }, + { 3, CF_PRIVATE_RTF, "Rich Text Format", "text/rtf", clipboard_process_text_raw }, + { 4, CF_PRIVATE_HTML, "HTML Format", "text/html", clipboard_process_html }, }; #define RDP_NUM_CLIPBOARD_FORMATS ARRAY_LENGTH(clipboard_supported_formats) @@ -152,7 +156,7 @@ clipboard_data_source_state_to_string(struct rdp_clipboard_data_source *source) } static void * -clipboard_process_text(struct rdp_clipboard_data_source *source, BOOL is_send) +clipboard_process_text_utf8(struct rdp_clipboard_data_source *source, BOOL is_send) { freerdp_peer *client = (freerdp_peer*)source->context; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; @@ -240,8 +244,8 @@ clipboard_process_text(struct rdp_clipboard_data_source *source, BOOL is_send) rdp_debug_clipboard_error(b, "RDP %s FAILED (%p:%s): %s (%d bytes)\n", __func__, source, clipboard_data_source_state_to_string(source), is_send ? "send" : "receive", (UINT32)source->data_contents.size); - //rdp_debug_clipboard_verbose(b, "RDP clipboard_process_html FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", - // source, is_send ? "send" : "receive", + //rdp_debug_clipboard_verbose(b, "RDP %s FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", + // __func__, source, is_send ? "send" : "receive", // (char *)source->data_contents.data, // (UINT32)source->data_contents.size); @@ -250,6 +254,43 @@ clipboard_process_text(struct rdp_clipboard_data_source *source, BOOL is_send) return NULL; } +static void * +clipboard_process_text_raw(struct rdp_clipboard_data_source *source, BOOL is_send) +{ + freerdp_peer *client = (freerdp_peer*)source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + if (!source->is_data_processed) { + if (is_send) { + /* Linux to Windows */ + /* Include terminating NULL in size */ + assert((source->data_contents.size + 1) <= source->data_contents.alloc); + assert(((char*)source->data_contents.data)[source->data_contents.size] == '\0'); + source->data_contents.size++; + } else { + /* Windows to Linux */ + char *data = (char*)source->data_contents.data; + size_t data_size = source->data_contents.size; + + /* Windows's data has trailing chars, which Linux doesn't expect. */ + while(data_size && ((data[data_size-1] == '\0') || (data[data_size-1] == '\n'))) + data_size -= 1; + source->data_contents.size = data_size; + } + source->is_data_processed = TRUE; + } + + rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s (%d bytes)\n", + __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); + //rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s \n\"%s\"\n (%d bytes)\n", + // __func__, source, is_send ? "send" : "receive", + // (char *)source->data_contents.data, + // (UINT32)source->data_contents.size); + + return source->data_contents.data; +} + /* based off sample code at https://docs.microsoft.com/en-us/troubleshoot/cpp/add-html-code-clipboard But this missing a lot of corner cases, it must be rewritten with use of proper HTML parser */ /* TODO: This doesn't work for converting HTML from Firefox in Wayland mode to Windows in certain case, @@ -340,8 +381,8 @@ clipboard_process_html(struct rdp_clipboard_data_source *source, BOOL is_send) rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%d bytes)\n", __func__, source, clipboard_data_source_state_to_string(source), is_send ? "send" : "receive", (UINT32)source->data_contents.size); - //rdp_debug_clipboard_verbose(b, "RDP clipboard_process_html (%p): %s \n\"%s\"\n (%d bytes)\n", - // source, is_send ? "send" : "receive", + //rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s \n\"%s\"\n (%d bytes)\n", + // __func__, source, is_send ? "send" : "receive", // (char *)source->data_contents.data, // (UINT32)source->data_contents.size); @@ -353,8 +394,8 @@ clipboard_process_html(struct rdp_clipboard_data_source *source, BOOL is_send) rdp_debug_clipboard_error(b, "RDP %s FAILED (%p:%s): %s (%d bytes)\n", __func__, source, clipboard_data_source_state_to_string(source), is_send ? "send" : "receive", (UINT32)source->data_contents.size); - //rdp_debug_clipboard_verbose(b, "RDP clipboard_process_html FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", - // source, is_send ? "send" : "receive", + //rdp_debug_clipboard_verbose(b, "RDP %s FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", + // __func__, source, is_send ? "send" : "receive", // (char *)source->data_contents.data, // (UINT32)source->data_contents.size); @@ -657,11 +698,16 @@ clipboard_data_source_unref(struct rdp_clipboard_data_source *source) if (source->refcount > 0) return; - if (source->transfer_event_source) + if (source->transfer_event_source) { + /* removing event source must be done from wayland display thread */ + ASSERT_COMPOSITOR_THREAD(b); wl_event_source_remove(source->transfer_event_source); + } if (source->defer_event_source) { rdp_defer_rdp_task_done(peerCtx); + /* removing event source must be done from wayland display thread */ + ASSERT_COMPOSITOR_THREAD(b); wl_event_source_remove(source->defer_event_source); } From e3aeffcd89b6932d061fd4409d8b5f057880d00f Mon Sep 17 00:00:00 2001 From: brdegeer <75335209+brdegeer@users.noreply.github.com> Date: Tue, 20 Jul 2021 10:26:54 -0700 Subject: [PATCH 1482/1642] Add mouse button swap support to RDP backend (#31) --- libweston/backend-rdp/rdp.c | 16 ++++++++++++---- libweston/backend-rdp/rdp.h | 1 + libweston/backend-rdp/rdprail.c | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 364ad49b7..7edeba8e2 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1580,10 +1580,18 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) need_frame = true; } - if (flags & PTR_FLAGS_BUTTON1) - button = BTN_LEFT; - else if (flags & PTR_FLAGS_BUTTON2) - button = BTN_RIGHT; + if (flags & PTR_FLAGS_BUTTON1) { + if (peerContext->mouseButtonSwap) + button = BTN_RIGHT; + else + button = BTN_LEFT; + } + else if (flags & PTR_FLAGS_BUTTON2) { + if (peerContext->mouseButtonSwap) + button = BTN_LEFT; + else + button = BTN_RIGHT; + } else if (flags & PTR_FLAGS_BUTTON3) button = BTN_MIDDLE; diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index b6a8be4b4..021ebe166 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -275,6 +275,7 @@ struct rdp_peer_context { struct rdp_peers_item item; bool button_state[5]; + bool mouseButtonSwap; char key_state[0xff/8]; // one bit per key. int verticalAccumWheelRotationPrecise; int verticalAccumWheelRotationDiscrete; diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 6163d6b6d..825f05588 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -518,6 +518,7 @@ rail_client_ClientSysparam_callback(int fd, uint32_t mask, void *arg) if (sysparam->params & SPI_MASK_SET_MOUSE_BUTTON_SWAP) { rdp_debug(b, "Client: ClientSysparam: mouseButtonSwap:%d\n", sysparam->mouseButtonSwap); + peerCtx->mouseButtonSwap = sysparam->mouseButtonSwap; } if (sysparam->params & SPI_MASK_SET_WORK_AREA) { From 3c01d123456603e98e670f9becbea7179f26a5cf Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 23 Jul 2021 10:44:42 -0700 Subject: [PATCH 1483/1642] support keyboard layout for Persian and Dari (#32) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 7edeba8e2..300e5203e 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1024,7 +1024,7 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_ITALIAN, "it", 0}, {KBD_ITALIAN_142, "it", "nodeadkeys"}, {KBD_JAPANESE, "jp", 0}, - {KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, "jp", 0}, // default to alphabet input instead of "kana". + {KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, "jp", 0}, // variant is changed to alphabetical input (0) from "kana". {KBD_KOREAN, "kr", 0}, {KBD_KOREAN_INPUT_SYSTEM_IME_2000, "kr", "kr104"}, {KBD_DUTCH, "nl", 0}, @@ -1050,7 +1050,14 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_ESTONIAN, "ee", 0}, {KBD_LATVIAN, "lv", 0}, {KBD_LITHUANIAN_IBM, "lt", "ibm"}, - {KBD_FARSI, "af", 0}, + // 0x429 (KBD_FARSI) is for Persian(Iran) + // TODO: define exact match with Windows layout in Xkb. + // Such as key ~ is 1,2,3...0 on Windows, not Persian numbers, + // but Xkb doesn't have that layout in "ir" group. + {KBD_FARSI, "ir", "pes"}, + // 0x50429 is for Dari(Afghanistan) + // TODO: define KBD_DARI in winpr's keyboard.h + {0x50429, "af", "basic"}, {KBD_VIETNAMESE, "vn", 0}, {KBD_ARMENIAN_EASTERN, "am", 0}, {KBD_AZERI_LATIN, 0, 0}, From 1c989c3d8e85cdbdbdab2625a64f5aff7910fb84 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 23 Jul 2021 13:41:54 -0700 Subject: [PATCH 1484/1642] fix hidden taskbar doesn't slide up wit maximized window (#33) Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 2 + libweston/backend-rdp/rdprail.c | 81 ++++++++++++++------------------- rdprail-shell/shell.c | 15 ++++++ 3 files changed, 51 insertions(+), 47 deletions(-) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index bc63d808d..5b93993d4 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -213,6 +213,8 @@ struct weston_surface_rail_state { bool is_minimized_requested; bool is_maximized; bool is_maximized_requested; + bool is_fullscreen; + bool is_fullscreen_requested; bool forceRecreateSurface; bool forceUpdateWindowState; bool error; diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 825f05588..f73c67122 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -46,6 +46,9 @@ #include "libweston-internal.h" +#define RAIL_WINDOW_FULLSCREEN_STYLE (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_GROUP | WS_TABSTOP) +#define RAIL_WINDOW_NORMAL_STYLE (RAIL_WINDOW_FULLSCREEN_STYLE | WS_THICKFRAME | WS_CAPTION) + extern PWtsApiFunctionTable FreeRDP_InitWtsApi(void); static void rdp_rail_destroy_window(struct wl_listener *listener, void *data); @@ -1342,52 +1345,8 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) (WINDOW_ORDER_TYPE_WINDOW | WINDOW_ORDER_STATE_NEW); window_order_info.windowId = window_id; - /* - #define WS_OVERLAPPED 0x00000000L - #define WS_POPUP 0x80000000L - #define WS_CHILD 0x40000000L - #define WS_MINIMIZE 0x20000000L - #define WS_VISIBLE 0x10000000L - #define WS_DISABLED 0x08000000L - #define WS_CLIPSIBLINGS 0x04000000L - #define WS_CLIPCHILDREN 0x02000000L - #define WS_MAXIMIZE 0x01000000L - #define WS_CAPTION 0x00C00000L - #define WS_BORDER 0x00800000L - #define WS_DLGFRAME 0x00400000L - #define WS_VSCROLL 0x00200000L - #define WS_HSCROLL 0x00100000L - #define WS_SYSMENU 0x00080000L - #define WS_THICKFRAME 0x00040000L - #define WS_GROUP 0x00020000L - #define WS_TABSTOP 0x00010000L - #define WS_MINIMIZEBOX 0x00020000L - #define WS_MAXIMIZEBOX 0x00010000L - - #define WS_EX_DLGMODALFRAME 0x00000001L - #define WS_EX_NOPARENTNOTIFY 0x00000004L - #define WS_EX_TOPMOST 0x00000008L - #define WS_EX_ACCEPTFILES 0x00000010L - #define WS_EX_TRANSPARENT 0x00000020L - #define WS_EX_MDICHILD 0x00000040L - #define WS_EX_TOOLWINDOW 0x00000080L - #define WS_EX_WINDOWEDGE 0x00000100L - #define WS_EX_CLIENTEDGE 0x00000200L - #define WS_EX_CONTEXTHELP 0x00000400L - #define WS_EX_RIGHT 0x00001000L - #define WS_EX_LEFT 0x00000000L - #define WS_EX_RTLREADING 0x00002000L - #define WS_EX_LTRREADING 0x00000000L - #define WS_EX_LEFTSCROLLBAR 0x00004000L - #define WS_EX_RIGHTSCROLLBAR 0x00000000L - #define WS_EX_CONTROLPARENT 0x00010000L - #define WS_EX_STATICEDGE 0x00020000L - #define WS_EX_APPWINDOW 0x00040000L - #define WS_EX_LAYERED 0x00080000L - */ - window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_STYLE; - window_state_order.style = (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_THICKFRAME | WS_GROUP | WS_TABSTOP); + window_state_order.style = RAIL_WINDOW_NORMAL_STYLE; window_state_order.extendedStyle = WS_EX_LAYERED; window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_OWNER; @@ -1849,10 +1808,26 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter window_id, rail_state->is_minimized_requested); } - if (rail_state->is_maximized != rail_state->is_maximized_requested) + if (rail_state->is_maximized != rail_state->is_maximized_requested) { rdp_debug_verbose(b, "WindowUpdate(0x%x - is_maximized:%d)\n", window_id, rail_state->is_maximized_requested); - rail_state->is_maximized = rail_state->is_maximized_requested; + rail_state->is_maximized = rail_state->is_maximized_requested; + } + + if (rail_state->is_fullscreen != rail_state->is_fullscreen_requested) { + rdp_debug_verbose(b, "WindowUpdate(0x%x - is_fullscreen:%d)\n", + window_id, rail_state->is_fullscreen_requested); + rail_state->is_fullscreen = rail_state->is_fullscreen_requested; + + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_STYLE; + if (rail_state->is_fullscreen) + window_state_order.style = RAIL_WINDOW_FULLSCREEN_STYLE; + else + window_state_order.style = RAIL_WINDOW_NORMAL_STYLE; + window_state_order.extendedStyle = WS_EX_LAYERED; + /* force update window geometry */ + rail_state->forceUpdateWindowState = true; + } if (rail_state->forceUpdateWindowState || rail_state->get_label != surface->get_label) { @@ -1948,6 +1923,16 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter window_state_order.visibilityRects = &window_vis; window_state_order.clientAreaWidth = newClientPos.width; window_state_order.clientAreaHeight = newClientPos.height; + if (!rail_state->is_fullscreen) { + /* when window is not in fullscreen, there should be 'some' area for title bar, + thus substracting 32 pixels out from window size for client area, this value + does not need to be accurate at all, all here need to tell RDP client is that + 'real' application client area size is different from window size. + To pursue accuracy if desired, this value can be pulled from X for X app, + but this seems not possible for Wayland native application. */ + if (window_state_order.clientAreaHeight > 8) + window_state_order.clientAreaHeight -= 8; + } /* if previous window size is 0 and new window is not, show and place in taskbar (if not set yet) */ @@ -3463,6 +3448,8 @@ rdp_rail_dump_window_iter(void *element, void *data) rail_state->is_minimized, rail_state->is_minimized_requested); fprintf(fp," isWindowMaximized:%d, isWindowMaximizedRequested:%d\n", rail_state->is_maximized, rail_state->is_maximized_requested); + fprintf(fp," isWindowFullscreen:%d, isWindowFullscreenRequested:%d\n", + rail_state->is_fullscreen, rail_state->is_fullscreen_requested); fprintf(fp," forceRecreateSurface:%d, error:%d\n", rail_state->forceRecreateSurface, rail_state->error); fprintf(fp," isUdatePending:%d, isFirstUpdateDone:%d\n", diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 95d8fafd2..00e9ad454 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -1571,6 +1571,15 @@ weston_view_set_initial_position(struct weston_view *view, static void unset_fullscreen(struct shell_surface *shsurf) { + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; + + if (!rail_state) + return; + rail_state->is_fullscreen_requested = false; + /* Unset the fullscreen output, driver configuration and transforms. */ wl_list_remove(&shsurf->fullscreen.transform.link); wl_list_init(&shsurf->fullscreen.transform.link); @@ -2336,8 +2345,14 @@ set_fullscreen(struct shell_surface *shsurf, bool fullscreen, struct weston_desktop_surface *desktop_surface = shsurf->desktop_surface; struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; int32_t width = 0, height = 0; + if (!rail_state) + return; + rail_state->is_fullscreen_requested = fullscreen; + if (fullscreen) { /* handle clients launching in fullscreen */ if (output == NULL && !weston_surface_is_mapped(surface)) { From cdbaee07c731a599de20e46e1b87947fd9b0feec Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 27 Jul 2021 19:48:58 -0700 Subject: [PATCH 1485/1642] support br_abnt2 and Japanese on US keyboard layout (#34) * support br_abnt2 and Japanese on US keyboard layout * change model to pc105 for br_abnt2 Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 19 ++++++++-- libweston/backend-rdp/rdprail.c | 63 +++++++++++++++++++++++++++------ 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 300e5203e..09debbae9 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1109,7 +1109,7 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_IRISH, 0, 0}, {KBD_BOSNIAN_CYRILLIC, "ba", "us"}, {KBD_UNITED_STATES_DVORAK, "us", "dvorak"}, - {KBD_PORTUGUESE_BRAZILIAN_ABNT2, "br", "nativo"}, + {KBD_PORTUGUESE_BRAZILIAN_ABNT2, "br", "abnt2"}, {KBD_CANADIAN_MULTILINGUAL_STANDARD, "ca", "multix"}, {KBD_GAELIC, "ie", "CloGaelach"}, @@ -1147,6 +1147,7 @@ convert_rdp_keyboard_to_xkb_rule_names( break; } } + /* Korean keyboard support (KeyboardType 8, LangID 0x412) */ if (KeyboardType == 8 && ((KeyboardLayout & 0xFFFF) == 0x412)) { /* TODO: PC/AT 101 Enhanced Korean Keyboard (Type B) and (Type C) is not supported yet @@ -1160,8 +1161,20 @@ convert_rdp_keyboard_to_xkb_rule_names( else if (KeyboardSubType == 6) // PC/AT 103 Enhanced Korean Keyboard xkbRuleNames->variant = "kr106"; // kr(hw_keys) } - weston_log("%s: matching layout=%s variant=%s options=%s\n", __FUNCTION__, - xkbRuleNames->layout, xkbRuleNames->variant, xkbRuleNames->options); + /* Japanese keyboard layout is used with other than Japanese 106/109 keyboard */ + else if (KeyboardType != 7 && ((KeyboardLayout & 0xFFFF) == 0x411)) { + /* when Japanese keyboard layout is used other than Japanese 106/109 keyboard (keyboard type 7), + use "us" layout, since the "jp" layout in xkb expects Japanese 106/109 keyboard layout. */ + xkbRuleNames->layout = "us"; + xkbRuleNames->variant = 0; + } + /* Brazilian ABNT2 keyboard */ + else if (KeyboardLayout == KBD_PORTUGUESE_BRAZILIAN_ABNT2) { + xkbRuleNames->model = "pc105"; + } + + weston_log("%s: matching model=%s layout=%s variant=%s options=%s\n", __FUNCTION__, + xkbRuleNames->model, xkbRuleNames->layout, xkbRuleNames->variant, xkbRuleNames->options); } static BOOL diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index f73c67122..feffb8dbf 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -751,8 +751,8 @@ languageGuid_to_string(const GUID *guid) } lang_GUID; static const lang_GUID c_GUID_NULL = GUID_NULL; - static const lang_GUID c_GUID_MSIME_JPN = GUID_MSIME_JPN; - static const lang_GUID c_GUID_MSIME_KOR = GUID_MSIME_KOR; + static const lang_GUID c_GUID_JPNIME = GUID_MSIME_JPN; + static const lang_GUID c_GUID_KORIME = GUID_MSIME_KOR; static const lang_GUID c_GUID_CHSIME = GUID_CHSIME; static const lang_GUID c_GUID_CHTIME = GUID_CHTIME; static const lang_GUID c_GUID_PROFILE_NEWPHONETIC = GUID_PROFILE_NEWPHONETIC; @@ -767,10 +767,10 @@ languageGuid_to_string(const GUID *guid) RPC_STATUS rpc_status; if (UuidEqual(guid, (GUID *)&c_GUID_NULL, &rpc_status)) return "GUID_NULL"; - else if (UuidEqual(guid, (GUID *)&c_GUID_MSIME_JPN, &rpc_status)) - return "GUID_MSIME_JPN"; - else if (UuidEqual(guid, (GUID *)&c_GUID_MSIME_KOR, &rpc_status)) - return "GUID_MSIME_KOR"; + else if (UuidEqual(guid, (GUID *)&c_GUID_JPNIME, &rpc_status)) + return "GUID_JPNIME"; + else if (UuidEqual(guid, (GUID *)&c_GUID_KORIME, &rpc_status)) + return "GUID_KORIME"; else if (UuidEqual(guid, (GUID *)&c_GUID_CHSIME, &rpc_status)) return "GUID_CHSIME"; else if (UuidEqual(guid, (GUID *)&c_GUID_CHTIME, &rpc_status)) @@ -804,6 +804,9 @@ rail_client_LanguageImeInfo_callback(int fd, uint32_t mask, void *arg) rdpSettings *settings = client->settings; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; + UINT32 new_keyboard_layout = 0; + struct xkb_keymap *keymap = NULL; + struct xkb_rule_names xkbRuleNames; char *s; ASSERT_COMPOSITOR_THREAD(b); @@ -829,12 +832,49 @@ rail_client_LanguageImeInfo_callback(int fd, uint32_t mask, void *arg) rdp_debug(b, "Client: LanguageImeInfo: KeyboardLayout: 0x%x\n", languageImeInfo->KeyboardLayout); if (languageImeInfo->ProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT) { - struct xkb_rule_names xkbRuleNames; - struct xkb_keymap *keymap = NULL; - settings->KeyboardLayout = languageImeInfo->KeyboardLayout; + new_keyboard_layout = languageImeInfo->KeyboardLayout; + } else if (languageImeInfo->ProfileType == TF_PROFILETYPE_INPUTPROCESSOR) { + typedef struct _lang_GUID + { + UINT32 Data1; + UINT16 Data2; + UINT16 Data3; + BYTE Data4_0; + BYTE Data4_1; + BYTE Data4_2; + BYTE Data4_3; + BYTE Data4_4; + BYTE Data4_5; + BYTE Data4_6; + BYTE Data4_7; + } lang_GUID; + + static const lang_GUID c_GUID_JPNIME = GUID_MSIME_JPN; + static const lang_GUID c_GUID_KORIME = GUID_MSIME_KOR; + static const lang_GUID c_GUID_CHSIME = GUID_CHSIME; + static const lang_GUID c_GUID_CHTIME = GUID_CHTIME; + + RPC_STATUS rpc_status; + if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, + (GUID *)&c_GUID_JPNIME, &rpc_status)) + new_keyboard_layout = KBD_JAPANESE; + else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, + (GUID *)&c_GUID_KORIME, &rpc_status)) + new_keyboard_layout = KBD_KOREAN; + else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, + (GUID *)&c_GUID_CHSIME, &rpc_status)) + new_keyboard_layout = KBD_CHINESE_SIMPLIFIED_US; + else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, + (GUID *)&c_GUID_CHTIME, &rpc_status)) + new_keyboard_layout = KBD_CHINESE_TRADITIONAL_US; + else + new_keyboard_layout = KBD_US; + } + + if (new_keyboard_layout && (new_keyboard_layout != settings->KeyboardLayout)) { convert_rdp_keyboard_to_xkb_rule_names(settings->KeyboardType, settings->KeyboardSubType, - settings->KeyboardLayout, + new_keyboard_layout, &xkbRuleNames); if (xkbRuleNames.layout) { keymap = xkb_keymap_new_from_names(b->compositor->xkb_context, @@ -842,11 +882,12 @@ rail_client_LanguageImeInfo_callback(int fd, uint32_t mask, void *arg) if (keymap) { weston_seat_update_keymap(peerCtx->item.seat, keymap); xkb_keymap_unref(keymap); + settings->KeyboardLayout = new_keyboard_layout; } } if (!keymap) { rdp_debug_error(b, "%s: Failed to switch to kbd_layout:0x%x kbd_type:0x%x kbd_subType:0x%x\n", - __func__, settings->KeyboardLayout, settings->KeyboardType, settings->KeyboardSubType); + __func__, new_keyboard_layout, settings->KeyboardType, settings->KeyboardSubType); } } From 7c0bbd09f40f796eb0d2c0974e8f91347d9ef08f Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 17 Aug 2021 09:55:54 -0700 Subject: [PATCH 1486/1642] support client focus proxy window (#35) Co-authored-by: Hideyuki Nagase --- clients/meson.build | 15 +++ clients/rdprail-shell.c | 182 +++++++++++++++++++++++++++++ protocol/meson.build | 1 + protocol/weston-rdprail-shell.xml | 35 ++++++ rdprail-shell/meson.build | 4 +- rdprail-shell/shell.c | 188 ++++++++++++++++++++++++++++-- rdprail-shell/shell.h | 15 ++- 7 files changed, 426 insertions(+), 14 deletions(-) create mode 100644 clients/rdprail-shell.c create mode 100644 protocol/weston-rdprail-shell.xml diff --git a/clients/meson.build b/clients/meson.build index 2c016b845..af4148f86 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -371,3 +371,18 @@ if get_option('shell-ivi') ) env_modmap += 'weston-ivi-shell-user-interface=@0@;'.format(exe_shell_ivi_ui.full_path()) endif + + +if get_option('shell-rdprail') + exe_shell_rdprail = executable( + 'weston-rdprail-shell', + 'rdprail-shell.c', + weston_rdprail_shell_client_protocol_h, + weston_rdprail_shell_protocol_c, + include_directories: common_inc, + dependencies: dep_toytoolkit, + install: true, + install_dir: get_option('libexecdir') + ) + env_modmap += 'weston-rdprail-shell=@0@;'.format(exe_shell_rdprail.full_path()) +endif diff --git a/clients/rdprail-shell.c b/clients/rdprail-shell.c new file mode 100644 index 000000000..69d6af8ca --- /dev/null +++ b/clients/rdprail-shell.c @@ -0,0 +1,182 @@ +/* + * Copyright © 2011 Kristian Høgsberg + * Copyright © 2011 Collabora, Ltd. + * + * 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 +#include +#include +#include +#include + +#include +#include "window.h" +#include "shared/cairo-util.h" +#include +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include +#include "shared/file-util.h" + +#include "weston-rdprail-shell-client-protocol.h" + +struct focus_proxy_window { + struct window *window; + struct widget *widget; +}; + +struct desktop { + struct display *display; + struct weston_rdprail_shell *shell; + + struct focus_proxy_window *focus_proxy_window; +}; + +static void +focus_proxy_window_redraw_handler(struct widget *widget, void *data) +{ +} + +static void +focus_proxy_window_resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ +} + +static struct focus_proxy_window * +focus_proxy_create(struct desktop *desktop) +{ + struct focus_proxy_window *focus_proxy_window = NULL; + + focus_proxy_window = xzalloc(sizeof *focus_proxy_window); + if (!focus_proxy_window) + goto error_exit; + + focus_proxy_window->window = window_create(desktop->display); + if (!focus_proxy_window->window) + goto error_exit; + + focus_proxy_window->widget = window_add_widget(focus_proxy_window->window, focus_proxy_window); + if (!focus_proxy_window->widget) + goto error_exit; + + widget_set_allocation(focus_proxy_window->widget, 0, 0, 0, 0); + + window_set_title(focus_proxy_window->window, "rdprail-shell focus proxy window"); + window_set_user_data(focus_proxy_window->window, focus_proxy_window); + + widget_set_redraw_handler(focus_proxy_window->widget, focus_proxy_window_redraw_handler); + widget_set_resize_handler(focus_proxy_window->widget, focus_proxy_window_resize_handler); + + struct wl_surface *s = window_get_wl_surface(focus_proxy_window->window); + weston_rdprail_shell_set_focus_proxy(desktop->shell, s); + + return focus_proxy_window; + +error_exit: + if (focus_proxy_window && focus_proxy_window->widget) + widget_destroy(focus_proxy_window->widget); + + if (focus_proxy_window && focus_proxy_window->window) + window_destroy(focus_proxy_window->window); + + if (focus_proxy_window) + free(focus_proxy_window); + + return NULL; +} + +static void +focus_proxy_destroy(struct focus_proxy_window* focus_proxy_window) +{ + widget_destroy(focus_proxy_window->widget); + window_destroy(focus_proxy_window->window); + + free(focus_proxy_window); +} + +static void +global_handler(struct display *display, uint32_t id, + const char *interface, uint32_t version, void *data) +{ + struct desktop *desktop = data; + + if (!strcmp(interface, "weston_rdprail_shell")) { + desktop->shell = display_bind(desktop->display, + id, + &weston_rdprail_shell_interface, + 1); + } +} + +static void +sigchild_handler(int s) +{ + int status; + pid_t pid; + + while (pid = waitpid(-1, &status, WNOHANG), pid > 0) + fprintf(stderr, "child %d exited\n", pid); +} + +int main(int argc, char *argv[]) +{ + struct desktop desktop = { 0 }; + + desktop.display = display_create(&argc, argv); + if (desktop.display == NULL) { + fprintf(stderr, "failed to create display: %s\n", + strerror(errno)); + return -1; + } + + display_set_user_data(desktop.display, &desktop); + display_set_global_handler(desktop.display, global_handler); + + desktop.focus_proxy_window = focus_proxy_create(&desktop); + if (desktop.focus_proxy_window == NULL) { + fprintf(stderr, "failed to create focus proxy window.\n"); + return -1; + } + + signal(SIGCHLD, sigchild_handler); + + display_run(desktop.display); + + focus_proxy_destroy(desktop.focus_proxy_window); + weston_rdprail_shell_destroy(desktop.shell); + display_destroy(desktop.display); + + return 0; +} diff --git a/protocol/meson.build b/protocol/meson.build index 25cea5a8f..072d4495a 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -29,6 +29,7 @@ generated_protocols = [ [ 'viewporter', 'stable' ], [ 'weston-debug', 'internal' ], [ 'weston-desktop-shell', 'internal' ], + [ 'weston-rdprail-shell', 'internal' ], [ 'weston-screenshooter', 'internal' ], [ 'weston-content-protection', 'internal' ], [ 'weston-test', 'internal' ], diff --git a/protocol/weston-rdprail-shell.xml b/protocol/weston-rdprail-shell.xml new file mode 100644 index 000000000..7ed9b0a7c --- /dev/null +++ b/protocol/weston-rdprail-shell.xml @@ -0,0 +1,35 @@ + + + + + This interface defines RDP-RAIL shell specific methods. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/rdprail-shell/meson.build b/rdprail-shell/meson.build index c442b2b54..8175adc96 100644 --- a/rdprail-shell/meson.build +++ b/rdprail-shell/meson.build @@ -12,8 +12,8 @@ if get_option('shell-rdprail') 'input-panel.c', 'app-list.c', 'img-load.c', - weston_desktop_shell_server_protocol_h, - weston_desktop_shell_protocol_c, + weston_rdprail_shell_server_protocol_h, + weston_rdprail_shell_protocol_c, input_method_unstable_v1_server_protocol_h, input_method_unstable_v1_protocol_c, ] diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 00e9ad454..884516f0c 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -40,7 +40,7 @@ #include "shell.h" #include "compositor/weston.h" -#include "weston-desktop-shell-server-protocol.h" +#include "weston-rdprail-shell-server-protocol.h" #include #include "shared/helpers.h" #include "shared/timespec-util.h" @@ -474,7 +474,7 @@ shell_grab_start(struct shell_grab *grab, const struct weston_pointer_grab_interface *interface, struct shell_surface *shsurf, struct weston_pointer *pointer, - enum weston_desktop_shell_cursor cursor) + enum weston_rdprail_shell_cursor cursor) { struct desktop_shell *shell = shsurf->shell; @@ -651,13 +651,18 @@ static void shell_configuration(struct desktop_shell *shell) { struct weston_config_section *section; - char *s; + char *s, *client; bool allow_zap; bool is_localmove_supported; section = weston_config_get_section(wet_get_config(shell->compositor), "shell", NULL, NULL); + client = wet_get_libexec_path("weston-rdprail-shell"); + weston_config_section_get_string(section, "client", &s, client); + free(client); + shell->client = s; + /* default to not allow zap */ weston_config_section_get_bool(section, "allow-zap", &allow_zap, false); @@ -1152,7 +1157,7 @@ surface_move(struct shell_surface *shsurf, struct weston_pointer *pointer, move->client_initiated = client_initiated; shell_grab_start(&move->base, &move_grab_interface, shsurf, - pointer, WESTON_DESKTOP_SHELL_CURSOR_MOVE); + pointer, WESTON_RDPRAIL_SHELL_CURSOR_MOVE); return 0; } @@ -2067,8 +2072,10 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, static void desktop_surface_removed(struct weston_desktop_surface *desktop_surface, - void *shell) + void *_shell) { + struct desktop_shell *shell = + (struct desktop_shell *)_shell; struct shell_surface *shsurf = weston_desktop_surface_get_user_data(desktop_surface); struct shell_surface *shsurf_child, *tmp; @@ -2078,6 +2085,10 @@ desktop_surface_removed(struct weston_desktop_surface *desktop_surface, if (!shsurf) return; + /* if this is focus proxy, reset to NULL */ + if (shell->focus_proxy_surface == surface) + shell->focus_proxy_surface = NULL; + wl_list_for_each_safe(shsurf_child, tmp, &shsurf->children_list, children_link) { wl_list_remove(&shsurf_child->children_link); wl_list_init(&shsurf_child->children_link); @@ -2716,7 +2727,7 @@ set_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer) return; shell_grab_start(grab, &busy_cursor_grab_interface, shsurf, pointer, - WESTON_DESKTOP_SHELL_CURSOR_BUSY); + WESTON_RDPRAIL_SHELL_CURSOR_BUSY); /* Mark the shsurf as ungrabbed so that button binding is able * to move it. */ shsurf->grabbed = 0; @@ -3223,7 +3234,7 @@ surface_rotate(struct shell_surface *shsurf, struct weston_pointer *pointer) } shell_grab_start(&rotate->base, &rotate_grab_interface, shsurf, - pointer, WESTON_DESKTOP_SHELL_CURSOR_ARROW); + pointer, WESTON_RDPRAIL_SHELL_CURSOR_ARROW); } /* @@ -3363,7 +3374,9 @@ activate(struct desktop_shell *shell, struct weston_view *view, shell_surface_update_layer(shsurf); if (shell->rdprail_api->notify_window_zorder_change) - shsurf->shell->rdprail_api->notify_window_zorder_change(shell->compositor, es); + shell->rdprail_api->notify_window_zorder_change( + shell->compositor, + shell->focus_proxy_surface == es ? NULL : es); } /* no-op func for checking black surface */ @@ -3444,9 +3457,14 @@ shell_backend_request_window_activate(void *shell_context, struct weston_seat *s struct shell_surface *shsurf; if (!surface) { - /* Here, focus is moving to a window in client side, thus none of Linux app has focus. */ - /* TODO: move focus to dummy marker window, thus Linux app can correctly show as - 'not focused' state (such as title bar) while client application has focus. */ + /* Here, focus is moving to a window in client side, thus none of Linux app has focus, + so move the focus to dummy marker window (focus_proxy), thus the rest of Linux app + window can correctly show as 'not focused' state (such as title bar) while client + (Windows) application has focus. */ + surface = shell->focus_proxy_surface; + } + if (!surface) { + /* if no proxy window provided, force set marker window to focus at backend */ if (shell->rdprail_api->notify_window_zorder_change) { shell->rdprail_api->notify_window_zorder_change(shell->compositor, NULL); /* schedule repaint to force send z order */ @@ -3620,6 +3638,145 @@ weston_view_set_initial_position(struct weston_view *view, weston_view_set_position(view, x, y); } +static bool +check_desktop_shell_crash_too_early(struct desktop_shell *shell) +{ + struct timespec now; + + if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) + return false; + + /* + * If the shell helper client dies before the session has been + * up for roughly 30 seconds, better just make Weston shut down, + * because the user likely has no way to interact with the desktop + * anyway. + */ + if (now.tv_sec - shell->startup_time.tv_sec < 30) { + shell_rdp_debug(shell, "Error: %s apparently cannot run at all.\n", + shell->client); + shell_rdp_debug(shell, STAMP_SPACE "Quitting..."); + weston_compositor_exit_with_code(shell->compositor, + EXIT_FAILURE); + + return true; + } + + return false; +} + +static void launch_desktop_shell_process(void *data); + +static void +respawn_desktop_shell_process(struct desktop_shell *shell) +{ + struct timespec time; + + /* if desktop-shell dies more than 5 times in 30 seconds, give up */ + weston_compositor_get_time(&time); + if (timespec_sub_to_msec(&time, &shell->child.deathstamp) > 30000) { + shell->child.deathstamp = time; + shell->child.deathcount = 0; + } + + shell->child.deathcount++; + if (shell->child.deathcount > 5) { + shell_rdp_debug(shell, "%s disconnected, giving up.\n", shell->client); + return; + } + + shell_rdp_debug(shell, "%s disconnected, respawning...\n", shell->client); + launch_desktop_shell_process(shell); +} + +static void +desktop_shell_client_destroy(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell; + + shell = container_of(listener, struct desktop_shell, + child.client_destroy_listener); + + wl_list_remove(&shell->child.client_destroy_listener.link); + shell->child.client = NULL; + + /* client is terminated, so focus_proxy is destroyed too. */ + shell->focus_proxy_surface = NULL; + + /* + * unbind_desktop_shell() will reset shell->child.desktop_shell + * before the respawned process has a chance to create a new + * desktop_shell object, because we are being called from the + * wl_client destructor which destroys all wl_resources before + * returning. + */ + + if (!check_desktop_shell_crash_too_early(shell)) + respawn_desktop_shell_process(shell); +} + +static void +launch_desktop_shell_process(void *data) +{ + struct desktop_shell *shell = data; + + shell->child.client = weston_client_start(shell->compositor, + shell->client); + + if (!shell->child.client) { + shell_rdp_debug(shell, "not able to start %s\n", shell->client); + return; + } + + shell->child.client_destroy_listener.notify = + desktop_shell_client_destroy; + wl_client_add_destroy_listener(shell->child.client, + &shell->child.client_destroy_listener); +} + +static void +desktop_shell_set_focus_proxy(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + shell->focus_proxy_surface = wl_resource_get_user_data(surface_resource); +} + +static const struct weston_rdprail_shell_interface rdprail_shell_implementation = { + desktop_shell_set_focus_proxy, +}; + +static void +unbind_desktop_shell(struct wl_resource *resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + + shell->child.desktop_shell = NULL; +} + +static void +bind_desktop_shell(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct desktop_shell *shell = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &weston_rdprail_shell_interface, + 1, id); + + if (client == shell->child.client) { + wl_resource_set_implementation(resource, + &rdprail_shell_implementation, + shell, unbind_desktop_shell); + shell->child.desktop_shell = resource; + return; + } + + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "permission to bind rdprail_shell denied"); +} + static void force_kill_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) @@ -4139,6 +4296,7 @@ wet_shell_init(struct weston_compositor *ec, struct desktop_shell *shell; struct workspace **pws; unsigned int i; + struct wl_event_loop *loop; char *debug_level; char *icon_path; @@ -4215,8 +4373,16 @@ wet_shell_init(struct weston_compositor *ec, if (!shell->desktop) return -1; + if (wl_global_create(ec->wl_display, + &weston_rdprail_shell_interface, 1, + shell, bind_desktop_shell) == NULL) + return -1; + setup_output_destroy_handler(ec, shell); + loop = wl_display_get_event_loop(ec->wl_display); + wl_event_loop_add_idle(loop, launch_desktop_shell_process, shell); + wl_list_for_each(seat, &ec->seat_list, link) handle_seat_created(NULL, seat); shell->seat_create_listener.notify = handle_seat_created; diff --git a/rdprail-shell/shell.h b/rdprail-shell/shell.h index 2cb24b8c1..9be9ada3c 100644 --- a/rdprail-shell/shell.h +++ b/rdprail-shell/shell.h @@ -31,7 +31,7 @@ #include #include -#include "weston-desktop-shell-server-protocol.h" +#include "weston-rdprail-shell-server-protocol.h" #define RDPRAIL_SHELL_DEBUG_LEVEL_NONE 0 #define RDPRAIL_SHELL_DEBUG_LEVEL_ERR 1 @@ -86,6 +86,15 @@ struct desktop_shell { struct wl_listener pointer_focus_listener; struct weston_surface *grab_surface; + struct { + struct wl_client *client; + struct wl_resource *desktop_shell; + struct wl_listener client_destroy_listener; + + unsigned deathcount; + struct timespec deathstamp; + } child; + bool prepare_event_sent; struct text_backend *text_backend; @@ -117,6 +126,8 @@ struct desktop_shell { struct wl_listener output_move_listener; struct wl_list output_list; + char *client; + struct timespec startup_time; bool is_localmove_supported; @@ -133,6 +144,8 @@ struct desktop_shell { bool is_blend_overlay_icon_taskbar; bool is_blend_overlay_icon_app_list; + struct weston_surface *focus_proxy_surface; + const struct weston_rdprail_api *rdprail_api; void *rdp_backend; From 25a4a9ec1c005d8e0bf391743f5349b9be6f3292 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 20 Aug 2021 14:25:11 -0700 Subject: [PATCH 1487/1642] fix repeated key inputs after focus change (#36) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 63 +++++++++++++++++++------------------ libweston/backend-rdp/rdp.h | 1 - rdprail-shell/shell.c | 42 ++++++++++++++++++------- 3 files changed, 63 insertions(+), 43 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 09debbae9..117156b26 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1736,8 +1736,10 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) enum wl_keyboard_key_state keyState; freerdp_peer *client = input->context->peer; RdpPeerContext *peerContext = (RdpPeerContext *)input->context; - struct rdp_backend *b = peerContext->rdpBackend; - bool need_release = false; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(peerContext->item.seat); + /* struct rdp_backend *b = peerContext->rdpBackend; */ + bool send_key = false; + bool send_release_key = false; int notify = 0; struct timespec time; @@ -1753,7 +1755,7 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) notify = 1; } - if (notify) { + if (keyboard && notify) { full_code = code; if (flags & KBD_FLAGS_EXTENDED) full_code |= KBD_FLAGS_EXTENDED; @@ -1776,22 +1778,10 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) /* RDP works same, there is no release for those 2 Korean keys, * thus generate release right after press. */ assert(keyState == WL_KEYBOARD_KEY_STATE_PRESSED); - need_release = true; + send_release_key = true; } else { vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, client->settings->KeyboardType); } - assert(vk_code <= 0xFF); - if (keyState == WL_KEYBOARD_KEY_STATE_RELEASED) { - /* Ignore release if key is not previously pressed. */ - if ((peerContext->key_state[vk_code>>3] & (1<<(vk_code&0x7))) == 0) { - rdp_debug_verbose(b, "%s: inconsistent key state vk_code:%x\n", - __func__, vk_code); - goto exit; - } - peerContext->key_state[vk_code>>3] &= ~(1<<(vk_code&0x7)); - } else if (!need_release /* when release is issued right after, no need to save state */) { - peerContext->key_state[vk_code>>3] |= (1<<(vk_code&0x7)); - } /* Korean keyboard support */ /* WinPR's GetKeycodeFromVirtualKeyCode() expects no extended bit for VK_HANGUL and VK_HANJA */ if (vk_code != VK_HANGUL && vk_code != VK_HANJA) @@ -1799,30 +1789,43 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) vk_code |= KBDEXT; scan_code = GetKeycodeFromVirtualKeyCode(vk_code, KEYCODE_TYPE_EVDEV); - /*weston_log("code=%x ext=%d vk_code=%x scan_code=%x\n", code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, vk_code, scan_code);*/ - weston_compositor_get_time(&time); - notify_key(peerContext->item.seat, &time, - scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); - - /*rdp_debug_verbose(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", - __func__, code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, - vk_code, scan_code, keyState, b->compositor->idle_inhibit);*/ - if (need_release) { - /* send release of same key */ - weston_compositor_get_time(&time); - notify_key(peerContext->item.seat, &time, - scan_code - 8, WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_AUTOMATIC); + /* Ignore release if key is not previously pressed. */ + if (keyState == WL_KEYBOARD_KEY_STATE_RELEASED) { + uint32_t *k, *end; + end = keyboard->keys.data + keyboard->keys.size; + for (k = keyboard->keys.data; k < end; k++) { + if (*k == (scan_code - 8)) { + send_key = true; + break; + } + } + } else { + send_key = true; + } + if (send_key) { /*rdp_debug_verbose(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", __func__, code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, vk_code, scan_code, keyState, b->compositor->idle_inhibit);*/ + notify_key(peerContext->item.seat, &time, + scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); + + if (send_release_key) { + /* send release of same key */ + weston_compositor_get_time(&time); + notify_key(peerContext->item.seat, &time, + scan_code - 8, WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_AUTOMATIC); + + /*rdp_debug_verbose(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", + __func__, code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, + vk_code, scan_code, keyState, b->compositor->idle_inhibit);*/ + } } } -exit: FREERDP_CB_RETURN(TRUE); } diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 021ebe166..7689210d9 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -276,7 +276,6 @@ struct rdp_peer_context { bool button_state[5]; bool mouseButtonSwap; - char key_state[0xff/8]; // one bit per key. int verticalAccumWheelRotationPrecise; int verticalAccumWheelRotationDiscrete; int horizontalAccumWheelRotationPrecise; diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 884516f0c..c990d7459 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -1459,19 +1459,37 @@ handle_keyboard_focus(struct wl_listener *listener, void *data) { struct weston_keyboard *keyboard = data; struct shell_seat *seat = get_shell_seat(keyboard->seat); + struct weston_surface* new_focused_surface = weston_surface_get_main_surface(keyboard->focus); + struct weston_surface* old_focused_surface = seat->focused_surface; + struct wl_array keys; + wl_array_init(&keys); + + if (new_focused_surface != old_focused_surface) { + if (old_focused_surface) { + struct shell_surface *shsurf = get_shell_surface(old_focused_surface); + if (shsurf) { + shell_surface_lose_keyboard_focus(shsurf); + /* when old focused window is focus proxy surface, server side window is + taking focus, thus let keyboard knows that */ + /* note: "keys" array is empty, thus it begin with no key pressed */ + if (old_focused_surface == shsurf->shell->focus_proxy_surface) + notify_keyboard_focus_in(keyboard->seat, &keys, STATE_UPDATE_AUTOMATIC); + } + } - if (seat->focused_surface) { - struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); - if (shsurf) - shell_surface_lose_keyboard_focus(shsurf); - } - - seat->focused_surface = weston_surface_get_main_surface(keyboard->focus); - - if (seat->focused_surface) { - struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); - if (shsurf) - shell_surface_gain_keyboard_focus(shsurf); + seat->focused_surface = new_focused_surface; + + if (new_focused_surface) { + struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); + if (shsurf) { + shell_surface_gain_keyboard_focus(shsurf); + /* when new focused window is focus proxy window, client side window is + taking focus and server side window is losing focus, let keyboard to + clear out currently pressed keys */ + if (new_focused_surface == shsurf->shell->focus_proxy_surface) + notify_keyboard_focus_out(keyboard->seat); + } + } } } From 67f58a2a86ccf7d06491edb33fd82d4127e60ba0 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Sun, 22 Aug 2021 17:45:35 -0700 Subject: [PATCH 1488/1642] Revert "fix repeated key inputs after focus change (#36)" (#37) This reverts commit 25a4a9ec1c005d8e0bf391743f5349b9be6f3292. --- libweston/backend-rdp/rdp.c | 63 ++++++++++++++++++------------------- libweston/backend-rdp/rdp.h | 1 + rdprail-shell/shell.c | 42 +++++++------------------ 3 files changed, 43 insertions(+), 63 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 117156b26..09debbae9 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1736,10 +1736,8 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) enum wl_keyboard_key_state keyState; freerdp_peer *client = input->context->peer; RdpPeerContext *peerContext = (RdpPeerContext *)input->context; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(peerContext->item.seat); - /* struct rdp_backend *b = peerContext->rdpBackend; */ - bool send_key = false; - bool send_release_key = false; + struct rdp_backend *b = peerContext->rdpBackend; + bool need_release = false; int notify = 0; struct timespec time; @@ -1755,7 +1753,7 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) notify = 1; } - if (keyboard && notify) { + if (notify) { full_code = code; if (flags & KBD_FLAGS_EXTENDED) full_code |= KBD_FLAGS_EXTENDED; @@ -1778,10 +1776,22 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) /* RDP works same, there is no release for those 2 Korean keys, * thus generate release right after press. */ assert(keyState == WL_KEYBOARD_KEY_STATE_PRESSED); - send_release_key = true; + need_release = true; } else { vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, client->settings->KeyboardType); } + assert(vk_code <= 0xFF); + if (keyState == WL_KEYBOARD_KEY_STATE_RELEASED) { + /* Ignore release if key is not previously pressed. */ + if ((peerContext->key_state[vk_code>>3] & (1<<(vk_code&0x7))) == 0) { + rdp_debug_verbose(b, "%s: inconsistent key state vk_code:%x\n", + __func__, vk_code); + goto exit; + } + peerContext->key_state[vk_code>>3] &= ~(1<<(vk_code&0x7)); + } else if (!need_release /* when release is issued right after, no need to save state */) { + peerContext->key_state[vk_code>>3] |= (1<<(vk_code&0x7)); + } /* Korean keyboard support */ /* WinPR's GetKeycodeFromVirtualKeyCode() expects no extended bit for VK_HANGUL and VK_HANJA */ if (vk_code != VK_HANGUL && vk_code != VK_HANJA) @@ -1789,43 +1799,30 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) vk_code |= KBDEXT; scan_code = GetKeycodeFromVirtualKeyCode(vk_code, KEYCODE_TYPE_EVDEV); + /*weston_log("code=%x ext=%d vk_code=%x scan_code=%x\n", code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, vk_code, scan_code);*/ + weston_compositor_get_time(&time); + notify_key(peerContext->item.seat, &time, + scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); - /* Ignore release if key is not previously pressed. */ - if (keyState == WL_KEYBOARD_KEY_STATE_RELEASED) { - uint32_t *k, *end; - end = keyboard->keys.data + keyboard->keys.size; - for (k = keyboard->keys.data; k < end; k++) { - if (*k == (scan_code - 8)) { - send_key = true; - break; - } - } - } else { - send_key = true; - } + /*rdp_debug_verbose(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", + __func__, code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, + vk_code, scan_code, keyState, b->compositor->idle_inhibit);*/ + + if (need_release) { + /* send release of same key */ + weston_compositor_get_time(&time); + notify_key(peerContext->item.seat, &time, + scan_code - 8, WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_AUTOMATIC); - if (send_key) { /*rdp_debug_verbose(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", __func__, code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, vk_code, scan_code, keyState, b->compositor->idle_inhibit);*/ - notify_key(peerContext->item.seat, &time, - scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); - - if (send_release_key) { - /* send release of same key */ - weston_compositor_get_time(&time); - notify_key(peerContext->item.seat, &time, - scan_code - 8, WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_AUTOMATIC); - - /*rdp_debug_verbose(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", - __func__, code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, - vk_code, scan_code, keyState, b->compositor->idle_inhibit);*/ - } } } +exit: FREERDP_CB_RETURN(TRUE); } diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 7689210d9..021ebe166 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -276,6 +276,7 @@ struct rdp_peer_context { bool button_state[5]; bool mouseButtonSwap; + char key_state[0xff/8]; // one bit per key. int verticalAccumWheelRotationPrecise; int verticalAccumWheelRotationDiscrete; int horizontalAccumWheelRotationPrecise; diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index c990d7459..884516f0c 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -1459,37 +1459,19 @@ handle_keyboard_focus(struct wl_listener *listener, void *data) { struct weston_keyboard *keyboard = data; struct shell_seat *seat = get_shell_seat(keyboard->seat); - struct weston_surface* new_focused_surface = weston_surface_get_main_surface(keyboard->focus); - struct weston_surface* old_focused_surface = seat->focused_surface; - struct wl_array keys; - wl_array_init(&keys); - - if (new_focused_surface != old_focused_surface) { - if (old_focused_surface) { - struct shell_surface *shsurf = get_shell_surface(old_focused_surface); - if (shsurf) { - shell_surface_lose_keyboard_focus(shsurf); - /* when old focused window is focus proxy surface, server side window is - taking focus, thus let keyboard knows that */ - /* note: "keys" array is empty, thus it begin with no key pressed */ - if (old_focused_surface == shsurf->shell->focus_proxy_surface) - notify_keyboard_focus_in(keyboard->seat, &keys, STATE_UPDATE_AUTOMATIC); - } - } - seat->focused_surface = new_focused_surface; - - if (new_focused_surface) { - struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); - if (shsurf) { - shell_surface_gain_keyboard_focus(shsurf); - /* when new focused window is focus proxy window, client side window is - taking focus and server side window is losing focus, let keyboard to - clear out currently pressed keys */ - if (new_focused_surface == shsurf->shell->focus_proxy_surface) - notify_keyboard_focus_out(keyboard->seat); - } - } + if (seat->focused_surface) { + struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); + if (shsurf) + shell_surface_lose_keyboard_focus(shsurf); + } + + seat->focused_surface = weston_surface_get_main_surface(keyboard->focus); + + if (seat->focused_surface) { + struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); + if (shsurf) + shell_surface_gain_keyboard_focus(shsurf); } } From ed53a9ab22d140cafe7e74f298f8be7a8142f42d Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 24 Aug 2021 12:03:11 -0700 Subject: [PATCH 1489/1642] fix repeated key inputs after focus change (take 2) (#38) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 58 +++++++++++++------------ libweston/backend-rdp/rdp.h | 1 - rdprail-shell/shell.c | 86 +++++++++++++++++++++++++++++++++---- 3 files changed, 107 insertions(+), 38 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 09debbae9..5c18cf516 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1736,8 +1736,10 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) enum wl_keyboard_key_state keyState; freerdp_peer *client = input->context->peer; RdpPeerContext *peerContext = (RdpPeerContext *)input->context; - struct rdp_backend *b = peerContext->rdpBackend; - bool need_release = false; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(peerContext->item.seat); + /*struct rdp_backend *b = peerContext->rdpBackend;*/ + bool send_key = false; + bool send_release_key = false; int notify = 0; struct timespec time; @@ -1753,7 +1755,7 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) notify = 1; } - if (notify) { + if (keyboard && notify) { full_code = code; if (flags & KBD_FLAGS_EXTENDED) full_code |= KBD_FLAGS_EXTENDED; @@ -1776,22 +1778,10 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) /* RDP works same, there is no release for those 2 Korean keys, * thus generate release right after press. */ assert(keyState == WL_KEYBOARD_KEY_STATE_PRESSED); - need_release = true; + send_release_key = true; } else { vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, client->settings->KeyboardType); } - assert(vk_code <= 0xFF); - if (keyState == WL_KEYBOARD_KEY_STATE_RELEASED) { - /* Ignore release if key is not previously pressed. */ - if ((peerContext->key_state[vk_code>>3] & (1<<(vk_code&0x7))) == 0) { - rdp_debug_verbose(b, "%s: inconsistent key state vk_code:%x\n", - __func__, vk_code); - goto exit; - } - peerContext->key_state[vk_code>>3] &= ~(1<<(vk_code&0x7)); - } else if (!need_release /* when release is issued right after, no need to save state */) { - peerContext->key_state[vk_code>>3] |= (1<<(vk_code&0x7)); - } /* Korean keyboard support */ /* WinPR's GetKeycodeFromVirtualKeyCode() expects no extended bit for VK_HANGUL and VK_HANJA */ if (vk_code != VK_HANGUL && vk_code != VK_HANJA) @@ -1799,30 +1789,42 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) vk_code |= KBDEXT; scan_code = GetKeycodeFromVirtualKeyCode(vk_code, KEYCODE_TYPE_EVDEV); - /*weston_log("code=%x ext=%d vk_code=%x scan_code=%x\n", code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, vk_code, scan_code);*/ - weston_compositor_get_time(&time); - notify_key(peerContext->item.seat, &time, - scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); - /*rdp_debug_verbose(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", - __func__, code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, - vk_code, scan_code, keyState, b->compositor->idle_inhibit);*/ + /* Ignore release if key is not previously pressed. */ + if (keyState == WL_KEYBOARD_KEY_STATE_RELEASED) { + uint32_t *k, *end; + end = keyboard->keys.data + keyboard->keys.size; + for (k = keyboard->keys.data; k < end; k++) { + if (*k == (scan_code - 8)) { + send_key = true; + break; + } + } + } else { + send_key = true; + } - if (need_release) { - /* send release of same key */ + if (send_key) { +send_release_key: weston_compositor_get_time(&time); notify_key(peerContext->item.seat, &time, - scan_code - 8, WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_AUTOMATIC); + scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); - /*rdp_debug_verbose(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", + /*rdp_debug(b, "RDP backend: %s code=%x ext=%d vk_code=%x scan_code=%x pressed=%d, idle_inhibit=%d\n", __func__, code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, vk_code, scan_code, keyState, b->compositor->idle_inhibit);*/ + + if (send_release_key) { + send_release_key = false; + assert(keyState == WL_KEYBOARD_KEY_STATE_PRESSED); + keyState = WL_KEYBOARD_KEY_STATE_RELEASED; + goto send_release_key; + } } } -exit: FREERDP_CB_RETURN(TRUE); } diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 021ebe166..7689210d9 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -276,7 +276,6 @@ struct rdp_peer_context { bool button_state[5]; bool mouseButtonSwap; - char key_state[0xff/8]; // one bit per key. int verticalAccumWheelRotationPrecise; int verticalAccumWheelRotationDiscrete; int horizontalAccumWheelRotationPrecise; diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 884516f0c..3bb5fb7cc 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -199,6 +199,7 @@ struct rotate_grab { struct shell_seat { struct weston_seat *seat; + struct desktop_shell *shell; struct wl_listener seat_destroy_listener; struct weston_surface *focused_surface; @@ -230,6 +231,9 @@ find_shell_output_from_weston_output(struct desktop_shell *shell, static void shell_surface_update_child_surface_layers(struct shell_surface *shsurf); +static void +shell_backend_request_window_activate(void *shell_context, struct weston_seat *seat, struct weston_surface *surface); + #define ICON_STRIDE( W, BPP ) ((((W) * (BPP) + 31) / 32) * 4) static int cached_tm_mday = -1; @@ -1459,20 +1463,59 @@ handle_keyboard_focus(struct wl_listener *listener, void *data) { struct weston_keyboard *keyboard = data; struct shell_seat *seat = get_shell_seat(keyboard->seat); + struct desktop_shell *shell = seat->shell; + struct weston_surface* new_focused_surface = weston_surface_get_main_surface(keyboard->focus); + struct weston_surface* old_focused_surface = seat->focused_surface; + + if (shell->debugLevel >= RDPRAIL_SHELL_DEBUG_LEVEL_VERBOSE) { + struct weston_desktop_surface *old_desktop_surface = NULL; + struct weston_desktop_surface *new_desktop_surface = NULL; + const char *old_title = NULL; + const char *new_title = NULL; + + if (old_focused_surface) + old_desktop_surface = weston_surface_get_desktop_surface(old_focused_surface); + if (new_focused_surface) + new_desktop_surface = weston_surface_get_desktop_surface(new_focused_surface); + if (old_desktop_surface) + old_title = weston_desktop_surface_get_title(old_desktop_surface); + if (new_desktop_surface) + new_title = weston_desktop_surface_get_title(new_desktop_surface); + + shell_rdp_debug_verbose(shell, "%s: moving focus from %p:%s to %p:%s\n", __func__, + old_focused_surface, old_title, new_focused_surface, new_title); + } - if (seat->focused_surface) { - struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); + if (old_focused_surface) { + struct shell_surface *shsurf = get_shell_surface(old_focused_surface); if (shsurf) shell_surface_lose_keyboard_focus(shsurf); } - seat->focused_surface = weston_surface_get_main_surface(keyboard->focus); + seat->focused_surface = new_focused_surface; - if (seat->focused_surface) { - struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); + if (new_focused_surface) { + struct shell_surface *shsurf = get_shell_surface(new_focused_surface); if (shsurf) shell_surface_gain_keyboard_focus(shsurf); } + + if (new_focused_surface == shell->focus_proxy_surface) { + /* When new focused window is focus proxy window, client side window is + taking focus and server side window is losing focus, thus let keyboard + to clear out currently pressed keys. This is because once server side + window is gone from client desktop, the client no longer sends keyboard + inputs including key release, thus if any keys are currently at pressed + state, it doesn't recieve release for those keys from RDP client. */ + while (keyboard->keys.size) { + struct timespec time; + uint32_t *k = keyboard->keys.data; + weston_compositor_get_time(&time); + notify_key(seat->seat, &time, *k, + WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_AUTOMATIC); + /* shell_rdp_debug_verbose(shell, "%s: released key:0x%x\n", __func__, *k); */ + } + } } /* The surface will be inserted into the list immediately after the link @@ -1935,7 +1978,7 @@ shell_seat_caps_changed(struct wl_listener *listener, void *data) } static struct shell_seat * -create_shell_seat(struct weston_seat *seat) +create_shell_seat(struct desktop_shell *shell, struct weston_seat *seat) { struct shell_seat *shseat; @@ -1946,6 +1989,7 @@ create_shell_seat(struct weston_seat *seat) } shseat->seat = seat; + shseat->shell = shell; shseat->seat_destroy_listener.notify = destroy_shell_seat; wl_signal_add(&seat->destroy_signal, @@ -3740,7 +3784,26 @@ desktop_shell_set_focus_proxy(struct wl_client *client, struct wl_resource *surface_resource) { struct desktop_shell *shell = wl_resource_get_user_data(resource); - shell->focus_proxy_surface = wl_resource_get_user_data(surface_resource); + struct weston_surface *surface; + struct shell_surface *shsurf; + + surface = wl_resource_get_user_data(surface_resource); + if (!surface) { + shell_rdp_debug(shell, "%s: surface is NULL\n", __func__); + return; + } + + shsurf = get_shell_surface(surface); + if (!shsurf) { + shell_rdp_debug(shell, "%s: surface:%p is not shell surface\n", __func__, surface); + return; + } + + shell->focus_proxy_surface = surface; + + /* Update the surface’s layer. This brings it to the top of the stacking + * order as appropriate. */ + shell_surface_update_layer(shsurf); } static const struct weston_rdprail_shell_interface rdprail_shell_implementation = { @@ -4074,8 +4137,12 @@ static void handle_seat_created(struct wl_listener *listener, void *data) { struct weston_seat *seat = data; + struct desktop_shell *shell; + + shell = container_of(listener, struct desktop_shell, + seat_create_listener); - create_shell_seat(seat); + create_shell_seat(shell, seat); } struct shell_workarea_change { @@ -4114,7 +4181,8 @@ shell_reposition_view_on_workarea_change(struct weston_view *view, void *data) but if it does, the server has no way to tell where it goes, thus here forcing backend to resend window state to client, this force window state, especially window position keeps in sync between server and client. */ - rail_state->forceUpdateWindowState = true; + if (rail_state) + rail_state->forceUpdateWindowState = true; /* If view's upper-left is within 10% of bottom-right of workarea boundary, adjust the position. */ int new_workarea_width = (int)workarea_change->new_workarea.width; From 6608c908c15d05e9c74a37d19f71dc425e92df7d Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 24 Aug 2021 16:34:39 -0700 Subject: [PATCH 1490/1642] fix clipboard crash (#39) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.h | 2 +- libweston/backend-rdp/rdpclip.c | 55 ++++++++++++++++++--------------- libweston/backend-rdp/rdpdisp.c | 8 ++--- libweston/backend-rdp/rdprail.c | 6 ++-- libweston/backend-rdp/rdputil.c | 20 ++++++------ 5 files changed, 47 insertions(+), 44 deletions(-) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 7689210d9..249d5292a 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -450,7 +450,7 @@ void rdp_id_manager_free(struct rdp_id_manager *id_manager); BOOL rdp_id_manager_allocate_id(struct rdp_id_manager *id_manager, void *object, UINT32 *new_id); void rdp_id_manager_free_id(struct rdp_id_manager *id_manager, UINT32 id); void dump_id_manager_state(FILE *fp, struct rdp_id_manager *id_manager, char* title); -struct wl_event_source *rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_fd_func_t func, void *data); +bool rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_fd_func_t func, void *data, struct wl_event_source **event_source); void rdp_defer_rdp_task_done(RdpPeerContext *peerCtx); // rdprail.c diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index 150b5ebbb..aaf5a8998 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -416,34 +416,31 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) void *ret = NULL; BITMAPFILEHEADER *bmfh = NULL; BITMAPINFOHEADER *bmih = NULL; + const UINT32 size_of_bmfh = sizeof(*bmfh); UINT32 color_table_size = 0; - size_t original_data_size = source->data_contents.size; - BOOL was_data_processed = source->is_data_processed; + /* size_t original_data_size = source->data_contents.size; */ + /* BOOL was_data_processed = source->is_data_processed; */ struct wl_array data_contents; wl_array_init(&data_contents); if (is_send) { - /* Linux to Windows */ - if (source->data_contents.size <= sizeof(BITMAPFILEHEADER)) + /* Linux to Windows (remove BITMAPFILEHEADER) */ + if (source->data_contents.size <= size_of_bmfh) goto error_return; bmfh = (BITMAPFILEHEADER *)source->data_contents.data; bmih = (BITMAPINFOHEADER *)(bmfh + 1); - if (bmih->biCompression == BI_BITFIELDS) - color_table_size = sizeof(RGBQUAD) * 3; - else - color_table_size = sizeof(RGBQUAD) * bmih->biClrUsed; /* size must be adjusted only once */ if (!source->is_data_processed) { - source->data_contents.size -= sizeof(*bmfh); + source->data_contents.size -= size_of_bmfh; source->is_data_processed = TRUE; } ret = (void *)bmih; // Skip BITMAPFILEHEADER. } else { - /* Windows to Linux */ + /* Windows to Linux (insert BITMAPFILEHEADER) */ if (!source->is_data_processed) { BITMAPFILEHEADER _bmfh = {}; bmih = (BITMAPINFOHEADER *)source->data_contents.data; @@ -454,7 +451,7 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) color_table_size = sizeof(RGBQUAD) * bmih->biClrUsed; bmfh->bfType = DIB_HEADER_MARKER; - bmfh->bfOffBits = sizeof(*bmfh) + bmih->biSize + color_table_size; + bmfh->bfOffBits = size_of_bmfh + bmih->biSize + color_table_size; if (bmih->biSizeImage) bmfh->bfSize = bmfh->bfOffBits + bmih->biSizeImage; else if (bmih->biCompression == BI_BITFIELDS || bmih->biCompression == BI_RGB) @@ -463,12 +460,19 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) else goto error_return; + /* source data must have enough size as described at its own bitmap header */ + if (source->data_contents.size < (bmfh->bfSize - size_of_bmfh)) + goto error_return; + if (!wl_array_add(&data_contents, bmfh->bfSize)) goto error_return; assert(data_contents.size == bmfh->bfSize); - memcpy(data_contents.data, bmfh, sizeof(*bmfh)); - memcpy((char *)data_contents.data + sizeof(*bmfh), source->data_contents.data, bmih->biSizeImage - sizeof(*bmfh)); + /* copy generated BITMAPFILEHEADER */ + memcpy(data_contents.data, bmfh, size_of_bmfh); + /* copy rest of bitmap data from source */ + memcpy((char *)data_contents.data + size_of_bmfh, + source->data_contents.data, bmfh->bfSize - size_of_bmfh); /* swap the data_contents with new one */ wl_array_release(&source->data_contents); @@ -480,10 +484,6 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) } else { bmfh = (BITMAPFILEHEADER *)source->data_contents.data; bmih = (BITMAPINFOHEADER *)(bmfh + 1); - if (bmih->biCompression == BI_BITFIELDS) - color_table_size = sizeof(RGBQUAD) * 3; - else - color_table_size = sizeof(RGBQUAD) * bmih->biClrUsed; } ret = source->data_contents.data; @@ -497,6 +497,8 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) __func__, source, clipboard_data_source_state_to_string(source), is_send ? "send" : "receive", (UINT32)source->data_contents.size); + + /* rdp_debug_clipboard_verbose_continue(b, " BITMAPFILEHEADER.bfType:0x%x\n", bmfh->bfType); rdp_debug_clipboard_verbose_continue(b, " BITMAPFILEHEADER.bfSize:%d\n", bmfh->bfSize); rdp_debug_clipboard_verbose_continue(b, " BITMAPFILEHEADER.bfOffBits:%d\n", bmfh->bfOffBits); @@ -512,6 +514,10 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biClrUsed:%d\n", bmih->biClrUsed); rdp_debug_clipboard_verbose_continue(b, " BITMAPINFOHEADER.biClrImportant:%d\n", bmih->biClrImportant); BITMAPINFO *bmi = (BITMAPINFO *)bmih; + if (bmih->biCompression == BI_BITFIELDS) + color_table_size = sizeof(RGBQUAD) * 3; + else + color_table_size = sizeof(RGBQUAD) * bmih->biClrUsed; for (UINT32 i = 0; i < color_table_size / sizeof(RGBQUAD); i++) { rdp_debug_clipboard_verbose_continue(b, " BITMAPINFO.bmiColors[%d]:%02x:%02x:%02x:%02x\n", i, @@ -543,6 +549,7 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) rdp_debug_clipboard_verbose_continue(b, " original_data_size:%d\n", (UINT32) original_data_size); rdp_debug_clipboard_verbose_continue(b, " new_data_size:%d\n", (UINT32) source->data_contents.size); rdp_debug_clipboard_verbose_continue(b, " data_processed:%d -> %d\n", was_data_processed, source->is_data_processed); + */ return ret; @@ -1452,11 +1459,9 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT } source->state = RDP_CLIPBOARD_SOURCE_FORMATLIST_READY; - source->defer_event_source = - rdp_defer_rdp_task_to_display_loop(peerCtx, - clipboard_data_source_publish, - source); - if (source->defer_event_source) { + if (rdp_defer_rdp_task_to_display_loop( + peerCtx, clipboard_data_source_publish, + source, &source->defer_event_source)) { isPublished = TRUE; } else { source->state = RDP_CLIPBOARD_SOURCE_FAILED; @@ -1619,9 +1624,9 @@ clipboard_client_format_data_request(CliprdrServerContext* context, const CLIPRD index = clipboard_find_supported_format_by_format_id(formatDataRequest->requestedFormatId); if (index >= 0) { peerCtx->clipboard_last_requested_format_index = index; - peerCtx->clipboard_data_request_event_source = - rdp_defer_rdp_task_to_display_loop(peerCtx, clipboard_data_source_request, peerCtx); - if (!peerCtx->clipboard_data_request_event_source) { + if (!rdp_defer_rdp_task_to_display_loop( + peerCtx, clipboard_data_source_request, + peerCtx, &peerCtx->clipboard_data_request_event_source)) { rdp_debug_clipboard_error(b, "Client: %s rdp_defer_rdp_task_to_display_loop failed\n", __func__); goto error_return; } diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 3a3600826..3a652fe6d 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -659,11 +659,9 @@ disp_client_monitor_layout_change(DispServerContext* context, const DISPLAY_CONT pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); wl_list_insert(&peerCtx->loop_event_source_list, &data->_base_event_source.link); pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); - data->_base_event_source.event_source = - rdp_defer_rdp_task_to_display_loop(peerCtx, - disp_monitor_layout_change_callback, - data); - if (!data->_base_event_source.event_source) { + if (!rdp_defer_rdp_task_to_display_loop( + peerCtx, disp_monitor_layout_change_callback, + data, &data->_base_event_source.event_source)) { pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); wl_list_remove(&data->_base_event_source.link); pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index feffb8dbf..e6ee84af5 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -87,9 +87,9 @@ struct rdp_dispatch_data { pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ wl_list_insert(&peerCtx->loop_event_source_list, &dispatch_data->_base_event_source.link); \ pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ - dispatch_data->_base_event_source.event_source = \ - rdp_defer_rdp_task_to_display_loop(peerCtx, callback, dispatch_data); \ - if (!dispatch_data->_base_event_source.event_source) { \ + if (!rdp_defer_rdp_task_to_display_loop( \ + peerCtx, callback, \ + dispatch_data, &dispatch_data->_base_event_source.event_source)) { \ rdp_debug_error(b, "%s: rdp_queue_deferred_task failed\n", __func__); \ pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ wl_list_remove(&dispatch_data->_base_event_source.link); \ diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index f59c81182..399855a5e 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -297,29 +297,29 @@ dump_id_manager_state(FILE *fp, struct rdp_id_manager *id_manager, char* title) /* this function is ONLY used to defer the task from RDP thread, to be performed at wayland display loop thread */ -struct wl_event_source * -rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_fd_func_t func, void *data) +bool +rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_fd_func_t func, void *data, struct wl_event_source **event_source) { + assert(event_source); + *event_source = NULL; if (peerCtx->vcm) { struct rdp_backend *b = peerCtx->rdpBackend; ASSERT_NOT_COMPOSITOR_THREAD(b); struct wl_event_loop *loop = wl_display_get_event_loop(b->compositor->wl_display); - struct wl_event_source *event_source = - wl_event_loop_add_fd(loop, - peerCtx->loop_event_source_fd, - WL_EVENT_READABLE, - func, data); - if (event_source) { + *event_source = wl_event_loop_add_fd(loop, + peerCtx->loop_event_source_fd, + WL_EVENT_READABLE, + func, data); + if (*event_source) { eventfd_write(peerCtx->loop_event_source_fd, 1); } else { rdp_debug_error(b, "%s: wl_event_loop_add_idle failed\n", __func__); } - return event_source; } else { /* RDP server is not opened, this must not be used */ assert(false); - return NULL; } + return (*event_source != NULL); } void From 7f13ce757578cab16c323aa75514e28438a1c61d Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 25 Aug 2021 16:03:48 -0700 Subject: [PATCH 1491/1642] clipboard to handle same data source is requested repeatedly when RDP client response is slow (#40) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 26 +++++++ libweston/backend-rdp/rdpclip.c | 126 +++++++++++++++++--------------- 2 files changed, 92 insertions(+), 60 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 5c18cf516..fc299e388 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -791,6 +791,11 @@ rdp_destroy(struct weston_compositor *ec) rdp_rail_destroy(b); + if (b->debugClipboard) { + weston_log_scope_destroy(b->debugClipboard); + b->debugClipboard = NULL; + } + if (b->debug) { weston_log_scope_destroy(b->debug); b->debug = NULL; @@ -2215,6 +2220,25 @@ rdp_backend_create(struct weston_compositor *compositor, rdp_debug(b, "RDP backend: WESTON_RDP_DEBUG_LEVEL: %d\n", b->debugLevel); /* After here, rdp_debug() is ready to be used */ + b->debugClipboard = weston_log_ctx_add_log_scope(b->compositor->weston_log_ctx, + "rdp-backend-clipboard", + "Debug messages from RDP backend clipboard\n", + NULL, NULL, NULL); + if (b->debugClipboard) { + s = getenv("WESTON_RDP_DEBUG_CLIPBOARD_LEVEL"); + if (s) { + if (!safe_strtoint(s, &b->debugClipboardLevel)) + b->debugClipboardLevel = RDP_DEBUG_CLIPBOARD_LEVEL_DEFAULT; + else if (b->debugClipboardLevel > RDP_DEBUG_LEVEL_VERBOSE) + b->debugClipboardLevel = RDP_DEBUG_LEVEL_VERBOSE; + } else { + /* by default, clipboard scope is disabled, so when it's enabled, + log with verbose mode to assist debugging */ + b->debugClipboardLevel = RDP_DEBUG_LEVEL_VERBOSE; // RDP_DEBUG_CLIPBOARD_LEVEL_DEFAULT; + } + } + rdp_debug_clipboard(b, "RDP backend: WESTON_RDP_DEBUG_CLIPBOARD_LEVEL: %d\n", b->debugClipboardLevel); + s = getenv("WESTON_RDP_MONITOR_REFRESH_RATE"); if (s) { if (!safe_strtoint(s, &b->rdp_monitor_refresh_rate) || @@ -2335,6 +2359,8 @@ rdp_backend_create(struct weston_compositor *compositor, err_compositor: weston_compositor_shutdown(compositor); err_free_strings: + if (b->debugClipboard) + weston_log_scope_destroy(b->debugClipboard); if (b->debug) weston_log_scope_destroy(b->debug); free(b->rdp_key); diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index aaf5a8998..a772aa1b8 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -100,6 +100,7 @@ enum rdp_clipboard_data_source_state { RDP_CLIPBOARD_SOURCE_TRANSFERRED, /* complete transfering data to comsumer */ RDP_CLIPBOARD_SOURCE_CANCEL_PENDING, /* data transfer cancel is requested */ RDP_CLIPBOARD_SOURCE_CANCELED, /* data transfer is canceled */ + RDP_CLIPBOARD_SOURCE_RETRY, /* retry later */ RDP_CLIPBOARD_SOURCE_FAILED, /* failure occured */ }; @@ -148,6 +149,8 @@ clipboard_data_source_state_to_string(struct rdp_clipboard_data_source *source) return "cancel pending"; case RDP_CLIPBOARD_SOURCE_CANCELED: return "cenceled"; + case RDP_CLIPBOARD_SOURCE_RETRY: + return "retry"; case RDP_CLIPBOARD_SOURCE_FAILED: return "failed"; } @@ -814,6 +817,7 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); assert(source->data_source_fd == fd); + assert(source->refcount == 1); /* event source is not removed here, but it will be removed when read is completed, until it's completed this function will be called whenever next chunk of data is @@ -850,9 +854,7 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) clipboard_client_send_format_data_response(peerCtx, source, data_to_send, source->data_contents.size); else goto error_exit; - /* make sure this is the last reference, so event source is removed at unref */ - assert(source->refcount == 1); - clipboard_data_source_unref(source); + goto send_exit; } else if (len < 0) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; rdp_debug_clipboard_error(b, "RDP %s (%p:%s) read failed (%s)\n", @@ -863,11 +865,15 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) ((char*)source->data_contents.data)[source->data_contents.size] = '\0'; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) read (%zu bytes)\n", __func__, source, clipboard_data_source_state_to_string(source), source->data_contents.size); + /* continue to read next batch */ + return 0; } - return 0; + assert(false); error_exit: clipboard_client_send_format_data_response_fail(peerCtx, source); + +send_exit: /* make sure this is the last reference, so event source is removed at unref */ assert(source->refcount == 1); clipboard_data_source_unref(source); @@ -894,13 +900,21 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); assert(source->data_source_fd == fd); + /* this data source must be tracked as inflight */ assert(source == peerCtx->clipboard_inflight_client_data_source); /* remove event source now, and if write is failed with EAGAIN, queue back to display loop. */ wl_event_source_remove(source->transfer_event_source); source->transfer_event_source = NULL; - if (source->is_canceled == FALSE && source->data_contents.data && source->data_contents.size) { + if (source->is_canceled) { + /* if source is being canceled, this must be the last reference */ + assert(source->refcount == 1); + source->state = RDP_CLIPBOARD_SOURCE_CANCELED; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) canceled\n", + __func__, source, clipboard_data_source_state_to_string(source)); + } else if (source->data_contents.data && source->data_contents.size) { + assert(source->refcount > 1); if (source->inflight_data_to_write) { assert(source->inflight_data_size); rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) transfer in chunck, count:%d\n", @@ -917,7 +931,7 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) } while (data_to_write && data_size) { source->state = RDP_CLIPBOARD_SOURCE_TRANSFERING; - size = write(fd, data_to_write, data_size); + size = write(source->data_source_fd, data_to_write, data_size); if (size <= 0) { if (errno != EAGAIN) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; @@ -952,9 +966,9 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) } } } - } else if (source->is_canceled) { - source->state = RDP_CLIPBOARD_SOURCE_CANCELED; - rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s)\n", + } else { + assert(source->refcount > 1); + rdp_debug_clipboard_error(b, "RDP %s (%p:%s) no data received from client\n", __func__, source, clipboard_data_source_state_to_string(source)); } @@ -963,8 +977,8 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) source->inflight_write_count = 0; source->inflight_data_to_write = NULL; source->inflight_data_size = 0; - clipboard_data_source_unref(source); peerCtx->clipboard_inflight_client_data_source = NULL; + clipboard_data_source_unref(source); return 0; } @@ -983,7 +997,8 @@ clipboard_data_source_accept(struct weston_data_source *base, RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - rdp_debug(b, "RDP %s (base:%p) mime-type:\"%s\"\n", __func__, base, mime_type); + rdp_debug_clipboard(b, "RDP %s (%p:%s) mime-type:\"%s\"\n", + __func__, source, clipboard_data_source_state_to_string(source), mime_type); } /* data-device informs the application requested the specified format data in given data_source (= client's clipboard) */ @@ -1009,13 +1024,20 @@ clipboard_data_source_send(struct weston_data_source *base, /* Here means server side (Linux application) request clipboard data, but server hasn't completed with previous request yet. If this happens, punt to idle loop and reattempt. */ - rdp_debug_clipboard_error(b, "\n\n\nRDP %s new (%p:%s) vs prev (%p:%s): outstanding RDP data request (client to server)\n\n\n", - __func__, source, clipboard_data_source_state_to_string(source), + rdp_debug_clipboard_error(b, "\n\n\nRDP %s new (%p:%s:fd %d) vs prev (%p:%s:fd %d): outstanding RDP data request (client to server)\n\n\n", + __func__, source, clipboard_data_source_state_to_string(source), fd, peerCtx->clipboard_inflight_client_data_source, - clipboard_data_source_state_to_string(peerCtx->clipboard_inflight_client_data_source)); - /* Here set error state after logging error, so log can show previous state. */ - source->state = RDP_CLIPBOARD_SOURCE_FAILED; - goto error_return_close_fd; + clipboard_data_source_state_to_string(peerCtx->clipboard_inflight_client_data_source), + peerCtx->clipboard_inflight_client_data_source->data_source_fd); + if (source == peerCtx->clipboard_inflight_client_data_source) { + /* when new source and previous source is same, update fd with new one and retry */ + source->state = RDP_CLIPBOARD_SOURCE_RETRY; + peerCtx->clipboard_inflight_client_data_source->data_source_fd = fd; + goto exit_return; + } else { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + goto error_return_close_fd; + } } if (source->base.mime_types.size == 0) { @@ -1038,6 +1060,10 @@ clipboard_data_source_send(struct weston_data_source *base, /* data is already in data_contents, no need to pull from client */ assert(source->transfer_event_source == NULL); source->state = RDP_CLIPBOARD_SOURCE_RECEIVED_DATA; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) data in cache \"%s\" index:%d formatId:%d %s\n", + __func__, source, clipboard_data_source_state_to_string(source), mime_type, index, + source->client_format_id_table[index], + clipboard_format_id_to_string(source->client_format_id_table[index], false)); source->transfer_event_source = wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, clipboard_data_source_write, source); @@ -1059,7 +1085,7 @@ clipboard_data_source_send(struct weston_data_source *base, formatDataRequest.dataLen = 4; formatDataRequest.requestedFormatId = source->client_format_id_table[index]; source->state = RDP_CLIPBOARD_SOURCE_REQUEST_DATA; - rdp_debug_clipboard(b, "RDP %s (%p:%s) request \"%s\" index:%d formatId:%d %s\n", + rdp_debug_clipboard(b, "RDP %s (%p:%s) request data \"%s\" index:%d formatId:%d %s\n", __func__, source, clipboard_data_source_state_to_string(source), mime_type, index, formatDataRequest.requestedFormatId, clipboard_format_id_to_string(formatDataRequest.requestedFormatId, false)); @@ -1074,6 +1100,7 @@ clipboard_data_source_send(struct weston_data_source *base, goto error_return_close_fd; } +exit_return: return; error_return_unref_source: @@ -1081,9 +1108,9 @@ clipboard_data_source_send(struct weston_data_source *base, assert(source->inflight_write_count == 0); assert(source->inflight_data_to_write == NULL); assert(source->inflight_data_size == 0); - clipboard_data_source_unref(source); assert(peerCtx->clipboard_inflight_client_data_source == source); peerCtx->clipboard_inflight_client_data_source = NULL; + clipboard_data_source_unref(source); error_return_close_fd: close(fd); @@ -1108,14 +1135,17 @@ clipboard_data_source_cancel(struct weston_data_source *base) if (source == peerCtx->clipboard_inflight_client_data_source) { source->is_canceled = TRUE; source->state = RDP_CLIPBOARD_SOURCE_CANCEL_PENDING; - rdp_debug_clipboard(b, "RDP %s (%p:%s): still inflight\n", - __func__, source, clipboard_data_source_state_to_string(source)); + rdp_debug_clipboard(b, "RDP %s (%p:%s): still inflight - refcount:%d\n", + __func__, source, clipboard_data_source_state_to_string(source), + source->refcount); assert(source->refcount > 1); } else { /* everything outside of the base has to be cleaned up */ source->state = RDP_CLIPBOARD_SOURCE_CANCELED; - rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source)); + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) - refcount:%d\n", + __func__, source, clipboard_data_source_state_to_string(source), + source->refcount); + assert(source->refcount == 1); assert(source->transfer_event_source == NULL); wl_array_release(&source->data_contents); wl_array_init(&source->data_contents); @@ -1227,8 +1257,8 @@ clipboard_data_source_request(int fd, uint32_t mask, void *arg) /* By now, the server side data availablity is already notified to client by clipboard_set_selection(). */ source->state = RDP_CLIPBOARD_SOURCE_PUBLISHED; - rdp_debug_clipboard(b, "RDP %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source)); + rdp_debug_clipboard(b, "RDP %s (%p:%s) for (base:%p)\n", + __func__, source, clipboard_data_source_state_to_string(source), selection_data_source); wl_signal_init(&source->base.destroy_signal); wl_array_init(&source->base.mime_types); wl_array_init(&source->data_contents); @@ -1262,6 +1292,7 @@ clipboard_data_source_request(int fd, uint32_t mask, void *arg) return 0; error_exit_free_source: + assert(source->refcount == 1); clipboard_data_source_unref(source); error_exit_response_fail: @@ -1283,6 +1314,7 @@ clipboard_set_selection(struct wl_listener *listener, void *data) struct rdp_backend *b = peerCtx->rdpBackend; struct weston_seat *seat = data; struct weston_data_source *selection_data_source = seat->selection_data_source; + struct rdp_clipboard_data_source* data_source; CLIPRDR_FORMAT_LIST formatList = {}; CLIPRDR_FORMAT format[RDP_NUM_CLIPBOARD_FORMATS] = {}; const char **mime_type; @@ -1302,8 +1334,9 @@ clipboard_set_selection(struct wl_listener *listener, void *data) /* another data source (from server side) gets selected, no longer need previous data from client. */ if (peerCtx->clipboard_client_data_source) { - clipboard_data_source_unref(peerCtx->clipboard_client_data_source); + data_source = peerCtx->clipboard_client_data_source; peerCtx->clipboard_client_data_source = NULL; + clipboard_data_source_unref(data_source); } wl_array_for_each(mime_type, &selection_data_source->mime_types) { @@ -1576,9 +1609,10 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR /* close fd to server clipboard stop pulling data. */ close(source->data_source_fd); source->data_source_fd = -1; - clipboard_data_source_unref(source); /* clear inflight data source from client to server. */ + assert(source == peerCtx->clipboard_inflight_client_data_source); peerCtx->clipboard_inflight_client_data_source = NULL; + clipboard_data_source_unref(source); } } else { rdp_debug_clipboard(b, "Client: %s client send data without server asking. protocol error", __func__); @@ -1654,31 +1688,11 @@ rdp_clipboard_init(freerdp_peer* client) RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; struct weston_seat *seat = peerCtx->item.seat; - char *s; assert(seat); ASSERT_COMPOSITOR_THREAD(b); - b->debugClipboard = weston_log_ctx_add_log_scope(b->compositor->weston_log_ctx, - "rdp-backend-clipboard", - "Debug messages from RDP backend clipboard\n", - NULL, NULL, NULL); - if (b->debugClipboard) { - s = getenv("WESTON_RDP_DEBUG_CLIPBOARD_LEVEL"); - if (s) { - if (!safe_strtoint(s, &b->debugClipboardLevel)) - b->debugClipboardLevel = RDP_DEBUG_CLIPBOARD_LEVEL_DEFAULT; - else if (b->debugClipboardLevel > RDP_DEBUG_LEVEL_VERBOSE) - b->debugClipboardLevel = RDP_DEBUG_LEVEL_VERBOSE; - } else { - /* by default, clipboard scope is disabled, so when it's enabled, - log with verbose mode to assist debugging */ - b->debugClipboardLevel = RDP_DEBUG_LEVEL_VERBOSE; // RDP_DEBUG_CLIPBOARD_LEVEL_DEFAULT; - } - } - rdp_debug_clipboard(b, "RDP backend: WESTON_RDP_DEBUG_CLIPBOARD_LEVEL: %d\n", b->debugClipboardLevel); - peerCtx->clipboard_server_context = cliprdr_server_context_new(peerCtx->vcm); if (!peerCtx->clipboard_server_context) goto error; @@ -1713,18 +1727,13 @@ rdp_clipboard_init(freerdp_peer* client) peerCtx->clipboard_server_context = NULL; } - if (b->debugClipboard) { - weston_log_scope_destroy(b->debugClipboard); - b->debugClipboard = NULL; - } - return -1; } void rdp_clipboard_destroy(RdpPeerContext *peerCtx) { - struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_clipboard_data_source* data_source; if (peerCtx->clipboard_selection_listener.notify) { wl_list_remove(&peerCtx->clipboard_selection_listener.link); @@ -1738,12 +1747,14 @@ rdp_clipboard_destroy(RdpPeerContext *peerCtx) } if (peerCtx->clipboard_inflight_client_data_source) { - clipboard_data_source_unref(peerCtx->clipboard_inflight_client_data_source); + data_source = peerCtx->clipboard_inflight_client_data_source; peerCtx->clipboard_inflight_client_data_source = NULL; + clipboard_data_source_unref(data_source); } if (peerCtx->clipboard_client_data_source) { - clipboard_data_source_unref(peerCtx->clipboard_client_data_source); + data_source = peerCtx->clipboard_client_data_source; peerCtx->clipboard_client_data_source = NULL; + clipboard_data_source_unref(data_source); } if (peerCtx->clipboard_server_context) { @@ -1751,9 +1762,4 @@ rdp_clipboard_destroy(RdpPeerContext *peerCtx) cliprdr_server_context_free(peerCtx->clipboard_server_context); peerCtx->clipboard_server_context = NULL; } - - if (b->debugClipboard) { - weston_log_scope_destroy(b->debugClipboard); - b->debugClipboard = NULL; - } } From 2385e44105a74d1d505615ca427a225bc4051775 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 26 Aug 2021 14:49:46 -0700 Subject: [PATCH 1492/1642] fix hangul/hanja keys are reversed on Korean 103/106 keyboard (#41) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index fc299e388..5efe50b91 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1768,13 +1768,17 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) /* Korean keyboard support */ /* WinPR's GetVirtualKeyCodeFromVirtualScanCode() can't handle hangul/hanja keys */ /* 0x1f1 and 0x1f2 keys are only exists on Korean 103 keyboard (Type 8:SubType 6) */ + /* From Linux's keyboard driver at drivers/input/keyboard/atkbd.c */ + #define ATKBD_RET_HANJA 0xf1 + #define ATKBD_RET_HANGEUL 0xf2 if (client->settings->KeyboardType == 8 && client->settings->KeyboardSubType == 6 && - ((full_code == 0x1f1) || (full_code == 0x1f2))) { - if (full_code == 0x1f1) - vk_code = VK_HANGUL; - else if (full_code == 0x1f2) + ((full_code == (KBD_FLAGS_EXTENDED | ATKBD_RET_HANJA)) || + (full_code == (KBD_FLAGS_EXTENDED | ATKBD_RET_HANGEUL)))) { + if (full_code == (KBD_FLAGS_EXTENDED | ATKBD_RET_HANJA)) vk_code = VK_HANJA; + else if (full_code == (KBD_FLAGS_EXTENDED | ATKBD_RET_HANGEUL)) + vk_code = VK_HANGUL; /* From Linux's keyboard driver at drivers/input/keyboard/atkbd.c */ /* * HANGEUL and HANJA keys do not send release events so we need to From ae2b0d48d46dfde99606d15dde4c99bbd5d6bd48 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 27 Aug 2021 10:48:10 -0700 Subject: [PATCH 1493/1642] fix menu is invisible at fullscreen mode (#42) Co-authored-by: Hideyuki Nagase --- include/libweston/libweston.h | 3 +++ libweston-desktop/xwayland.c | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 140870be3..ca0bcba56 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -854,6 +854,9 @@ enum weston_layer_position { /* For fullscreen applications that should cover UI. */ WESTON_LAYER_POSITION_FULLSCREEN = 0xb0000000, + /* For menu/popup window that should be above fullscreen applications */ + WESTON_LAYER_POSITION_POPUP_UI = 0xc0000000, + /* For special UI like on-screen keyboard that fullscreen applications * will need. */ WESTON_LAYER_POSITION_TOP_UI = 0xe0000000, diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index 14b883ea0..3542ce5c8 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -463,8 +463,17 @@ weston_desktop_xwayland_init(struct weston_desktop *desktop) weston_layer_init(&xwayland->layer, compositor); /* We put this layer on top of regular shell surfaces, but hopefully * below any UI the shell would add */ + /* Previously, (WESTON_LAYER_POSITION_NORMAL + 1) is used, but this is below + fullscreen layer, thus dropdown menu is invisible when top level window is + fullscreen. Here solution is define WESTON_LAYER_POSITION_POPUP_UI, which + is just above fullscreen layer, but below TOP_UI. This means it shows up + above UI layer, which is used for shell panel, but coming up popup menu + above shell panel is acceptable than menu is totally invisible with fullscreen. + Ideally the layer to be placed should depend on state of top level window, + fullscreen or not, but determine that from XWAYLAND (or overrside_redirect), + there seems no ideal way. Need further investigation. */ weston_layer_set_position(&xwayland->layer, - WESTON_LAYER_POSITION_NORMAL + 1); + WESTON_LAYER_POSITION_POPUP_UI); compositor->xwayland = xwayland; compositor->xwayland_interface = &weston_desktop_xwayland_interface; From 9a55de1bde7ad5a9774391d83ed3371a68370502 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 13 Sep 2021 12:08:50 -0700 Subject: [PATCH 1494/1642] enable allow_zap to be configured from .wslgconfig (#43) Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 3bb5fb7cc..ef11cff31 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -670,6 +670,10 @@ shell_configuration(struct desktop_shell *shell) /* default to not allow zap */ weston_config_section_get_bool(section, "allow-zap", &allow_zap, false); + if (!allow_zap) { + s = getenv("WESTON_RDPRAIL_SHELL_ALLOW_ZAP"); + allow_zap = (s && (strcmp(s, "true") == 0)); + } shell->allow_zap = allow_zap; /* set "none" to default to disable optional key-bindings */ From b49e1942ac6242c9dd2118fbd7072e1692f49208 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 24 Sep 2021 09:54:19 -0700 Subject: [PATCH 1495/1642] support image_name in system_distro for appId (#44) Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 2 +- libweston/backend-rdp/rdprail.c | 4 +- rdprail-shell/app-list.c | 188 ++++++++++++++++++++++++++++++-- rdprail-shell/shell.c | 32 ++++-- rdprail-shell/shell.h | 1 + 5 files changed, 203 insertions(+), 24 deletions(-) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index 5b93993d4..049662c2b 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -95,7 +95,7 @@ struct weston_rdprail_shell_api { /** Get app_id and pid */ - pid_t (*get_window_app_id)(struct weston_surface *surface, + pid_t (*get_window_app_id)(void *shell_context, struct weston_surface *surface, char *app_id, size_t app_id_size, char *image_name, size_t image_name_size); /** Start/stop application list update diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index e6ee84af5..e2af3d6fe 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -645,8 +645,8 @@ rail_client_ClientGetAppidReq_callback(int fd, uint32_t mask, void *arg) goto Exit; } - pid = b->rdprail_shell_api->get_window_app_id( - surface, &appId[0], sizeof(appId)-1, &imageName[0], sizeof(imageName)-1); + pid = b->rdprail_shell_api->get_window_app_id(b->rdprail_shell_context, + surface, &appId[0], sizeof(appId), &imageName[0], sizeof(imageName)); if (appId[0] == '\0') { rdp_debug_error(b, "Client: ClientGetAppidReq: WindowId:0x%x does not have appId, or not top level window.\n", getAppidReq->windowId); goto Exit; diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index b699ea646..c9d4557d4 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -58,7 +58,7 @@ #include #include -#define NUM_CONTROL_EVENT 4 +#define NUM_CONTROL_EVENT 5 struct app_list_context { wHashTable* table; @@ -67,6 +67,7 @@ struct app_list_context { HANDLE startRdpNotifyEvent; // control event: wait index 1 HANDLE stopRdpNotifyEvent; // control event: wait index 2 HANDLE loadIconEvent; // control event: wait index 3 + HANDLE findImageNameEvent; // control event: wait index 4 HANDLE replyEvent; bool isRdpNotifyStarted; bool isAppListNamespaceAttached; @@ -75,10 +76,15 @@ struct app_list_context { pixman_image_t* default_icon; pixman_image_t* default_overlay_icon; struct { - CRITICAL_SECTION lock; // use at load_icon_file. pixman_image_t* image; // use as reply message at load_icon_file. const char *key; // use as send message at load_icon_file. } load_icon; + struct { + pid_t pid; + bool is_wayland; + char *image_name; + size_t image_name_size; + } find_image_name; struct { char requestedClientLanguageId[32]; // 32 = RDPAPPLIST_LANG_SIZE. char currentClientLanguageId[32]; @@ -670,6 +676,99 @@ app_list_stop_rdp_notify(struct desktop_shell *shell) send_app_entry(shell, NULL, NULL, false, false, true, false, false, false); } +static void +translate_to_windows_path(struct desktop_shell *shell, char *image_name, size_t image_name_size) +{ + bool is_succeeded = false; + + attach_app_list_namespace(shell); + + if (is_file_exist("/usr/bin/wslpath")) { + pid_t pid; + int pipe[2] = {}; + int imageNameLength, len; + + pipe2(pipe, O_CLOEXEC); + if (!pipe[0] || !pipe[1]) { + shell_rdp_debug(shell, "app_list_monitor_thread: pipe2 failed: %s\n", strerror(errno)); + goto Exit; + } + + pid = fork(); + if (pid == -1) { + shell_rdp_debug(shell, "app_list_monitor_thread: fork() failed: %s\n", strerror(errno)); + close(pipe[0]); + close(pipe[1]); + goto Exit; + } + + if (pid == 0) { + if (dup2(pipe[1], STDOUT_FILENO) < 0) { + shell_rdp_debug(shell, "app_list_monitor_thread: dup2 failed: %s\n", strerror(errno)); + } else { + char *argv[] = { + "/usr/bin/wslpath", + "-w", + image_name, + 0 + }; + + close(pipe[0]); + close(pipe[1]); + + if (execv(argv[0], argv) < 0) + shell_rdp_debug(shell, "app_list_monitor_thread: execv failed: %s\n", strerror(errno)); + } + _exit(EXIT_SUCCESS); + } + + close(pipe[1]); + + imageNameLength = 0; + while ((len = read(pipe[0], image_name + imageNameLength, + image_name_size - imageNameLength)) != 0) { + if (len < 0) { + shell_rdp_debug(shell, "app_list_monitor_thread: read error: %s\n", strerror(errno)); + /* if already read some, clear it, otherwise leave as-is and fallback. */ + if (imageNameLength) { + imageNameLength = 0; + image_name[0] = '\0'; + } + break; + } + imageNameLength += len; + } + + close(pipe[0]); + + /* trim trailing '\n' */ + while (imageNameLength > 0 && + image_name[imageNameLength - 1] == '\n') { + image_name[imageNameLength - 1] = '\0'; + imageNameLength--; + } + + if (imageNameLength) + is_succeeded = true; + } + +Exit: + detach_app_list_namespace(shell); + + if (!is_succeeded) { + /* fallback when wslpath doesn't exst, fork/pipe failed, or nothing read, + here simply patch '/' with '\'. */ + int i = 0; + while (image_name[i] != '\0') { + if (image_name[i] == '/') + image_name[i] = '\\'; + i++; + } + } + + shell_rdp_debug_verbose(shell, "app_list_monitor_thread: Windows image_path:%s\n", image_name); +} + static DWORD WINAPI app_list_monitor_thread(LPVOID arg) { @@ -724,6 +823,7 @@ app_list_monitor_thread(LPVOID arg) events[num_events++] = context->startRdpNotifyEvent; events[num_events++] = context->stopRdpNotifyEvent; events[num_events++] = context->loadIconEvent; + events[num_events++] = context->findImageNameEvent; assert(num_events == NUM_CONTROL_EVENT); if (shell->rdprail_api->notify_app_list) { @@ -845,6 +945,36 @@ app_list_monitor_thread(LPVOID arg) continue; } + /* Find ImageName event */ + if (status == WAIT_OBJECT_0 + 4) { + assert(context->find_image_name.image_name); + assert(context->find_image_name.image_name_size); + shell_rdp_debug_verbose(shell, "app_list_monitor_thread: findImageNameEvent is signalled. pid:%d\n", + context->find_image_name.pid); + + /* read execuable name from /proc */ + context->find_image_name.image_name[0] = '\0'; + sprintf(path, "/proc/%d/exe", context->find_image_name.pid); + if (!context->find_image_name.is_wayland) + attach_app_list_namespace(shell); + if (readlink(path, context->find_image_name.image_name, context->find_image_name.image_name_size) < 0) + shell_rdp_debug(shell, "app_list_monitor_thread: readlink failed %s:%s\n", path, strerror(errno)); + if (!context->find_image_name.is_wayland) + detach_app_list_namespace(shell); + shell_rdp_debug_verbose(shell, "app_list_monitor_thread: Linux image_path:%s\n", + context->find_image_name.image_name); + + /* If image name is provided, convert to Windows-style path. */ + if (context->find_image_name.image_name[0] != '\0') { + translate_to_windows_path(shell, + context->find_image_name.image_name, + context->find_image_name.image_name_size); + } + + SetEvent(context->replyEvent); + continue; + } + /* Somethings are changed in watch folders */ if (shell->rdprail_api->notify_app_list && num_watch) { len = read(fd[status - WAIT_OBJECT_0 - NUM_CONTROL_EVENT], buf, sizeof buf); @@ -901,8 +1031,6 @@ start_app_list_monitor(struct desktop_shell *shell) context->isRdpNotifyStarted = false; - InitializeCriticalSectionAndSpinCount(&(context->load_icon.lock), 4000); - context->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!context->stopEvent) goto Error_Exit; @@ -922,6 +1050,11 @@ start_app_list_monitor(struct desktop_shell *shell) if (!context->loadIconEvent) goto Error_Exit; + /* bManualReset = TRUE, ideally here needs FALSE, but winpr doesn't support it */ + context->findImageNameEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!context->findImageNameEvent) + goto Error_Exit; + /* bManualReset = TRUE, ideally here needs FALSE, but winpr doesn't support it */ context->replyEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!context->replyEvent) @@ -940,6 +1073,11 @@ start_app_list_monitor(struct desktop_shell *shell) context->replyEvent = NULL; } + if (context->findImageNameEvent) { + CloseHandle(context->findImageNameEvent); + context->findImageNameEvent = NULL; + } + if (context->loadIconEvent) { CloseHandle(context->loadIconEvent); context->loadIconEvent = NULL; @@ -960,8 +1098,6 @@ start_app_list_monitor(struct desktop_shell *shell) context->stopEvent = NULL; } - DeleteCriticalSection(&(context->load_icon.lock)); - return; } @@ -993,6 +1129,11 @@ stop_app_list_monitor(struct desktop_shell *shell) context->replyEvent = NULL; } + if (context->findImageNameEvent) { + CloseHandle(context->findImageNameEvent); + context->findImageNameEvent = NULL; + } + if (context->loadIconEvent) { CloseHandle(context->loadIconEvent); context->loadIconEvent = NULL; @@ -1008,8 +1149,6 @@ stop_app_list_monitor(struct desktop_shell *shell) context->stopEvent = NULL; } - DeleteCriticalSection(&(context->load_icon.lock)); - context->isRdpNotifyStarted = false; assert(context->weston_pidfd <= 0); @@ -1025,9 +1164,6 @@ pixman_image_t* app_list_load_icon_file(struct desktop_shell *shell, const char if (context) { /* hand off to worker thread where can access user-distro files */ - /* TODO: should not need critical secion as long as this is called only - from display loop thread */ - EnterCriticalSection(&context->load_icon.lock); assert(context->load_icon.image == NULL); assert(context->load_icon.key == NULL); context->load_icon.key = key; @@ -1041,7 +1177,6 @@ pixman_image_t* app_list_load_icon_file(struct desktop_shell *shell, const char image = context->load_icon.image; context->load_icon.image = NULL; context->load_icon.key = NULL; - LeaveCriticalSection(&context->load_icon.lock); return image; } @@ -1049,6 +1184,35 @@ pixman_image_t* app_list_load_icon_file(struct desktop_shell *shell, const char return NULL; } +void app_list_find_image_name(struct desktop_shell *shell, pid_t pid, char *image_name, size_t image_name_size, bool is_wayland) +{ +#ifdef HAVE_WINPR2 + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + + if (context) { + assert(context->find_image_name.pid == (pid_t) 0); + assert(context->find_image_name.image_name == NULL); + assert(context->find_image_name.image_name_size == 0); + context->find_image_name.pid = pid; + context->find_image_name.is_wayland = is_wayland; + context->find_image_name.image_name = image_name; + context->find_image_name.image_name_size = image_name_size; + + /* signal worker thread to load icon at worker thread */ + SetEvent(context->findImageNameEvent); + WaitForSingleObject(context->replyEvent, INFINITE); + /* here must reset since winpr doesn't support auto reset event */ + ResetEvent(context->replyEvent); + + context->find_image_name.pid = (pid_t) 0; + context->find_image_name.is_wayland = false; + context->find_image_name.image_name = NULL; + context->find_image_name.image_name_size = 0; + } +#endif + return; +} + bool app_list_start_backend_update(struct desktop_shell *shell, char *clientLanguageId) { #ifdef HAVE_WINPR2 diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index ef11cff31..832bafa32 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -4249,16 +4249,18 @@ shell_backend_set_desktop_workarea(struct weston_output *output, void *context, } static pid_t -shell_backend_get_app_id(struct weston_surface *surface, char *app_id, size_t app_id_size, char *image_name, size_t image_name_size) +shell_backend_get_app_id(void *shell_context, struct weston_surface *surface, char *app_id, size_t app_id_size, char *image_name, size_t image_name_size) { + struct desktop_shell *shell = (struct desktop_shell *)shell_context; struct weston_desktop_surface *desktop_surface; struct shell_surface *shsurf; const struct weston_xwayland_surface_api *api; pid_t pid; const char *id; char *class_name; - char path[32] = {}; + bool is_wayland = true; + assert(shell); assert(app_id); assert(app_id_size); assert(image_name); @@ -4290,6 +4292,8 @@ shell_backend_get_app_id(struct weston_surface *surface, char *app_id, size_t ap if (class_name) { strncpy(app_id, class_name, app_id_size); free(class_name); + /* app_id is from Xwayland */ + is_wayland = false; } } } @@ -4297,15 +4301,25 @@ shell_backend_get_app_id(struct weston_surface *surface, char *app_id, size_t ap /* obtain pid for execuable path */ pid = weston_desktop_surface_get_pid(desktop_surface); - if (pid > 0 && !is_system_distro()) { - sprintf(path, "/proc/%d/exe", pid); - if (readlink(path, image_name, image_name_size) < 0) - weston_log("shell_backend_get_app_id: readlink failed %s:%s\n", path, strerror(errno)); + /* find image name via user-distro for Xwayland */ + if (pid > 0) + app_list_find_image_name(shell, pid, image_name, image_name_size, is_wayland); + + /* if app_id is not obtained but image name, use image name (only name) as app_id. */ + /* NOTE: image name is Windows's style path, so separator is '\\', not '/'. */ + if (app_id[0] == '\0' && image_name[0] != '\0') { + char *p = strrchr(image_name, '\\'); + if (p && p[1] != '\0') + p++; + else + p = image_name; + strncpy(app_id, p, app_id_size); + } else if (app_id[0] != '\0' && image_name[0] == '\0') { + strncpy(image_name, app_id, image_name_size); } - /* if app_id is not specified by above, use execuable path as app_id */ - if (app_id[0] == '\0' && image_name[0] != '\0') - strncpy(app_id, image_name, app_id_size); + shell_rdp_debug_verbose(shell, "shell_backend_get_app_id: 0x%p: pid:%d, app_id:%s, image_name:%s\n", + surface, pid, app_id, image_name); return pid; } diff --git a/rdprail-shell/shell.h b/rdprail-shell/shell.h index 9be9ada3c..01c23b9fe 100644 --- a/rdprail-shell/shell.h +++ b/rdprail-shell/shell.h @@ -200,5 +200,6 @@ void app_list_destroy(struct desktop_shell *shell); pixman_image_t *app_list_load_icon_file(struct desktop_shell *shell, const char *key); bool app_list_start_backend_update(struct desktop_shell *shell, char *clientLanguageId); void app_list_stop_backend_update(struct desktop_shell *shell); +void app_list_find_image_name(struct desktop_shell *shell, pid_t pid, char *image_name, size_t image_name_size, bool is_wayland); // img-load.c pixman_image_t *load_icon_image(struct desktop_shell *shell, const char *filename); From 7d82481b049e181ca5cd1bb509413e8a0019d05e Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 27 Sep 2021 11:10:51 -0700 Subject: [PATCH 1496/1642] fix shared memory 'File exists' error (#45) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdprail.c | 6 ++++-- libweston/backend-rdp/rdputil.c | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index e2af3d6fe..4065e6850 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2105,9 +2105,11 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter if (b->use_gfxredir) { assert(rail_state->isUpdatePending == FALSE); - if (rail_state->surfaceBuffer) + if (rail_state->surfaceBuffer) { rdp_destroy_shared_buffer(surface); - + /* at window resize, reset name as old name might still be referenced by client */ + rail_state->shared_memory.name[0] = '\0'; + } assert(rail_state->surfaceBuffer == NULL); assert(rail_state->shared_memory.addr == NULL); rail_state->shared_memory.size = copyBufferSize; diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index 399855a5e..b97629028 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -187,7 +187,7 @@ rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memor } rdp_debug_verbose(b, "%s: allocated %d: %s (%ld bytes) at %p\n", - __func__, fd, shared_memory->name, shared_memory->size, shared_memory->addr); + __func__, fd, shared_memory->name, shared_memory->size, addr); shared_memory->fd = fd; shared_memory->addr = addr; @@ -211,6 +211,10 @@ rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memor void rdp_free_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory) { + rdp_debug_verbose(b, "%s: freed %d: %s (%ld bytes) at %p\n", __func__, + shared_memory->fd, shared_memory->name, + shared_memory->size, shared_memory->addr); + if (shared_memory->addr) { munmap(shared_memory->addr, shared_memory->size); shared_memory->addr = NULL; From b1c52cdaa964bc04c4ae392146605363645d4eb2 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 6 Oct 2021 11:25:34 -0700 Subject: [PATCH 1497/1642] fix two finger scroll on arm64 (#46) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 5efe50b91..7f7c0efd5 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1530,10 +1530,9 @@ rdp_notify_wheel_scroll(RdpPeerContext *peerContext, UINT16 flags, uint32_t axis * * https://blogs.msdn.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value */ + ivalue = ((int)flags & 0x000000ff); if (flags & PTR_FLAGS_WHEEL_NEGATIVE) - ivalue = (int)((char)(flags & 0xff)); - else - ivalue = (flags & 0xff); + ivalue = (0xff - ivalue) * -1; /* * Flip the scroll direction as the RDP direction is inverse of X/Wayland @@ -1560,6 +1559,8 @@ rdp_notify_wheel_scroll(RdpPeerContext *peerContext, UINT16 flags, uint32_t axis */ *accumWheelRotationPrecise += ivalue; *accumWheelRotationDiscrete += ivalue; + rdp_debug_verbose(b, "wheel: rawValue:%d accumPrecise:%d accumDiscrete %d\n", + ivalue, *accumWheelRotationPrecise, *accumWheelRotationDiscrete); if (abs(*accumWheelRotationPrecise) >= 12) { value = (double)(*accumWheelRotationPrecise / 12); From 6a8cd49888c25bbabf8ba5fb9c8ee0883ad3175e Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 19 Oct 2021 12:35:13 -0700 Subject: [PATCH 1498/1642] Build weston with FreeRDP 3.0 (#47) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/meson.build | 21 ++++++++++----- libweston/backend-rdp/rdp.c | 18 ++++++++++++- libweston/backend-rdp/rdprail.c | 10 +++++-- rdprail-shell/app-list.c | 44 ++++++++++++++++++++++--------- rdprail-shell/meson.build | 9 +++++-- 5 files changed, 78 insertions(+), 24 deletions(-) diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index 59e9db6a0..da22ad64d 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -4,19 +4,28 @@ endif config_h.set('BUILD_RDP_COMPOSITOR', '1') -dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) +dep_frdp = dependency('freerdp3', version: '>= 3.0.0', required: false) if not dep_frdp.found() - error('RDP-backend requires freerdp2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) + if not dep_frdp.found() + error('RDP-backend requires freerdp2 or 3 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + endif endif -dep_frdp_server = dependency('freerdp-server2', version: '>= 2.0.0', required: false) +dep_frdp_server = dependency('freerdp-server3', version: '>= 3.0.0', required: false) if not dep_frdp_server.found() - error('RDP-backend requires freerdp2-server2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + dep_frdp_server = dependency('freerdp-server2', version: '>= 2.0.0', required: false) + if not dep_frdp_server.found() + error('RDP-backend requires freerdp-server2 or 3 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + endif endif -dep_wpr = dependency('winpr2', version: '>= 2.0.0', required: false) +dep_wpr = dependency('winpr3', version: '>= 3.0.0', required: false) if not dep_wpr.found() - error('RDP-backend requires winpr2 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + dep_wpr = dependency('winpr2', version: '>= 2.0.0', required: false) + if not dep_wpr.found() + error('RDP-backend requires winpr2 or 3 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + endif endif dep_rdpapplist = dependency('rdpapplist', version: '>= 1.0.0', required: false) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 7f7c0efd5..2b18732e9 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -45,6 +45,7 @@ #include "rdp.h" +#include #include #if FREERDP_VERSION_MAJOR >= 2 @@ -1061,8 +1062,11 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { // but Xkb doesn't have that layout in "ir" group. {KBD_FARSI, "ir", "pes"}, // 0x50429 is for Dari(Afghanistan) - // TODO: define KBD_DARI in winpr's keyboard.h +#if WINPR_VERSION_MAJOR >= 3 + {KBD_PERSIAN, "af", "basic"}, +#else {0x50429, "af", "basic"}, +#endif {KBD_VIETNAMESE, "vn", 0}, {KBD_ARMENIAN_EASTERN, "am", 0}, {KBD_AZERI_LATIN, 0, 0}, @@ -1944,7 +1948,11 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) client->update->SuppressOutput = (pSuppressOutput)xf_suppress_output; +#if FREERDP_VERSION_MAJOR >= 3 + input = client->context->input; +#else input = client->input; +#endif input->SynchronizeEvent = xf_input_synchronize_event; input->MouseEvent = xf_mouseEvent; input->ExtendedMouseEvent = xf_extendedMouseEvent; @@ -2268,6 +2276,14 @@ rdp_backend_create(struct weston_compositor *compositor, } rdp_debug(b, "RDP backend: Environment dump - end\n"); +#ifdef FREERDP_GIT_REVISION + rdp_debug(b, "RDP backend: FreeRDP version: %s, Git version: %s\n", + FREERDP_VERSION_FULL, FREERDP_GIT_REVISION); +#else + rdp_debug(b, "RDP backend: FreeRDP version: %s\n", + FREERDP_VERSION_FULL); +#endif + compositor->backend = &b->base; fd = use_vsock_fd(config->port); diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 4065e6850..7c2c7d36f 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -4030,16 +4030,22 @@ rdp_rail_backend_create(struct rdp_backend *b) use_gfxredir = false; dlerror(); /* clear error */ +#if FREERDP_VERSION_MAJOR >= 3 + b->libFreeRDPServer = dlopen("libfreerdp-server3.so", RTLD_NOW); +#else b->libFreeRDPServer = dlopen("libfreerdp-server2.so", RTLD_NOW); +#endif if (!b->libFreeRDPServer) { - rdp_debug_error(b, "dlopen(libfreerdp-server2.so) failed with %s\n", dlerror()); + rdp_debug_error(b, "dlopen(libfreerdp-server%d.so) failed with %s\n", + FREERDP_VERSION_MAJOR, dlerror()); } else { *(void **)(&b->gfxredir_server_context_new) = dlsym(b->libFreeRDPServer, "gfxredir_server_context_new"); *(void **)(&b->gfxredir_server_context_free) = dlsym(b->libFreeRDPServer, "gfxredir_server_context_free"); if (b->gfxredir_server_context_new && b->gfxredir_server_context_new) { use_gfxredir = true; } else { - rdp_debug(b, "libfreerdp-server2.so doesn't support graphics redirection API.\n"); + rdp_debug(b, "libfreerdp-server%d.so doesn't support graphics redirection API.\n", + FREERDP_VERSION_MAJOR); dlclose(b->libFreeRDPServer); b->libFreeRDPServer = NULL; } diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index c9d4557d4..c1fc69ddc 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -50,7 +50,8 @@ #include "shell.h" #include "shared/helpers.h" -#ifdef HAVE_WINPR2 +#ifdef HAVE_WINPR +#include #include #include #include @@ -586,7 +587,11 @@ app_list_desktop_file_changed(struct desktop_shell *shell, char *folder, char *f send_app_entry(shell, key, entry, false, false, false, false, false, false); } } else { +#if WINPR_VERSION_MAJOR >= 3 + if (HashTable_Insert(context->table, key, (void *)entry) < 0) +#else if (HashTable_Add(context->table, key, (void *)entry) < 0) +#endif free_app_entry(entry); else if (context->isRdpNotifyStarted) send_app_entry(shell, key, entry, true, false, false, false, false, false); @@ -1154,11 +1159,11 @@ stop_app_list_monitor(struct desktop_shell *shell) assert(context->weston_pidfd <= 0); assert(context->app_list_pidfd <= 0); } -#endif // HAVE_WINPR2 +#endif // HAVE_WINPR pixman_image_t* app_list_load_icon_file(struct desktop_shell *shell, const char *key) { -#ifdef HAVE_WINPR2 +#ifdef HAVE_WINPR struct app_list_context *context = (struct app_list_context *)shell->app_list_context; pixman_image_t* image = NULL; @@ -1186,7 +1191,7 @@ pixman_image_t* app_list_load_icon_file(struct desktop_shell *shell, const char void app_list_find_image_name(struct desktop_shell *shell, pid_t pid, char *image_name, size_t image_name_size, bool is_wayland) { -#ifdef HAVE_WINPR2 +#ifdef HAVE_WINPR struct app_list_context *context = (struct app_list_context *)shell->app_list_context; if (context) { @@ -1215,7 +1220,7 @@ void app_list_find_image_name(struct desktop_shell *shell, pid_t pid, char *imag bool app_list_start_backend_update(struct desktop_shell *shell, char *clientLanguageId) { -#ifdef HAVE_WINPR2 +#ifdef HAVE_WINPR struct app_list_context *context = (struct app_list_context *)shell->app_list_context; if (context) { if (!clientLanguageId || *clientLanguageId == '\0') @@ -1232,7 +1237,7 @@ bool app_list_start_backend_update(struct desktop_shell *shell, char *clientLang void app_list_stop_backend_update(struct desktop_shell *shell) { -#ifdef HAVE_WINPR2 +#ifdef HAVE_WINPR struct app_list_context *context = (struct app_list_context *)shell->app_list_context; if (context) { SetEvent(context->stopRdpNotifyEvent); @@ -1245,9 +1250,12 @@ void app_list_stop_backend_update(struct desktop_shell *shell) void app_list_init(struct desktop_shell *shell) { -#ifdef HAVE_WINPR2 +#ifdef HAVE_WINPR struct app_list_context *context; wHashTable* table; +#if WINPR_VERSION_MAJOR >= 3 + wObject* obj; +#endif char *iconpath; shell->app_list_context = NULL; @@ -1261,13 +1269,23 @@ void app_list_init(struct desktop_shell *shell) free(context); return; } + +#if WINPR_VERSION_MAJOR >= 3 + if (!HashTable_SetupForStringData(table, false)) { + free(context); + return; + } + obj = HashTable_ValueObject(table); + obj->fnObjectNew = NULL; // make sure value won't be cloned. + obj->fnObjectFree = free_app_entry; +#else table->hash = HashTable_StringHash; table->keyCompare = HashTable_StringCompare; - //table->valueCompare = HashTable_StringCompare; table->keyClone = HashTable_StringClone; - table->valueClone = NULL; // make sure value won't be cloned. table->keyFree = HashTable_StringFree; + table->valueClone = NULL; // make sure value won't be cloned. table->valueFree = free_app_entry; +#endif context->table = table; shell->app_list_context = (void *)context; @@ -1289,12 +1307,12 @@ void app_list_init(struct desktop_shell *shell) start_app_list_monitor(shell); #else shell->app_list_context = NULL; -#endif // HAVE_WINPR2 +#endif // HAVE_WINPR } void app_list_destroy(struct desktop_shell *shell) { -#ifdef HAVE_WINPR2 +#ifdef HAVE_WINPR struct app_list_context *context = (struct app_list_context *)shell->app_list_context; wHashTable* table; int count; @@ -1319,6 +1337,6 @@ void app_list_destroy(struct desktop_shell *shell) shell->app_list_context = NULL; } #else - asseer(!shell->app_list_context); -#endif // HAVE_WINPR2 + assert(!shell->app_list_context); +#endif // HAVE_WINPR } diff --git a/rdprail-shell/meson.build b/rdprail-shell/meson.build index 8175adc96..82a221ab0 100644 --- a/rdprail-shell/meson.build +++ b/rdprail-shell/meson.build @@ -1,7 +1,12 @@ if get_option('shell-rdprail') - dep_winpr = dependency('winpr2', version: '>= 2.0.0', required: false) + dep_winpr = dependency('winpr3', version: '>= 3.0.0', required: false) if dep_winpr.found() - config_h.set('HAVE_WINPR2', '1') + config_h.set('HAVE_WINPR', '1') + else + dep_winpr = dependency('winpr2', version: '>= 2.0.0', required: false) + if dep_winpr.found() + config_h.set('HAVE_WINPR', '1') + endif endif dep_librsvg = dependency('librsvg-2.0', version: '>= 2.36.0', required: false) if dep_librsvg.found() From 7b902d79019eb20c1461164761527634e1af0dd2 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 21 Oct 2021 12:55:42 -0700 Subject: [PATCH 1499/1642] Fix reference to grfxredirection channel (#48) Based on code review feedback from FreeRDP folks, we updated head headers of the grfx redirection protocol. This change make weston compatible with the updated definition. Co-authored-by: Steve Pronovost --- libweston/backend-rdp/rdprail.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 7c2c7d36f..e292c987d 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -1106,7 +1106,7 @@ gfxredir_client_graphics_redirection_caps_advertise(GfxRedirServerContext* conte confirmPdu.version = selected->version; /* return the version of selected caps */ confirmPdu.length = selected->length; /* must return same length as selected caps from advertised */ - confirmPdu.capsData = &selected->capsData[0]; /* return caps data in selected caps */ + confirmPdu.capsData = (const BYTE*)(selected+1); /* return caps data in selected caps */ peerCtx->gfxredir_server_context->GraphicsRedirectionCapsConfirm(context, &confirmPdu); } From 9adeb341314c19468c10cf2b265a833c887ad172 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 8 Nov 2021 08:04:54 -0800 Subject: [PATCH 1500/1642] disable keep display power on by screen update by default (#49) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.h | 2 ++ libweston/backend-rdp/rdprail.c | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 249d5292a..b385e9b5e 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -186,6 +186,8 @@ struct rdp_backend { bool enable_window_zorder_sync; + bool keep_display_power_by_screenupdate; + bool enable_hi_dpi_support; bool enable_fractional_hi_dpi_support; uint32_t debug_desktop_scaling_factor; /* must be between 100 to 500 */ diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index e292c987d..0a7797ff7 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2615,7 +2615,7 @@ rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage) rdp_rail_sync_window_zorder(b->compositor); peerCtx->is_window_zorder_dirty = false; } - if (iter_data.isUpdatePending) { + if (iter_data.isUpdatePending && b->keep_display_power_by_screenupdate) { /* By default, compositor won't update idle timer by screen activity, thus, here manually call wake function to postpone idle timer when RDP backend sends frame to client. */ @@ -4120,6 +4120,14 @@ rdp_rail_backend_create(struct rdp_backend *b) } rdp_debug(b, "RDP backend: enable_window_zorder_sync = %d\n", b->enable_window_zorder_sync); + b->keep_display_power_by_screenupdate = false; + s = getenv("WESTON_RDP_ENABLE_DISPLAY_POWER_BY_SCREENUPDATE"); + if (s) { + if (strcmp(s, "true") == 0) + b->keep_display_power_by_screenupdate = true; + } + rdp_debug(b, "RDP backend: keep_display_power_by_screenupdate = %d\n", b->keep_display_power_by_screenupdate); + b->rdprail_shell_name = NULL; b->enable_distro_name_title = true; From dd98a512d47f9af1dfcb6d80c76e0a1b8a9d71ca Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 9 Nov 2021 08:32:05 -0800 Subject: [PATCH 1501/1642] rdprail-shell should not call into weston_desktop_api_maximized_requested directly (#50) Co-authored-by: Hideyuki Nagase --- libweston-desktop/libweston-desktop.c | 2 +- rdprail-shell/shell.c | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/libweston-desktop/libweston-desktop.c b/libweston-desktop/libweston-desktop.c index c72039211..f2fff1b3a 100644 --- a/libweston-desktop/libweston-desktop.c +++ b/libweston-desktop/libweston-desktop.c @@ -225,7 +225,7 @@ weston_desktop_api_fullscreen_requested(struct weston_desktop *desktop, desktop->user_data); } -WL_EXPORT void +void weston_desktop_api_maximized_requested(struct weston_desktop *desktop, struct weston_desktop_surface *surface, bool maximized) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 832bafa32..28646dc29 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -2627,10 +2627,7 @@ shell_backend_request_window_maximize(struct weston_surface *surface) if (api && api->is_xwayland_surface(surface)) { api->set_maximized(surface, true); } else { - struct weston_desktop_surface *desktop_surface = - weston_surface_get_desktop_surface(surface); - - weston_desktop_api_maximized_requested(shsurf->shell->desktop, desktop_surface, true); + set_maximized(shsurf, true); } } @@ -2659,10 +2656,7 @@ shell_backend_request_window_restore(struct weston_surface *surface) if (api && api->is_xwayland_surface(surface)) { api->set_maximized(surface, false); } else { - struct weston_desktop_surface *desktop_surface = - weston_surface_get_desktop_surface(surface); - - weston_desktop_api_maximized_requested(shsurf->shell->desktop, desktop_surface, false); + set_maximized(shsurf, false); } } } From 85906558d8587de51151e18dd24ad84ce7dbf473 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 9 Nov 2021 15:00:06 -0800 Subject: [PATCH 1502/1642] backend-rdp: port fixes from upstream (#51) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/meson.build | 40 ++------ libweston/backend-rdp/rdp.c | 159 ++++++++++++------------------ libweston/backend-rdp/rdp.h | 58 +---------- 3 files changed, 70 insertions(+), 187 deletions(-) diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index da22ad64d..fd2884c72 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -6,25 +6,25 @@ config_h.set('BUILD_RDP_COMPOSITOR', '1') dep_frdp = dependency('freerdp3', version: '>= 3.0.0', required: false) if not dep_frdp.found() - dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) + dep_frdp = dependency('freerdp2', version: '>= 2.2.0', required: false) if not dep_frdp.found() - error('RDP-backend requires freerdp2 or 3 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + error('RDP-backend requires freerdp >= 2.2.0 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') endif endif dep_frdp_server = dependency('freerdp-server3', version: '>= 3.0.0', required: false) if not dep_frdp_server.found() - dep_frdp_server = dependency('freerdp-server2', version: '>= 2.0.0', required: false) + dep_frdp_server = dependency('freerdp-server2', version: '>= 2.2.0', required: false) if not dep_frdp_server.found() - error('RDP-backend requires freerdp-server2 or 3 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + error('RDP-backend requires freerdp-server >= 2.2.0 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') endif endif dep_wpr = dependency('winpr3', version: '>= 3.0.0', required: false) if not dep_wpr.found() - dep_wpr = dependency('winpr2', version: '>= 2.0.0', required: false) + dep_wpr = dependency('winpr2', version: '>= 2.2.0', required: false) if not dep_wpr.found() - error('RDP-backend requires winpr2 or 3 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + error('RDP-backend requires winpr >= 2.2.0 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') endif endif @@ -33,38 +33,10 @@ if dep_rdpapplist.found() config_h.set('HAVE_FREERDP_RDPAPPLIST_H', '1') endif -if cc.has_header('freerdp/version.h', dependencies: dep_frdp) - config_h.set('HAVE_FREERDP_VERSION_H', '1') -endif - if cc.has_header('freerdp/channels/gfxredir.h', dependencies: dep_frdp) config_h.set('HAVE_FREERDP_GFXREDIR_H', '1') endif -if cc.has_member( - 'SURFACE_BITS_COMMAND', 'bmp', - dependencies : dep_frdp, - prefix : '#include ' -) - config_h.set('HAVE_SURFACE_BITS_BMP', '1') -endif - -if cc.has_type( - 'enum SURFCMD_CMDTYPE', - dependencies : dep_frdp, - prefix : '#include ' -) - config_h.set('HAVE_SURFCMD_CMDTYPE', '1') -endif - -if cc.has_function( - 'nsc_context_set_parameters', - dependencies : dep_frdp, - prefix: '#include ' -) - config_h.set('HAVE_NSC_CONTEXT_SET_PARAMETERS', '1') -endif - if cc.has_member( 'rdpsnd_server_context', 'use_dynamic_virtual_channel', dependencies : dep_frdp, diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 2b18732e9..16b75d94d 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -83,7 +83,7 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p uint32_t *ptr; RFX_RECT *rfxRect; rdpUpdate *update = peer->update; - SURFACE_BITS_COMMAND cmd; + SURFACE_BITS_COMMAND cmd = { 0 }; RdpPeerContext *context = (RdpPeerContext *)peer->context; Stream_Clear(context->encode_stream); @@ -92,22 +92,16 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p width = (damage->extents.x2 - damage->extents.x1); height = (damage->extents.y2 - damage->extents.y1); -#ifdef HAVE_SKIP_COMPRESSION cmd.skipCompression = TRUE; -#else - memset(&cmd, 0, sizeof(*cmd)); -#endif -#ifdef HAVE_SURFCMD_CMDTYPE cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS; -#endif cmd.destLeft = damage->extents.x1; cmd.destTop = damage->extents.y1; cmd.destRight = damage->extents.x2; cmd.destBottom = damage->extents.y2; - SURFACE_BPP(cmd) = 32; - SURFACE_CODECID(cmd) = peer->settings->RemoteFxCodecId; - SURFACE_WIDTH(cmd) = width; - SURFACE_HEIGHT(cmd) = height; + cmd.bmp.bpp = 32; + cmd.bmp.codecID = peer->settings->RemoteFxCodecId; + cmd.bmp.width = width; + cmd.bmp.height = height; ptr = pixman_image_get_data(image) + damage->extents.x1 + damage->extents.y1 * (pixman_image_get_stride(image) / sizeof(uint32_t)); @@ -130,8 +124,8 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p pixman_image_get_stride(image) ); - SURFACE_BITMAP_DATA_LEN(cmd) = Stream_GetPosition(context->encode_stream); - SURFACE_BITMAP_DATA(cmd) = Stream_Buffer(context->encode_stream); + cmd.bmp.bitmapDataLength = Stream_GetPosition(context->encode_stream); + cmd.bmp.bitmapData = Stream_Buffer(context->encode_stream); update->SurfaceBits(update->context, &cmd); } @@ -143,7 +137,7 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p int width, height; uint32_t *ptr; rdpUpdate *update = peer->update; - SURFACE_BITS_COMMAND cmd; + SURFACE_BITS_COMMAND cmd = { 0 }; RdpPeerContext *context = (RdpPeerContext *)peer->context; Stream_Clear(context->encode_stream); @@ -152,22 +146,16 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p width = (damage->extents.x2 - damage->extents.x1); height = (damage->extents.y2 - damage->extents.y1); -#ifdef HAVE_SKIP_COMPRESSION cmd.skipCompression = TRUE; -#else - memset(cmd, 0, sizeof(*cmd)); -#endif -#ifdef HAVE_SURFCMD_CMDTYPE cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; -#endif cmd.destLeft = damage->extents.x1; cmd.destTop = damage->extents.y1; cmd.destRight = damage->extents.x2; cmd.destBottom = damage->extents.y2; - SURFACE_BPP(cmd) = 32; - SURFACE_CODECID(cmd) = peer->settings->NSCodecId; - SURFACE_WIDTH(cmd) = width; - SURFACE_HEIGHT(cmd) = height; + cmd.bmp.bpp = 32; + cmd.bmp.codecID = peer->settings->NSCodecId; + cmd.bmp.width = width; + cmd.bmp.height = height; ptr = pixman_image_get_data(image) + damage->extents.x1 + damage->extents.y1 * (pixman_image_get_stride(image) / sizeof(uint32_t)); @@ -176,8 +164,8 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p width, height, pixman_image_get_stride(image)); - SURFACE_BITMAP_DATA_LEN(cmd) = Stream_GetPosition(context->encode_stream); - SURFACE_BITMAP_DATA(cmd) = Stream_Buffer(context->encode_stream); + cmd.bmp.bitmapDataLength = Stream_GetPosition(context->encode_stream); + cmd.bmp.bitmapData = Stream_Buffer(context->encode_stream); update->SurfaceBits(update->context, &cmd); } @@ -200,7 +188,7 @@ static void rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_peer *peer) { rdpUpdate *update = peer->update; - SURFACE_BITS_COMMAND cmd; + SURFACE_BITS_COMMAND cmd = { 0 }; SURFACE_FRAME_MARKER marker; pixman_box32_t *rect, subrect; int nrects, i; @@ -214,20 +202,17 @@ rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_p marker.frameAction = SURFACECMD_FRAMEACTION_BEGIN; update->SurfaceFrameMarker(peer->context, &marker); - memset(&cmd, 0, sizeof(cmd)); -#ifdef HAVE_SURFCMD_CMDTYPE cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; -#endif - SURFACE_BPP(cmd) = 32; - SURFACE_CODECID(cmd) = 0; + cmd.bmp.bpp = 32; + cmd.bmp.codecID = 0; for (i = 0; i < nrects; i++, rect++) { /*weston_log("rect(%d,%d, %d,%d)\n", rect->x1, rect->y1, rect->x2, rect->y2);*/ cmd.destLeft = rect->x1; cmd.destRight = rect->x2; - SURFACE_WIDTH(cmd) = rect->x2 - rect->x1; + cmd.bmp.width = rect->x2 - rect->x1; - heightIncrement = peer->settings->MultifragMaxRequestSize / (16 + SURFACE_WIDTH(cmd) * 4); + heightIncrement = peer->settings->MultifragMaxRequestSize / (16 + cmd.bmp.width * 4); remainingHeight = rect->y2 - rect->y1; top = rect->y1; @@ -235,25 +220,25 @@ rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_p subrect.x2 = rect->x2; while (remainingHeight) { - SURFACE_HEIGHT(cmd) = (remainingHeight > heightIncrement) ? heightIncrement : remainingHeight; + cmd.bmp.height = (remainingHeight > heightIncrement) ? heightIncrement : remainingHeight; cmd.destTop = top; - cmd.destBottom = top + SURFACE_HEIGHT(cmd); - SURFACE_BITMAP_DATA_LEN(cmd) = SURFACE_WIDTH(cmd) * SURFACE_HEIGHT(cmd) * 4; - SURFACE_BITMAP_DATA(cmd) = (BYTE *)realloc(SURFACE_BITMAP_DATA(cmd), SURFACE_BITMAP_DATA_LEN(cmd)); + cmd.destBottom = top + cmd.bmp.height; + cmd.bmp.bitmapDataLength = cmd.bmp.width * cmd.bmp.height * 4; + cmd.bmp.bitmapData = (BYTE *)realloc(cmd.bmp.bitmapData, cmd.bmp.bitmapDataLength); subrect.y1 = top; - subrect.y2 = top + SURFACE_HEIGHT(cmd); - pixman_image_flipped_subrect(&subrect, image, SURFACE_BITMAP_DATA(cmd)); + subrect.y2 = top + cmd.bmp.height; + pixman_image_flipped_subrect(&subrect, image, cmd.bmp.bitmapData); /*weston_log("* sending (%d,%d, %d,%d)\n", subrect.x1, subrect.y1, subrect.x2, subrect.y2); */ update->SurfaceBits(peer->context, &cmd); - remainingHeight -= SURFACE_HEIGHT(cmd); - top += SURFACE_HEIGHT(cmd); + remainingHeight -= cmd.bmp.height; + top += cmd.bmp.height; } } - free(SURFACE_BITMAP_DATA(cmd)); + free(cmd.bmp.bitmapData); marker.frameAction = SURFACECMD_FRAMEACTION_END; update->SurfaceFrameMarker(peer->context, &marker); @@ -394,7 +379,7 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) rdpSettings *settings; pixman_image_t *new_shadow_buffer; struct weston_mode *local_mode, *previous_mode; - const struct pixman_renderer_output_options options = { }; + const struct pixman_renderer_output_options options = { .use_shadow = true, }; bool HiDefRemoteApp = false; if (rdpBackend->rdp_peer && rdpBackend->rdp_peer->settings->HiDefRemoteApp) @@ -857,7 +842,7 @@ int rdp_implant_listener(struct rdp_backend *b, freerdp_listener* instance) } -static FREERDP_CB_RET_TYPE +static BOOL rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) { context->item.peer = client; @@ -865,14 +850,9 @@ rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) context->loop_event_source_fd = -1; -#if FREERDP_VERSION_MAJOR == 1 && FREERDP_VERSION_MINOR == 1 - context->rfx_context = rfx_context_new(); -#else context->rfx_context = rfx_context_new(TRUE); -#endif - if (!context->rfx_context) { - FREERDP_CB_RETURN(FALSE); - } + if (!context->rfx_context) + return FALSE; context->rfx_context->mode = RLGR3; context->rfx_context->width = client->settings->DesktopWidth; @@ -883,22 +863,18 @@ rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) if (!context->nsc_context) goto out_error_nsc; -#ifdef HAVE_NSC_CONTEXT_SET_PARAMETERS nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, DEFAULT_PIXEL_FORMAT); -#else - nsc_context_set_pixel_format(context->nsc_context, DEFAULT_PIXEL_FORMAT); -#endif context->encode_stream = Stream_New(NULL, 65536); if (!context->encode_stream) goto out_error_stream; - FREERDP_CB_RETURN(TRUE); + return TRUE; out_error_nsc: rfx_context_free(context->rfx_context); out_error_stream: nsc_context_free(context->nsc_context); - FREERDP_CB_RETURN(FALSE); + return FALSE; } static void @@ -938,19 +914,10 @@ rdp_peer_context_free(freerdp_peer* client, RdpPeerContext* context) if (context->item.flags & RDP_PEER_ACTIVATED) { weston_seat_release_keyboard(context->item.seat); weston_seat_release_pointer(context->item.seat); - if (!client->settings->RemoteApplicationMode) { - /* XXX we should weston_seat_release(context->item.seat); here - * but it would crash on reconnect */ - } else { - /* Without understanding full details of above comments, but - in RAIL mode, only one peer per backend can be activated, - and no "deactivate_all" PDU to be used (since no client - side desktop resize is allowed), so safe to free seat here - to prevent possible memory leak. */ - weston_seat_release(context->item.seat); - context->item.seat = NULL; - context->item.flags &= ~RDP_PEER_ACTIVATED; - } + weston_seat_release(context->item.seat); + free(context->item.seat); + context->item.seat = NULL; + context->item.flags &= ~RDP_PEER_ACTIVATED; } Stream_Free(context->encode_stream, TRUE); @@ -1348,8 +1315,8 @@ xf_peer_activate(freerdp_peer* client) pixman_region32_init_rect(&b->head_default->regionWeston, 0, 0, weston_output->width, weston_output->height); - RFX_RESET(peerCtx->rfx_context, weston_output->width, weston_output->height); - NSC_RESET(peerCtx->nsc_context, weston_output->width, weston_output->height); + rfx_context_reset(peerCtx->rfx_context, weston_output->width, weston_output->height); + nsc_context_reset(peerCtx->nsc_context, weston_output->width, weston_output->height); } if (settings->RemoteApplicationMode) @@ -1589,7 +1556,7 @@ rdp_notify_wheel_scroll(RdpPeerContext *peerContext, UINT16 flags, uint32_t axis return false; } -static FREERDP_CB_RET_TYPE +static BOOL xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) { RdpPeerContext *peerContext = (RdpPeerContext *)input->context; @@ -1653,10 +1620,10 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) if (need_frame) notify_pointer_frame(peerContext->item.seat); - FREERDP_CB_RETURN(TRUE); + return TRUE; } -static FREERDP_CB_RET_TYPE +static BOOL xf_extendedMouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) { RdpPeerContext *peerContext = (RdpPeerContext *)input->context; @@ -1691,10 +1658,10 @@ xf_extendedMouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) if (need_frame) notify_pointer_frame(peerContext->item.seat); - FREERDP_CB_RETURN(TRUE); + return TRUE; } -static FREERDP_CB_RET_TYPE +static BOOL xf_input_synchronize_event(rdpInput *input, UINT32 flags) { freerdp_peer *client = input->context->peer; @@ -1736,10 +1703,10 @@ xf_input_synchronize_event(rdpInput *input, UINT32 flags) pixman_region32_fini(&damage); } - FREERDP_CB_RETURN(TRUE); + return TRUE; } -static FREERDP_CB_RET_TYPE +static BOOL xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) { uint32_t scan_code, vk_code, full_code; @@ -1755,7 +1722,7 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) struct timespec time; if (!(peerContext->item.flags & RDP_PEER_ACTIVATED)) - FREERDP_CB_RETURN(TRUE); + return TRUE; if (flags & KBD_FLAGS_DOWN) { keyState = WL_KEYBOARD_KEY_STATE_PRESSED; @@ -1839,10 +1806,10 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) } } - FREERDP_CB_RETURN(TRUE); + return TRUE; } -static FREERDP_CB_RET_TYPE +static BOOL xf_input_unicode_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) { RdpPeerContext *peerContext = (RdpPeerContext *)input->context; @@ -1850,11 +1817,11 @@ xf_input_unicode_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) rdp_debug_error(b, "Client sent a unicode keyboard event (flags:0x%X code:0x%X)\n", flags, code); - FREERDP_CB_RETURN(TRUE); + return TRUE; } -static FREERDP_CB_RET_TYPE +static BOOL xf_suppress_output(rdpContext *context, BYTE allow, const RECTANGLE_16 *area) { RdpPeerContext *peerContext = (RdpPeerContext *)context; @@ -1864,7 +1831,7 @@ xf_suppress_output(rdpContext *context, BYTE allow, const RECTANGLE_16 *area) else peerContext->item.flags &= (~RDP_PEER_OUTPUT_ENABLED); - FREERDP_CB_RETURN(TRUE); + return TRUE; } static BOOL @@ -2024,16 +1991,16 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) } -static FREERDP_CB_RET_TYPE +static BOOL rdp_incoming_peer(freerdp_listener *instance, freerdp_peer *client) { struct rdp_backend *b = (struct rdp_backend *)instance->param4; if (rdp_peer_init(client, b) < 0) { rdp_debug_error(b, "error when treating incoming peer\n"); - FREERDP_CB_RETURN(FALSE); + return FALSE; } - FREERDP_CB_RETURN(TRUE); + return TRUE; } #if HAVE_OPENSSL @@ -2192,6 +2159,8 @@ rdp_backend_create(struct weston_compositor *compositor, char *fd_str; char *fd_tail; int fd, ret; + + struct weston_head *base, *next; struct rdp_output *output; char *s; int i; @@ -2342,7 +2311,7 @@ rdp_backend_create(struct weston_compositor *compositor, } if (rdp_implant_listener(b, b->listener) < 0) - goto err_compositor; + goto err_listener; } else { /* get the socket from RDP_FD var */ fd_str = getenv("RDP_FD"); @@ -2370,14 +2339,12 @@ rdp_backend_create(struct weston_compositor *compositor, err_listener: freerdp_listener_free(b->listener); err_output: - wl_list_for_each(output, &b->output_list, link) { + wl_list_for_each(output, &b->output_list, link) weston_output_release(&output->base); - } - if (b->head_default) { - rdp_head_destroy(compositor, b->head_default); - assert(b->head_default == NULL); - } err_compositor: + wl_list_for_each_safe(base, next, &compositor->head_list, compositor_link) + rdp_head_destroy(compositor, to_rdp_head(base)); + weston_compositor_shutdown(compositor); err_free_strings: if (b->debugClipboard) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index b385e9b5e..70bb88cc7 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -26,55 +26,7 @@ #ifndef RDP_H #define RDP_H -#if HAVE_FREERDP_VERSION_H #include -#else -/* assume it's a early 1.1 version */ -#define FREERDP_VERSION_MAJOR 1 -#define FREERDP_VERSION_MINOR 1 -#define FREERDP_VERSION_REVISION 0 -#endif - -#define FREERDP_VERSION_NUMBER ((FREERDP_VERSION_MAJOR * 0x10000) + \ - (FREERDP_VERSION_MINOR * 0x100) + FREERDP_VERSION_REVISION) - - -#if FREERDP_VERSION_NUMBER >= 0x10201 -#define HAVE_SKIP_COMPRESSION -#endif - -#if FREERDP_VERSION_NUMBER < 0x10202 -# define FREERDP_CB_RET_TYPE void -# define FREERDP_CB_RETURN(V) return -# define NSC_RESET(C, W, H) -# define RFX_RESET(C, W, H) do { rfx_context_reset(C); C->width = W; C->height = H; } while(0) -#else -#if FREERDP_VERSION_MAJOR >= 2 -# define NSC_RESET(C, W, H) nsc_context_reset(C, W, H) -# define RFX_RESET(C, W, H) rfx_context_reset(C, W, H) -#else -# define NSC_RESET(C, W, H) do { nsc_context_reset(C); C->width = W; C->height = H; } while(0) -# define RFX_RESET(C, W, H) do { rfx_context_reset(C); C->width = W; C->height = H; } while(0) -#endif -#define FREERDP_CB_RET_TYPE BOOL -#define FREERDP_CB_RETURN(V) return TRUE -#endif - -#ifdef HAVE_SURFACE_BITS_BMP -#define SURFACE_BPP(cmd) cmd.bmp.bpp -#define SURFACE_CODECID(cmd) cmd.bmp.codecID -#define SURFACE_WIDTH(cmd) cmd.bmp.width -#define SURFACE_HEIGHT(cmd) cmd.bmp.height -#define SURFACE_BITMAP_DATA(cmd) cmd.bmp.bitmapData -#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bmp.bitmapDataLength -#else -#define SURFACE_BPP(cmd) cmd.bpp -#define SURFACE_CODECID(cmd) cmd.codecID -#define SURFACE_WIDTH(cmd) cmd.width -#define SURFACE_HEIGHT(cmd) cmd.height -#define SURFACE_BITMAP_DATA(cmd) cmd.bitmapData -#define SURFACE_BITMAP_DATA_LEN(cmd) cmd.bitmapDataLength -#endif #include #include @@ -115,15 +67,7 @@ #define RDP_MODE_FREQ 60 * 1000 #define RDP_MAX_MONITOR 16 // RDP max monitors. -#if FREERDP_VERSION_MAJOR >= 2 && defined(PIXEL_FORMAT_BGRA32) && !defined(PIXEL_FORMAT_B8G8R8A8) - /* The RDP API is truly wonderful: the pixel format definition changed - * from BGRA32 to B8G8R8A8, but some versions ship with a definition of - * PIXEL_FORMAT_BGRA32 which doesn't actually build. Try really, really, - * hard to find one which does. */ -# define DEFAULT_PIXEL_FORMAT PIXEL_FORMAT_BGRA32 -#else -# define DEFAULT_PIXEL_FORMAT RDP_PIXEL_FORMAT_B8G8R8A8 -#endif +#define DEFAULT_PIXEL_FORMAT PIXEL_FORMAT_BGRA32 struct rdp_output; struct rdp_clipboard_data_source; From efe5ca921fb0dc5488d6daa7f695f48e33e2ded4 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 9 Nov 2021 19:27:05 -0800 Subject: [PATCH 1503/1642] backend-rdp: hold mutex while accessing rail_state from non-compositor thread (#52) * backend-rdp: add mutex to hashtable access * hold mutex while accessing rail_state from non-compositor thread * reset tid under lock Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.h | 6 +++ libweston/backend-rdp/rdpdisp.c | 2 +- libweston/backend-rdp/rdprail.c | 27 ++++++------ libweston/backend-rdp/rdputil.c | 73 ++++++++++++++++++++++++++++++--- 4 files changed, 90 insertions(+), 18 deletions(-) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 70bb88cc7..4e6e2a9cb 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -80,6 +80,8 @@ struct rdp_id_manager { UINT32 id_high_limit; UINT32 id_total; UINT32 id_used; + pthread_mutex_t mutex; + pid_t mutex_tid; struct hash_table *hash_table; }; @@ -393,6 +395,10 @@ BOOL rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_ void rdp_free_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory); BOOL rdp_id_manager_init(struct rdp_backend *rdp_backend, struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 high_limit); void rdp_id_manager_free(struct rdp_id_manager *id_manager); +void rdp_id_manager_lock(struct rdp_id_manager *id_manager); +void rdp_id_manager_unlock(struct rdp_id_manager *id_manager); +void *rdp_id_manager_lookup(struct rdp_id_manager *id_manager, UINT32 id); +void rdp_id_manager_for_each(struct rdp_id_manager *id_manager, hash_table_iterator_func_t func, void *data); BOOL rdp_id_manager_allocate_id(struct rdp_id_manager *id_manager, void *object, UINT32 *new_id); void rdp_id_manager_free_id(struct rdp_id_manager *id_manager, UINT32 id); void dump_id_manager_state(FILE *fp, struct rdp_id_manager *id_manager, char* title); diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 3a652fe6d..bc8bd69d2 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -593,7 +593,7 @@ disp_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MON peerCtx->rail_grfx_server_context->ResetGraphics(peerCtx->rail_grfx_server_context, &resetGraphics); /* force recreate all surface and redraw. */ - hash_table_for_each(peerCtx->windowId.hash_table, disp_force_recreate_iter, NULL); + rdp_id_manager_for_each(&peerCtx->windowId, disp_force_recreate_iter, NULL); weston_compositor_damage_all(b->compositor); Exit: diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 0a7797ff7..9d2aec608 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -311,7 +311,7 @@ rail_client_Activate_callback(int fd, uint32_t mask, void *arg) b->rdprail_shell_api->request_window_activate && b->rdprail_shell_context) { if (activate->windowId && activate->enabled) { - surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, activate->windowId); + surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, activate->windowId); if (!surface) rdp_debug_error(b, "Client: ClientActivate: WindowId:0x%x is not found.\n", activate->windowId); } @@ -348,7 +348,7 @@ rail_client_SnapArrange_callback(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); - surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, snap->windowId); + surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, snap->windowId); if (surface) { rail_state = (struct weston_surface_rail_state *)surface->backend_state; if (b->rdprail_shell_api && @@ -394,7 +394,7 @@ rail_client_WindowMove_callback(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); - surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, windowMove->windowId); + surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, windowMove->windowId); if (surface) { if (b->rdprail_shell_api && b->rdprail_shell_api->request_window_move) { @@ -429,7 +429,7 @@ rail_client_Syscommand_callback(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); - surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, syscommand->windowId); + surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, syscommand->windowId); if (!surface) { rdp_debug_error(b, "Client: ClientSyscommand: WindowId:0x%x is not found.\n", syscommand->windowId); goto Exit; @@ -639,7 +639,7 @@ rail_client_ClientGetAppidReq_callback(int fd, uint32_t mask, void *arg) if (b->rdprail_shell_api && b->rdprail_shell_api->get_window_app_id) { - surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, getAppidReq->windowId); + surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, getAppidReq->windowId); if (!surface) { rdp_debug_error(b, "Client: ClientGetAppidReq: WindowId:0x%x is not found.\n", getAppidReq->windowId); goto Exit; @@ -1130,13 +1130,16 @@ gfxredir_client_present_buffer_ack(GfxRedirServerContext* context, const GFXREDI peerCtx->acknowledgedFrameId = (UINT32)presentAck->presentId; - surface = (struct weston_surface *)hash_table_lookup(peerCtx->windowId.hash_table, presentAck->windowId); + /* when accessing ID outside of wayland display loop thread, aquire lock */ + rdp_id_manager_lock(&peerCtx->windowId); + surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, presentAck->windowId); if (surface) { rail_state = (struct weston_surface_rail_state *)surface->backend_state; rail_state->isUpdatePending = FALSE; } else { rdp_debug_error(b, "Client: PresentBufferAck: WindowId:0x%lx is not found.\n", presentAck->windowId); } + rdp_id_manager_unlock(&peerCtx->windowId); return CHANNEL_RC_OK; } @@ -1333,7 +1336,7 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) /* windowId can be assigned only after activation completed */ if (!rdp_id_manager_allocate_id(&peerCtx->windowId, (void*)surface, &window_id)) { rail_state->error = true; - rdp_debug_error(b, "CreateWindow(): fail to insert windowId.hash_table (windowId:%d surface:%p.\n", + rdp_debug_error(b, "CreateWindow(): fail to insert windowId (windowId:%d surface:%p.\n", window_id, surface); return; } @@ -1581,6 +1584,7 @@ rdp_rail_destroy_window(struct wl_listener *listener, void *data) RDPGFX_DELETE_SURFACE_PDU deleteSurface = {}; rdp_debug_verbose(b, "DeleteSurface(surfaceId:0x%x for windowsId:0x%x)\n", rail_state->surface_id, window_id); + deleteSurface.surfaceId = (UINT16)rail_state->surface_id; peerCtx->rail_grfx_server_context->DeleteSurface(peerCtx->rail_grfx_server_context, &deleteSurface); @@ -2602,7 +2606,7 @@ rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage) peerCtx->currentFrameId, peerCtx->acknowledgedFrameId, peerCtx->isAcknowledgedSuspended); struct update_window_iter_data iter_data = {}; iter_data.output_id = output->id; - hash_table_for_each(peerCtx->windowId.hash_table, rdp_rail_update_window_iter, (void*) &iter_data); + rdp_id_manager_for_each(&peerCtx->windowId, rdp_rail_update_window_iter, (void*) &iter_data); if (iter_data.needEndFrame) { /* if frame is started at above iteration, send EndFrame here. */ RDPGFX_END_FRAME_PDU endFrame = {}; @@ -3151,8 +3155,7 @@ rdp_rail_peer_context_free(freerdp_peer* client, RdpPeerContext* context) { struct rdp_loop_event_source *current, *next; - if (context->windowId.hash_table) - hash_table_for_each(context->windowId.hash_table, rdp_rail_destroy_window_iter, NULL); + rdp_id_manager_for_each(&context->windowId, rdp_rail_destroy_window_iter, NULL); #ifdef HAVE_FREERDP_RDPAPPLIST_H if (context->applist_server_context) { @@ -3555,7 +3558,7 @@ rdp_rail_dump_window_binding(struct weston_keyboard *keyboard, size_t len; FILE *fp = open_memstream(&str, &len); assert(fp); - fprintf(fp,"\nrdp debug binding 'W' - dump all window from window hash_table.\n"); + fprintf(fp,"\nrdp debug binding 'W' - dump all window.\n"); peerCtx = (RdpPeerContext *)b->rdp_peer->context; dump_id_manager_state(fp, &peerCtx->windowId, "windowId"); dump_id_manager_state(fp, &peerCtx->surfaceId, "surfaceId"); @@ -3565,7 +3568,7 @@ rdp_rail_dump_window_binding(struct weston_keyboard *keyboard, #endif // HAVE_FREERDP_GFXREDIR_H context.peerCtx = peerCtx; context.fp = fp; - hash_table_for_each(peerCtx->windowId.hash_table, rdp_rail_dump_window_iter, (void*)&context); + rdp_id_manager_for_each(&peerCtx->windowId, rdp_rail_dump_window_iter, (void*)&context); err = fclose(fp); assert(err == 0); rdp_debug_error(b, "%s", str); diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index b97629028..35750ce8e 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -229,6 +229,10 @@ rdp_free_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *s BOOL rdp_id_manager_init(struct rdp_backend *rdp_backend, struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 high_limit) { + ASSERT_COMPOSITOR_THREAD(rdp_backend); + + assert(id_manager->hash_table == NULL); + assert(low_limit > 0); assert(low_limit < high_limit); id_manager->rdp_backend = rdp_backend; id_manager->id_total = high_limit - low_limit; @@ -237,20 +241,30 @@ rdp_id_manager_init(struct rdp_backend *rdp_backend, struct rdp_id_manager *id_m id_manager->id_high_limit = high_limit; id_manager->id = low_limit; id_manager->hash_table = hash_table_create(); - if (!id_manager->hash_table) + if (id_manager->hash_table) { + pthread_mutex_init(&id_manager->mutex, NULL); + /* by default, pretend mutex is held by compositor thread, + so it can be accessed without trigerring assert */ + id_manager->mutex_tid = rdp_backend->compositor_tid; + } else { rdp_debug_error(rdp_backend, "%s: unable to create hash_table.\n", __func__); + } return id_manager->hash_table != NULL; } void rdp_id_manager_free(struct rdp_id_manager *id_manager) { + ASSERT_COMPOSITOR_THREAD(id_manager->rdp_backend); + if (id_manager->id_used != 0) rdp_debug_error(id_manager->rdp_backend, "%s: possible id leak: %d\n", __func__, id_manager->id_used); if (id_manager->hash_table) { hash_table_destroy(id_manager->hash_table); - id_manager->hash_table = NULL; + pthread_mutex_destroy(&id_manager->mutex); } + id_manager->mutex_tid = 0; + id_manager->hash_table = NULL; id_manager->id = 0; id_manager->id_low_limit = 0; id_manager->id_high_limit = 0; @@ -259,31 +273,80 @@ rdp_id_manager_free(struct rdp_id_manager *id_manager) id_manager->rdp_backend = NULL; } +void +rdp_id_manager_lock(struct rdp_id_manager *id_manager) +{ + ASSERT_NOT_COMPOSITOR_THREAD(id_manager->rdp_backend); + + pthread_mutex_lock(&id_manager->mutex); + id_manager->mutex_tid = rdp_get_tid(); +} + +void +rdp_id_manager_unlock(struct rdp_id_manager *id_manager) +{ + ASSERT_NOT_COMPOSITOR_THREAD(id_manager->rdp_backend); + + /* At unlock, restore compositor thread as owner */ + id_manager->mutex_tid = id_manager->rdp_backend->compositor_tid; + pthread_mutex_unlock(&id_manager->mutex); +} + +void * +rdp_id_manager_lookup(struct rdp_id_manager *id_manager, UINT32 id) +{ + /* lookup can be done under compositor thread or after mutex held by rdp_id_manager_lock */ + assert(id_manager->mutex_tid == rdp_get_tid()); + + assert(id_manager->hash_table); + return hash_table_lookup(id_manager->hash_table, id); +} + +void +rdp_id_manager_for_each(struct rdp_id_manager *id_manager, hash_table_iterator_func_t func, void *data) +{ + ASSERT_COMPOSITOR_THREAD(id_manager->rdp_backend); + + if (!id_manager->hash_table) + return; + + hash_table_for_each(id_manager->hash_table, func, data); +} + BOOL rdp_id_manager_allocate_id(struct rdp_id_manager *id_manager, void *object, UINT32 *new_id) { UINT32 id = 0; + + ASSERT_COMPOSITOR_THREAD(id_manager->rdp_backend); + assert(id_manager->hash_table); + for(;id_manager->id_used < id_manager->id_total;) { id = id_manager->id++; if (id_manager->id == id_manager->id_high_limit) id_manager->id = id_manager->id_low_limit; /* Make sure this id is not currently used */ - if (hash_table_lookup(id_manager->hash_table, id) == NULL) { + if (rdp_id_manager_lookup(id_manager, id) == NULL) { if (hash_table_insert(id_manager->hash_table, id, object) < 0) break; /* successfully to reserve new id for given object */ id_manager->id_used++; *new_id = id; - return TRUE; + break; } } - return FALSE; + return id != 0; } void rdp_id_manager_free_id(struct rdp_id_manager *id_manager, UINT32 id) { + ASSERT_COMPOSITOR_THREAD(id_manager->rdp_backend); + assert(id_manager->hash_table); + + pthread_mutex_lock(&id_manager->mutex); hash_table_remove(id_manager->hash_table, id); + pthread_mutex_unlock(&id_manager->mutex); id_manager->id_used--; } From 10669a96149f6505dcf7a24bd589925de5041b4d Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 3 Jan 2022 13:17:09 -0800 Subject: [PATCH 1504/1642] roundup fractional scaling by default (#53) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.h | 1 + libweston/backend-rdp/rdpdisp.c | 2 ++ libweston/backend-rdp/rdprail.c | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 4e6e2a9cb..6ed89b789 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -136,6 +136,7 @@ struct rdp_backend { bool enable_hi_dpi_support; bool enable_fractional_hi_dpi_support; + bool enable_fractional_hi_dpi_roundup; uint32_t debug_desktop_scaling_factor; /* must be between 100 to 500 */ int rdp_monitor_refresh_rate; diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index bc8bd69d2..e387ca5de 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -72,6 +72,8 @@ disp_get_client_scale_from_monitor(RdpPeerContext *peerCtx, struct rdp_monitor_m return (float)b->debug_desktop_scaling_factor / 100.f; else if (b->enable_fractional_hi_dpi_support) return (float)monitorMode->monitorDef.attributes.desktopScaleFactor / 100.0f; + else if (b->enable_fractional_hi_dpi_roundup) + return (float)(int)((monitorMode->monitorDef.attributes.desktopScaleFactor + 50) / 100); else return (float)(int)(monitorMode->monitorDef.attributes.desktopScaleFactor / 100); } else { diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 9d2aec608..61b7fb403 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -4098,6 +4098,25 @@ rdp_rail_backend_create(struct rdp_backend *b) } rdp_debug(b, "RDP backend: enable_fractional_hi_dpi_support = %d\n", b->enable_fractional_hi_dpi_support); + b->enable_fractional_hi_dpi_roundup = true; + if (b->enable_hi_dpi_support) { + if (b->enable_fractional_hi_dpi_support) { + /* if fractional support is enabled, no round up */ + b->enable_fractional_hi_dpi_roundup = false; + } else { + s = getenv("WESTON_RDP_DISABLE_FRACTIONAL_HI_DPI_SCALING_ROUNDUP"); + if (s) { + if (strcmp(s, "true") == 0) + b->enable_fractional_hi_dpi_roundup = false; + else if (strcmp(s, "false") == 0) + b->enable_fractional_hi_dpi_roundup = true; + } + } + } else { + b->enable_fractional_hi_dpi_roundup = false; + } + rdp_debug(b, "RDP backend: enable_fractional_hi_dpi_roundup = %d\n", b->enable_fractional_hi_dpi_roundup); + b->debug_desktop_scaling_factor = 0; if (b->enable_hi_dpi_support) { char *debug_desktop_scaling_factor = getenv("WESTON_RDP_DEBUG_DESKTOP_SCALING_FACTOR"); From 6a3f1f669f6e53c0db3057d4f62da3a473c07c71 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 3 Jan 2022 13:18:28 -0800 Subject: [PATCH 1505/1642] fix clipboard race condition ends up as assertion failure (#54) * add debug msg and asserts * cleanup must be done at compositor thread Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdpclip.c | 134 ++++++++++++++++++++------------ xwayland/selection.c | 31 ++++---- 2 files changed, 103 insertions(+), 62 deletions(-) diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index a772aa1b8..d21f2b391 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -148,7 +148,7 @@ clipboard_data_source_state_to_string(struct rdp_clipboard_data_source *source) case RDP_CLIPBOARD_SOURCE_CANCEL_PENDING: return "cancel pending"; case RDP_CLIPBOARD_SOURCE_CANCELED: - return "cenceled"; + return "canceled"; case RDP_CLIPBOARD_SOURCE_RETRY: return "retry"; case RDP_CLIPBOARD_SOURCE_FAILED: @@ -721,13 +721,18 @@ clipboard_data_source_unref(struct rdp_clipboard_data_source *source) wl_event_source_remove(source->defer_event_source); } - if (source->data_source_fd != -1) + if (source->data_source_fd != -1) { + ASSERT_COMPOSITOR_THREAD(b); close(source->data_source_fd); + } - wl_array_release(&source->data_contents); + if (!wl_list_empty(&source->base.destroy_signal.listener_list)) { + ASSERT_COMPOSITOR_THREAD(b); + wl_signal_emit(&source->base.destroy_signal, + &source->base); + } - wl_signal_emit(&source->base.destroy_signal, - &source->base); + wl_array_release(&source->data_contents); wl_array_for_each(p, &source->base.mime_types) free(*p); @@ -880,6 +885,60 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) return 0; } +/* client's reply with error for data request, clean up */ +static int +clipboard_data_source_fail(int fd, uint32_t mask, void *arg) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; + freerdp_peer *client = (freerdp_peer *) source->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", __func__, + source, clipboard_data_source_state_to_string(source), fd); + + ASSERT_COMPOSITOR_THREAD(b); + + assert(source->data_source_fd == fd); + /* this data source must be tracked as inflight */ + assert(source == peerCtx->clipboard_inflight_client_data_source); + + /* remove event source now, and if write is failed with EAGAIN, queue back to display loop. */ + wl_event_source_remove(source->transfer_event_source); + source->transfer_event_source = NULL; + + if (source->data_contents.size) { + /* if data is recieved, but failed by other reason, + then keep data and format index for future request, + otherwise data is purged at last reference release. */ + /* wl_array_release(&source->data_contents); */ + /* wl_array_init(&source->data_contents); */ + } else { + /* data has been never recieved, thus must be empty. */ + assert(source->data_contents.size == 0); + assert(source->data_contents.alloc == 0); + assert(source->data_contents.data == NULL); + /* clear previous requested format so it can be requested later again. */ + source->format_index = -1; + } + /* don't clear format id table, so it allows to retry to get data from client. */ + /* memset(source->client_format_id_table, 0, sizeof(source->client_format_id_table)); */ + /* data has never been sent to write(), thus must be no inflight write. */ + assert(source->inflight_write_count == 0); + assert(source->inflight_data_to_write == NULL); + assert(source->inflight_data_size == 0); + /* data never has been sent to write(), so must not be processed. */ + assert(source->is_data_processed == FALSE); + /* close fd to server clipboard stop pulling data. */ + close(source->data_source_fd); + source->data_source_fd = -1; + /* clear inflight data source from client to server. */ + peerCtx->clipboard_inflight_client_data_source = NULL; + clipboard_data_source_unref(source); + + return 0; +} + /* Send client's clipboard data to the requesting application at server side */ static int clipboard_data_source_write(int fd, uint32_t mask, void *arg) @@ -1273,6 +1332,10 @@ clipboard_data_source_request(int fd, uint32_t mask, void *arg) source->data_source_fd = p[0]; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) pipe write:%d -> read:%d\n", + __func__, source, clipboard_data_source_state_to_string(source), + p[1], p[0]); + /* Request data from data source */ source->state = RDP_CLIPBOARD_SOURCE_REQUEST_DATA; selection_data_source->send(selection_data_source, requested_mime_type, p[1]); @@ -1505,7 +1568,7 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = {}; formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE; - formatListResponse.msgFlags = source ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + formatListResponse.msgFlags = (source && isPublished) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; formatListResponse.dataLen = 0; if (peerCtx->clipboard_server_context->ServerFormatListResponse(peerCtx->clipboard_server_context, &formatListResponse) != 0) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; @@ -1514,8 +1577,10 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT return -1; } - if (!isPublished && source) + if (!isPublished && source) { + assert(source->refcount == 1); clipboard_data_source_unref(source); + } return 0; } @@ -1566,53 +1631,20 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR source->state = RDP_CLIPBOARD_SOURCE_FAILED; source->data_response_fail_count++; } - rdp_debug_clipboard_verbose(b, "Client: %s (%p:%s:%d)\n", + rdp_debug_clipboard_verbose(b, "Client: %s (%p:%s) fail count:%d)\n", __func__, source, clipboard_data_source_state_to_string(source), source->data_response_fail_count); - if (Success) { - assert(source->transfer_event_source == NULL); - source->transfer_event_source = - wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, - clipboard_data_source_write, source); - if (!source->transfer_event_source) { - source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "Client: %s (%p:%s) wl_event_loop_add_fd failed\n", - __func__, source, clipboard_data_source_state_to_string(source)); - } - } - + assert(source->transfer_event_source == NULL); + source->transfer_event_source = + wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + Success ? clipboard_data_source_write : clipboard_data_source_fail, source); if (!source->transfer_event_source) { - if (formatDataResponse->msgFlags == CB_RESPONSE_OK) { - /* if data is recieved, but failed to sent to write(), - then keep data and format index for future request, - otherwise data is purged at last reference release. */ - /* wl_array_release(&source->data_contents); */ - /* wl_array_init(&source->data_contents); */ - } else { - /* data has been never recieved, thus must be empty. */ - assert(source->data_contents.size == 0); - assert(source->data_contents.alloc == 0); - assert(source->data_contents.data == NULL); - /* clear previous requested format so it can be requested later again. */ - source->format_index = -1; - } - /* don't clear format id table, so it allows to retry to get data from client. */ - /* memset(source->client_format_id_table, 0, sizeof(source->client_format_id_table)); */ - /* data has never been sent to write(), thus must be no inflight write. */ - assert(source->inflight_write_count == 0); - assert(source->inflight_data_to_write == NULL); - assert(source->inflight_data_size == 0); - /* data never has been sent to write(), so must not be processed. */ - assert(source->is_data_processed == FALSE); - /* close fd to server clipboard stop pulling data. */ - close(source->data_source_fd); - source->data_source_fd = -1; - /* clear inflight data source from client to server. */ - assert(source == peerCtx->clipboard_inflight_client_data_source); - peerCtx->clipboard_inflight_client_data_source = NULL; - clipboard_data_source_unref(source); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + rdp_debug_clipboard_error(b, "Client: %s (%p:%s) wl_event_loop_add_fd failed\n", + __func__, source, clipboard_data_source_state_to_string(source)); + return -1; } } else { rdp_debug_clipboard(b, "Client: %s client send data without server asking. protocol error", __func__); @@ -1630,6 +1662,7 @@ clipboard_client_format_list_response(CliprdrServerContext* context, const CLIPR RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; rdp_debug_clipboard(b, "Client: %s msgFlags:0x%x\n", __func__, formatListResponse->msgFlags); + ASSERT_NOT_COMPOSITOR_THREAD(b); return 0; } @@ -1734,6 +1767,9 @@ void rdp_clipboard_destroy(RdpPeerContext *peerCtx) { struct rdp_clipboard_data_source* data_source; + struct rdp_backend *b = peerCtx->rdpBackend; + + ASSERT_COMPOSITOR_THREAD(b); if (peerCtx->clipboard_selection_listener.notify) { wl_list_remove(&peerCtx->clipboard_selection_listener.link); diff --git a/xwayland/selection.c b/xwayland/selection.c index d40c0b47b..a99717894 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -60,13 +60,15 @@ writable_callback(int fd, uint32_t mask, void *data) if (wm->property_source) wl_event_source_remove(wm->property_source); wm->property_source = NULL; + weston_log("write error to target fd:%d start:%d reminder:%d %s\n", + fd, wm->property_start, remainder, strerror(errno)); close(fd); - weston_log("write error to target fd: %s\n", strerror(errno)); + wm->data_source_fd = -1; return 1; } - weston_log("wrote %d (chunk size %d) of %d bytes\n", - wm->property_start + len, + weston_log("wrote fd:%d %d (chunk size %d) of %d bytes\n", + fd, wm->property_start + len, len, xcb_get_property_value_length(wm->property_reply)); wm->property_start += len; @@ -82,8 +84,9 @@ writable_callback(int fd, uint32_t mask, void *data) wm->selection_window, wm->atom.wl_selection); } else { - weston_log("transfer complete\n"); + weston_log("transfer write complete\n"); close(fd); + wm->data_source_fd = -1; } } @@ -139,8 +142,9 @@ weston_wm_get_incr_chunk(struct weston_wm *wm) * for freeing it */ weston_wm_write_property(wm, reply); } else { - weston_log("transfer complete\n"); + weston_log("transfer write complete (zero size reply)\n"); close(wm->data_source_fd); + wm->data_source_fd = -1; free(reply); } } @@ -403,19 +407,20 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) len = read(fd, p, available); if (len == -1) { - weston_log("read error from data source: %s\n", - strerror(errno)); + weston_log("read error from data source fd:%d %s\n", + fd, strerror(errno)); weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); if (wm->property_source) wl_event_source_remove(wm->property_source); wm->property_source = NULL; close(fd); + wm->data_source_fd = -1; wl_array_release(&wm->source_data); return 1; } - weston_log("read %d (available %d, mask 0x%x)\n", - len, available, mask); + weston_log("read fd:%d %d (available %d, mask 0x%x)\n", + fd, len, available, mask); /*remove actual data due to privacy*/ /*weston_log("read %d (available %d, mask 0x%x) bytes: \"%.*s\"\n", len, available, mask, len, (char *) p);*/ @@ -454,7 +459,7 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) weston_wm_flush_source_data(wm); } } else if (len == 0 && !wm->incr) { - weston_log("non-incr transfer complete\n"); + weston_log("non-incr transfer read complete\n"); /* Non-incr transfer all done. */ weston_wm_flush_source_data(wm); weston_wm_send_selection_notify(wm, wm->selection_request.property); @@ -463,10 +468,11 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) wl_event_source_remove(wm->property_source); wm->property_source = NULL; close(fd); + wm->data_source_fd = -1; wl_array_release(&wm->source_data); wm->selection_request.requestor = XCB_NONE; } else if (len == 0 && wm->incr) { - weston_log("incr transfer complete\n"); + weston_log("incr transfer read complete\n"); wm->flush_property_on_delete = 1; if (wm->selection_property_set) { @@ -482,9 +488,8 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) if (wm->property_source) wl_event_source_remove(wm->property_source); wm->property_source = NULL; - close(wm->data_source_fd); - wm->data_source_fd = -1; close(fd); + wm->data_source_fd = -1; } else { weston_log("nothing happened, buffered the bytes\n"); } From 1bfd5803ff7c91d2d07141648571de1d652e4058 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 11 Jan 2022 12:22:32 -0800 Subject: [PATCH 1506/1642] fix taskbar icon look up from desktop file (#55) Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 42 ++++++++++++++++++++++++++++++---------- rdprail-shell/shell.c | 39 ++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index c1fc69ddc..99f23d0a1 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -526,18 +526,43 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr return false; } +static bool +app_list_key_from_file(char *key, size_t key_length, char *file) +{ + char *ext = is_desktop_file(file); + if (ext) { + copy_string(key, key_length, file); + key[ext-file] = '\0'; // drop ".desktop" extention for key. + /* Despite wayland protocol specification notes below, + many applications specify only last component of + reserse DNS as app ID, so "FooViewer" only in below example, + thus here make only that part as key. + [from xdg-shell.xml] + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + */ + char *s = strrchr(key, '.'); + if (s && + s != key && + *(++s) != '\0') + copy_string(key, key_length, s); + } + return ext != NULL; +} + static void app_list_desktop_file_removed(struct desktop_shell *shell, char *file) { struct app_list_context *context = (struct app_list_context *)shell->app_list_context; struct app_entry *entry; char key[512]; - char *ext; - copy_string(key, sizeof key, file); - ext = is_desktop_file(key); - assert(ext); - key[ext-key] = '\0'; // drop extention for key. + if (!app_list_key_from_file(key, sizeof key, file)) + return; if (context->isRdpNotifyStarted) { entry = (struct app_entry *)HashTable_GetItemValue(context->table, (void*)key); @@ -554,15 +579,12 @@ app_list_desktop_file_changed(struct desktop_shell *shell, char *folder, char *f struct app_list_context *context = (struct app_list_context *)shell->app_list_context; char key[512]; char full_path[512]; - char *ext; bool entry_filled = false; struct app_entry *entry; struct app_entry *entry_old; - copy_string(key, sizeof key, file); - ext = is_desktop_file(key); - assert(ext); - key[ext-key] = '\0'; // drop extention for key. + if (!app_list_key_from_file(key, sizeof key, file)) + return; copy_string(full_path, sizeof full_path, folder); append_string(full_path, sizeof full_path, "/"); diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 28646dc29..e80ac0ab7 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -374,33 +374,36 @@ shell_surface_set_window_icon(struct weston_desktop_surface *desktop_surface, format = PIXMAN_a8r8g8b8; break; default: - shell_rdp_debug_error(shsurf->shell, "shell_surface_set_window_icon(): unsupported bpp: %d\n", bpp); + shell_rdp_debug_error(shsurf->shell, "%s: unsupported bpp: %d\n", __func__, bpp); return; } image = pixman_image_create_bits_no_clear(format, width, height, bits, ICON_STRIDE(width, bpp)); - if (image) - shsurf->icon.is_default_icon_used = false; + if (!image) { + shell_rdp_debug_error(shsurf->shell, "%s: pixman_image_create_bits_no_clear failed\n", __func__); + return; + } + shsurf->icon.is_default_icon_used = false; + } + if (!image) { + /* If this is X app, query from X first */ + api = shsurf->shell->xwayland_surface_api; + if (!api) { + api = weston_xwayland_surface_get_api(shsurf->shell->compositor); + shsurf->shell->xwayland_surface_api = api; + } + if (api && api->is_xwayland_surface(surface)) { + /* trigger_set_window_icon calls back this function + with custom icon image obtained from X app. */ + if (api->trigger_set_window_icon(surface)) + return; + } } if (!image) { - /* Try .desktop for icon for non-overlay icon */ + /* Next, try icon from .desktop file */ id = weston_desktop_surface_get_app_id(desktop_surface); if (id) image = app_list_load_icon_file(shsurf->shell, id); - if (!image) { - /* If this is X app, query from X */ - api = shsurf->shell->xwayland_surface_api; - if (!api) { - api = weston_xwayland_surface_get_api(shsurf->shell->compositor); - shsurf->shell->xwayland_surface_api = api; - } - if (api && api->is_xwayland_surface(surface)) { - /* trigger_set_window_icon calls back this function - with custom icon image obtained from X app. */ - if (api->trigger_set_window_icon(surface)) - return; - } - } if (image) shsurf->icon.is_default_icon_used = false; } From ad6066f0aed0465d69ba9ecaafcba139d7e44634 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 19 Jan 2022 09:39:07 -0800 Subject: [PATCH 1507/1642] add US international keyboard layout (#57) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 16b75d94d..8d47a475c 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -985,6 +985,7 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_GREEK_319, "gr", "extended"}, {KBD_GREEK_POLYTONIC, "gr", "polytonic"}, {KBD_US, "us", 0}, + {KBD_UNITED_STATES_INTERNATIONAL, "us", "intl"}, {KBD_US_ENGLISH_TABLE_FOR_IBM_ARABIC_238_L, "ara", "buckwalter"}, {KBD_SPANISH, "es", 0}, {KBD_SPANISH_VARIATION, "es", "nodeadkeys"}, From a106a223241779380b85af2b3b18621cb152c933 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 1 Mar 2022 14:48:19 -0800 Subject: [PATCH 1508/1642] set default to disable scaling roundup (#58) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdprail.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 61b7fb403..1c37d277f 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -4098,7 +4098,7 @@ rdp_rail_backend_create(struct rdp_backend *b) } rdp_debug(b, "RDP backend: enable_fractional_hi_dpi_support = %d\n", b->enable_fractional_hi_dpi_support); - b->enable_fractional_hi_dpi_roundup = true; + b->enable_fractional_hi_dpi_roundup = false; if (b->enable_hi_dpi_support) { if (b->enable_fractional_hi_dpi_support) { /* if fractional support is enabled, no round up */ @@ -4112,8 +4112,6 @@ rdp_rail_backend_create(struct rdp_backend *b) b->enable_fractional_hi_dpi_roundup = true; } } - } else { - b->enable_fractional_hi_dpi_roundup = false; } rdp_debug(b, "RDP backend: enable_fractional_hi_dpi_roundup = %d\n", b->enable_fractional_hi_dpi_roundup); From 52f13ac77cb24f4e508b3d4fa6178c59bcc48ca2 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 22 Mar 2022 13:22:06 -0700 Subject: [PATCH 1509/1642] add Hebrew (Standard) layout (#59) * add Hebrew (Standard) layout * locally define KBD IDs if missing in FreeRDP Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 8d47a475c..825433373 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -968,6 +968,16 @@ struct rdp_to_xkb_keyboard_layout { /* table reversed from https://github.com/awakecoding/FreeRDP/blob/master/libfreerdp/locale/xkb_layout_ids.c#L811 */ +/* Locally define missing keyboard layout IDs in FreeRDP 2.x */ +#ifndef KBD_HEBREW_STANDARD +/* 0x2040d is for Hebrew (Standard) */ +#define KBD_HEBREW_STANDARD 0x2040d +#endif +#ifndef KBD_PERSIAN +/* 0x50429 is for Dari (Afghanistan) */ +#define KBD_PERSIAN 0x50429 +#endif + static const struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_ARABIC_101, "ara", 0}, @@ -992,6 +1002,7 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_FINNISH, "fi", 0}, {KBD_FRENCH, "fr", 0}, {KBD_HEBREW, "il", 0}, + {KBD_HEBREW_STANDARD, "il", "basic"}, {KBD_HUNGARIAN, "hu", 0}, {KBD_HUNGARIAN_101_KEY, "hu", "standard"}, {KBD_ICELANDIC, "is", 0}, @@ -1029,12 +1040,7 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { // Such as key ~ is 1,2,3...0 on Windows, not Persian numbers, // but Xkb doesn't have that layout in "ir" group. {KBD_FARSI, "ir", "pes"}, - // 0x50429 is for Dari(Afghanistan) -#if WINPR_VERSION_MAJOR >= 3 {KBD_PERSIAN, "af", "basic"}, -#else - {0x50429, "af", "basic"}, -#endif {KBD_VIETNAMESE, "vn", 0}, {KBD_ARMENIAN_EASTERN, "am", 0}, {KBD_AZERI_LATIN, 0, 0}, From e937ba3ff4f8de37e172964bbae6e4a748f3504d Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 31 Mar 2022 13:32:16 -0700 Subject: [PATCH 1510/1642] fix rdp_defer_rdp_task_to_display_loop (#60) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdputil.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index 35750ce8e..5abb6533f 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -379,6 +379,7 @@ rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_fd_fun func, data); if (*event_source) { eventfd_write(peerCtx->loop_event_source_fd, 1); + return true; } else { rdp_debug_error(b, "%s: wl_event_loop_add_idle failed\n", __func__); } @@ -386,7 +387,7 @@ rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_fd_fun /* RDP server is not opened, this must not be used */ assert(false); } - return (*event_source != NULL); + return false; } void From 651d33374f9b9e93e1cf1210faff13bcd339f132 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 5 Apr 2022 11:17:11 -0700 Subject: [PATCH 1511/1642] fix cursor image being sent to client with no change (#62) * fix cursor image being sent to client with no change * change debug message to verbose Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdprail.c | 54 ++++++++++++++------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 1c37d277f..7642a0178 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -1151,12 +1151,19 @@ rdp_rail_create_cursor(struct weston_surface *surface) struct weston_compositor *compositor = surface->compositor; struct rdp_backend *b = (struct rdp_backend*)compositor->backend; RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; + struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; ASSERT_COMPOSITOR_THREAD(b); + rail_state->clientPos.width = 0; /* triggers force update on next update. */ + rail_state->clientPos.height = 0; + pixman_region32_init_rect(&rail_state->damage, + 0, 0, surface->width_from_buffer, surface->height_from_buffer); + if (peerCtx->cursorSurface) rdp_debug_error(b, "cursor surface already exists old %p vs new %p\n", peerCtx->cursorSurface, surface); peerCtx->cursorSurface = surface; + return 0; } @@ -1171,8 +1178,6 @@ rdp_rail_update_cursor(struct weston_surface *surface) BOOL isCursorResized = FALSE; BOOL isCursorHidden = FALSE; BOOL isCursorDamanged = FALSE; - int numViews; - struct weston_view *view; struct weston_rdp_rail_window_pos newPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; struct weston_rdp_rail_window_pos newClientPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; int contentBufferWidth; @@ -1181,44 +1186,29 @@ rdp_rail_update_cursor(struct weston_surface *surface) ASSERT_COMPOSITOR_THREAD(b); assert(rail_state); - /* obtain view's global position */ - numViews = 0; - wl_list_for_each(view, &surface->views, surface_link) { - float sx, sy; - weston_view_to_global_float(view, 0, 0, &sx, &sy); - newPos.x = (int)sx; - newPos.y = (int)sy; - numViews++; - break; // just handle the first view for this hack - } - if (numViews == 0) { - view = NULL; - rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); - } - - if (newPos.x < 0 || newPos.y < 0) - isCursorHidden = TRUE; - weston_surface_get_content_size(surface, &contentBufferWidth, &contentBufferHeight); newClientPos.width = contentBufferWidth; newClientPos.height = contentBufferHeight; if (surface->output) to_client_coordinate(peerCtx, surface->output, - &newClientPos.x, &newClientPos.y, &newClientPos.width, &newClientPos.height); + &newClientPos.x, &newClientPos.y, /* these are zero since position at client side doesn't matter */ + &newClientPos.width, &newClientPos.height); - if (newClientPos.width > 0 && newClientPos.height > 0) - isCursorResized = TRUE; - else + if (newPos.x < 0 || newPos.y < 0 || /* check if negative in weston space */ + newClientPos.width <= 0 || newClientPos.height <= 0) { isCursorHidden = TRUE; + rdp_debug_verbose(b, "CursorUpdate: hidden\n"); + } else if (rail_state->clientPos.width != newClientPos.width || /* check if size changed in client side */ + rail_state->clientPos.height != newClientPos.height) { + isCursorResized = TRUE; + rdp_debug_verbose(b, "CursorUpdate: resized\n"); + } else if (pixman_region32_not_empty(&rail_state->damage)) { + isCursorDamanged = TRUE; + rdp_debug_verbose(b, "CursorUpdate: dirty\n"); + } - rail_state->pos = newPos; rail_state->clientPos = newClientPos; - - if (!isCursorHidden && !isCursorResized) { - if ((surface->damage.extents.x2 - surface->damage.extents.x1) > 0 || - (surface->damage.extents.y2 - surface->damage.extents.y1) > 0) - isCursorDamanged = TRUE; - } + pixman_region32_clear(&rail_state->damage); if (isCursorHidden) { /* hide pointer */ @@ -1593,8 +1583,8 @@ rdp_rail_destroy_window(struct wl_listener *listener, void *data) } rail_state->isWindowCreated = FALSE; } - pixman_region32_fini(&rail_state->damage); } + pixman_region32_fini(&rail_state->damage); rdp_id_manager_free_id(&peerCtx->windowId, window_id); rail_state->window_id = 0; From fc79745681ffa84e4356dd0d3f6f504df4471276 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 18 Apr 2022 12:05:12 -0500 Subject: [PATCH 1512/1642] rdp: Fix use after free (#65) Looks like a typo here has resulted in using a freed buffer. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 825433373..0dd378c2d 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -2070,7 +2070,7 @@ rdp_generate_session_tls(struct rdp_backend *b) bio_x509 = BIO_new(BIO_s_mem()); assert(bio_x509 != NULL); - PEM_write_bio_X509(bio, x509); + PEM_write_bio_X509(bio_x509, x509); BIO_get_mem_ptr(bio_x509, &mem_x509); b->server_cert_content = (char *)calloc(mem_x509->length+1, 1); memcpy(b->server_cert_content, mem_x509->data, mem_x509->length); From 7fe0231375ae892755fecee75fd90eb7c6245fbe Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 18 Apr 2022 10:26:43 -0700 Subject: [PATCH 1513/1642] fix hi-dpi snap/move window (#64) Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 2 +- libweston/backend-rdp/rdp.h | 14 ------------ libweston/backend-rdp/rdprail.c | 38 +++++++++++++++++++++++---------- rdprail-shell/shell.c | 3 ++- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index 049662c2b..64a0caa0a 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -75,7 +75,7 @@ struct weston_rdprail_shell_api { /** Move a window. */ - void (*request_window_move)(struct weston_surface *surface, int x, int y); + void (*request_window_move)(struct weston_surface *surface, int x, int y, int width, int height); /** Snap a window. */ diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 6ed89b789..cdb51b5f8 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -483,20 +483,6 @@ rdp_matrix_transform_scale(struct weston_matrix *matrix, int *sx, int *sy) } } -/* TO BE REMOVED */ -static inline int32_t -to_weston_x(RdpPeerContext *peer, int32_t x) -{ - return x - peer->regionClientHeads.extents.x1; -} - -/* TO BE REMOVED */ -static inline int32_t -to_weston_y(RdpPeerContext *peer, int32_t y) -{ - return y - peer->regionClientHeads.extents.y1; -} - static inline void to_weston_scale_only(RdpPeerContext *peer, struct weston_output *output, float scale, int *x, int *y) { diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 7642a0178..42f6ec8ae 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -338,8 +338,9 @@ rail_client_SnapArrange_callback(int fd, uint32_t mask, void *arg) struct rdp_backend *b = peerCtx->rdpBackend; struct weston_surface *surface; struct weston_surface_rail_state *rail_state; + pixman_rectangle32_t snapArrangeRect; - rdp_debug(b, "SnapArrange(%d) - (%d, %d, %d, %d)\n", + rdp_debug(b, "Client: SnapArrange: WindowId:0x%x at (%d, %d) %dx%d\n", snap->windowId, snap->left, snap->top, @@ -353,12 +354,18 @@ rail_client_SnapArrange_callback(int fd, uint32_t mask, void *arg) rail_state = (struct weston_surface_rail_state *)surface->backend_state; if (b->rdprail_shell_api && b->rdprail_shell_api->request_window_move) { - /* TODO: HI-DPI MULTIMON */ + snapArrangeRect.x = snap->left; + snapArrangeRect.y = snap->top; + snapArrangeRect.width = snap->right - snap->left; + snapArrangeRect.height = snap->bottom - snap->top; + to_weston_coordinate(peerCtx, + &snapArrangeRect.x, &snapArrangeRect.y, + &snapArrangeRect.width, &snapArrangeRect.height); b->rdprail_shell_api->request_window_snap(surface, - to_weston_x(peerCtx, snap->left), - to_weston_y(peerCtx, snap->top), - snap->right - snap->left, - snap->bottom - snap->top); + snapArrangeRect.x, + snapArrangeRect.y, + snapArrangeRect.width, + snapArrangeRect.height); } rail_state->forceUpdateWindowState = true; @@ -384,8 +391,9 @@ rail_client_WindowMove_callback(int fd, uint32_t mask, void *arg) RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; struct weston_surface *surface; + pixman_rectangle32_t windowMoveRect; - rdp_debug(b, "WindowMove(%d) - (%d, %d, %d, %d)\n", + rdp_debug(b, "Client: WindowMove: WindowId:0x0x at (%d, %d) %dx%d\n", windowMove->windowId, windowMove->left, windowMove->top, @@ -398,10 +406,18 @@ rail_client_WindowMove_callback(int fd, uint32_t mask, void *arg) if (surface) { if (b->rdprail_shell_api && b->rdprail_shell_api->request_window_move) { - /* TODO: HI-DPI MULTIMON */ + windowMoveRect.x = windowMove->left; + windowMoveRect.y = windowMove->top; + windowMoveRect.width = windowMove->right - windowMove->left; + windowMoveRect.height = windowMove->bottom - windowMove->top; + to_weston_coordinate(peerCtx, + &windowMoveRect.x, &windowMoveRect.y, + &windowMoveRect.width, &windowMoveRect.height); b->rdprail_shell_api->request_window_move(surface, - to_weston_x(peerCtx, windowMove->left), - to_weston_y(peerCtx, windowMove->top)); + windowMoveRect.x, + windowMoveRect.y, + windowMoveRect.width, + windowMoveRect.height); } } @@ -2682,7 +2698,7 @@ rdp_rail_peer_activate(freerdp_peer* client) UINT32 railHandshakeFlags = (TS_RAIL_ORDER_HANDSHAKEEX_FLAGS_HIDEF | TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED - /*| TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED*/); + /* | TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED */); handshakeEx.buildNumber = 0; handshakeEx.railHandshakeFlags = railHandshakeFlags; if (peerCtx->rail_server_context->ServerHandshakeEx(peerCtx->rail_server_context, &handshakeEx) != CHANNEL_RC_OK) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index e80ac0ab7..9aa008bee 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -2665,7 +2665,7 @@ shell_backend_request_window_restore(struct weston_surface *surface) } static void -shell_backend_request_window_move(struct weston_surface *surface, int x, int y) +shell_backend_request_window_move(struct weston_surface *surface, int x, int y, int width, int height) { struct weston_view *view; struct shell_surface *shsurf = get_shell_surface(surface); @@ -2680,6 +2680,7 @@ shell_backend_request_window_move(struct weston_surface *surface, int x, int y) assert(!shsurf->snapped.is_maximized_requested); weston_view_set_position(view, x, y); + //TODO: support window resize (width x height) } static void From 05388f8eaa9d58ef15657332f580ace1f30d2457 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 19 Apr 2022 16:43:33 -0700 Subject: [PATCH 1514/1642] Revert "enable font attribute fallback for international text (#22)" (#68) This reverts commit ed4f3bbbf5efbd5bde880704bc03497f2446c3f2. --- shared/cairo-util.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/shared/cairo-util.c b/shared/cairo-util.c index 5d1387aed..f558e1d0c 100644 --- a/shared/cairo-util.c +++ b/shared/cairo-util.c @@ -461,7 +461,6 @@ create_layout(cairo_t *cr, const char *title) { PangoLayout *layout; PangoFontDescription *desc; - PangoAttrList *attrs; layout = pango_cairo_create_layout(cr); if (title) { @@ -469,13 +468,6 @@ create_layout(cairo_t *cr, const char *title) desc = pango_font_description_from_string("Sans Bold 10"); pango_layout_set_font_description(layout, desc); pango_font_description_free(desc); - /* enable fallback attribute */ - attrs = pango_attr_list_new(); - if (attrs) { - pango_attr_list_insert(attrs, pango_attr_fallback_new(true)); - pango_layout_set_attributes(layout, attrs); - pango_attr_list_unref(attrs); - } } pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); From 685a5d198196db9277a6b01be8ff2817196c307c Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 20 Apr 2022 16:23:16 -0700 Subject: [PATCH 1515/1642] allow configuring rdp-backend from compositor executable (#67) * allow configuring rdp-backend from compositor * fix build error without HAVE_FREERDP_GFXREDIR_H Co-authored-by: Hideyuki Nagase --- compositor/main.c | 97 ++++++++++++++++++++++ include/libweston/backend-rdp.h | 21 ++++- libweston/backend-rdp/rdp.c | 57 ++++++------- libweston/backend-rdp/rdp.h | 11 ++- libweston/backend-rdp/rdprail.c | 139 +++++++------------------------- libweston/backend-rdp/rdputil.c | 2 + 6 files changed, 182 insertions(+), 145 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 54d36cd05..65bd4a8f8 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2704,6 +2704,56 @@ weston_rdp_backend_config_init(struct weston_rdp_backend_config *config) config->env_socket = 0; config->no_clients_resize = 0; config->force_no_compression = 0; + config->redirect_clipboard = false; + config->redirect_audio_playback = false; + config->redirect_audio_capture = false; + config->rdp_monitor_refresh_rate = WESTON_RDP_MODE_FREQ; + config->rail_config.use_rdpapplist = false; + config->rail_config.use_shared_memory = false; + config->rail_config.enable_hi_dpi_support = false; + config->rail_config.enable_fractional_hi_dpi_support = false; + config->rail_config.enable_fractional_hi_dpi_roundup = false; + config->rail_config.debug_desktop_scaling_factor = 0; + config->rail_config.enable_window_zorder_sync = false; + config->rail_config.enable_window_snap_arrange = false; + config->rail_config.enable_distro_name_title = false; + config->rail_config.enable_copy_warning_title = false; + config->rail_config.enable_display_power_by_screenupdate = false; +} + +static bool +read_rdp_config_bool(char *config_name, bool default_value) +{ + char *s; + + s = getenv(config_name); + if (s) { + if (strcmp(s, "true") == 0) + return true; + else if (strcmp(s, "false") == 0) + return false; + else if (strcmp(s, "1") == 0) + return true; + else if (strcmp(s, "0") == 0) + return false; + } + + return default_value; +} + +static int +read_rdp_config_int(char *config_name, int default_value) +{ + char *s; + int i; + + s = getenv(config_name); + if (s) { + if (safe_strtoint(s, &i)) + return i; + } + + return default_value; } static int @@ -2735,6 +2785,53 @@ load_rdp_backend(struct weston_compositor *c, parse_options(rdp_options, ARRAY_LENGTH(rdp_options), argc, argv); + /* certain configurations are read from environment variables */ + config.redirect_clipboard = read_rdp_config_bool("WESTON_RDP_CLIPBOARD", true); + config.redirect_audio_playback = read_rdp_config_bool("WESTON_RDP_AUDIO_PLAYBACK", true); + config.redirect_audio_capture = read_rdp_config_bool("WESTON_RDP_AUDIO_CAPTURE", true); + config.rdp_monitor_refresh_rate = read_rdp_config_int("WESTON_RDP_MONITOR_REFRESH_RATE", WESTON_RDP_MODE_FREQ); + + config.rail_config.use_rdpapplist = read_rdp_config_bool("WESTON_RDP_APPLIST", true); + config.rail_config.use_shared_memory = read_rdp_config_bool("WESTON_RDP_SHARED_MEMORY", true); + + /* Configure HI-DPI scaling */ + config.rail_config.enable_hi_dpi_support = read_rdp_config_bool("WESTON_RDP_HI_DPI_SCALING", true); + if (config.rail_config.enable_hi_dpi_support) { + /* Disable by default for now. */ + config.rail_config.enable_fractional_hi_dpi_support = + read_rdp_config_bool("WESTON_RDP_FRACTIONAL_HI_DPI_SCALING", false); + } else { + config.rail_config.enable_fractional_hi_dpi_support = false; + } + /* if fractional support is enabled, no round up */ + if (config.rail_config.enable_fractional_hi_dpi_support) { + config.rail_config.enable_fractional_hi_dpi_roundup = false; + } else { + config.rail_config.enable_fractional_hi_dpi_roundup = + read_rdp_config_bool("WESTON_RDP_FRACTIONAL_HI_DPI_SCALING_ROUNDUP", false); + } + if (config.rail_config.enable_hi_dpi_support) { + config.rail_config.debug_desktop_scaling_factor = + read_rdp_config_int("WESTON_RDP_DEBUG_DESKTOP_SCALING_FACTOR", 0); + if (config.rail_config.debug_desktop_scaling_factor != 0) { + if (config.rail_config.debug_desktop_scaling_factor < 100 || + config.rail_config.debug_desktop_scaling_factor > 500) { + config.rail_config.debug_desktop_scaling_factor = 0; + } + } + } else { + config.rail_config.debug_desktop_scaling_factor = 0; + } + + config.rail_config.enable_window_zorder_sync = read_rdp_config_bool("WESTON_RDP_WINDOW_ZORDER_SYNC", true); + config.rail_config.enable_window_snap_arrange = read_rdp_config_bool("WESTON_RDP_WINDOW_SNAP_ARRANGE", false); + + config.rail_config.enable_display_power_by_screenupdate = + read_rdp_config_bool("WESTON_RDP_DISPLAY_POWER_BY_SCREENUPDATE", false); + + config.rail_config.enable_distro_name_title = read_rdp_config_bool("WESTON_RDP_APPEND_DISTRONAME_TITLE", true); + config.rail_config.enable_copy_warning_title = read_rdp_config_bool("WESTON_RDP_COPY_WARNING_TITLE", true); + wet_set_simple_head_configurator(c, rdp_backend_output_configure); ret = weston_compositor_load_backend(c, WESTON_BACKEND_RDP, diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index 64a0caa0a..9292a4348 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -33,6 +33,8 @@ extern "C" { #include #include +#define WESTON_RDP_MODE_FREQ 60 // Hz + #define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v1" struct weston_rdp_output_api { @@ -233,7 +235,7 @@ struct weston_surface_rail_state { uint32_t surface_id; }; -#define WESTON_RDP_BACKEND_CONFIG_VERSION 2 +#define WESTON_RDP_BACKEND_CONFIG_VERSION 3 struct weston_rdp_backend_config { struct weston_backend_config base; @@ -245,6 +247,23 @@ struct weston_rdp_backend_config { int env_socket; int no_clients_resize; int force_no_compression; + bool redirect_clipboard; + bool redirect_audio_playback; + bool redirect_audio_capture; + int rdp_monitor_refresh_rate; + struct { + bool use_rdpapplist; + bool use_shared_memory; + bool enable_hi_dpi_support; + bool enable_fractional_hi_dpi_support; + bool enable_fractional_hi_dpi_roundup; + int debug_desktop_scaling_factor; + bool enable_window_zorder_sync; + bool enable_window_snap_arrange; + bool enable_distro_name_title; + bool enable_copy_warning_title; + bool enable_display_power_by_screenupdate; + } rail_config; }; #ifdef __cplusplus diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 0dd378c2d..83bf0b269 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1176,7 +1176,6 @@ xf_peer_activate(freerdp_peer* client) pixman_region32_t damage; char seat_name[50]; POINTER_SYSTEM_UPDATE pointer_system; - char *s; peerCtx = (RdpPeerContext *)client->context; b = peerCtx->rdpBackend; @@ -1214,23 +1213,9 @@ xf_peer_activate(freerdp_peer* client) } /* override settings by env variables */ - s = getenv("WESTON_RDP_DISABLE_CLIPBOARD"); - if (s) { - if (strcmp(s, "true") == 0) - settings->RedirectClipboard = FALSE; - } - - s = getenv("WESTON_RDP_DISABLE_AUDIO_PLAYBACK"); - if (s) { - if (strcmp(s, "true") == 0) - settings->AudioPlayback = FALSE; - } - - s = getenv("WESTON_RDP_DISABLE_AUDIO_CAPTURE"); - if (s) { - if (strcmp(s, "true") == 0) - settings->AudioCapture = FALSE; - } + settings->RedirectClipboard = b->redirect_clipboard; + settings->AudioPlayback = b->redirect_audio_playback; + settings->AudioCapture = b->redirect_audio_capture; if (settings->RemoteApplicationMode || settings->RedirectClipboard || @@ -2166,10 +2151,10 @@ rdp_backend_create(struct weston_compositor *compositor, char *fd_str; char *fd_tail; int fd, ret; + char *s; struct weston_head *base, *next; struct rdp_output *output; - char *s; int i; struct timespec ts; @@ -2186,6 +2171,10 @@ rdp_backend_create(struct weston_compositor *compositor, b->server_key = config->server_key ? strdup(config->server_key) : NULL; b->no_clients_resize = config->no_clients_resize; b->force_no_compression = config->force_no_compression; + b->redirect_clipboard = config->redirect_clipboard; + b->redirect_audio_playback = config->redirect_audio_playback; + b->redirect_audio_capture = config->redirect_audio_capture; + b->rdp_monitor_refresh_rate = config->rdp_monitor_refresh_rate * 1000; wl_list_init(&b->output_list); wl_list_init(&b->head_list); @@ -2228,18 +2217,7 @@ rdp_backend_create(struct weston_compositor *compositor, } rdp_debug_clipboard(b, "RDP backend: WESTON_RDP_DEBUG_CLIPBOARD_LEVEL: %d\n", b->debugClipboardLevel); - s = getenv("WESTON_RDP_MONITOR_REFRESH_RATE"); - if (s) { - if (!safe_strtoint(s, &b->rdp_monitor_refresh_rate) || - b->rdp_monitor_refresh_rate == 0) { - b->rdp_monitor_refresh_rate = RDP_MODE_FREQ; - } else { - b->rdp_monitor_refresh_rate *= 1000; - } - } else { - b->rdp_monitor_refresh_rate = RDP_MODE_FREQ; - } - rdp_debug(b, "RDP backend: WESTON_RDP_MONITOR_REFRESH_RATE: %d\n", b->rdp_monitor_refresh_rate); + rdp_debug(b, "RDP backend: rdp_monitor_refresh_rate: %d\n", b->rdp_monitor_refresh_rate); clock_getres(CLOCK_MONOTONIC, &ts); rdp_debug(b, "RDP backend: timer resolution tv_sec:%ld tv_nsec:%ld\n", (intmax_t)ts.tv_sec, ts.tv_nsec); @@ -2294,7 +2272,7 @@ rdp_backend_create(struct weston_compositor *compositor, if (rdp_head_create(compositor, TRUE, NULL) == NULL) goto err_compositor; - if (rdp_rail_backend_create(b) < 0) + if (rdp_rail_backend_create(b, config) < 0) goto err_output; compositor->capabilities |= WESTON_CAP_ARBITRARY_MODES; @@ -2378,6 +2356,21 @@ config_init_to_defaults(struct weston_rdp_backend_config *config) config->env_socket = 0; config->no_clients_resize = 0; config->force_no_compression = 0; + config->redirect_clipboard = false; + config->redirect_audio_playback = false; + config->redirect_audio_capture = false; + config->rdp_monitor_refresh_rate = WESTON_RDP_MODE_FREQ; + config->rail_config.use_rdpapplist = false; + config->rail_config.use_shared_memory = false; + config->rail_config.enable_hi_dpi_support = false; + config->rail_config.enable_fractional_hi_dpi_support = false; + config->rail_config.enable_fractional_hi_dpi_roundup = false; + config->rail_config.debug_desktop_scaling_factor = 0; + config->rail_config.enable_window_zorder_sync = false; + config->rail_config.enable_window_snap_arrange = false; + config->rail_config.enable_distro_name_title = false; + config->rail_config.enable_copy_warning_title = false; + config->rail_config.enable_display_power_by_screenupdate = false; } WL_EXPORT int diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index cdb51b5f8..007934426 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -64,7 +64,6 @@ #include "shared/timespec-util.h" #define MAX_FREERDP_FDS 32 -#define RDP_MODE_FREQ 60 * 1000 #define RDP_MAX_MONITOR 16 // RDP max monitors. #define DEFAULT_PIXEL_FORMAT PIXEL_FORMAT_BGRA32 @@ -115,6 +114,9 @@ struct rdp_backend { char *rdp_key; int no_clients_resize; int force_no_compression; + bool redirect_clipboard; + bool redirect_audio_playback; + bool redirect_audio_capture; const struct weston_rdprail_shell_api *rdprail_shell_api; void *rdprail_shell_context; @@ -131,8 +133,9 @@ struct rdp_backend { struct wl_listener create_window_listener; bool enable_window_zorder_sync; + bool enable_window_snap_arrange; - bool keep_display_power_by_screenupdate; + bool enable_display_power_by_screenupdate; bool enable_hi_dpi_support; bool enable_fractional_hi_dpi_support; @@ -392,8 +395,10 @@ void rdp_debug_print(struct weston_log_scope *log_scope, bool cont, char *fmt, . void assert_compositor_thread(struct rdp_backend *b); void assert_not_compositor_thread(struct rdp_backend *b); #endif // ENABLE_RDP_THREAD_CHECK +#ifdef HAVE_FREERDP_GFXREDIR_H BOOL rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory); void rdp_free_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory); +#endif // HAVE_FREERDP_GFXREDIR_H BOOL rdp_id_manager_init(struct rdp_backend *rdp_backend, struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 high_limit); void rdp_id_manager_free(struct rdp_id_manager *id_manager); void rdp_id_manager_lock(struct rdp_id_manager *id_manager); @@ -407,7 +412,7 @@ bool rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_f void rdp_defer_rdp_task_done(RdpPeerContext *peerCtx); // rdprail.c -int rdp_rail_backend_create(struct rdp_backend *b); +int rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config *config); void rdp_rail_destroy(struct rdp_backend *b); BOOL rdp_rail_peer_activate(freerdp_peer* client); void rdp_rail_sync_window_status(freerdp_peer* client); diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 42f6ec8ae..b2ff52f53 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -1483,6 +1483,7 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) return; } +#ifdef HAVE_FREERDP_GFXREDIR_H static void rdp_destroy_shared_buffer(struct weston_surface *surface) { @@ -1517,6 +1518,7 @@ rdp_destroy_shared_buffer(struct weston_surface *surface) rail_state->surfaceBuffer = NULL; } +#endif // HAVE_FREERDP_GFXREDIR_H static void rdp_rail_destroy_window(struct wl_listener *listener, void *data) @@ -1910,7 +1912,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter } #ifdef HAVE_FREERDP_GFXREDIR_H /* this is for debugging only */ - if (b->enable_copy_warning_title) { + if (!b->use_gfxredir && b->enable_copy_warning_title) { if (snprintf(window_title_mod, sizeof window_title_mod, "[WARN:COPY MODE] %s (%s)", @@ -2625,7 +2627,7 @@ rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage) rdp_rail_sync_window_zorder(b->compositor); peerCtx->is_window_zorder_dirty = false; } - if (iter_data.isUpdatePending && b->keep_display_power_by_screenupdate) { + if (iter_data.isUpdatePending && b->enable_display_power_by_screenupdate) { /* By default, compositor won't update idle timer by screen activity, thus, here manually call wake function to postpone idle timer when RDP backend sends frame to client. */ @@ -2697,8 +2699,9 @@ rdp_rail_peer_activate(freerdp_peer* client) RAIL_HANDSHAKE_EX_ORDER handshakeEx = {}; UINT32 railHandshakeFlags = (TS_RAIL_ORDER_HANDSHAKEEX_FLAGS_HIDEF - | TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED - /* | TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED */); + | TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED); + if (b->enable_window_snap_arrange) + railHandshakeFlags |= TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED; handshakeEx.buildNumber = 0; handshakeEx.railHandshakeFlags = railHandshakeFlags; if (peerCtx->rail_server_context->ServerHandshakeEx(peerCtx->rail_server_context, &handshakeEx) != CHANNEL_RC_OK) @@ -2791,8 +2794,11 @@ rdp_rail_peer_activate(freerdp_peer* client) /* wait graphics channel (and optionally graphics redir channel) reponse from client */ waitRetry = 0; - while (!peerCtx->activationGraphicsCompleted || - (gfxredir_server_opened && !peerCtx->activationGraphicsRedirectionCompleted)) { + while (!peerCtx->activationGraphicsCompleted +#ifdef HAVE_FREERDP_GFXREDIR_H + || (gfxredir_server_opened && !peerCtx->activationGraphicsRedirectionCompleted) +#endif // HAVE_FREERDP_GFXREDIR_H + ) { if (++waitRetry > 10000) // timeout after 100 sec. goto error_exit; USleep(10000); // wait 0.01 sec. @@ -3959,9 +3965,8 @@ struct weston_rdprail_api rdprail_api = { }; int -rdp_rail_backend_create(struct rdp_backend *b) +rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config *config) { - char *s; int ret = weston_plugin_api_register(b->compositor, WESTON_RDPRAIL_API_NAME, &rdprail_api, sizeof(rdprail_api)); if (ret < 0) { @@ -3970,15 +3975,7 @@ rdp_rail_backend_create(struct rdp_backend *b) } #ifdef HAVE_FREERDP_RDPAPPLIST_H - bool use_rdpapplist = true; - - s = getenv("WESTON_RDP_DISABLE_APPLIST"); - if (s) { - rdp_debug(b, "WESTON_RDP_DISABLE_APPLIST is set to %s.\n", s); - if (strcmp(s, "true") == 0) - use_rdpapplist = false; - } - + bool use_rdpapplist = config->rail_config.use_rdpapplist; if (use_rdpapplist) { use_rdpapplist = false; @@ -4012,19 +4009,12 @@ rdp_rail_backend_create(struct rdp_backend *b) #endif // HAVE_FREERDP_RDPAPPLIST_H #ifdef HAVE_FREERDP_GFXREDIR_H - bool use_gfxredir = true; - - s = getenv("WESTON_RDP_DISABLE_SHARED_MEMORY"); - if (s) { - rdp_debug(b, "WESTON_RDP_DISABLE_SHARED_MEMORY is set to %s.\n", s); - if (strcmp(s, "true") == 0) - use_gfxredir = false; - } - + bool use_gfxredir = config->rail_config.use_shared_memory; /* check if shared memory mount path is set */ if (use_gfxredir) { use_gfxredir = false; - s = getenv("WSL2_SHARED_MEMORY_MOUNT_POINT"); + /* shared memory mounth point path is always given as environment variable from WSL */ + char *s = getenv("WSL2_SHARED_MEMORY_MOUNT_POINT"); if (s) { b->shared_memory_mount_path = s; b->shared_memory_mount_path_size = strlen(b->shared_memory_mount_path); @@ -4078,104 +4068,35 @@ rdp_rail_backend_create(struct rdp_backend *b) rdp_debug(b, "RDP backend: use_gfxredir = %d\n", b->use_gfxredir); #endif // HAVE_FREERDP_GFXREDIR_H - /* - * Configure HI-DPI scaling. - */ - b->enable_hi_dpi_support = true; - s = getenv("WESTON_RDP_DISABLE_HI_DPI_SCALING"); - if (s) { - if (strcmp(s, "true") == 0) - b->enable_hi_dpi_support = false; - else if (strcmp(s, "false") == 0) - b->enable_hi_dpi_support = true; - } + b->enable_hi_dpi_support = config->rail_config.enable_hi_dpi_support; rdp_debug(b, "RDP backend: enable_hi_dpi_support = %d\n", b->enable_hi_dpi_support); - b->enable_fractional_hi_dpi_support = false; - if (b->enable_hi_dpi_support) { - /* Disable by default for now. b->enable_fractional_hi_dpi_support = true; */ - s = getenv("WESTON_RDP_DISABLE_FRACTIONAL_HI_DPI_SCALING"); - if (s) { - if (strcmp(s, "true") == 0) - b->enable_fractional_hi_dpi_support = false; - else if (strcmp(s, "false") == 0) - b->enable_fractional_hi_dpi_support = true; - } - } + b->enable_fractional_hi_dpi_support = config->rail_config.enable_fractional_hi_dpi_support; rdp_debug(b, "RDP backend: enable_fractional_hi_dpi_support = %d\n", b->enable_fractional_hi_dpi_support); - b->enable_fractional_hi_dpi_roundup = false; - if (b->enable_hi_dpi_support) { - if (b->enable_fractional_hi_dpi_support) { - /* if fractional support is enabled, no round up */ - b->enable_fractional_hi_dpi_roundup = false; - } else { - s = getenv("WESTON_RDP_DISABLE_FRACTIONAL_HI_DPI_SCALING_ROUNDUP"); - if (s) { - if (strcmp(s, "true") == 0) - b->enable_fractional_hi_dpi_roundup = false; - else if (strcmp(s, "false") == 0) - b->enable_fractional_hi_dpi_roundup = true; - } - } - } + b->enable_fractional_hi_dpi_roundup = config->rail_config.enable_fractional_hi_dpi_roundup; rdp_debug(b, "RDP backend: enable_fractional_hi_dpi_roundup = %d\n", b->enable_fractional_hi_dpi_roundup); - b->debug_desktop_scaling_factor = 0; - if (b->enable_hi_dpi_support) { - char *debug_desktop_scaling_factor = getenv("WESTON_RDP_DEBUG_DESKTOP_SCALING_FACTOR"); - if (debug_desktop_scaling_factor) { - if (!safe_strtoint(debug_desktop_scaling_factor, &b->debug_desktop_scaling_factor) || - (b->debug_desktop_scaling_factor < 100 || b->debug_desktop_scaling_factor > 500)) { - b->debug_desktop_scaling_factor = 0; - rdp_debug(b, "WESTON_RDP_DEBUG_DESKTOP_SCALING_FACTOR = %s is invalid and ignored.\n", - debug_desktop_scaling_factor); - } else { - rdp_debug(b, "WESTON_RDP_DEBUG_DESKTOP_SCALING_FACTOR = %d is set.\n", - b->debug_desktop_scaling_factor); - } - } - } + b->debug_desktop_scaling_factor = config->rail_config.debug_desktop_scaling_factor; rdp_debug(b, "RDP backend: debug_desktop_scaling_factor = %d\n", b->debug_desktop_scaling_factor); - b->enable_window_zorder_sync = true; - s = getenv("WESTON_RDP_DISABLE_WINDOW_ZORDER_SYNC"); - if (s) { - if (strcmp(s, "true") == 0) - b->enable_window_zorder_sync = false; - } + b->enable_window_zorder_sync = config->rail_config.enable_window_zorder_sync; rdp_debug(b, "RDP backend: enable_window_zorder_sync = %d\n", b->enable_window_zorder_sync); - b->keep_display_power_by_screenupdate = false; - s = getenv("WESTON_RDP_ENABLE_DISPLAY_POWER_BY_SCREENUPDATE"); - if (s) { - if (strcmp(s, "true") == 0) - b->keep_display_power_by_screenupdate = true; - } - rdp_debug(b, "RDP backend: keep_display_power_by_screenupdate = %d\n", b->keep_display_power_by_screenupdate); + b->enable_window_snap_arrange = config->rail_config.enable_window_snap_arrange; + rdp_debug(b, "RDP backend: enable_window_snap_arrange = %d\n", b->enable_window_snap_arrange); - b->rdprail_shell_name = NULL; + b->enable_display_power_by_screenupdate = config->rail_config.enable_display_power_by_screenupdate; + rdp_debug(b, "RDP backend: enable_display_power_by_screenupdate = %d\n", b->enable_display_power_by_screenupdate); - b->enable_distro_name_title = true; - s = getenv("WESTON_RDP_DISABLE_APPEND_DISTRONAME_TITLE"); - if (s) { - if (strcmp(s, "true") == 0) - b->enable_distro_name_title = false; - } + b->enable_distro_name_title = config->rail_config.enable_distro_name_title; rdp_debug(b, "RDP backend: enable_distro_name_title = %d\n", b->enable_distro_name_title); - b->enable_copy_warning_title = false; - if (b->debugLevel >= RDP_DEBUG_LEVEL_WARN && - !b->use_gfxredir) { - b->enable_copy_warning_title = true; - s = getenv("WESTON_RDP_DISABLE_COPY_WARNING_TITLE"); - if (s) { - if (strcmp(s, "true") == 0) - b->enable_copy_warning_title = false; - } - } + b->enable_copy_warning_title = config->rail_config.enable_copy_warning_title; rdp_debug(b, "RDP backend: enable_copy_warning_title = %d\n", b->enable_copy_warning_title); + b->rdprail_shell_name = NULL; + /* M to dump all outstanding monitor info */ b->debug_binding_M = weston_compositor_add_debug_binding(b->compositor, KEY_M, rdp_rail_dump_monitor_binding, b); diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index 5abb6533f..0831a15be 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -120,6 +120,7 @@ void assert_not_compositor_thread(struct rdp_backend *b) } #endif // ENABLE_RDP_THREAD_CHECK +#ifdef HAVE_FREERDP_GFXREDIR_H BOOL rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory) { @@ -225,6 +226,7 @@ rdp_free_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *s shared_memory->fd = -1; } } +#endif // HAVE_FREERDP_GFXREDIR_H BOOL rdp_id_manager_init(struct rdp_backend *rdp_backend, struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 high_limit) From 2d22c94a087cfdc7fb934300ee47cab5623a6222 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 28 Apr 2022 12:03:52 -0700 Subject: [PATCH 1516/1642] fix missing free for client string at shell destruction (#70) Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 1 + 1 file changed, 1 insertion(+) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 9aa008bee..c59d2fa79 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -4078,6 +4078,7 @@ shell_destroy(struct wl_listener *listener, void *data) if (shell->debug) weston_log_scope_destroy(shell->debug); + free(shell->client); free(shell); } From 30c7251e5836d6d7025eba911bc1a45b424b507a Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 28 Apr 2022 15:05:07 -0700 Subject: [PATCH 1517/1642] refactor dispatching task to display loop for multithreading-safe (#69) * refactor dispatching task to display loop * eliminate bool task_list_mutex_initialized * define custom function type for callback * debug message to weston_log Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 25 ++- libweston/backend-rdp/rdp.h | 28 ++-- libweston/backend-rdp/rdpclip.c | 156 +++++++----------- libweston/backend-rdp/rdpdisp.c | 34 ++-- libweston/backend-rdp/rdprail.c | 276 +++++++++++++++----------------- libweston/backend-rdp/rdputil.c | 143 ++++++++++++++--- 6 files changed, 345 insertions(+), 317 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 83bf0b269..5458a37c9 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -848,7 +848,9 @@ rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) context->item.peer = client; context->item.flags = RDP_PEER_OUTPUT_ENABLED; - context->loop_event_source_fd = -1; + context->loop_task_event_source_fd = -1; + context->loop_task_event_source = NULL; + wl_list_init(&context->loop_task_list); context->rfx_context = rfx_context_new(TRUE); if (!context->rfx_context) @@ -886,9 +888,6 @@ rdp_peer_context_free(freerdp_peer* client, RdpPeerContext* context) wl_list_remove(&context->item.link); - if (context->loop_event_source_fd != -1) - close(context->loop_event_source_fd); - for (i = 0; i < ARRAY_LENGTH(context->events); i++) { if (context->events[i]) wl_event_source_remove(context->events[i]); @@ -907,6 +906,8 @@ rdp_peer_context_free(freerdp_peer* client, RdpPeerContext* context) if (context->vcm) WTSCloseServer(context->vcm); + rdp_destroy_dispatch_task_event_source(context); + /* clear the peer, in RAIL mode, this allows new peer to connect */ if (context->rdpBackend->rdp_peer == client) context->rdpBackend->rdp_peer = NULL; @@ -1942,12 +1943,11 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) for ( ; i < ARRAY_LENGTH(peerCtx->events); i++) peerCtx->events[i] = 0; - peerCtx->loop_event_source_fd = eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC); - if (peerCtx->loop_event_source_fd == -1) - goto error_peer_initialize; + if (!rdp_initialize_dispatch_task_event_source(peerCtx)) + goto error_dispatch_initialize; if (!rdp_rail_peer_init(client, peerCtx)) - goto error_peer_initialize; + goto error_rail_initialize; /* This tracks the single peer connected. This field only used for RAIL mode and, with RAIL mode, there can be only one peer per backend, and that @@ -1961,11 +1961,10 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) wl_list_insert(&b->output_default->peers, &peerCtx->item.link); return 0; -error_peer_initialize: - if (peerCtx->loop_event_source_fd != -1) { - close(peerCtx->loop_event_source_fd); - peerCtx->loop_event_source_fd = -1; - } +error_rail_initialize: + rdp_destroy_dispatch_task_event_source(peerCtx); + +error_dispatch_initialize: for (i = 0; i < ARRAY_LENGTH(peerCtx->events); i++) { if (peerCtx->events[i]) { wl_event_source_remove(peerCtx->events[i]); diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 007934426..d472b6bcf 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -84,11 +84,6 @@ struct rdp_id_manager { struct hash_table *hash_table; }; -struct rdp_loop_event_source { - struct wl_list link; - struct wl_event_source *event_source; -}; - struct rdp_backend { struct weston_backend base; struct weston_compositor *compositor; @@ -262,10 +257,13 @@ struct rdp_peer_context { struct wl_client *clientExec; struct wl_listener clientExec_destroy_listener; struct weston_surface *cursorSurface; + // list of outstanding event_source sent from FreeRDP thread to display loop. - int loop_event_source_fd; - pthread_mutex_t loop_event_source_list_mutex; - struct wl_list loop_event_source_list; + int loop_task_event_source_fd; + struct wl_event_source *loop_task_event_source; + pthread_mutex_t loop_task_list_mutex; + struct wl_list loop_task_list; // struct rdp_loop_task::link + // RAIL power management. struct wl_listener idle_listener; struct wl_listener wake_listener; @@ -314,8 +312,6 @@ struct rdp_peer_context { struct rdp_clipboard_data_source* clipboard_inflight_client_data_source; struct wl_listener clipboard_selection_listener; - struct wl_event_source *clipboard_data_request_event_source; - UINT32 clipboard_last_requested_format_index; // Application List support BOOL isAppListEnabled; @@ -323,6 +319,14 @@ struct rdp_peer_context { typedef struct rdp_peer_context RdpPeerContext; +typedef void (*rdp_loop_task_func_t)(bool freeOnly, void *data); + +struct rdp_loop_task { + struct wl_list link; + RdpPeerContext *peerCtx; + rdp_loop_task_func_t func; +}; + #define RDP_RAIL_MARKER_WINDOW_ID 0xFFFFFFFE #define RDP_RAIL_DESKTOP_WINDOW_ID 0xFFFFFFFF @@ -410,6 +414,10 @@ void rdp_id_manager_free_id(struct rdp_id_manager *id_manager, UINT32 id); void dump_id_manager_state(FILE *fp, struct rdp_id_manager *id_manager, char* title); bool rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_fd_func_t func, void *data, struct wl_event_source **event_source); void rdp_defer_rdp_task_done(RdpPeerContext *peerCtx); +bool rdp_event_loop_add_fd(struct wl_event_loop *loop, int fd, uint32_t mask, wl_event_loop_fd_func_t func, void *data, struct wl_event_source **event_source); +void rdp_dispatch_task_to_display_loop(RdpPeerContext *peerCtx, rdp_loop_task_func_t func, struct rdp_loop_task *task); +bool rdp_initialize_dispatch_task_event_source(RdpPeerContext *peerCtx); +void rdp_destroy_dispatch_task_event_source(RdpPeerContext *peerCtx); // rdprail.c int rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config *config); diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index d21f2b391..48bb9997b 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -106,8 +106,8 @@ enum rdp_clipboard_data_source_state { struct rdp_clipboard_data_source { struct weston_data_source base; + struct rdp_loop_task task_base; struct wl_event_source *transfer_event_source; /* used for read/write with pipe */ - struct wl_event_source *defer_event_source; /* used for defer task to display loop */ struct wl_array data_contents; void *context; int refcount; @@ -123,6 +123,12 @@ struct rdp_clipboard_data_source { UINT32 client_format_id_table[RDP_NUM_CLIPBOARD_FORMATS]; }; +struct rdp_clipboard_data_request { + struct rdp_loop_task task_base; + RdpPeerContext *peerCtx; + UINT32 requested_format_index; +}; + static char * clipboard_data_source_state_to_string(struct rdp_clipboard_data_source *source) { @@ -714,13 +720,6 @@ clipboard_data_source_unref(struct rdp_clipboard_data_source *source) wl_event_source_remove(source->transfer_event_source); } - if (source->defer_event_source) { - rdp_defer_rdp_task_done(peerCtx); - /* removing event source must be done from wayland display thread */ - ASSERT_COMPOSITOR_THREAD(b); - wl_event_source_remove(source->defer_event_source); - } - if (source->data_source_fd != -1) { ASSERT_COMPOSITOR_THREAD(b); close(source->data_source_fd); @@ -764,10 +763,6 @@ clipboard_client_send_format_data_response(RdpPeerContext *peerCtx, struct rdp_c peerCtx->clipboard_server_context->ServerFormatDataResponse(peerCtx->clipboard_server_context, &formatDataResponse); /* if here failed to send response, what can we do ? */ - /* now client can send new data request */ - assert(peerCtx->clipboard_data_request_event_source == RDP_INVALID_EVENT_SOURCE); - peerCtx->clipboard_data_request_event_source = NULL; - return 0; } @@ -793,10 +788,6 @@ clipboard_client_send_format_data_response_fail(RdpPeerContext *peerCtx, struct peerCtx->clipboard_server_context->ServerFormatDataResponse(peerCtx->clipboard_server_context, &formatDataResponse); /* if here failed to send response, what can we do ? */ - /* now client can send new data request */ - assert(peerCtx->clipboard_data_request_event_source == RDP_INVALID_EVENT_SOURCE); - peerCtx->clipboard_data_request_event_source = NULL; - return 0; } @@ -1002,12 +993,10 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) source->inflight_data_to_write = data_to_write; source->inflight_data_size = data_size; source->inflight_write_count++; - source->transfer_event_source = - wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, - clipboard_data_source_write, source); - if (!source->transfer_event_source) { + if (!rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + clipboard_data_source_write, source, &source->transfer_event_source)) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed\n", + rdp_debug_clipboard_error(b, "RDP %s (%p:%s) rdp_event_loop_add_fd failed\n", __func__, source, clipboard_data_source_state_to_string(source)); break; } @@ -1123,12 +1112,10 @@ clipboard_data_source_send(struct weston_data_source *base, __func__, source, clipboard_data_source_state_to_string(source), mime_type, index, source->client_format_id_table[index], clipboard_format_id_to_string(source->client_format_id_table[index], false)); - source->transfer_event_source = - wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, - clipboard_data_source_write, source); - if (!source->transfer_event_source) { + if (!rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + clipboard_data_source_write, source, &source->transfer_event_source)) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed\n", + rdp_debug_clipboard_error(b, "RDP %s (%p:%s) rdp_event_loop_add_fd failed\n", __func__, source, clipboard_data_source_state_to_string(source)); goto error_return_unref_source; } @@ -1226,10 +1213,10 @@ clipboard_data_source_cancel(struct weston_data_source *base) \**********************************/ /* Publish client's available clipboard formats to compositor (make them visible to applications in server) */ -static int -clipboard_data_source_publish(int fd, uint32_t mask, void *arg) +static void +clipboard_data_source_publish(bool freeOnly, void *arg) { - struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; + struct rdp_clipboard_data_source *source = wl_container_of(arg, source, task_base); freerdp_peer *client = (freerdp_peer*)source->context; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; @@ -1240,35 +1227,34 @@ clipboard_data_source_publish(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); - rdp_defer_rdp_task_done(peerCtx); - assert(source->defer_event_source); - wl_event_source_remove(source->defer_event_source); - source->defer_event_source = NULL; - /* here is going to publish new data, if previous data from client is still referenced, unref it after selection */ source_prev = peerCtx->clipboard_client_data_source; - peerCtx->clipboard_client_data_source = source; - - source->transfer_event_source = NULL; - source->base.accept = clipboard_data_source_accept; - source->base.send = clipboard_data_source_send; - source->base.cancel = clipboard_data_source_cancel; - source->state = RDP_CLIPBOARD_SOURCE_PUBLISHED; - weston_seat_set_selection(peerCtx->item.seat, &source->base, - wl_display_next_serial(b->compositor->wl_display)); - + if (!freeOnly) { + peerCtx->clipboard_client_data_source = source; + source->transfer_event_source = NULL; + source->base.accept = clipboard_data_source_accept; + source->base.send = clipboard_data_source_send; + source->base.cancel = clipboard_data_source_cancel; + source->state = RDP_CLIPBOARD_SOURCE_PUBLISHED; + weston_seat_set_selection(peerCtx->item.seat, &source->base, + wl_display_next_serial(b->compositor->wl_display)); + } else { + peerCtx->clipboard_client_data_source = NULL; + clipboard_data_source_unref(source); + } if (source_prev) clipboard_data_source_unref(source_prev); - return 0; + return; } /* Request the specified clipboard data from data-device at server side */ -static int -clipboard_data_source_request(int fd, uint32_t mask, void *arg) +static void +clipboard_data_source_request(bool freeOnly, void *arg) { - RdpPeerContext *peerCtx = (RdpPeerContext *)arg; + struct rdp_clipboard_data_request *request = wl_container_of(arg, request, task_base); + RdpPeerContext *peerCtx = request->peerCtx; struct rdp_backend *b = peerCtx->rdpBackend; struct weston_seat *seat = peerCtx->item.seat; struct weston_data_source *selection_data_source = seat->selection_data_source; @@ -1281,14 +1267,10 @@ clipboard_data_source_request(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); - rdp_defer_rdp_task_done(peerCtx); - assert(peerCtx->clipboard_data_request_event_source); - assert(peerCtx->clipboard_data_request_event_source != RDP_INVALID_EVENT_SOURCE); - wl_event_source_remove(peerCtx->clipboard_data_request_event_source); - /* set to invalid, so it still validate incoming request, but won't free event source at error. */ - peerCtx->clipboard_data_request_event_source = RDP_INVALID_EVENT_SOURCE; + if (freeOnly) + goto error_exit_free_request; - index = peerCtx->clipboard_last_requested_format_index; + index = request->requested_format_index; assert(index >= 0 && index < (int)RDP_NUM_CLIPBOARD_FORMATS); requested_mime_type = clipboard_supported_formats[index].mime_type; rdp_debug_clipboard(b, "RDP %s (base:%p) requested mime type:\"%s\"\n", @@ -1342,17 +1324,17 @@ clipboard_data_source_request(int fd, uint32_t mask, void *arg) /* p[1] should be closed by data source */ /* wait until data is ready on pipe */ - source->transfer_event_source = - wl_event_loop_add_fd(loop, p[0], WL_EVENT_READABLE, - clipboard_data_source_read, source); - if (!source->transfer_event_source) { + if (!rdp_event_loop_add_fd(loop, p[0], WL_EVENT_READABLE, + clipboard_data_source_read, source, &source->transfer_event_source)) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s (%p:%s) wl_event_loop_add_fd failed.\n", + rdp_debug_clipboard_error(b, "RDP %s (%p:%s) rdp_event_loop_add_fd failed.\n", __func__, source, clipboard_data_source_state_to_string(source)); goto error_exit_free_source; } - return 0; + free(request); + + return; error_exit_free_source: assert(source->refcount == 1); @@ -1361,7 +1343,10 @@ clipboard_data_source_request(int fd, uint32_t mask, void *arg) error_exit_response_fail: clipboard_client_send_format_data_response_fail(peerCtx, NULL); - return 0; +error_exit_free_request: + free(request); + + return; } /*************************************\ @@ -1555,15 +1540,8 @@ clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT } source->state = RDP_CLIPBOARD_SOURCE_FORMATLIST_READY; - if (rdp_defer_rdp_task_to_display_loop( - peerCtx, clipboard_data_source_publish, - source, &source->defer_event_source)) { - isPublished = TRUE; - } else { - source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "Client: %s (%p:%s) rdp_defer_rdp_task_to_display_loop failed\n", - __func__, source, clipboard_data_source_state_to_string(source)); - } + rdp_dispatch_task_to_display_loop(peerCtx, clipboard_data_source_publish, &source->task_base); + isPublished = TRUE; } CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = {}; @@ -1637,12 +1615,10 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR source->data_response_fail_count); assert(source->transfer_event_source == NULL); - source->transfer_event_source = - wl_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, - Success ? clipboard_data_source_write : clipboard_data_source_fail, source); - if (!source->transfer_event_source) { + if (!rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + Success ? clipboard_data_source_write : clipboard_data_source_fail, source, &source->transfer_event_source)) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "Client: %s (%p:%s) wl_event_loop_add_fd failed\n", + rdp_debug_clipboard_error(b, "Client: %s (%p:%s) rdp_event_loop_add_fd failed\n", __func__, source, clipboard_data_source_state_to_string(source)); return -1; } @@ -1673,6 +1649,7 @@ clipboard_client_format_data_request(CliprdrServerContext* context, const CLIPRD freerdp_peer *client = (freerdp_peer*)context->custom; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_clipboard_data_request *request; int index; rdp_debug_clipboard(b, "Client: %s requestedFormatId:%d - %s\n", @@ -1681,27 +1658,21 @@ clipboard_client_format_data_request(CliprdrServerContext* context, const CLIPRD ASSERT_NOT_COMPOSITOR_THREAD(b); - if (peerCtx->clipboard_data_request_event_source) { - rdp_debug_clipboard_error(b, "Client: %s (outstanding event:%p) client requests data while server hasn't responded previous request yet. protocol error.\n", - __func__, peerCtx->clipboard_data_request_event_source); - return -1; - } - /* Make sure clients requested the format we knew */ index = clipboard_find_supported_format_by_format_id(formatDataRequest->requestedFormatId); if (index >= 0) { - peerCtx->clipboard_last_requested_format_index = index; - if (!rdp_defer_rdp_task_to_display_loop( - peerCtx, clipboard_data_source_request, - peerCtx, &peerCtx->clipboard_data_request_event_source)) { - rdp_debug_clipboard_error(b, "Client: %s rdp_defer_rdp_task_to_display_loop failed\n", __func__); + request = zalloc(sizeof(*request)); + if (!request) { + rdp_debug_clipboard_error(b, "zalloc failed\n", __func__); goto error_return; } + request->peerCtx = peerCtx; + request->requested_format_index = index; + rdp_dispatch_task_to_display_loop(peerCtx, clipboard_data_source_request, &request->task_base); } else { rdp_debug_clipboard_error(b, "Client: %s client requests data format the server never reported in format list response. protocol error.\n", __func__); - return -1; + goto error_return; } - return 0; error_return: @@ -1775,18 +1746,13 @@ rdp_clipboard_destroy(RdpPeerContext *peerCtx) wl_list_remove(&peerCtx->clipboard_selection_listener.link); peerCtx->clipboard_selection_listener.notify = NULL; } - if (peerCtx->clipboard_data_request_event_source && - peerCtx->clipboard_data_request_event_source != RDP_INVALID_EVENT_SOURCE) { - rdp_defer_rdp_task_done(peerCtx); - wl_event_source_remove(peerCtx->clipboard_data_request_event_source); - peerCtx->clipboard_data_request_event_source = NULL; - } if (peerCtx->clipboard_inflight_client_data_source) { data_source = peerCtx->clipboard_inflight_client_data_source; peerCtx->clipboard_inflight_client_data_source = NULL; clipboard_data_source_unref(data_source); } + if (peerCtx->clipboard_client_data_source) { data_source = peerCtx->clipboard_client_data_source; peerCtx->clipboard_client_data_source = NULL; diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index e387ca5de..8d22a845c 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -605,32 +605,27 @@ disp_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MON } struct disp_schedule_monitor_layout_change_data { - struct rdp_loop_event_source _base_event_source; + struct rdp_loop_task _base; DispServerContext* context; DISPLAY_CONTROL_MONITOR_LAYOUT_PDU displayControl; }; -static int -disp_monitor_layout_change_callback(int fd, uint32_t mask, void* dataIn) +static void +disp_monitor_layout_change_callback(bool freeOnly, void* dataIn) { - struct disp_schedule_monitor_layout_change_data *data = (struct disp_schedule_monitor_layout_change_data *)dataIn; + struct disp_schedule_monitor_layout_change_data *data = wl_container_of(dataIn, data, _base); DispServerContext* context = data->context; freerdp_peer *client = (freerdp_peer*)context->custom; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); - rdp_defer_rdp_task_done(peerCtx); - assert(data->_base_event_source.event_source); - wl_event_source_remove(data->_base_event_source.event_source); - pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); - wl_list_remove(&data->_base_event_source.link); - pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); + if (!freeOnly) + disp_monitor_layout_change(context, &data->displayControl); - disp_monitor_layout_change(context, &data->displayControl); - free(dataIn); + free(data); - return 0; + return; } UINT @@ -658,18 +653,7 @@ disp_client_monitor_layout_change(DispServerContext* context, const DISPLAY_CONT memcpy(data->displayControl.Monitors, displayControl->Monitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT) * displayControl->NumMonitors); - pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); - wl_list_insert(&peerCtx->loop_event_source_list, &data->_base_event_source.link); - pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); - if (!rdp_defer_rdp_task_to_display_loop( - peerCtx, disp_monitor_layout_change_callback, - data, &data->_base_event_source.event_source)) { - pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); - wl_list_remove(&data->_base_event_source.link); - pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); - free(data); - return ERROR_INTERNAL_ERROR; - } + rdp_dispatch_task_to_display_loop(peerCtx, disp_monitor_layout_change_callback, &data->_base); return 0; } diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index b2ff52f53..40440c6d5 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -55,8 +55,8 @@ static void rdp_rail_destroy_window(struct wl_listener *listener, void *data); static void rdp_rail_schedule_update_window(struct wl_listener *listener, void *data); static void rdp_rail_dump_window_label(struct weston_surface *surface, char *label, uint32_t label_size); -struct rdp_dispatch_data { - struct rdp_loop_event_source _base_event_source; +struct rdp_rail_dispatch_data { + struct rdp_loop_task task_base; freerdp_peer *client; union { RAIL_SYSPARAM_ORDER u_sysParam; @@ -78,47 +78,23 @@ struct rdp_dispatch_data { freerdp_peer *client = (freerdp_peer*)(context)->custom; \ RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; \ struct rdp_backend *b = peerCtx->rdpBackend; \ - struct rdp_dispatch_data *dispatch_data; \ - dispatch_data = (struct rdp_dispatch_data *)malloc(sizeof(*dispatch_data)); \ + struct rdp_rail_dispatch_data *dispatch_data; \ + dispatch_data = (struct rdp_rail_dispatch_data *)malloc(sizeof(*dispatch_data)); \ if (dispatch_data) { \ ASSERT_NOT_COMPOSITOR_THREAD(b); \ dispatch_data->client = client; \ dispatch_data->u_##arg_type = *(arg); \ - pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ - wl_list_insert(&peerCtx->loop_event_source_list, &dispatch_data->_base_event_source.link); \ - pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ - if (!rdp_defer_rdp_task_to_display_loop( \ - peerCtx, callback, \ - dispatch_data, &dispatch_data->_base_event_source.event_source)) { \ - rdp_debug_error(b, "%s: rdp_queue_deferred_task failed\n", __func__); \ - pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ - wl_list_remove(&dispatch_data->_base_event_source.link); \ - pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ - free(dispatch_data); \ - } \ + rdp_dispatch_task_to_display_loop(peerCtx, callback, &dispatch_data->task_base); \ } else { \ rdp_debug_error(b, "%s: malloc failed\n", __func__); \ } \ } -#define RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, dispatch_data) \ - { \ - ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); \ - rdp_defer_rdp_task_done(peerCtx); \ - assert(dispatch_data->_base_event_source.event_source); \ - wl_event_source_remove(dispatch_data->_base_event_source.event_source); \ - pthread_mutex_lock(&peerCtx->loop_event_source_list_mutex); \ - wl_list_remove(&dispatch_data->_base_event_source.link); \ - pthread_mutex_unlock(&peerCtx->loop_event_source_list_mutex); \ - free(dispatch_data); \ - return 0; \ - } - #ifdef HAVE_FREERDP_RDPAPPLIST_H -static int -applist_client_Caps_callback(int fd, uint32_t mask, void *arg) +static void +applist_client_Caps_callback(bool freeOnly, void *arg) { - struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); const RDPAPPLIST_CLIENT_CAPS_PDU* caps = &data->u_appListCaps; freerdp_peer *client = data->client; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; @@ -129,7 +105,8 @@ applist_client_Caps_callback(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); - if (b->rdprail_shell_api && + if (!freeOnly && + b->rdprail_shell_api && b->rdprail_shell_api->start_app_list_update) { strncpy(clientLanguageId, caps->clientLanguageId, RDPAPPLIST_LANG_SIZE); @@ -141,7 +118,7 @@ applist_client_Caps_callback(int fd, uint32_t mask, void *arg) clientLanguageId); } - RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); + free(data); } static UINT @@ -179,10 +156,10 @@ rail_ClientExec_destroy(struct wl_listener *listener, void *data) peerCtx->clientExec = NULL; } -static int -rail_client_Exec_callback(int fd, uint32_t mask, void *arg) +static void +rail_client_Exec_callback(bool freeOnly, void *arg) { - struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); const RAIL_EXEC_ORDER* exec = &data->u_exec; freerdp_peer *client = data->client; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; @@ -199,7 +176,8 @@ rail_client_Exec_callback(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); - if (exec->RemoteApplicationProgram) { + if (!freeOnly && + exec->RemoteApplicationProgram) { if (!utf8_string_to_rail_string(exec->RemoteApplicationProgram, &orderResult.exeOrFile)) goto send_result; @@ -238,10 +216,13 @@ rail_client_Exec_callback(int fd, uint32_t mask, void *arg) } send_result: - orderResult.flags = exec->flags; - orderResult.execResult = result; - orderResult.rawResult = 0; - peerCtx->rail_server_context->ServerExecResult(peerCtx->rail_server_context, &orderResult); + + if (!freeOnly) { + orderResult.flags = exec->flags; + orderResult.execResult = result; + orderResult.rawResult = 0; + peerCtx->rail_server_context->ServerExecResult(peerCtx->rail_server_context, &orderResult); + } if (orderResult.exeOrFile.string) free(orderResult.exeOrFile.string); @@ -254,7 +235,7 @@ rail_client_Exec_callback(int fd, uint32_t mask, void *arg) if (exec->RemoteApplicationArguments) free(exec->RemoteApplicationArguments); - RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); + free(data); } static UINT @@ -293,10 +274,10 @@ rail_client_Exec(RailServerContext* context, const RAIL_EXEC_ORDER* arg) return CHANNEL_RC_NO_BUFFER; } -static int -rail_client_Activate_callback(int fd, uint32_t mask, void *arg) +static void +rail_client_Activate_callback(bool freeOnly, void *arg) { - struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); const RAIL_ACTIVATE_ORDER* activate = &data->u_activate; freerdp_peer *client = data->client; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; @@ -307,7 +288,8 @@ rail_client_Activate_callback(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); - if (b->rdprail_shell_api && + if (!freeOnly && + b->rdprail_shell_api && b->rdprail_shell_api->request_window_activate && b->rdprail_shell_context) { if (activate->windowId && activate->enabled) { @@ -318,7 +300,7 @@ rail_client_Activate_callback(int fd, uint32_t mask, void *arg) b->rdprail_shell_api->request_window_activate(b->rdprail_shell_context, peerCtx->item.seat, surface); } - RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); + free(data); } static UINT @@ -328,10 +310,10 @@ rail_client_Activate(RailServerContext* context, const RAIL_ACTIVATE_ORDER* arg) return CHANNEL_RC_OK; } -static int -rail_client_SnapArrange_callback(int fd, uint32_t mask, void *arg) +static void +rail_client_SnapArrange_callback(bool freeOnly, void *arg) { - struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); const RAIL_SNAP_ARRANGE* snap = &data->u_snapArrange; freerdp_peer *client = data->client; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; @@ -349,7 +331,9 @@ rail_client_SnapArrange_callback(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); - surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, snap->windowId); + surface = NULL; + if (!freeOnly) + surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, snap->windowId); if (surface) { rail_state = (struct weston_surface_rail_state *)surface->backend_state; if (b->rdprail_shell_api && @@ -372,7 +356,7 @@ rail_client_SnapArrange_callback(int fd, uint32_t mask, void *arg) rdp_rail_schedule_update_window(NULL, (void*)surface); } - RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); + free(data); } static UINT @@ -382,10 +366,10 @@ rail_client_SnapArrange(RailServerContext* context, const RAIL_SNAP_ARRANGE* arg return CHANNEL_RC_OK; } -static int -rail_client_WindowMove_callback(int fd, uint32_t mask, void *arg) +static void +rail_client_WindowMove_callback(bool freeOnly, void *arg) { - struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); const RAIL_WINDOW_MOVE_ORDER* windowMove = &data->u_windowMove; freerdp_peer *client = data->client; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; @@ -402,7 +386,9 @@ rail_client_WindowMove_callback(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); - surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, windowMove->windowId); + surface = NULL; + if (!freeOnly) + surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, windowMove->windowId); if (surface) { if (b->rdprail_shell_api && b->rdprail_shell_api->request_window_move) { @@ -418,12 +404,11 @@ rail_client_WindowMove_callback(int fd, uint32_t mask, void *arg) windowMoveRect.y, windowMoveRect.width, windowMoveRect.height); + rdp_debug(b, "Surface Size (%d, %d)\n", surface->width, surface->height); } } - rdp_debug(b, "Surface Size (%d, %d)\n", surface->width, surface->height); - - RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); + free(data); } static UINT @@ -433,10 +418,10 @@ rail_client_WindowMove(RailServerContext* context, const RAIL_WINDOW_MOVE_ORDER* return CHANNEL_RC_OK; } -static int -rail_client_Syscommand_callback(int fd, uint32_t mask, void *arg) +static void +rail_client_Syscommand_callback(bool freeOnly, void *arg) { - struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); const RAIL_SYSCOMMAND_ORDER* syscommand = &data->u_sysCommand; freerdp_peer *client = data->client; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; @@ -445,7 +430,9 @@ rail_client_Syscommand_callback(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); - surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, syscommand->windowId); + surface = NULL; + if (!freeOnly) + surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, syscommand->windowId); if (!surface) { rdp_debug_error(b, "Client: ClientSyscommand: WindowId:0x%x is not found.\n", syscommand->windowId); goto Exit; @@ -498,7 +485,7 @@ rail_client_Syscommand_callback(int fd, uint32_t mask, void *arg) syscommand->windowId, surface, commandString, syscommand->command); Exit: - RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); + free(data); } static UINT @@ -508,10 +495,10 @@ rail_client_Syscommand(RailServerContext* context, const RAIL_SYSCOMMAND_ORDER* return CHANNEL_RC_OK; } -static int -rail_client_ClientSysparam_callback(int fd, uint32_t mask, void *arg) +static void +rail_client_ClientSysparam_callback(bool freeOnly, void *arg) { - struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); const RAIL_SYSPARAM_ORDER* sysparam = &data->u_sysParam; freerdp_peer *client = data->client; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; @@ -592,8 +579,9 @@ rail_client_ClientSysparam_callback(int fd, uint32_t mask, void *arg) rdp_debug(b, "Client: ClientSysparam: setScreenSaveSecure:%d\n", sysparam->setScreenSaveSecure); } - if (sysparam->params & SPI_MASK_SET_WORK_AREA) { - if (b->rdprail_shell_api && + if (!freeOnly) { + if (sysparam->params & SPI_MASK_SET_WORK_AREA && + b->rdprail_shell_api && b->rdprail_shell_api->set_desktop_workarea) { workareaRectClient.x = (INT32)(INT16)sysparam->workArea.left; workareaRectClient.y = (INT32)(INT16)sysparam->workArea.top; @@ -623,7 +611,7 @@ rail_client_ClientSysparam_callback(int fd, uint32_t mask, void *arg) } } - RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); + free(data); } static UINT @@ -633,10 +621,10 @@ rail_client_ClientSysparam(RailServerContext* context, const RAIL_SYSPARAM_ORDER return CHANNEL_RC_OK; } -static int -rail_client_ClientGetAppidReq_callback(int fd, uint32_t mask, void *arg) +static void +rail_client_ClientGetAppidReq_callback(bool freeOnly, void *arg) { - struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); const RAIL_GET_APPID_REQ_ORDER* getAppidReq = &data->u_getAppidReq; freerdp_peer *client = data->client; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; @@ -652,7 +640,8 @@ rail_client_ClientGetAppidReq_callback(int fd, uint32_t mask, void *arg) ASSERT_COMPOSITOR_THREAD(b); - if (b->rdprail_shell_api && + if (!freeOnly && + b->rdprail_shell_api && b->rdprail_shell_api->get_window_app_id) { surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, getAppidReq->windowId); @@ -692,7 +681,7 @@ rail_client_ClientGetAppidReq_callback(int fd, uint32_t mask, void *arg) } Exit: - RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); + free(data); } static UINT @@ -811,10 +800,10 @@ languageGuid_to_string(const GUID *guid) return "Unknown GUID"; } -static int -rail_client_LanguageImeInfo_callback(int fd, uint32_t mask, void *arg) +static void +rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) { - struct rdp_dispatch_data* data = (struct rdp_dispatch_data*)arg; + struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); const RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo = &data->u_languageImeInfo; freerdp_peer *client = data->client; rdpSettings *settings = client->settings; @@ -847,67 +836,69 @@ rail_client_LanguageImeInfo_callback(int fd, uint32_t mask, void *arg) languageGuid_to_string(&languageImeInfo->ProfileGUID)); rdp_debug(b, "Client: LanguageImeInfo: KeyboardLayout: 0x%x\n", languageImeInfo->KeyboardLayout); - if (languageImeInfo->ProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT) { - new_keyboard_layout = languageImeInfo->KeyboardLayout; - } else if (languageImeInfo->ProfileType == TF_PROFILETYPE_INPUTPROCESSOR) { - typedef struct _lang_GUID - { - UINT32 Data1; - UINT16 Data2; - UINT16 Data3; - BYTE Data4_0; - BYTE Data4_1; - BYTE Data4_2; - BYTE Data4_3; - BYTE Data4_4; - BYTE Data4_5; - BYTE Data4_6; - BYTE Data4_7; - } lang_GUID; - - static const lang_GUID c_GUID_JPNIME = GUID_MSIME_JPN; - static const lang_GUID c_GUID_KORIME = GUID_MSIME_KOR; - static const lang_GUID c_GUID_CHSIME = GUID_CHSIME; - static const lang_GUID c_GUID_CHTIME = GUID_CHTIME; - - RPC_STATUS rpc_status; - if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, - (GUID *)&c_GUID_JPNIME, &rpc_status)) - new_keyboard_layout = KBD_JAPANESE; - else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, - (GUID *)&c_GUID_KORIME, &rpc_status)) - new_keyboard_layout = KBD_KOREAN; - else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, - (GUID *)&c_GUID_CHSIME, &rpc_status)) - new_keyboard_layout = KBD_CHINESE_SIMPLIFIED_US; - else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, - (GUID *)&c_GUID_CHTIME, &rpc_status)) - new_keyboard_layout = KBD_CHINESE_TRADITIONAL_US; - else - new_keyboard_layout = KBD_US; - } - - if (new_keyboard_layout && (new_keyboard_layout != settings->KeyboardLayout)) { - convert_rdp_keyboard_to_xkb_rule_names(settings->KeyboardType, - settings->KeyboardSubType, - new_keyboard_layout, - &xkbRuleNames); - if (xkbRuleNames.layout) { - keymap = xkb_keymap_new_from_names(b->compositor->xkb_context, - &xkbRuleNames, 0); - if (keymap) { - weston_seat_update_keymap(peerCtx->item.seat, keymap); - xkb_keymap_unref(keymap); - settings->KeyboardLayout = new_keyboard_layout; - } + if (!freeOnly) { + if (languageImeInfo->ProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT) { + new_keyboard_layout = languageImeInfo->KeyboardLayout; + } else if (languageImeInfo->ProfileType == TF_PROFILETYPE_INPUTPROCESSOR) { + typedef struct _lang_GUID + { + UINT32 Data1; + UINT16 Data2; + UINT16 Data3; + BYTE Data4_0; + BYTE Data4_1; + BYTE Data4_2; + BYTE Data4_3; + BYTE Data4_4; + BYTE Data4_5; + BYTE Data4_6; + BYTE Data4_7; + } lang_GUID; + + static const lang_GUID c_GUID_JPNIME = GUID_MSIME_JPN; + static const lang_GUID c_GUID_KORIME = GUID_MSIME_KOR; + static const lang_GUID c_GUID_CHSIME = GUID_CHSIME; + static const lang_GUID c_GUID_CHTIME = GUID_CHTIME; + + RPC_STATUS rpc_status; + if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, + (GUID *)&c_GUID_JPNIME, &rpc_status)) + new_keyboard_layout = KBD_JAPANESE; + else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, + (GUID *)&c_GUID_KORIME, &rpc_status)) + new_keyboard_layout = KBD_KOREAN; + else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, + (GUID *)&c_GUID_CHSIME, &rpc_status)) + new_keyboard_layout = KBD_CHINESE_SIMPLIFIED_US; + else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, + (GUID *)&c_GUID_CHTIME, &rpc_status)) + new_keyboard_layout = KBD_CHINESE_TRADITIONAL_US; + else + new_keyboard_layout = KBD_US; } - if (!keymap) { - rdp_debug_error(b, "%s: Failed to switch to kbd_layout:0x%x kbd_type:0x%x kbd_subType:0x%x\n", - __func__, new_keyboard_layout, settings->KeyboardType, settings->KeyboardSubType); + + if (new_keyboard_layout && (new_keyboard_layout != settings->KeyboardLayout)) { + convert_rdp_keyboard_to_xkb_rule_names(settings->KeyboardType, + settings->KeyboardSubType, + new_keyboard_layout, + &xkbRuleNames); + if (xkbRuleNames.layout) { + keymap = xkb_keymap_new_from_names(b->compositor->xkb_context, + &xkbRuleNames, 0); + if (keymap) { + weston_seat_update_keymap(peerCtx->item.seat, keymap); + xkb_keymap_unref(keymap); + settings->KeyboardLayout = new_keyboard_layout; + } + } + if (!keymap) { + rdp_debug_error(b, "%s: Failed to switch to kbd_layout:0x%x kbd_type:0x%x kbd_subType:0x%x\n", + __func__, new_keyboard_layout, settings->KeyboardType, settings->KeyboardSubType); + } } } - RDP_DISPATCH_DISPLAY_LOOP_COMPLETED(peerCtx, data); + free(data); } static UINT @@ -3165,8 +3156,6 @@ rdp_rail_destroy_window_iter(void *element, void *data) void rdp_rail_peer_context_free(freerdp_peer* client, RdpPeerContext* context) { - struct rdp_loop_event_source *current, *next; - rdp_id_manager_for_each(&context->windowId, rdp_rail_destroy_window_iter, NULL); #ifdef HAVE_FREERDP_RDPAPPLIST_H @@ -3204,14 +3193,6 @@ rdp_rail_peer_context_free(freerdp_peer* client, RdpPeerContext* context) rail_server_context_free(context->rail_server_context); } - /* after stopping all FreeRDP server context, no more work to be queued, free anything remained */ - wl_list_for_each_safe(current, next, &context->loop_event_source_list, link) { - wl_event_source_remove(current->event_source); - wl_list_remove(¤t->link); - free(current); - } - pthread_mutex_destroy(&context->loop_event_source_list_mutex); - if (context->clientExec_destroy_listener.notify) { wl_list_remove(&context->clientExec_destroy_listener.link); context->clientExec_destroy_listener.notify = NULL; @@ -3311,9 +3292,6 @@ rdp_rail_peer_init(freerdp_peer *client, RdpPeerContext *peerCtx) } #endif // HAVE_FREERDP_GFXREDIR_H - pthread_mutex_init(&peerCtx->loop_event_source_list_mutex, NULL); - wl_list_init(&peerCtx->loop_event_source_list); - peerCtx->currentFrameId = 0; peerCtx->acknowledgedFrameId = 0; diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index 0831a15be..f02e60602 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -364,38 +364,131 @@ dump_id_manager_state(FILE *fp, struct rdp_id_manager *id_manager, char* title) fprintf(fp,"\n"); } -/* this function is ONLY used to defer the task from RDP thread, - to be performed at wayland display loop thread */ bool -rdp_defer_rdp_task_to_display_loop(RdpPeerContext *peerCtx, wl_event_loop_fd_func_t func, void *data, struct wl_event_source **event_source) +rdp_event_loop_add_fd(struct wl_event_loop *loop, int fd, uint32_t mask, wl_event_loop_fd_func_t func, void *data, struct wl_event_source **event_source) { - assert(event_source); - *event_source = NULL; - if (peerCtx->vcm) { - struct rdp_backend *b = peerCtx->rdpBackend; - ASSERT_NOT_COMPOSITOR_THREAD(b); - struct wl_event_loop *loop = wl_display_get_event_loop(b->compositor->wl_display); - *event_source = wl_event_loop_add_fd(loop, - peerCtx->loop_event_source_fd, - WL_EVENT_READABLE, - func, data); - if (*event_source) { - eventfd_write(peerCtx->loop_event_source_fd, 1); - return true; - } else { - rdp_debug_error(b, "%s: wl_event_loop_add_idle failed\n", __func__); - } - } else { - /* RDP server is not opened, this must not be used */ - assert(false); + *event_source = wl_event_loop_add_fd(loop, fd, 0, func, data); + if (!*event_source) { + weston_log("%s: wl_event_loop_add_fd failed.\n", __func__); + return false; } - return false; + + wl_event_source_fd_update(*event_source, mask); + return true; } void -rdp_defer_rdp_task_done(RdpPeerContext *peerCtx) +rdp_dispatch_task_to_display_loop(RdpPeerContext *peerCtx, rdp_loop_task_func_t func, struct rdp_loop_task *task) +{ + /* this function is ONLY used to queue the task from FreeRDP thread, + and the task to be processed at wayland display loop thread. */ + ASSERT_NOT_COMPOSITOR_THREAD(peerCtx->rdpBackend); + + task->peerCtx = peerCtx; + task->func = func; + + pthread_mutex_lock(&peerCtx->loop_task_list_mutex); + /* this inserts at head */ + wl_list_insert(&peerCtx->loop_task_list, &task->link); + pthread_mutex_unlock(&peerCtx->loop_task_list_mutex); + + eventfd_write(peerCtx->loop_task_event_source_fd, 1); +} + +static int +rdp_dispatch_task(int fd, uint32_t mask, void *arg) { + RdpPeerContext *peerCtx = (RdpPeerContext *)arg; + struct rdp_loop_task *task, *tmp; eventfd_t dummy; - eventfd_read(peerCtx->loop_event_source_fd, &dummy); + + /* this must be called back at wayland display loop thread */ + ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); + + eventfd_read(peerCtx->loop_task_event_source_fd, &dummy); + + pthread_mutex_lock(&peerCtx->loop_task_list_mutex); + /* dequeue the first task which is at last, so use reverse. */ + assert(!wl_list_empty(&peerCtx->loop_task_list)); + wl_list_for_each_reverse_safe(task, tmp, &peerCtx->loop_task_list, link) { + wl_list_remove(&task->link); + break; + } + pthread_mutex_unlock(&peerCtx->loop_task_list_mutex); + + /* Dispatch and task will be freed by caller. */ + task->func(false, task); + + return 0; +} + +bool +rdp_initialize_dispatch_task_event_source(RdpPeerContext *peerCtx) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + struct wl_event_loop *loop; + + if (pthread_mutex_init(&peerCtx->loop_task_list_mutex, NULL) == -1) { + rdp_debug_error(b, "%s: pthread_mutex_init failed. %s\n", __func__, strerror(errno)); + goto error_mutex; + } + + assert(peerCtx->loop_task_event_source_fd == -1); + peerCtx->loop_task_event_source_fd = eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC); + if (peerCtx->loop_task_event_source_fd == -1) { + rdp_debug_error(b, "%s: eventfd(EFD_SEMAPHORE) failed. %s\n", __func__, strerror(errno)); + goto error_event_source_fd; + } + + assert(wl_list_empty(&peerCtx->loop_task_list)); + + loop = wl_display_get_event_loop(b->compositor->wl_display); + assert(peerCtx->loop_task_event_source == NULL); + if (!rdp_event_loop_add_fd( + loop, peerCtx->loop_task_event_source_fd, WL_EVENT_READABLE, + rdp_dispatch_task, peerCtx, &peerCtx->loop_task_event_source)) { + goto error_event_loop_add_fd; + } + + return true; + +error_event_loop_add_fd: + close(peerCtx->loop_task_event_source_fd); + peerCtx->loop_task_event_source_fd = -1; + +error_event_source_fd: + pthread_mutex_destroy(&peerCtx->loop_task_list_mutex); + +error_mutex: + return false; +} + +void +rdp_destroy_dispatch_task_event_source(RdpPeerContext *peerCtx) +{ + struct rdp_loop_task *task, *tmp; + + /* This function must be called all virtual channel thread at FreeRDP is terminated, + that ensures no more incoming tasks. */ + + if (peerCtx->loop_task_event_source) { + wl_event_source_remove(peerCtx->loop_task_event_source); + peerCtx->loop_task_event_source = NULL; + } + + wl_list_for_each_reverse_safe(task, tmp, &peerCtx->loop_task_list, link) { + wl_list_remove(&task->link); + /* inform caller task is not really scheduled prior to context destruction, + inform them to clean them up. */ + task->func(true /* freeOnly */, task); + } + assert(wl_list_empty(&peerCtx->loop_task_list)); + + if (peerCtx->loop_task_event_source_fd != -1) { + close(peerCtx->loop_task_event_source_fd); + peerCtx->loop_task_event_source_fd = -1; + } + + pthread_mutex_destroy(&peerCtx->loop_task_list_mutex); } From f69fd6431276a91b577c6d8e6c59a8598e7dd39d Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 29 Apr 2022 13:59:20 -0700 Subject: [PATCH 1518/1642] report marker window zorder (#71) Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 7 +++-- libweston/backend-rdp/rdp.h | 3 ++- libweston/backend-rdp/rdprail.c | 45 ++++++++++++++++----------------- rdprail-shell/shell.c | 18 ++++++------- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index 9292a4348..b6d2282e0 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -165,8 +165,11 @@ struct weston_rdprail_api { /** Update window zorder */ - void (*notify_window_zorder_change)(struct weston_compositor *compositor, - struct weston_surface *surface); + void (*notify_window_zorder_change)(struct weston_compositor *compositor); + + /** Notify window proxy surface + */ + void (*notify_window_proxy_surface)(struct weston_surface *proxy_surface); }; static inline const struct weston_rdprail_api * diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index d472b6bcf..c4c5a3d1e 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -139,6 +139,8 @@ struct rdp_backend { int rdp_monitor_refresh_rate; + struct weston_surface *proxy_surface; + #ifdef HAVE_FREERDP_RDPAPPLIST_H /* import from libfreerdp-server2.so */ RdpAppListServerContext* (*rdpapplist_server_context_new)(HANDLE vcm); @@ -269,7 +271,6 @@ struct rdp_peer_context { struct wl_listener wake_listener; bool is_window_zorder_dirty; - struct weston_surface *active_surface; // Multiple monitor support (monitor topology) pixman_region32_t regionClientHeads; diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 40440c6d5..11e57bf0e 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -1603,9 +1603,6 @@ rdp_rail_destroy_window(struct wl_listener *listener, void *data) creation/destruction of certain type of window, such as dropdown menu (popup in Wayland, override_redirect in X), thus do it here. */ peerCtx->is_window_zorder_dirty = true; - if (peerCtx->active_surface == surface) { - peerCtx->active_surface = NULL; - } if (rail_state->repaint_listener.notify) { wl_list_remove(&rail_state->repaint_listener.link); @@ -2543,7 +2540,7 @@ rdp_rail_sync_window_zorder(struct weston_compositor *compositor) if (!b->enable_window_zorder_sync) return; - numWindowId = peerCtx->windowId.id_used + 1; // +1 for marker window. + numWindowId = peerCtx->windowId.id_used + 1; // +1 for marker window (aka proxy_surface) windowIdArray = zalloc(numWindowId * sizeof(UINT32)); if (!windowIdArray) { rdp_debug_error(b, "%s: zalloc(%ld bytes) failed\n", __func__, numWindowId * sizeof(UINT32)); @@ -2551,27 +2548,21 @@ rdp_rail_sync_window_zorder(struct weston_compositor *compositor) } rdp_debug_verbose(b, "Dump Window Z order\n"); - if (!peerCtx->active_surface) { - /* if no active window, put marker window top as client window has focus. */ - rdp_debug_verbose(b, " window[%d]: %x: %s\n", iCurrent, RDP_RAIL_MARKER_WINDOW_ID, "marker window"); - windowIdArray[iCurrent++] = RDP_RAIL_MARKER_WINDOW_ID; - } /* walk windows in z-order */ struct weston_layer *layer; wl_list_for_each(layer, &compositor->layer_list, link) { struct weston_view *view; wl_list_for_each(view, &layer->view_list.link, layer_link.link) { - iCurrent = rdp_insert_window_zorder_array(view, windowIdArray, numWindowId, iCurrent); - if (iCurrent == UINT_MAX) - goto Exit; + if (view->surface == b->proxy_surface) { + rdp_debug_verbose(b, " window[%d]: %x: %s\n", iCurrent, RDP_RAIL_MARKER_WINDOW_ID, "marker window"); + windowIdArray[iCurrent++] = RDP_RAIL_MARKER_WINDOW_ID; + } else { + iCurrent = rdp_insert_window_zorder_array(view, windowIdArray, numWindowId, iCurrent); + if (iCurrent == UINT_MAX) + goto Exit; + } } } - if (peerCtx->active_surface) { - /* TODO: marker window better be placed correct place relative to client window, not always bottom */ - /* In order to do that, dummpy window to be created to track where is the highest client window. */ - rdp_debug_verbose(b, " window[%d]: %x: %s\n", iCurrent, RDP_RAIL_MARKER_WINDOW_ID, "marker window"); - windowIdArray[iCurrent++] = RDP_RAIL_MARKER_WINDOW_ID; - } assert(iCurrent <= numWindowId); assert(iCurrent > 0); rdp_debug_verbose(b, " send Window Z order: numWindowIds:%d\n", iCurrent); @@ -2885,16 +2876,23 @@ rdp_rail_wake_handler(struct wl_listener *listener, void *data) } static void -rdp_rail_notify_window_zorder_change(struct weston_compositor *compositor, struct weston_surface *active_surface) +rdp_rail_notify_window_proxy_surface(struct weston_surface *proxy_surface) +{ + struct rdp_backend *b = to_rdp_backend(proxy_surface->compositor); + + ASSERT_COMPOSITOR_THREAD(b); + + b->proxy_surface = proxy_surface; +} + +static void +rdp_rail_notify_window_zorder_change(struct weston_compositor *compositor) { struct rdp_backend *b = to_rdp_backend(compositor); - freerdp_peer* client = b->rdp_peer; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; ASSERT_COMPOSITOR_THREAD(b); - /* active_surface is NULL while client window has focus */ - peerCtx->active_surface = active_surface; /* z order will be sent to client at next repaint */ peerCtx->is_window_zorder_dirty = true; } @@ -3940,6 +3938,7 @@ struct weston_rdprail_api rdprail_api = { #endif // HAVE_FREERDP_RDPAPPLIST_H .get_primary_output = rdp_rail_get_primary_output, .notify_window_zorder_change = rdp_rail_notify_window_zorder_change, + .notify_window_proxy_surface = rdp_rail_notify_window_proxy_surface, }; int diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index c59d2fa79..6b1377b8d 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -2137,8 +2137,11 @@ desktop_surface_removed(struct weston_desktop_surface *desktop_surface, return; /* if this is focus proxy, reset to NULL */ - if (shell->focus_proxy_surface == surface) + if (shell->focus_proxy_surface == surface) { shell->focus_proxy_surface = NULL; + if (shell->rdprail_api->notify_window_proxy_surface) + shell->rdprail_api->notify_window_proxy_surface(NULL); + } wl_list_for_each_safe(shsurf_child, tmp, &shsurf->children_list, children_link) { wl_list_remove(&shsurf_child->children_link); @@ -3420,9 +3423,7 @@ activate(struct desktop_shell *shell, struct weston_view *view, shell_surface_update_layer(shsurf); if (shell->rdprail_api->notify_window_zorder_change) - shell->rdprail_api->notify_window_zorder_change( - shell->compositor, - shell->focus_proxy_surface == es ? NULL : es); + shell->rdprail_api->notify_window_zorder_change(shell->compositor); } /* no-op func for checking black surface */ @@ -3510,12 +3511,7 @@ shell_backend_request_window_activate(void *shell_context, struct weston_seat *s surface = shell->focus_proxy_surface; } if (!surface) { - /* if no proxy window provided, force set marker window to focus at backend */ - if (shell->rdprail_api->notify_window_zorder_change) { - shell->rdprail_api->notify_window_zorder_change(shell->compositor, NULL); - /* schedule repaint to force send z order */ - weston_compositor_schedule_repaint(shell->compositor); - } + /* if no proxy window provided, nothing here can do */ return; } @@ -3801,6 +3797,8 @@ desktop_shell_set_focus_proxy(struct wl_client *client, return; } + if (shell->rdprail_api->notify_window_proxy_surface) + shell->rdprail_api->notify_window_proxy_surface(surface); shell->focus_proxy_surface = surface; /* Update the surface’s layer. This brings it to the top of the stacking From 420ce93cdb139b6622172cf0e26d00e57c8c648b Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 6 May 2022 11:40:33 -0700 Subject: [PATCH 1519/1642] move rest of shell config setup to shell_configuration() (#73) Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 111 +++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 45 deletions(-) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 6b1377b8d..91801cd3b 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -654,6 +654,26 @@ get_modifier(char *modifier) return 0; // default to no binding-modifier. } +static bool +read_rdpshell_config_bool(char *config_name, bool default_value) +{ + char *s; + + s = getenv(config_name); + if (s) { + if (strcmp(s, "true") == 0) + return true; + else if (strcmp(s, "false") == 0) + return false; + else if (strcmp(s, "1") == 0) + return true; + else if (strcmp(s, "0") == 0) + return false; + } + + return default_value; +} + static void shell_configuration(struct desktop_shell *shell) { @@ -673,21 +693,63 @@ shell_configuration(struct desktop_shell *shell) /* default to not allow zap */ weston_config_section_get_bool(section, "allow-zap", &allow_zap, false); - if (!allow_zap) { - s = getenv("WESTON_RDPRAIL_SHELL_ALLOW_ZAP"); - allow_zap = (s && (strcmp(s, "true") == 0)); - } + allow_zap = read_rdpshell_config_bool("WESTON_RDPRAIL_SHELL_ALLOW_ZAP", allow_zap); shell->allow_zap = allow_zap; + shell_rdp_debug(shell, "RDPRAIL-shell: allow-zap:%d\n", shell->allow_zap); /* set "none" to default to disable optional key-bindings */ weston_config_section_get_string(section, "binding-modifier", &s, "none"); shell->binding_modifier = get_modifier(s); + shell_rdp_debug(shell, "RDPRAIL-shell: binding-modifier:%s\n", s); free(s); + /* default to disable local move (not fully supported yet */ weston_config_section_get_bool(section, "local-move", &is_localmove_supported, false); + is_localmove_supported = read_rdpshell_config_bool( + "WESTON_RDPRAIL_SHELL_LOCAL_MOVE", is_localmove_supported); shell->is_localmove_supported = is_localmove_supported; + shell_rdp_debug(shell, "RDPRAIL-shell: local-move:%d\n", shell->is_localmove_supported); + + /* distro name is provided from WSL via enviromment variable */ + shell->distroNameLength = 0; + shell->distroName = getenv("WSL2_DISTRO_NAME"); + if (!shell->distroName) + shell->distroName = getenv("WSL_DISTRO_NAME"); + if (shell->distroName) + shell->distroNameLength = strlen(shell->distroName); + shell_rdp_debug(shell, "RDPRAIL-shell: distro name:%s (len:%ld)\n", + shell->distroName, shell->distroNameLength); + + /* default icon path is provided from WSL via enviromment variable */ + s = getenv("WSL2_DEFAULT_APP_ICON"); + if (s && (strcmp(s, "disabled") != 0)) + shell->image_default_app_icon = load_icon_image(shell, s); + shell_rdp_debug(shell, "RDPRAIL-shell: WSL2_DEFAULT_APP_ICON:%s (loaded:%s)\n", + s, shell->image_default_app_icon ? "yes" : "no"); + + /* default overlay icon path is provided from WSL via enviromment variable */ + s = getenv("WSL2_DEFAULT_APP_OVERLAY_ICON"); + if (s && (strcmp(s, "disabled") != 0)) + shell->image_default_app_overlay_icon = load_icon_image(shell, s); + shell_rdp_debug(shell, "RDPRAIL-shell: WSL2_DEFAULT_APP_OVERLAY_ICON:%s (loaded:%s)\n", + s, shell->image_default_app_overlay_icon ? "yes" : "no"); + + shell->is_appid_with_distro_name = read_rdpshell_config_bool( + "WESTON_RDPRAIL_SHELL_APPEND_DISTRONAME_STARTMENU", true); + shell_rdp_debug(shell, "RDPRAIL-shell: WESTON_RDPRAIL_SHELL_APPEND_DISTRONAME_STARTMEN:%d\n", + shell->is_appid_with_distro_name); + + shell->is_blend_overlay_icon_app_list = read_rdpshell_config_bool( + "WESTON_RDPRAIL_SHELL_BLEND_OVERLAY_ICON_APPLIST", true); + shell_rdp_debug(shell, "RDPRAIL-shell: WESTON_RDPRAIL_SHELL_BLEND_OVERLAY_ICON_APPLIST:%d\n", + shell->is_blend_overlay_icon_app_list); + + shell->is_blend_overlay_icon_taskbar = read_rdpshell_config_bool( + "WESTON_RDPRAIL_SHELL_BLEND_OVERLAY_ICON_TASKBAR", true); + shell_rdp_debug(shell, "RDPRAIL-shell: WESTON_RDPRAIL_SHELL_BLEND_OVERLAY_ICON_TASKBAR:%d\n", + shell->is_blend_overlay_icon_taskbar); shell->workspaces.num = 1; } @@ -4381,7 +4443,6 @@ wet_shell_init(struct weston_compositor *ec, unsigned int i; struct wl_event_loop *loop; char *debug_level; - char *icon_path; shell = zalloc(sizeof *shell); if (shell == NULL) @@ -4477,46 +4538,6 @@ wet_shell_init(struct weston_compositor *ec, clock_gettime(CLOCK_MONOTONIC, &shell->startup_time); - shell->distroName = getenv("WSL2_DISTRO_NAME"); - if (!shell->distroName) - shell->distroName = getenv("WSL_DISTRO_NAME"); - if (shell->distroName) { - shell->distroNameLength = strlen(shell->distroName); - shell_rdp_debug(shell, "%s: distro name %s (%ld)\n", - __func__, shell->distroName, shell->distroNameLength); - } - - if (getenv("WESTON_RDPRAIL_SHELL_DISABLE_APPEND_DISTRONAME_STARTMENU")) - shell->is_appid_with_distro_name = false; - else - shell->is_appid_with_distro_name = true; - shell_rdp_debug(shell, "WESTON_RDPRAIL_SHELL_DISABLE_APPEND_DISTRONAME_STARTMEN:%d\n", - shell->is_appid_with_distro_name); - - icon_path = getenv("WSL2_DEFAULT_APP_ICON"); - if (icon_path && (strcmp(icon_path, "disabled") != 0)) - shell->image_default_app_icon = load_icon_image(shell, icon_path); - shell_rdp_debug(shell, "WSL2_DEFAULT_APP_ICON:%s\n", icon_path); - - icon_path = getenv("WSL2_DEFAULT_APP_OVERLAY_ICON"); - if (icon_path && (strcmp(icon_path, "disabled") != 0)) - shell->image_default_app_overlay_icon = load_icon_image(shell, icon_path); - shell_rdp_debug(shell, "WSL2_DEFAULT_APP_OVERLAY_ICON:%s\n", icon_path); - - if (getenv("WESTON_RDPRAIL_SHELL_DISABLE_BLEND_OVERLAY_ICON_TASKBAR")) - shell->is_blend_overlay_icon_taskbar = false; - else - shell->is_blend_overlay_icon_taskbar = true; - shell_rdp_debug(shell, "WESTON_RDPRAIL_SHELL_DISABLE_BLEND_OVERLAY_ICON_TASKBAR:%d\n", - shell->is_blend_overlay_icon_taskbar); - - if (getenv("WESTON_RDPRAIL_SHELL_DISABLE_BLEND_OVERLAY_ICON_APPLIST")) - shell->is_blend_overlay_icon_app_list = false; - else - shell->is_blend_overlay_icon_app_list = true; - shell_rdp_debug(shell, "WESTON_RDPRAIL_SHELL_DISABLE_BLEND_OVERLAY_ICON_APPLIST:%d\n", - shell->is_blend_overlay_icon_app_list); - if (shell->rdprail_api->shell_initialize_notify) shell->rdp_backend = shell->rdprail_api->shell_initialize_notify(ec, &rdprail_shell_api, (void*)shell, shell->distroName); From 35921b17e09392f29995942e146a2762c515fdcf Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 6 May 2022 14:08:00 -0700 Subject: [PATCH 1520/1642] set keyboard model to pc105 (#74) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 5458a37c9..ceed7a7f3 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1100,19 +1100,6 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {0x00000000, 0, 0}, }; -/* taken from 2.2.7.1.6 Input Capability Set (TS_INPUT_CAPABILITYSET) */ -static const char *rdp_keyboard_types[] = { - "", /* 0: unused */ - "", /* 1: IBM PC/XT or compatible (83-key) keyboard */ - "", /* 2: Olivetti "ICO" (102-key) keyboard */ - "", /* 3: IBM PC/AT (84-key) or similar keyboard */ - "pc102",/* 4: IBM enhanced (101- or 102-key) keyboard */ - "", /* 5: Nokia 1050 and similar keyboards */ - "", /* 6: Nokia 9140 and similar keyboards */ - "jp106",/* 7: Japanese keyboard */ /* alternative ja106 */ - "pc102" /* 8: Korean keyboard which is based on pc101, + 2 special Korean keys */ -}; - void convert_rdp_keyboard_to_xkb_rule_names( UINT32 KeyboardType, @@ -1122,8 +1109,7 @@ convert_rdp_keyboard_to_xkb_rule_names( { int i; memset(xkbRuleNames, 0, sizeof(*xkbRuleNames)); - if (KeyboardType <= ARRAY_LENGTH(rdp_keyboard_types)) - xkbRuleNames->model = rdp_keyboard_types[KeyboardType]; + xkbRuleNames->model = "pc105"; for (i = 0; rdp_keyboards[i].rdpLayoutCode; i++) { if (rdp_keyboards[i].rdpLayoutCode == KeyboardLayout) { xkbRuleNames->layout = rdp_keyboards[i].xkbLayout; @@ -1136,7 +1122,7 @@ convert_rdp_keyboard_to_xkb_rule_names( if (KeyboardType == 8 && ((KeyboardLayout & 0xFFFF) == 0x412)) { /* TODO: PC/AT 101 Enhanced Korean Keyboard (Type B) and (Type C) is not supported yet because default Xkb settings for Korean layout doesn't have corresponding - configuration. + configuration, thus here only supports Type A. (Type B): KeyboardSubType:4: rctrl_hangul/ratl_hanja (Type C): KeyboardSubType:5: shift_space_hangul/crtl_space_hanja */ if (KeyboardSubType == 0 || @@ -1152,10 +1138,6 @@ convert_rdp_keyboard_to_xkb_rule_names( xkbRuleNames->layout = "us"; xkbRuleNames->variant = 0; } - /* Brazilian ABNT2 keyboard */ - else if (KeyboardLayout == KBD_PORTUGUESE_BRAZILIAN_ABNT2) { - xkbRuleNames->model = "pc105"; - } weston_log("%s: matching model=%s layout=%s variant=%s options=%s\n", __FUNCTION__, xkbRuleNames->model, xkbRuleNames->layout, xkbRuleNames->variant, xkbRuleNames->options); From 3da8580666ef4d98e6628eccfae80d9e2ef35ded Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 6 May 2022 14:09:44 -0700 Subject: [PATCH 1521/1642] add option to disable window shadow remoting (#72) * add option to disable window shadow remoting * for shared memory path * for snap and move window callback * set default to enable * init config default at backend * fix to copyBufferHeight Co-authored-by: Hideyuki Nagase --- compositor/main.c | 2 + include/libweston/backend-rdp.h | 9 +- libweston/backend-rdp/rdp.c | 1 + libweston/backend-rdp/rdp.h | 1 + libweston/backend-rdp/rdprail.c | 237 +++++++++++++++++++++----------- rdprail-shell/shell.c | 25 ++++ 6 files changed, 193 insertions(+), 82 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 65bd4a8f8..41a01d987 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2716,6 +2716,7 @@ weston_rdp_backend_config_init(struct weston_rdp_backend_config *config) config->rail_config.debug_desktop_scaling_factor = 0; config->rail_config.enable_window_zorder_sync = false; config->rail_config.enable_window_snap_arrange = false; + config->rail_config.enable_window_shadow_remoting = false; config->rail_config.enable_distro_name_title = false; config->rail_config.enable_copy_warning_title = false; config->rail_config.enable_display_power_by_screenupdate = false; @@ -2825,6 +2826,7 @@ load_rdp_backend(struct weston_compositor *c, config.rail_config.enable_window_zorder_sync = read_rdp_config_bool("WESTON_RDP_WINDOW_ZORDER_SYNC", true); config.rail_config.enable_window_snap_arrange = read_rdp_config_bool("WESTON_RDP_WINDOW_SNAP_ARRANGE", false); + config.rail_config.enable_window_shadow_remoting = read_rdp_config_bool("WESTON_RDP_WINDOW_SHADOW_REMOTING", true); config.rail_config.enable_display_power_by_screenupdate = read_rdp_config_bool("WESTON_RDP_DISPLAY_POWER_BY_SCREENUPDATE", false); diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index b6d2282e0..8a060fa6e 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -112,6 +112,10 @@ struct weston_rdprail_shell_api { /** Request launch shell process */ struct wl_client* (*request_launch_shell_process)(void *shell_context, char *exec_name); + + /** Query window geometry + */ + void (*get_window_geometry)(struct weston_surface *surface, struct weston_geometry *geometry); }; #define WESTON_RDPRAIL_API_NAME "weston_rdprail_api_v1" @@ -206,8 +210,8 @@ struct weston_surface_rail_state { struct weston_rdp_rail_window_pos clientPos; int bufferWidth; int bufferHeight; - float bufferScaleWidth; - float bufferScaleHeight; + float bufferScaleFactorWidth; + float bufferScaleFactorHeight; pixman_region32_t damage; struct weston_output *output; struct weston_surface *parent_surface; @@ -263,6 +267,7 @@ struct weston_rdp_backend_config { int debug_desktop_scaling_factor; bool enable_window_zorder_sync; bool enable_window_snap_arrange; + bool enable_window_shadow_remoting; bool enable_distro_name_title; bool enable_copy_warning_title; bool enable_display_power_by_screenupdate; diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index ceed7a7f3..f90afc59e 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -2349,6 +2349,7 @@ config_init_to_defaults(struct weston_rdp_backend_config *config) config->rail_config.debug_desktop_scaling_factor = 0; config->rail_config.enable_window_zorder_sync = false; config->rail_config.enable_window_snap_arrange = false; + config->rail_config.enable_window_shadow_remoting = false; config->rail_config.enable_distro_name_title = false; config->rail_config.enable_copy_warning_title = false; config->rail_config.enable_display_power_by_screenupdate = false; diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index c4c5a3d1e..fe2796fc2 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -129,6 +129,7 @@ struct rdp_backend { bool enable_window_zorder_sync; bool enable_window_snap_arrange; + bool enable_window_shadow_remoting; bool enable_display_power_by_screenupdate; diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 11e57bf0e..4d5819dda 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -321,6 +321,7 @@ rail_client_SnapArrange_callback(bool freeOnly, void *arg) struct weston_surface *surface; struct weston_surface_rail_state *rail_state; pixman_rectangle32_t snapArrangeRect; + struct weston_geometry windowGeometry; rdp_debug(b, "Client: SnapArrange: WindowId:0x%x at (%d, %d) %dx%d\n", snap->windowId, @@ -337,7 +338,7 @@ rail_client_SnapArrange_callback(bool freeOnly, void *arg) if (surface) { rail_state = (struct weston_surface_rail_state *)surface->backend_state; if (b->rdprail_shell_api && - b->rdprail_shell_api->request_window_move) { + b->rdprail_shell_api->request_window_snap) { snapArrangeRect.x = snap->left; snapArrangeRect.y = snap->top; snapArrangeRect.width = snap->right - snap->left; @@ -345,6 +346,16 @@ rail_client_SnapArrange_callback(bool freeOnly, void *arg) to_weston_coordinate(peerCtx, &snapArrangeRect.x, &snapArrangeRect.y, &snapArrangeRect.width, &snapArrangeRect.height); + if (!b->enable_window_shadow_remoting && + b->rdprail_shell_api && + b->rdprail_shell_api->get_window_geometry) { + /* window_geometry here is last commited geometry */ + b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + snapArrangeRect.x -= windowGeometry.x; + snapArrangeRect.y -= windowGeometry.y; + snapArrangeRect.width += (surface->width - windowGeometry.width); + snapArrangeRect.height += (surface->height - windowGeometry.height); + } b->rdprail_shell_api->request_window_snap(surface, snapArrangeRect.x, snapArrangeRect.y, @@ -376,6 +387,7 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) struct rdp_backend *b = peerCtx->rdpBackend; struct weston_surface *surface; pixman_rectangle32_t windowMoveRect; + struct weston_geometry windowGeometry; rdp_debug(b, "Client: WindowMove: WindowId:0x0x at (%d, %d) %dx%d\n", windowMove->windowId, @@ -399,6 +411,16 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) to_weston_coordinate(peerCtx, &windowMoveRect.x, &windowMoveRect.y, &windowMoveRect.width, &windowMoveRect.height); + if (!b->enable_window_shadow_remoting && + b->rdprail_shell_api && + b->rdprail_shell_api->get_window_geometry) { + /* window_geometry here is last commited geometry */ + b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + windowMoveRect.x -= windowGeometry.x; + windowMoveRect.y -= windowGeometry.y; + windowMoveRect.width += (surface->width - windowGeometry.width); + windowMoveRect.height += (surface->height - windowGeometry.height); + } b->rdprail_shell_api->request_window_move(surface, windowMoveRect.x, windowMoveRect.y, @@ -1278,6 +1300,7 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) WINDOW_STATE_ORDER window_state_order = {}; struct weston_rdp_rail_window_pos pos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; struct weston_rdp_rail_window_pos clientPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; + struct weston_geometry windowGeometry = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; RECTANGLE_16 window_rect = { 0, 0, surface->width, surface->height }; RECTANGLE_16 window_vis = { 0, 0, surface->width, surface->height }; int numViews; @@ -1372,6 +1395,16 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); } + if (!b->enable_window_shadow_remoting && + b->rdprail_shell_api && + b->rdprail_shell_api->get_window_geometry) { + b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + clientPos.x += windowGeometry.x; + clientPos.y += windowGeometry.y; + clientPos.width = windowGeometry.width; + clientPos.height = windowGeometry.height; + } + /* apply global to output transform, and translate to client coordinate */ if (surface->output) to_client_coordinate(peerCtx, surface->output, @@ -1679,6 +1712,8 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter WINDOW_STATE_ORDER window_state_order = {}; struct weston_rdp_rail_window_pos newPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; struct weston_rdp_rail_window_pos newClientPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; + struct weston_geometry windowGeometry = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; + struct weston_geometry contentBufferWindowGeometry; RECTANGLE_16 window_rect; RECTANGLE_16 window_vis; int numViews; @@ -1803,6 +1838,17 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); } + if (!b->enable_window_shadow_remoting && + b->rdprail_shell_api && + b->rdprail_shell_api->get_window_geometry) { + b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + newClientPos.x += windowGeometry.x; + newClientPos.y += windowGeometry.y; + newClientPos.width = windowGeometry.width; + newClientPos.height = windowGeometry.height; + } + contentBufferWindowGeometry = windowGeometry; + /* apply global to output transform, and translate to client coordinate */ if (surface->output) to_client_coordinate(peerCtx, surface->output, @@ -2055,49 +2101,57 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter /* update window buffer contents */ { BOOL isBufferSizeChanged = FALSE; - float scaleWidth = 1.0f, scaleHeight = 1.0f; + float scaleFactorWidth = 1.0f, scaleFactorHeight = 1.0f; int damageWidth, damageHeight; int copyBufferStride, copyBufferSize; + int copyBufferWidth, copyBufferHeight; int clientBufferWidth, clientBufferHeight; - int contentBufferStride, contentBufferSize; int contentBufferWidth, contentBufferHeight; int bufferBpp = 4; // Bytes Per Pixel. bool hasAlpha = view ? !weston_view_is_opaque(view, &view->transform.boundingbox) : false; pixman_box32_t damageBox = *pixman_region32_extents(&rail_state->damage); long page_size = sysconf(_SC_PAGESIZE); + /* clientBuffer represents Windows size on client desktop */ + /* this size is adjusted on whether including or excluding window shadow */ clientBufferWidth = newClientPos.width; clientBufferHeight = newClientPos.height; + /* contentBuffer represents buffer from Linux application. */ + /* this size could be larger than it's window size for native HI-DPI rendering */ weston_surface_get_content_size(surface, &contentBufferWidth, &contentBufferHeight); - contentBufferStride = contentBufferWidth * bufferBpp; - contentBufferSize = contentBufferStride * contentBufferHeight; - copyBufferSize = (contentBufferSize + page_size - 1) & ~(page_size - 1); - copyBufferStride = contentBufferStride; + /* scale window geometry to content buffer base */ + rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, + &contentBufferWindowGeometry.x, &contentBufferWindowGeometry.y); + rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, + &contentBufferWindowGeometry.width, &contentBufferWindowGeometry.height); - if (contentBufferWidth && contentBufferHeight) { + /* copy buffer represents the buffer allocated to share with RDP client. */ + /* this can be shared memory buffer or grfx channel surface */ + copyBufferWidth = contentBufferWindowGeometry.width; + copyBufferHeight = contentBufferWindowGeometry.height; + copyBufferStride = copyBufferWidth * bufferBpp; + copyBufferSize = ((copyBufferStride * copyBufferHeight) + page_size - 1) & ~(page_size - 1); + if (copyBufferWidth && copyBufferHeight) { #ifdef HAVE_FREERDP_GFXREDIR_H if (b->use_gfxredir) { - scaleWidth = 1.0f; // scaling is done by client. - scaleHeight = 1.0f; // scaling is done by client. + scaleFactorWidth = 1.0f; // scaling is done by client. + scaleFactorHeight = 1.0f; // scaling is done by client. - if (rail_state->bufferWidth != contentBufferWidth || - rail_state->bufferHeight != contentBufferHeight) - isBufferSizeChanged = TRUE; } else { #else { #endif // HAVE_FREERDP_GFXREDIR_H - scaleWidth = (float)clientBufferWidth / contentBufferWidth; - scaleHeight = (float)clientBufferHeight / contentBufferHeight; - - if (rail_state->bufferWidth != contentBufferWidth || - rail_state->bufferHeight != contentBufferHeight) - isBufferSizeChanged = TRUE; + scaleFactorWidth = (float)surface->width / contentBufferWidth; + scaleFactorHeight = (float)surface->height / contentBufferHeight; } + if (rail_state->bufferWidth != copyBufferWidth || + rail_state->bufferHeight != copyBufferHeight) + isBufferSizeChanged = TRUE; + if (isBufferSizeChanged || rail_state->forceRecreateSurface || (rail_state->surfaceBuffer == NULL && rail_state->surface_id == 0)) { @@ -2137,17 +2191,17 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter createBuffer.poolId = openPool.poolId; createBuffer.bufferId = new_buffer_id; createBuffer.offset = 0; - createBuffer.stride = contentBufferStride; - createBuffer.width = contentBufferWidth; - createBuffer.height = contentBufferHeight; + createBuffer.stride = copyBufferStride; + createBuffer.width = copyBufferWidth; + createBuffer.height = copyBufferHeight; createBuffer.format = GFXREDIR_BUFFER_PIXEL_FORMAT_ARGB_8888; if (peerCtx->gfxredir_server_context->CreateBuffer( peerCtx->gfxredir_server_context, &createBuffer) == 0) { rail_state->surfaceBuffer = rail_state->shared_memory.addr; rail_state->buffer_id = createBuffer.bufferId; rail_state->pool_id = openPool.poolId; - rail_state->bufferWidth = contentBufferWidth; - rail_state->bufferHeight = contentBufferHeight; + rail_state->bufferWidth = copyBufferWidth; + rail_state->bufferHeight = copyBufferHeight; } } } @@ -2164,24 +2218,24 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter RDPGFX_CREATE_SURFACE_PDU createSurface = {}; /* create surface */ rdp_debug_verbose(b, "CreateSurface(surfaceId:0x%x - (%d, %d) size:%d for windowsId:0x%x)\n", - new_surface_id, contentBufferWidth, contentBufferHeight, contentBufferSize, window_id); + new_surface_id, copyBufferWidth, copyBufferHeight, copyBufferSize, window_id); createSurface.surfaceId = (UINT16)new_surface_id; - createSurface.width = contentBufferWidth; - createSurface.height = contentBufferHeight; + createSurface.width = copyBufferWidth; + createSurface.height = copyBufferHeight; /* regardless buffer as alpha or not, always use alpha to avoid mstsc bug */ createSurface.pixelFormat = GFX_PIXEL_FORMAT_ARGB_8888; if (peerCtx->rail_grfx_server_context->CreateSurface(peerCtx->rail_grfx_server_context, &createSurface) == 0) { /* store new surface id */ old_surface_id = rail_state->surface_id; rail_state->surface_id = new_surface_id; - rail_state->bufferWidth = contentBufferWidth; - rail_state->bufferHeight = contentBufferHeight; + rail_state->bufferWidth = copyBufferWidth; + rail_state->bufferHeight = copyBufferHeight; } } } rail_state->forceRecreateSurface = false; - /* When creating a new surface we need to upload it's entire content, expand damage */ + /* make entire content buffer damaged */ damageBox.x1 = 0; damageBox.y1 = 0; damageBox.x2 = contentBufferWidth; @@ -2191,41 +2245,52 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, &damageBox.x1, &damageBox.y1); rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, &damageBox.x2, &damageBox.y2); } + /* damageBox represents damaged area in contentBuffer */ + /* if it's not remoting window shadow, exclude the area from damageBox */ + if (!b->enable_window_shadow_remoting) { + if (damageBox.x1 < contentBufferWindowGeometry.x) + damageBox.x1 = contentBufferWindowGeometry.x; + if (damageBox.x2 > contentBufferWindowGeometry.x + contentBufferWindowGeometry.width) + damageBox.x2 = contentBufferWindowGeometry.x + contentBufferWindowGeometry.width; + if (damageBox.y1 < contentBufferWindowGeometry.y) + damageBox.y1 = contentBufferWindowGeometry.y; + if (damageBox.y2 > contentBufferWindowGeometry.y + contentBufferWindowGeometry.height) + damageBox.y2 = contentBufferWindowGeometry.y + contentBufferWindowGeometry.height; + damageWidth = damageBox.x2 - damageBox.x1; + damageHeight = damageBox.y2 - damageBox.y1; + } else { + damageWidth = damageBox.x2 - damageBox.x1; + if (damageWidth > contentBufferWidth) { + rdp_debug(b, "damageWidth (%d) is larger than content width(%d), clamp to avoid protocol error.\n", + damageWidth, contentBufferWidth); + damageBox.x1 = 0; + damageBox.x2 = contentBufferWidth; + damageWidth = contentBufferWidth; + } + damageHeight = damageBox.y2 - damageBox.y1; + if (damageHeight > contentBufferHeight) { + rdp_debug(b, "damageHeight (%d) is larger than content height(%d), clamp to avoid protocol error.\n", + damageHeight, contentBufferHeight); + damageBox.y1 = 0; + damageBox.y2 = contentBufferHeight; + damageHeight = contentBufferHeight; + } + } } else { /* no content buffer bound, thus no damage */ - damageBox.x1 = 0; - damageBox.y1 = 0; - damageBox.x2 = 0; - damageBox.y2 = 0; - } - - damageWidth = damageBox.x2 - damageBox.x1; - if (damageWidth > contentBufferWidth) { - rdp_debug(b, "damageWidth (%d) is larger than content width(%d), clamp to avoid protocol error.\n", - damageWidth, contentBufferWidth); - damageBox.x1 = 0; - damageBox.x2 = contentBufferWidth; - damageWidth = contentBufferWidth; - } - damageHeight = damageBox.y2 - damageBox.y1; - if (damageHeight > contentBufferHeight) { - rdp_debug(b, "damageHeight (%d) is larger than content height(%d), clamp to avoid protocol error.\n", - damageHeight, contentBufferHeight); - damageBox.y1 = 0; - damageBox.y2 = contentBufferHeight; - damageHeight = contentBufferHeight; + damageWidth = 0; + damageHeight = 0; } - /* Check to see if we have any content update to send to the new surface */ if (damageWidth > 0 && damageHeight > 0) { #ifdef HAVE_FREERDP_GFXREDIR_H if (b->use_gfxredir && rail_state->surfaceBuffer) { - int copyDamageX1 = (float)damageBox.x1 * scaleWidth; - int copyDamageY1 = (float)damageBox.y1 * scaleHeight; - int copyDamageWidth = (float)damageWidth * scaleWidth; - int copyDamageHeight = (float)damageHeight * scaleHeight; + int copyDamageX1 = (float)(damageBox.x1 - contentBufferWindowGeometry.x) * scaleFactorWidth; + int copyDamageY1 = (float)(damageBox.y1 - contentBufferWindowGeometry.y) * scaleFactorHeight; + int copyDamageWidth = (float)damageWidth * scaleFactorWidth; + int copyDamageHeight = (float)damageHeight * scaleFactorHeight; int copyStartOffset = copyDamageX1*bufferBpp + copyDamageY1*copyBufferStride; BYTE *copyBufferBits = (BYTE*)(rail_state->surfaceBuffer) + copyStartOffset; @@ -2233,8 +2298,8 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter damageBox.x1, damageBox.y1, damageWidth, damageHeight); rdp_debug_verbose(b, "copy target: x:%d, y:%d, width:%d, height:%d, stride:%d\n", copyDamageX1, copyDamageY1, copyDamageWidth, copyDamageHeight, copyBufferStride); - rdp_debug_verbose(b, "copy scale: scaleWidth:%5.3f, scaleHeight:%5.3f\n", - scaleWidth, scaleHeight); + rdp_debug_verbose(b, "copy scale: scaleFactorWidth:%5.3f, scaleFactorHeight:%5.3f\n", + scaleFactorWidth, scaleFactorHeight); if (weston_surface_copy_content(surface, copyBufferBits, copyBufferSize, copyBufferStride, @@ -2373,10 +2438,10 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter surfaceCommand.surfaceId = rail_state->surface_id; surfaceCommand.contextId = 0; surfaceCommand.format = PIXEL_FORMAT_BGRA32; - surfaceCommand.left = damageBox.x1; - surfaceCommand.top = damageBox.y1; - surfaceCommand.right = damageBox.x2; - surfaceCommand.bottom = damageBox.y2; + surfaceCommand.left = damageBox.x1 - contentBufferWindowGeometry.x; + surfaceCommand.top = damageBox.y1 - contentBufferWindowGeometry.y; + surfaceCommand.right = damageBox.x2 - contentBufferWindowGeometry.x; + surfaceCommand.bottom = damageBox.y2 - contentBufferWindowGeometry.y; surfaceCommand.width = damageWidth; surfaceCommand.height = damageHeight; surfaceCommand.extra = NULL; @@ -2419,7 +2484,9 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter #else { #endif // HAVE_FREERDP_GFXREDIR_H - if (new_surface_id || rail_state->bufferScaleWidth != scaleWidth || rail_state->bufferScaleHeight != scaleHeight) { + if (new_surface_id || + rail_state->bufferScaleFactorWidth != scaleFactorWidth || + rail_state->bufferScaleFactorHeight != scaleFactorHeight) { /* map surface to window */ assert(new_surface_id == 0 || (new_surface_id == rail_state->surface_id)); rdp_debug_verbose(b, "MapSurfaceToWindow(surfaceId:0x%x - windowsId:%x)\n", @@ -2433,13 +2500,13 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU mapSurfaceToScaledWindow = {}; mapSurfaceToScaledWindow.surfaceId = (UINT16)rail_state->surface_id; mapSurfaceToScaledWindow.windowId = window_id; - mapSurfaceToScaledWindow.mappedWidth = contentBufferWidth; - mapSurfaceToScaledWindow.mappedHeight = contentBufferHeight; - mapSurfaceToScaledWindow.targetWidth = newClientPos.width; - mapSurfaceToScaledWindow.targetHeight = newClientPos.height; + mapSurfaceToScaledWindow.mappedWidth = copyBufferWidth; + mapSurfaceToScaledWindow.mappedHeight = copyBufferHeight; + mapSurfaceToScaledWindow.targetWidth = clientBufferWidth; + mapSurfaceToScaledWindow.targetHeight = clientBufferHeight; peerCtx->rail_grfx_server_context->MapSurfaceToScaledWindow(peerCtx->rail_grfx_server_context, &mapSurfaceToScaledWindow); - rail_state->bufferScaleWidth = scaleWidth; - rail_state->bufferScaleHeight = scaleHeight; + rail_state->bufferScaleFactorWidth = scaleFactorWidth; + rail_state->bufferScaleFactorHeight = scaleFactorHeight; } /* destroy old surface */ @@ -3437,36 +3504,43 @@ rdp_rail_dump_window_iter(void *element, void *data) struct weston_surface *surface = (struct weston_surface *)element; struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; struct rdp_rail_dump_window_context *context = (struct rdp_rail_dump_window_context *)data; + struct rdp_backend *b = context->peerCtx->rdpBackend; assert(rail_state); // this iter is looping from window hash table, thus it must have rail_state initialized. FILE *fp = context->fp; char label[256] = {}; + struct weston_geometry windowGeometry = {}; struct weston_view *view; int contentBufferWidth, contentBufferHeight; weston_surface_get_content_size(surface, &contentBufferWidth, &contentBufferHeight); + if (b->rdprail_shell_api && + b->rdprail_shell_api->get_window_geometry) + b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + rdp_rail_dump_window_label(surface, label, sizeof(label)); fprintf(fp," %s\n", label); fprintf(fp," WindowId:0x%x, SurfaceId:0x%x\n", rail_state->window_id, rail_state->surface_id); fprintf(fp," PoolId:0x%x, BufferId:0x%x\n", rail_state->pool_id, rail_state->buffer_id); - fprintf(fp," Position x:%d, y:%d\n", - rail_state->pos.x, rail_state->pos.y); - fprintf(fp," width:%d, height:%d\n", + fprintf(fp," Position x:%d, y:%d width:%d height:%d\n", + rail_state->pos.x, rail_state->pos.y, rail_state->pos.width, rail_state->pos.height); - fprintf(fp," RDP client position x:%d, y:%d\n", - rail_state->clientPos.x, rail_state->clientPos.y); - fprintf(fp," RDP client width:%d, height:%d\n", + fprintf(fp," RDP client position x:%d, y:%d width:%d height:%d\n", + rail_state->clientPos.x, rail_state->clientPos.y, rail_state->clientPos.width, rail_state->clientPos.height); + fprintf(fp," Window geometry x:%d, y:%d, width:%d height:%d\n", + windowGeometry.x, windowGeometry.y, + windowGeometry.width, windowGeometry.height); + fprintf(fp," input extents: x1:%d, y1:%d, x2:%d, y2:%d\n", + surface->input.extents.x1, surface->input.extents.y1, + surface->input.extents.x2, surface->input.extents.y2); fprintf(fp," bufferWidth:%d, bufferHeight:%d\n", rail_state->bufferWidth, rail_state->bufferHeight); - fprintf(fp," bufferScaleWidth:%.2f, bufferScaleHeight:%.2f\n", - rail_state->bufferScaleWidth, rail_state->bufferScaleHeight); + fprintf(fp," bufferScaleFactorWidth:%.2f, bufferScaleFactorHeight:%.2f\n", + rail_state->bufferScaleFactorWidth, rail_state->bufferScaleFactorHeight); fprintf(fp," contentBufferWidth:%d, contentBufferHeight:%d\n", contentBufferWidth, contentBufferHeight); - fprintf(fp," input extents: x1:%d, y1:%d, x2:%d, y2:%d\n", - surface->input.extents.x1, surface->input.extents.y1, - surface->input.extents.x2, surface->input.extents.y2); fprintf(fp," is_opaque:%d\n", surface->is_opaque); if (!surface->is_opaque && pixman_region32_not_empty(&surface->opaque)) { int numRects = 0; @@ -4063,6 +4137,9 @@ rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config b->enable_window_snap_arrange = config->rail_config.enable_window_snap_arrange; rdp_debug(b, "RDP backend: enable_window_snap_arrange = %d\n", b->enable_window_snap_arrange); + b->enable_window_shadow_remoting = config->rail_config.enable_window_shadow_remoting; + rdp_debug(b, "RDP backend: enable_window_shadow_remoting = %d\n", b->enable_window_shadow_remoting); + b->enable_display_power_by_screenupdate = config->rail_config.enable_display_power_by_screenupdate; rdp_debug(b, "RDP backend: enable_display_power_by_screenupdate = %d\n", b->enable_display_power_by_screenupdate); diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 91801cd3b..a278a89bd 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -4417,6 +4417,30 @@ shell_backend_launch_shell_process(void *shell_context, char *exec_name) return weston_client_start(shell->compositor, exec_name); } +static void +shell_backend_get_window_geometry(struct weston_surface *surface, struct weston_geometry *geometry) +{ + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + if (desktop_surface) { + *geometry = weston_desktop_surface_get_geometry(desktop_surface); + /* clamp geometry to surface size */ + if (geometry->x < 0) + geometry->x = 0; + if (geometry->y < 0) + geometry->y = 0; + if (geometry->width > (geometry->x + surface->width)) + geometry->width = (geometry->x + surface->width); + if (geometry->height > (geometry->y + surface->height)) + geometry->height = (geometry->y + surface->height); + } else { + geometry->x = 0; + geometry->y = 0; + geometry->width = surface->width; + geometry->height = surface->height; + } +} + static const struct weston_rdprail_shell_api rdprail_shell_api = { .request_window_restore = shell_backend_request_window_restore, .request_window_minimize = shell_backend_request_window_minimize, @@ -4431,6 +4455,7 @@ static const struct weston_rdprail_shell_api rdprail_shell_api = { .stop_app_list_update = shell_backend_stop_app_list_update, .request_window_icon = shell_backend_request_window_icon, .request_launch_shell_process = shell_backend_launch_shell_process, + .get_window_geometry = shell_backend_get_window_geometry, }; WL_EXPORT int From 2d4c61ec3f4384f1a674d8e3323b4e16d059e341 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 10 May 2022 09:20:12 -0700 Subject: [PATCH 1522/1642] need to check content buffer size (#76) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdprail.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 4d5819dda..747716390 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2134,7 +2134,8 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter copyBufferStride = copyBufferWidth * bufferBpp; copyBufferSize = ((copyBufferStride * copyBufferHeight) + page_size - 1) & ~(page_size - 1); - if (copyBufferWidth && copyBufferHeight) { + if (contentBufferWidth && contentBufferHeight && + copyBufferWidth && copyBufferHeight) { #ifdef HAVE_FREERDP_GFXREDIR_H if (b->use_gfxredir) { scaleFactorWidth = 1.0f; // scaling is done by client. @@ -2306,7 +2307,11 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter copyDamageWidth, copyDamageHeight, damageBox.x1, damageBox.y1, damageWidth, damageHeight, false /* y-flip */, true /* is_argb */) < 0) { - rdp_debug_error(b, "weston_surface_copy_content failed for windowId:0x%x\n",window_id); + rdp_debug_error(b, + "weston_surface_copy_content failed for windowId:0x%x, copyBuffer:%dx%d %d, damage:(%d,%d) %dx%d, content:%dx%d\n", + window_id, copyDamageWidth, copyDamageHeight, copyBufferSize, + damageBox.x1, damageBox.y1, damageWidth, damageHeight, + contentBufferWidth, contentBufferHeight); return -1; } @@ -2382,7 +2387,9 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter data, damageSize, 0, 0, 0, damageBox.x1, damageBox.y1, damageWidth, damageHeight, false /* y-flip */, true /* is_argb */) < 0) { - rdp_debug_error(b, "weston_surface_copy_content failed for cursor shape\n"); + rdp_debug_error(b, + "weston_surface_copy_content failed for windowId:0x%x, damageSize:%d, damage:(%d,%d) %dx%d, content:%dx%d\n", + window_id, damageSize, damageBox.x1, damageBox.y1, damageWidth, damageHeight, contentBufferWidth, contentBufferHeight); free(data); free(alpha); return -1; From 6eebe8febc213ba5034737e11c5ab36b3f0bbacc Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 10 May 2022 09:20:28 -0700 Subject: [PATCH 1523/1642] use glib's keyfile to parse desktop file (#75) * use glib's keyfile to parse desktop file * remove icon_file local variable * add debug message and adjust code format Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 265 ++++++++++++++++---------------------- rdprail-shell/meson.build | 5 + 2 files changed, 119 insertions(+), 151 deletions(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index 99f23d0a1..ec533aed5 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -50,7 +50,11 @@ #include "shell.h" #include "shared/helpers.h" -#ifdef HAVE_WINPR +#if HAVE_GLIB +#include +#endif + +#if HAVE_WINPR #include #include #include @@ -58,6 +62,11 @@ #include #include #include +#endif + +#define GROUP_DESKTOP_ENTRY "Desktop Entry" + +#if HAVE_GLIB && HAVE_WINPR #define NUM_CONTROL_EVENT 5 @@ -345,7 +354,7 @@ send_app_entry(struct desktop_shell *shell, char *key, struct app_entry *entry, pixman_image_unref(app_list_data.appIcon); } -static char * +static void trim_command_exec(char *s) { /* https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html @@ -359,47 +368,6 @@ trim_command_exec(char *s) *(p+1) == 'F' || *(p+1) == 'U') *p = '\0'; } - return s; -} - -static int -app_list_config_section_get_string_by_language_id( - struct weston_config_section *section, - const char *base_key, - char **value, - const char *default_value, - const char *lang_id) -{ - char key[32]; - char *patch; - - if (lang_id && *lang_id != '\0') { - /* append language code and contry code to base key, such as Key[zh_TW] */ - copy_string(key, sizeof key, base_key); - append_string(key, sizeof key, "["); - append_string(key, sizeof key, lang_id); - append_string(key, sizeof key, "]"); - - /* first, try language code and country code, such as Key[zh_CN] */ - if (weston_config_section_get_string(section, - key, value, default_value) == 0) - return 0; - - /* second, try language code only, such as Key[ja] */ - if (strchr(lang_id, '_')) { - patch = strrchr(key, '_'); - assert(patch); - *patch++ = ']'; - *patch = '\0'; - if (weston_config_section_get_string(section, - key, value, default_value) == 0) - return 0; - } - } - - /* finally try with base key only without language/country code */ - return weston_config_section_get_string(section, - base_key, value, default_value); } static bool @@ -407,122 +375,117 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr { struct app_list_context *context = (struct app_list_context *)shell->app_list_context; char *lang_id = context->lang_info.currentClientLanguageId; - struct weston_config *config; - struct weston_config_section *section; char *s; - char *icon_file; - bool is_terminal, is_no_display; + GKeyFile *key_file; + + key_file = g_key_file_new(); + if (!key_file) + return false; entry->shell = shell; entry->file = strdup(file); if (!entry->file) - return false; + goto exit; attach_app_list_namespace(shell); - config = weston_config_parse(file); + if (!g_key_file_load_from_file(key_file, file, G_KEY_FILE_NONE, NULL)) { + shell_rdp_debug(shell, "desktop file: %s is failed to be loaded\n", entry->file); + detach_app_list_namespace(shell); + goto exit; + } detach_app_list_namespace(shell); - if (config) { - section = weston_config_get_section(config, - "Desktop Entry", NULL, NULL); - if (!section) - return false; - if (weston_config_section_get_string(section, - "Type", &s, NULL) == 0) { - if (strcmp(s, "Application") != 0) { - shell_rdp_debug(shell, "desktop file: %s is not app (%s)\n", entry->file, s); - free(s); - return false; // not application. - } - free(s); - } - if (weston_config_section_get_bool(section, - "NoDisplay", &is_no_display, false) == 0) { - if (is_no_display) { - shell_rdp_debug(shell, "desktop file: %s has NoDisplay specified\n", entry->file); - return false; // terminal based app is not included. - } - } - if (weston_config_section_get_bool(section, - "Terminal", &is_terminal, false) == 0) { - if (is_terminal) { - shell_rdp_debug(shell, "desktop file: %s is terminal based app\n", entry->file); - return false; // terminal based app is not included. - } - } - /*TODO: OnlyShowIn/NotShowIn support for WSL environment. */ - /* Need $XDG_CURRENT_DESKTOP keyword for WSL GUI environment */ - if (weston_config_section_get_string(section, - "OnlyShowIn", &s, NULL) == 0) { - shell_rdp_debug(shell, "desktop file: %s has OnlyShowIn %s\n", entry->file, s); + if (!g_key_file_has_group(key_file, GROUP_DESKTOP_ENTRY)) { + shell_rdp_debug(shell, "desktop file: %s is missing %s section\n", + entry->file, GROUP_DESKTOP_ENTRY); + goto exit; + } + + if (g_key_file_get_boolean(key_file, GROUP_DESKTOP_ENTRY, "Hidden", NULL)) { + shell_rdp_debug(shell, "desktop file: %s is hidden\n", + entry->file); + goto exit; + } + + s = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "Type", NULL); + if (s) { + if (strcmp(s, "Application") != 0) { + shell_rdp_debug(shell, "desktop file: %s is not app (%s)\n", entry->file, s); free(s); - return false; // terminal based app is not included. - } - if (app_list_config_section_get_string_by_language_id(section, - "Name", &s, NULL, lang_id) == 0) { - if (shell->is_appid_with_distro_name) { - char *t; - size_t len = strlen(s); - /* 4 = ' ' + '(' + ')' + null */ - len += (4 + shell->distroNameLength); - t = zalloc(len); - if (t) { - copy_string(t, len, s); - append_string(t, len, " ("); - append_string(t, len, shell->distroName); - append_string(t, len, ")"); - entry->name = t; - free(s); - } else { - entry->name = s; - } - } else { - entry->name = s; - } - } else { - /* name is required */ - return false; - } - if (weston_config_section_get_string(section, - "Exec", &s, NULL) == 0) { - entry->exec = trim_command_exec(s); - } else { - /* exec is required */ - return false; - } - if (weston_config_section_get_string(section, - "TryExec", &s, NULL) == 0) { - entry->try_exec = trim_command_exec(s); + goto exit; } - if (weston_config_section_get_string(section, - "Path", &s, NULL) == 0) { - entry->working_dir = s; + free(s); + } + if (g_key_file_get_boolean(key_file, GROUP_DESKTOP_ENTRY, "NoDisplay", NULL)) { + shell_rdp_debug(shell, "desktop file: %s has NoDisplay specified\n", entry->file); + goto exit; // NoDisplay app is not included. + } + if (g_key_file_get_boolean(key_file, GROUP_DESKTOP_ENTRY, "Terminal", NULL)) { + shell_rdp_debug(shell, "desktop file: %s is terminal based app\n", entry->file); + goto exit; // terminal based app is not included. + } + /*TODO: OnlyShowIn/NotShowIn support for WSL environment. */ + /* Need $XDG_CURRENT_DESKTOP keyword for WSL GUI environment */ + s = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "OnlyShowIn", NULL); + if (s) { + shell_rdp_debug(shell, "desktop file: %s has OnlyShowIn %s\n", entry->file, s); + free(s); + goto exit; + } + entry->name = g_key_file_get_locale_string(key_file, GROUP_DESKTOP_ENTRY, "Name", lang_id, NULL); + if (!entry->name) { + /* name is required */ + shell_rdp_debug(shell, "desktop file: %s is missing Name key\n", entry->file); + goto exit; + } + if (shell->is_appid_with_distro_name) { + char *t; + size_t len = strlen(entry->name); + /* 4 = ' ' + '(' + ')' + null */ + len += (4 + shell->distroNameLength); + t = zalloc(len); + if (t) { + copy_string(t, len, entry->name); + append_string(t, len, " ("); + append_string(t, len, shell->distroName); + append_string(t, len, ")"); + free(entry->name); + entry->name = t; } - if (weston_config_section_get_string(section, - "Icon", &s, NULL) == 0) { - entry->icon = s; - - attach_app_list_namespace(shell); - icon_file = find_icon_file(s); - detach_app_list_namespace(shell); + } + entry->exec = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "Exec", NULL); + if (!entry->exec) { + shell_rdp_debug(shell, "desktop file: %s is missing Exec key\n", entry->file); + goto exit; + } + trim_command_exec(entry->exec); + entry->try_exec = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "TryExec", NULL); + if (entry->try_exec) + trim_command_exec(entry->try_exec); + entry->working_dir = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "Path", NULL); + entry->icon = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "Icon", NULL); + if (entry->icon) { + attach_app_list_namespace(shell); + entry->icon_file = find_icon_file(entry->icon); + detach_app_list_namespace(shell); + } + g_key_file_free(key_file); - if (icon_file) - entry->icon_file = icon_file; - } - weston_config_destroy(config); + shell_rdp_debug(shell, "desktop file: %s\n", entry->file); + shell_rdp_debug(shell, " Name[%s]:%s\n", lang_id, entry->name); + shell_rdp_debug(shell, " Exec:%s\n", entry->exec); + shell_rdp_debug(shell, " TryExec:%s\n", entry->try_exec); + shell_rdp_debug(shell, " WorkingDir:%s\n", entry->working_dir); + shell_rdp_debug(shell, " Icon name:%s\n", entry->icon); + shell_rdp_debug(shell, " Icon file:%s\n", entry->icon_file); - shell_rdp_debug(shell, "desktop file: %s\n", entry->file); - shell_rdp_debug(shell, " Name[%s]:%s\n", lang_id, entry->name); - shell_rdp_debug(shell, " Exec:%s\n", entry->exec); - shell_rdp_debug(shell, " TryExec:%s\n", entry->try_exec); - shell_rdp_debug(shell, " WorkingDir:%s\n", entry->working_dir); - shell_rdp_debug(shell, " Icon name:%s\n", entry->icon); - shell_rdp_debug(shell, " Icon file:%s\n", entry->icon_file); + return true; - return true; - } +exit: + g_key_file_free(key_file); + /* caller will clean up partially filled entry upon returning false */ return false; } @@ -1181,11 +1144,11 @@ stop_app_list_monitor(struct desktop_shell *shell) assert(context->weston_pidfd <= 0); assert(context->app_list_pidfd <= 0); } -#endif // HAVE_WINPR +#endif // HAVE_WINPR && HAVE_GLIB pixman_image_t* app_list_load_icon_file(struct desktop_shell *shell, const char *key) { -#ifdef HAVE_WINPR +#if HAVE_WINPR && HAVE_GLIB struct app_list_context *context = (struct app_list_context *)shell->app_list_context; pixman_image_t* image = NULL; @@ -1213,7 +1176,7 @@ pixman_image_t* app_list_load_icon_file(struct desktop_shell *shell, const char void app_list_find_image_name(struct desktop_shell *shell, pid_t pid, char *image_name, size_t image_name_size, bool is_wayland) { -#ifdef HAVE_WINPR +#if HAVE_WINPR && HAVE_GLIB struct app_list_context *context = (struct app_list_context *)shell->app_list_context; if (context) { @@ -1242,7 +1205,7 @@ void app_list_find_image_name(struct desktop_shell *shell, pid_t pid, char *imag bool app_list_start_backend_update(struct desktop_shell *shell, char *clientLanguageId) { -#ifdef HAVE_WINPR +#if HAVE_WINPR && HAVE_GLIB struct app_list_context *context = (struct app_list_context *)shell->app_list_context; if (context) { if (!clientLanguageId || *clientLanguageId == '\0') @@ -1259,7 +1222,7 @@ bool app_list_start_backend_update(struct desktop_shell *shell, char *clientLang void app_list_stop_backend_update(struct desktop_shell *shell) { -#ifdef HAVE_WINPR +#if HAVE_WINPR && HAVE_GLIB struct app_list_context *context = (struct app_list_context *)shell->app_list_context; if (context) { SetEvent(context->stopRdpNotifyEvent); @@ -1272,7 +1235,7 @@ void app_list_stop_backend_update(struct desktop_shell *shell) void app_list_init(struct desktop_shell *shell) { -#ifdef HAVE_WINPR +#if HAVE_WINPR && HAVE_GLIB struct app_list_context *context; wHashTable* table; #if WINPR_VERSION_MAJOR >= 3 @@ -1329,12 +1292,12 @@ void app_list_init(struct desktop_shell *shell) start_app_list_monitor(shell); #else shell->app_list_context = NULL; -#endif // HAVE_WINPR +#endif // HAVE_WINPR && HAVE_GLIB } void app_list_destroy(struct desktop_shell *shell) { -#ifdef HAVE_WINPR +#if HAVE_WINPR && HAVE_GLIB struct app_list_context *context = (struct app_list_context *)shell->app_list_context; wHashTable* table; int count; @@ -1360,5 +1323,5 @@ void app_list_destroy(struct desktop_shell *shell) } #else assert(!shell->app_list_context); -#endif // HAVE_WINPR +#endif // HAVE_WINPR && HAVE_GLIB } diff --git a/rdprail-shell/meson.build b/rdprail-shell/meson.build index 82a221ab0..004ed361e 100644 --- a/rdprail-shell/meson.build +++ b/rdprail-shell/meson.build @@ -12,6 +12,10 @@ if get_option('shell-rdprail') if dep_librsvg.found() config_h.set('HAVE_LIBRSVG2', '1') endif + dep_glib = dependency('glib-2.0', version : '>= 2.0.0', required: false) + if dep_glib.found() + config_h.set('HAVE_GLIB', 1) + endif srcs_shell_rdprail = [ 'shell.c', 'input-panel.c', @@ -30,6 +34,7 @@ if get_option('shell-rdprail') dep_libweston_public, dep_librsvg, dep_winpr, + dep_glib, ] plugin_shell_rdprail = shared_library( 'rdprail-shell', From 16794c5a08225b93207aa67a4037e61d72ee6f70 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 11 May 2022 15:02:08 -0700 Subject: [PATCH 1524/1642] optimize remove/add event source when trasnfering clipboard data from client to server (#77) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdpclip.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index 48bb9997b..f9769d6b6 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -938,8 +938,6 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) freerdp_peer *client = (freerdp_peer *) source->context; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - struct weston_seat *seat = peerCtx->item.seat; - struct wl_event_loop *loop = wl_display_get_event_loop(seat->compositor->wl_display); void *data_to_write; size_t data_size; ssize_t size; @@ -953,10 +951,6 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) /* this data source must be tracked as inflight */ assert(source == peerCtx->clipboard_inflight_client_data_source); - /* remove event source now, and if write is failed with EAGAIN, queue back to display loop. */ - wl_event_source_remove(source->transfer_event_source); - source->transfer_event_source = NULL; - if (source->is_canceled) { /* if source is being canceled, this must be the last reference */ assert(source->refcount == 1); @@ -989,17 +983,10 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) __func__, source, clipboard_data_source_state_to_string(source), strerror(errno)); break; } - /* buffer is full, schedule write for next chunk. */ + /* buffer is full, wait until data_source_fd is writable again */ source->inflight_data_to_write = data_to_write; source->inflight_data_size = data_size; source->inflight_write_count++; - if (!rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, - clipboard_data_source_write, source, &source->transfer_event_source)) { - source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s (%p:%s) rdp_event_loop_add_fd failed\n", - __func__, source, clipboard_data_source_state_to_string(source)); - break; - } return 0; } else { assert(data_size >= (size_t)size); @@ -1020,8 +1007,13 @@ clipboard_data_source_write(int fd, uint32_t mask, void *arg) __func__, source, clipboard_data_source_state_to_string(source)); } + /* Here write is either completed, cancelled or failed, thus close pipe. */ close(source->data_source_fd); source->data_source_fd = -1; + /* and remove event source. */ + wl_event_source_remove(source->transfer_event_source); + source->transfer_event_source = NULL; + /* and reset the inflight transfer state. */ source->inflight_write_count = 0; source->inflight_data_to_write = NULL; source->inflight_data_size = 0; From b54b474f4507a214c13614c7a0ed9c5e3ff2532e Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 12 May 2022 15:49:01 -0700 Subject: [PATCH 1525/1642] clean up 'todo' coordinate conversion to client space for local window move. (#78) * clean up 'todo' for localmove client coordinate conversion * no need to adjust by input extent Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.h | 14 ------- libweston/backend-rdp/rdprail.c | 73 ++++++++++++++++++++++++++------- rdprail-shell/shell.c | 15 ++++--- 3 files changed, 69 insertions(+), 33 deletions(-) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index fe2796fc2..ed9caed69 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -540,20 +540,6 @@ to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32 return NULL; } -/* TO BE REMOVED */ -static inline int32_t -to_client_x(RdpPeerContext *peer, int32_t x) -{ - return x + peer->regionClientHeads.extents.x1; -} - -/* TO BE REMOVED */ -static inline int32_t -to_client_y(RdpPeerContext *peer, int32_t y) -{ - return y + peer->regionClientHeads.extents.y1; -} - static inline void to_client_scale_only(RdpPeerContext *peer, struct weston_output *output, float scale, int *x, int *y) { diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 747716390..dad7424b2 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -389,7 +389,7 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) pixman_rectangle32_t windowMoveRect; struct weston_geometry windowGeometry; - rdp_debug(b, "Client: WindowMove: WindowId:0x0x at (%d, %d) %dx%d\n", + rdp_debug(b, "Client: WindowMove: WindowId:0x%x at (%d,%d) %dx%d\n", windowMove->windowId, windowMove->left, windowMove->top, @@ -426,7 +426,6 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) windowMoveRect.y, windowMoveRect.width, windowMoveRect.height); - rdp_debug(b, "Surface Size (%d, %d)\n", surface->width, surface->height); } } @@ -1356,7 +1355,7 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) /* windowId can be assigned only after activation completed */ if (!rdp_id_manager_allocate_id(&peerCtx->windowId, (void*)surface, &window_id)) { rail_state->error = true; - rdp_debug_error(b, "CreateWindow(): fail to insert windowId (windowId:%d surface:%p.\n", + rdp_debug_error(b, "CreateWindow(): fail to insert windowId (windowId:0x%x surface:%p).\n", window_id, surface); return; } @@ -3099,6 +3098,7 @@ rdp_rail_start_window_move( { struct weston_compositor *compositor = surface->compositor; struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct weston_geometry windowGeometry = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; struct rdp_backend *b = (struct rdp_backend*)compositor->backend; RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context;; RAIL_MINMAXINFO_ORDER minmax_order; @@ -3125,11 +3125,27 @@ rdp_rail_start_window_move( rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); } - /* TODO: HI-DPI MULTIMON */ + if (!b->enable_window_shadow_remoting && + b->rdprail_shell_api && + b->rdprail_shell_api->get_window_geometry) { + b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + posX += windowGeometry.x; + posY += windowGeometry.y; + } + + /* apply global to output transform, and translate to client coordinate */ + if (surface->output) { + to_client_coordinate(peerCtx, surface->output, + &posX, &posY, &minSize.width, &minSize.height); + to_client_coordinate(peerCtx, surface->output, + &pointerGrabX, &pointerGrabY, &maxSize.width, &maxSize.height); + } rdp_debug(b, "====================== StartWindowMove =============================\n"); - rdp_debug(b, "WindowsPosition - Pre-move (%d, %d, %d, %d).\n", - to_client_x(peerCtx, posX), to_client_y(peerCtx, posY), surface->width, surface->height); + rdp_debug(b, "WindowsPosition: Pre-move (%d,%d) at client.\n", posX, posY); + rdp_debug(b, "pointerGrab: (%d,%d)\n", pointerGrabX, pointerGrabY); + rdp_debug(b, "minSize: (%dx%d)\n", minSize.width, minSize.height); + rdp_debug(b, "maxSize: (%dx%d)\n", maxSize.width, maxSize.height); /* Inform the RDP client about the minimum/maximum width and height allowed * on this window. @@ -3144,11 +3160,12 @@ rdp_rail_start_window_move( minmax_order.maxTrackWidth = maxSize.width; minmax_order.maxTrackHeight = maxSize.height; - rdp_debug(b, "maxPosX: %d, maxPosY: %d, maxWidth: %d, maxHeight: %d, minTrackWidth: %d, minTrackHeight: %d, maxTrackWidth: %d, maxTrackHeight: %d\n", + rdp_debug(b, "Minmax order: maxPosX:%d, maxPosY:%d, maxWidth:%d, maxHeight:%d\n", minmax_order.maxPosX, minmax_order.maxPosY, minmax_order.maxWidth, - minmax_order.maxHeight, + minmax_order.maxHeight); + rdp_debug(b, "Minmax order: minTrackWidth:%d, minTrackHeight:%d, maxTrackWidth:%d, maxTrackHeight:%d\n", minmax_order.minTrackWidth, minmax_order.minTrackHeight, minmax_order.maxTrackWidth, @@ -3165,10 +3182,17 @@ rdp_rail_start_window_move( move_order.posX = pointerGrabX - posX; move_order.posY = pointerGrabY - posY; - rdp_debug(b, "posX: %d, posY: %d \n", move_order.posX, move_order.posY); + rdp_debug(b, "Move order: windowId:0x%x, isMoveSizeStart:%d, moveType:%d, pos:(%d,%d)\n", + move_order.windowId, + move_order.isMoveSizeStart, + move_order.moveSizeType, + move_order.posX, + move_order.posY); peerCtx->rail_server_context->ServerLocalMoveSize( peerCtx->rail_server_context, &move_order); + + rdp_debug(b, "====================== StartWindowMove =============================\n"); } void @@ -3176,6 +3200,7 @@ rdp_rail_end_window_move(struct weston_surface* surface) { struct weston_compositor *compositor = surface->compositor; struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct weston_geometry windowGeometry = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; struct rdp_backend *b = (struct rdp_backend*)compositor->backend; RdpPeerContext *peerCtx = NULL; RAIL_LOCALMOVESIZE_ORDER move_order; @@ -3194,8 +3219,8 @@ rdp_rail_end_window_move(struct weston_surface* surface) struct weston_view *view; wl_list_for_each(view, &surface->views, surface_link) { numViews++; - posX = to_client_x(peerCtx, view->geometry.x); - posY = to_client_y(peerCtx, view->geometry.y); + posX = view->geometry.x; + posY = view->geometry.y; break; } if (numViews == 0) { @@ -3203,18 +3228,38 @@ rdp_rail_end_window_move(struct weston_surface* surface) rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); } - /* TODO: HI-DPI MULTIMON */ + if (!b->enable_window_shadow_remoting && + b->rdprail_shell_api && + b->rdprail_shell_api->get_window_geometry) { + b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + posX += windowGeometry.x; + posY += windowGeometry.y; + } + + /* apply global to output transform, and translate to client coordinate */ + if (surface->output) + to_client_coordinate(peerCtx, surface->output, + &posX, &posY, NULL, NULL); + + rdp_debug(b, "====================== EndWindowMove =============================\n"); + rdp_debug(b, "WindowsPosition: Post-move (%d,%d) at client.\n", posX, posY); move_order.windowId = rail_state->window_id; move_order.isMoveSizeStart = false; move_order.moveSizeType = RAIL_WMSZ_MOVE; move_order.posX = posX; - move_order.posY = posY; + move_order.posY = posY; + + rdp_debug(b, "Move order: windowId:0x%x, isMoveSizeStart:%d, moveType:%d, pos:(%d,%d)\n", + move_order.windowId, + move_order.isMoveSizeStart, + move_order.moveSizeType, + move_order.posX, + move_order.posY); peerCtx->rail_server_context->ServerLocalMoveSize( peerCtx->rail_server_context, &move_order); - rdp_debug(b, "WindowsPosition - Post-move (%d, %d, %d, %d).\n", posX, posY, surface->width, surface->height); rdp_debug(b, "====================== EndWindowMove =============================\n"); } diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index a278a89bd..b4ae5a16b 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -2744,8 +2744,14 @@ shell_backend_request_window_move(struct weston_surface *surface, int x, int y, } assert(!shsurf->snapped.is_maximized_requested); + + if (surface->width != width || surface->height != height) { + //TODO: support window resize (width x height) + shell_rdp_debug(shsurf->shell, "%s: surface:%p is resized (%dx%d) -> (%d,%d)\n", + __func__, surface, surface->width, surface->height, width, height); + } + weston_view_set_position(view, x, y); - //TODO: support window resize (width x height) } static void @@ -2810,11 +2816,10 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, height = min_size.height; else if (max_size.width > 0 && width > max_size.width) width = max_size.width; - - weston_desktop_surface_set_size(desktop_surface, width, height); - x -= surface->input.extents.x1; - y -= surface->input.extents.y1; + shell_rdp_debug(shsurf->shell, "%s: surface:%p is resized (%dx%d) -> (%d,%d)\n", + __func__, surface, surface->width, surface->height, width, height); + weston_desktop_surface_set_size(desktop_surface, width, height); } weston_view_set_position(view, x, y); From cf4bbc8cbadadae0cd0fdb27a5004fef89cf90f6 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 13 May 2022 11:31:53 -0700 Subject: [PATCH 1526/1642] Check height instead of checking width a second time (#79) Co-authored-by: Hideyuki Nagase --- desktop-shell/shell.c | 4 ++-- rdprail-shell/shell.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index cf85cbc6f..03d3e0f61 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -1648,8 +1648,8 @@ resize_grab_motion(struct weston_pointer_grab *grab, width = max_size.width; if (height < min_size.height) height = min_size.height; - else if (max_size.width > 0 && width > max_size.width) - width = max_size.width; + else if (max_size.height > 0 && height > max_size.height) + height = max_size.height; weston_desktop_surface_set_size(shsurf->desktop_surface, width, height); } diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index b4ae5a16b..901b67f68 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -1291,8 +1291,8 @@ resize_grab_motion(struct weston_pointer_grab *grab, width = max_size.width; if (height < min_size.height) height = min_size.height; - else if (max_size.width > 0 && width > max_size.width) - width = max_size.width; + else if (max_size.height > 0 && height > max_size.height) + height = max_size.height; weston_desktop_surface_set_size(shsurf->desktop_surface, width, height); } @@ -2814,8 +2814,8 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, width = max_size.width; if (height < min_size.height) height = min_size.height; - else if (max_size.width > 0 && width > max_size.width) - width = max_size.width; + else if (max_size.height > 0 && height > max_size.height) + height = max_size.height; shell_rdp_debug(shsurf->shell, "%s: surface:%p is resized (%dx%d) -> (%d,%d)\n", __func__, surface, surface->width, surface->height, width, height); From b30541ea5c6e0b47de3633b528e128ebe5efb995 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 13 May 2022 13:53:57 -0700 Subject: [PATCH 1527/1642] weston_desktop_surface_set_size expects window geometry coordinates (#80) Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 901b67f68..3d17be4bd 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -1824,7 +1824,7 @@ set_unsnap(struct shell_surface *shsurf, int grabX, int grabY) { if (!shsurf->snapped.is_snapped) return; - + /* * Reposition the window such that the mouse remain within the * new bound of the window after resize. @@ -1832,8 +1832,8 @@ set_unsnap(struct shell_surface *shsurf, int grabX, int grabY) /* Need to fix RDP event processing while doing a local move first otherwise this undo the move! if (grabX - shsurf->view->geometry.x > shsurf->snapped.saved_width) { weston_view_set_position(shsurf->view, grabX - shsurf->snapped.saved_width/2, shsurf->view->geometry.y); - } - + } + weston_desktop_surface_set_size(shsurf->desktop_surface, shsurf->snapped.saved_width, shsurf->snapped.saved_height);*/ shsurf->snapped.is_snapped = false; } @@ -2421,7 +2421,7 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, if (shsurf->resize_edges) { sx = 0; sy = 0; - + if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_LEFT) sx = shsurf->last_width - surface->width; if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_TOP) @@ -2752,6 +2752,9 @@ shell_backend_request_window_move(struct weston_surface *surface, int x, int y, } weston_view_set_position(view, x, y); + + shell_rdp_debug(shsurf->shell, "%s: surface:%p is moved to (%d,%d) %dx%d\n", + __func__, surface, x, y, width, height); } static void @@ -2790,20 +2793,24 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, shell_backend_request_window_maximize(surface); return; - } + } if (!shsurf->snapped.is_snapped) { shsurf->snapped.saved_width = surface->width; shsurf->snapped.saved_height = surface->height; } shsurf->snapped.is_snapped = true; - + if (surface->width != width || surface->height != height) { struct weston_desktop_surface *desktop_surface = weston_surface_get_desktop_surface(surface); struct weston_size max_size = weston_desktop_surface_get_max_size(desktop_surface); struct weston_size min_size = weston_desktop_surface_get_min_size(desktop_surface); + struct weston_geometry geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + /* weston_desktop_surface_set_size() expects the size in window geometry coordinates */ + width -= (surface->width - geometry.width); + height -= (surface->height - geometry.height); min_size.width = MAX(1, min_size.width); min_size.height = MAX(1, min_size.height); @@ -2826,8 +2833,11 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, shsurf->snapped.x = x; shsurf->snapped.y = y; - shsurf->snapped.width = width; - shsurf->snapped.height = height; + shsurf->snapped.width = width; // save width in window geometry coordinates. + shsurf->snapped.height = height; // save height in window geometry coordinates. + + shell_rdp_debug(shsurf->shell, "%s: surface:%p is snapped at (%d,%d) %dx%d\n", + __func__, surface, x, y, width, height); } static void From 4e99bb16dd44fc6daf4a45c46ba3e053c0637afa Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 10 Jun 2022 07:48:40 -0700 Subject: [PATCH 1528/1642] send window zorder list before window update (#84) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdprail.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index dad7424b2..9b501b23b 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2665,6 +2665,12 @@ rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage) RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; if (peerCtx->isAcknowledgedSuspended || ((peerCtx->currentFrameId - peerCtx->acknowledgedFrameId) < 2)) { + /* notify window z order to client first, + mstsc/msrdc needs this to be sent before window update. */ + if (peerCtx->is_window_zorder_dirty) { + rdp_rail_sync_window_zorder(b->compositor); + peerCtx->is_window_zorder_dirty = false; + } rdp_debug_verbose(b, "currentFrameId:0x%x, acknowledgedFrameId:0x%x, isAcknowledgedSuspended:%d\n", peerCtx->currentFrameId, peerCtx->acknowledgedFrameId, peerCtx->isAcknowledgedSuspended); struct update_window_iter_data iter_data = {}; @@ -2677,11 +2683,6 @@ rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage) rdp_debug_verbose(b, "EndFrame(frameId:0x%x)\n", endFrame.frameId); peerCtx->rail_grfx_server_context->EndFrame(peerCtx->rail_grfx_server_context, &endFrame); } - if (peerCtx->is_window_zorder_dirty) { - /* notify window z order to client */ - rdp_rail_sync_window_zorder(b->compositor); - peerCtx->is_window_zorder_dirty = false; - } if (iter_data.isUpdatePending && b->enable_display_power_by_screenupdate) { /* By default, compositor won't update idle timer by screen activity, thus, here manually call wake function to postpone idle timer when From b91e7ff432af602145d216b17d3320b3345f98c5 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 13 Jun 2022 17:05:05 -0500 Subject: [PATCH 1529/1642] gl-renderer: Fix assert (#81) Ensure both target dimensions match. Signed-off-by: Derek Foreman --- libweston/renderer-gl/gl-renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 22992e4a6..7240c2111 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -2720,7 +2720,7 @@ gl_renderer_surface_copy_content(struct weston_surface *surface, { /*TODO:add scaling support*/ assert(target_width == width); - assert(target_width == height); + assert(target_height == height); static const GLfloat verts[4 * 2] = { 0.0f, 0.0f, From 5046e0b8f52728369dc0070019ad3ff651d6d7eb Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 22 Jun 2022 11:23:33 -0700 Subject: [PATCH 1530/1642] fix window doesn't redraw properly moving across different DPI monitors (#85) Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 1 + libweston/backend-rdp/rdprail.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index 8a060fa6e..dc1a812e6 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -214,6 +214,7 @@ struct weston_surface_rail_state { float bufferScaleFactorHeight; pixman_region32_t damage; struct weston_output *output; + int32_t output_scale; struct weston_surface *parent_surface; uint32_t parent_window_id; bool isCursor; diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 9b501b23b..31db61aa0 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -1853,6 +1853,13 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter to_client_coordinate(peerCtx, surface->output, &newClientPos.x, &newClientPos.y, &newClientPos.width, &newClientPos.height); + /* when window move to new output with different scale, refresh all state to client. */ + if (rail_state->output_scale != surface->output->current_scale) { + rail_state->forceUpdateWindowState = true; + rail_state->forceRecreateSurface = true; + rail_state->output_scale = surface->output->current_scale; + } + /* Adjust the Windows size and position on the screen */ if (rail_state->clientPos.x != newClientPos.x || rail_state->clientPos.y != newClientPos.y || From 67acc75f4bdec2d7b546c6d76ece039e7a811fdd Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 22 Jun 2022 13:27:03 -0500 Subject: [PATCH 1531/1642] Clipboard changes (#82) * rdp: Make clipboard html header static Signed-off-by: Derek Foreman * rdp: Make thread checks unconditional Weston policy is that we make sure asserts are light/fast, and we don't make them conditional. The thread checks are lightweight enough that we can run them all the time. Signed-off-by: Derek Foreman * rdp: some style changes Weston coding style disallows camelCaseVariables, and prefers char *foo over char* foo Updating just rdp_clipboard_destroy() in this change as it's small and self contained. Signed-off-by: Derek Foreman * rdp: Prefer stdint datatypes instead of winpr ones Trying to use stdint types for things that don't interface with FreeRDP or winpr Signed-off-by: Derek Foreman * rdp: Reformat switch statement Change to match upstream style. Signed-off-by: Derek Foreman * rdp: More style nitpicks Avoiding camelCaseVariables, using a temporary to try to reduce line length, changing some indenting. Signed-off-by: Derek Foreman * rdp: Remove index from clipboard_supported_formats Since the index is sequential, it's the same as the ordinal position in the array. We can just use that consistently instead. Signed-off-by: Derek Foreman * rdp: Replace peerCtx with ctx in struct rdp_clipboard_data_request Upstream weston doesn't allow camelCase variables Signed-off-by: Derek Foreman * rdp: Rename all uses of peerCtx to ctx Just removing more camelCase usage Signed-off-by: Derek Foreman * rdp: Remove unnecessary returns All of these are at the end of functions that don't return values. Signed-off-by: Derek Foreman * rdp: remove size_of_bmfh variable This isn't really a significant savings over typing sizeof(*bmfh) Signed-off-by: Derek Foreman * rdp: Remove rdp_debug_clipboard_error Weston upstream prefers that anything at an "error" level of severity be output via weston_log Signed-off-by: Derek Foreman * rdp: style changes Lots of nitpicky style changes to make this more closely match upstream coding style. Signed-off-by: Derek Foreman * rdp: Reorder error case to reduce indenting No functional change. Signed-off-by: Derek Foreman * rdp: Remove some unused returns These functions currently only return 0 right now. For upstream weston we removed the return value. If a way to handle errors is determined later we can add the returns then. Signed-off-by: Derek Foreman * rdp: Fix error in debug text This is the case where strdup fails, not wl_array_add Signed-off-by: Derek Foreman * rdp: bail early when zalloc fails in clipboard_client_format_list This lets us drop a level of indenting. Signed-off-by: Derek Foreman * rdp: Simplify clipboard_client_format_list isPublished is always set if source is valid and never set if it isn't, so we can just throw away isPublished and test source directly. This also shows that the clipboard_data_source_unref block can never evaluate to true, so we can remove that too. For future reference, that was the only call site for clipboard_data_source_unref that wasn't from the compositor main thread. Signed-off-by: Derek Foreman * rdp: assert that we always unref data source from the compositor thread We now only unref from the compositor thread, so we can adjust the assertions accordingly. Signed-off-by: Derek Foreman * rdp: Remove misplaced comment This was likely copy and pasted from clipboard_data_source_write, which no longer has this comment. Signed-off-by: Derek Foreman * rdp: Remove stale define Nothing uses this anymore Signed-off-by: Derek Foreman * rdp: Change BOOL to bool for callbacks Not really any strong reason that this is separate from previous style changes, except perhaps that BOOL and bool are incompatible types, so this is closer to being a functional change than the changes in the large omnibus style patch. Signed-off-by: Derek Foreman * rdp: Remove an unnecessary goto This one doesn't really help code readability. Signed-off-by: Derek Foreman * rdp: bail early in clipboard_client_format_data_response This lets us reduce indentation Signed-off-by: Derek Foreman * rdp: Use temp vars to simplify conditionals No functional change. This just stops breaking the if conditions across multiple lines. Signed-off-by: Derek Foreman * rdp: Reindent switch statement Just matching upstream weston coding style. Signed-off-by: Derek Foreman * rdp: use a temp var to reduce some line lengths Signed-off-by: Derek Foreman * rdp: Early return to reduce some indentation Signed-off-by: Derek Foreman * rdp: remove some comments We've removed the commented out code when landing this upstream. Signed-off-by: Derek Foreman * rdp: Rework clipboard_data_source_read Some small changes to reduce a bit of indenting and remove an assert(false) by changing control flow a little bit. Signed-off-by: Derek Foreman * rdp: bail early in clipboard_data_source_write This reduces indenting and subjectively makes the code a little easier to read. Signed-off-by: Derek Foreman * rdp: Check for EINTR when writing clipboard data Use the usual posix paradigm of retrying on EINTR. Signed-off-by: Derek Foreman * rdp: Early returns in clipboard processing functions Once again just reducing a level of indention - though there are a few other style changes here. Some indention changes for multi-line function calls, and removal of typecasting void pointers. Signed-off-by: Derek Foreman * rdp: move variables to top of block While not strictly a weston coding style requirement anymore, this lets us reduce some line lengths a little bit, and the vast majority of weston still declares variables at top of block. Signed-off-by: Derek Foreman * rdp: Put wl_array read from fd into a function This is a pretty unusual use of wl_array, so let's compartmentalize it as much as possible into a function. There's a slight functional change here - we always ensure that there's enough space for a null terminator after the read, but that slack byte will be uninitialized and the consumers of the data may need to set it to a null. We also add EINTR handling to the read. Signed-off-by: Derek Foreman * rdp: Check that we have enough data for a bitmap image header Make sure we can't read off the end of our clipboard data if we don't have a full bitmap image header's worth of data Signed-off-by: Derek Foreman * rdp: Only call clipboard processing functions once If we store the relevant data we can simplify all these functions a little bit because they won't need to be called again to get the data start. They now return bool for success/failure. This is primarily motivated by removing a little bit of trickiness from our wl_array usage, and avoiding manipulating .size directly where we can. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.h | 27 +- libweston/backend-rdp/rdpclip.c | 1794 ++++++++++++++++--------------- libweston/backend-rdp/rdpdisp.c | 12 +- libweston/backend-rdp/rdprail.c | 54 +- libweston/backend-rdp/rdputil.c | 56 +- 5 files changed, 1017 insertions(+), 926 deletions(-) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index ed9caed69..05e3714e8 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -332,16 +332,6 @@ struct rdp_loop_task { #define RDP_RAIL_MARKER_WINDOW_ID 0xFFFFFFFE #define RDP_RAIL_DESKTOP_WINDOW_ID 0xFFFFFFFF -#define ENABLE_RDP_THREAD_CHECK - -#ifdef ENABLE_RDP_THREAD_CHECK -#define ASSERT_COMPOSITOR_THREAD(b) assert_compositor_thread(b) -#define ASSERT_NOT_COMPOSITOR_THREAD(b) assert_not_compositor_thread(b) -#else -#define ASSERT_COMPOSITOR_THREAD(b) -#define ASSERT_NOT_COMPOSITOR_THREAD(b) -#endif // ENABLE_RDP_THREAD_CHECK - #define RDP_DEBUG_LEVEL_NONE 0 #define RDP_DEBUG_LEVEL_ERR 1 #define RDP_DEBUG_LEVEL_WARN 2 @@ -383,9 +373,6 @@ struct rdp_loop_task { #define rdp_debug_clipboard_continue(b, ...) \ if (b->debugClipboardLevel >= RDP_DEBUG_LEVEL_INFO) \ rdp_debug_print(b->debugClipboard, true, __VA_ARGS__) -#define rdp_debug_clipboard_error(b, ...) \ - if (b->debugClipboardLevel >= RDP_DEBUG_LEVEL_ERR) \ - rdp_debug_print(b->debugClipboard, false, __VA_ARGS__) /* To enable rdp_debug message, add "--logger-scopes=rdp-backend". */ @@ -397,10 +384,16 @@ void rdp_head_destroy(struct weston_compositor *compositor, struct rdp_head *hea // rdputil.c pid_t rdp_get_tid(void); void rdp_debug_print(struct weston_log_scope *log_scope, bool cont, char *fmt, ...); -#ifdef ENABLE_RDP_THREAD_CHECK -void assert_compositor_thread(struct rdp_backend *b); -void assert_not_compositor_thread(struct rdp_backend *b); -#endif // ENABLE_RDP_THREAD_CHECK + +int +rdp_wl_array_read_fd(struct wl_array *array, int fd); + +void +assert_compositor_thread(struct rdp_backend *b); + +void +assert_not_compositor_thread(struct rdp_backend *b); + #ifdef HAVE_FREERDP_GFXREDIR_H BOOL rdp_allocate_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory); void rdp_free_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *shared_memory); diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index f9769d6b6..f14cd1681 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -39,67 +39,72 @@ #include "libweston-internal.h" -#define RDP_INVALID_EVENT_SOURCE ((void*)(-1)) - /* From MSDN, RegisterClipboardFormat API. Registered clipboard formats are identified by values in the range 0xC000 through 0xFFFF. */ -#define CF_PRIVATE_RTF 49309 // fake format ID for "Rich Text Format". -#define CF_PRIVATE_HTML 49405 // fake format ID for "HTML Format". +#define CF_PRIVATE_RTF 49309 /* fake format ID for "Rich Text Format". */ +#define CF_PRIVATE_HTML 49405 /* fake format ID for "HTML Format".*/ - /* 1 2 3 4 5 6 7 8 */ - /*01234567890 1 2345678901234 5 67890123456 7 89012345678901234567890 1 234567890123456789012 3 4*/ -char rdp_clipboard_html_header[] = "Version:0.9\r\nStartHTML:-1\r\nEndHTML:-1\r\nStartFragment:00000000\r\nEndFragment:00000000\r\n"; -#define RDP_CLIPBOARD_FRAGMENT_START_OFFSET (53) //--------------------------------------------+ | -#define RDP_CLIPBOARD_FRAGMENT_END_OFFSET (75) //----------------------------------------------------------------------+ + /* 1 2 3 4 5 6 7 8 */ + /*01234567890 1 2345678901234 5 67890123456 7 89012345678901234567890 1 234567890123456789012 3 4*/ +static const char rdp_clipboard_html_header[] = "Version:0.9\r\nStartHTML:-1\r\nEndHTML:-1\r\nStartFragment:00000000\r\nEndFragment:00000000\r\n"; +#define RDP_CLIPBOARD_FRAGMENT_START_OFFSET (53) //---------------------------------------------------------+ | +#define RDP_CLIPBOARD_FRAGMENT_END_OFFSET (75) //-----------------------------------------------------------------------------------+ /* * https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format * * The fragment should be preceded and followed by the HTML comments and * (no space allowed between the !-- and the text) to conveniently - * indicate where the fragment starts and ends. + * indicate where the fragment starts and ends. */ -char rdp_clipboard_html_fragment_start[] = "\r\n"; -char rdp_clipboard_html_fragment_end[] = "\r\n"; +static const char rdp_clipboard_html_fragment_start[] = "\r\n"; +static const char rdp_clipboard_html_fragment_end[] = "\r\n"; struct rdp_clipboard_data_source; -typedef void *(*pfn_process_data)(struct rdp_clipboard_data_source *source, BOOL is_send); +typedef bool (*pfn_process_data)(struct rdp_clipboard_data_source *source, bool is_send); struct rdp_clipboard_supported_format { - UINT32 index; - UINT32 format_id; + uint32_t format_id; char *format_name; char *mime_type; pfn_process_data pfn; }; -static void *clipboard_process_text_utf8(struct rdp_clipboard_data_source *, BOOL); -static void *clipboard_process_text_raw(struct rdp_clipboard_data_source *, BOOL); -static void *clipboard_process_bmp(struct rdp_clipboard_data_source *, BOOL); -static void *clipboard_process_html(struct rdp_clipboard_data_source *, BOOL); +static bool +clipboard_process_text_utf8(struct rdp_clipboard_data_source *source, bool is_send); + +static bool +clipboard_process_text_raw(struct rdp_clipboard_data_source *source, bool is_send); + +static bool +clipboard_process_bmp(struct rdp_clipboard_data_source *source , bool is_send); + +static bool +clipboard_process_html(struct rdp_clipboard_data_source *source, bool is_send); -//TODO: need to support to 1:n or m:n format conversion. -//For example, CF_UNICODETEXT to "UTF8_STRING" as well as "text/plain;charset=utf-8". +/* TODO: need to support to 1:n or m:n format conversion. + * For example, CF_UNICODETEXT to "UTF8_STRING" as well as "text/plain;charset=utf-8". + */ struct rdp_clipboard_supported_format clipboard_supported_formats[] = { - { 0, CF_UNICODETEXT, NULL, "text/plain;charset=utf-8", clipboard_process_text_utf8 }, - { 1, CF_TEXT, NULL, "STRING", clipboard_process_text_raw }, - { 2, CF_DIB, NULL, "image/bmp", clipboard_process_bmp }, - { 3, CF_PRIVATE_RTF, "Rich Text Format", "text/rtf", clipboard_process_text_raw }, - { 4, CF_PRIVATE_HTML, "HTML Format", "text/html", clipboard_process_html }, + { CF_UNICODETEXT, NULL, "text/plain;charset=utf-8", clipboard_process_text_utf8 }, + { CF_TEXT, NULL, "STRING", clipboard_process_text_raw }, + { CF_DIB, NULL, "image/bmp", clipboard_process_bmp }, + { CF_PRIVATE_RTF, "Rich Text Format", "text/rtf", clipboard_process_text_raw }, + { CF_PRIVATE_HTML, "HTML Format", "text/html", clipboard_process_html }, }; #define RDP_NUM_CLIPBOARD_FORMATS ARRAY_LENGTH(clipboard_supported_formats) enum rdp_clipboard_data_source_state { RDP_CLIPBOARD_SOURCE_ALLOCATED = 0, - RDP_CLIPBOARD_SOURCE_FORMATLIST_READY, /* format list is obtained from provider */ - RDP_CLIPBOARD_SOURCE_PUBLISHED, /* availablity of some or none clipboard data is notified to comsumer */ - RDP_CLIPBOARD_SOURCE_REQUEST_DATA, /* data request is sent to provider */ - RDP_CLIPBOARD_SOURCE_RECEIVED_DATA, /* data is received from provider, waiting data to be dispatched to consumer */ + RDP_CLIPBOARD_SOURCE_FORMATLIST_READY, /* format list obtained from provider */ + RDP_CLIPBOARD_SOURCE_PUBLISHED, /* availablity of some or no clipboard data notified to consumer */ + RDP_CLIPBOARD_SOURCE_REQUEST_DATA, /* data request sent to provider */ + RDP_CLIPBOARD_SOURCE_RECEIVED_DATA, /* data was received from provider, waiting data to be dispatched to consumer */ RDP_CLIPBOARD_SOURCE_TRANSFERING, /* transfering data to consumer */ - RDP_CLIPBOARD_SOURCE_TRANSFERRED, /* complete transfering data to comsumer */ - RDP_CLIPBOARD_SOURCE_CANCEL_PENDING, /* data transfer cancel is requested */ - RDP_CLIPBOARD_SOURCE_CANCELED, /* data transfer is canceled */ + RDP_CLIPBOARD_SOURCE_TRANSFERRED, /* completed transfering data to consumer */ + RDP_CLIPBOARD_SOURCE_CANCEL_PENDING, /* data transfer cancel requested */ + RDP_CLIPBOARD_SOURCE_CANCELED, /* data transfer canceled */ RDP_CLIPBOARD_SOURCE_RETRY, /* retry later */ RDP_CLIPBOARD_SOURCE_FAILED, /* failure occured */ }; @@ -114,19 +119,22 @@ struct rdp_clipboard_data_source { int data_source_fd; int format_index; enum rdp_clipboard_data_source_state state; - UINT32 data_response_fail_count; - UINT32 inflight_write_count; + uint32_t data_response_fail_count; + uint32_t inflight_write_count; void *inflight_data_to_write; size_t inflight_data_size; - BOOL is_data_processed; - BOOL is_canceled; - UINT32 client_format_id_table[RDP_NUM_CLIPBOARD_FORMATS]; + bool is_data_processed; + void *processed_data_start; + uint32_t processed_data_size; + bool processed_data_is_send; + bool is_canceled; + uint32_t client_format_id_table[RDP_NUM_CLIPBOARD_FORMATS]; }; struct rdp_clipboard_data_request { struct rdp_loop_task task_base; - RdpPeerContext *peerCtx; - UINT32 requested_format_index; + RdpPeerContext *ctx; + uint32_t requested_format_index; }; static char * @@ -135,8 +143,7 @@ clipboard_data_source_state_to_string(struct rdp_clipboard_data_source *source) if (!source) return "null"; - switch (source->state) - { + switch (source->state) { case RDP_CLIPBOARD_SOURCE_ALLOCATED: return "allocated"; case RDP_CLIPBOARD_SOURCE_FORMATLIST_READY: @@ -164,95 +171,103 @@ clipboard_data_source_state_to_string(struct rdp_clipboard_data_source *source) return "unknown"; } -static void * -clipboard_process_text_utf8(struct rdp_clipboard_data_source *source, BOOL is_send) +static bool +clipboard_process_text_utf8(struct rdp_clipboard_data_source *source, bool is_send) { - freerdp_peer *client = (freerdp_peer*)source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; struct wl_array data_contents; wl_array_init(&data_contents); - if (!source->is_data_processed) { - if (is_send) { - /* Linux to Windows (convert utf-8 to UNICODE) */ - /* Include terminating NULL in size */ - assert((source->data_contents.size + 1) <= source->data_contents.alloc); - assert(((char*)source->data_contents.data)[source->data_contents.size] == '\0'); - source->data_contents.size++; - - /* obtain size in UNICODE */ - size_t data_size = MultiByteToWideChar(CP_UTF8, 0, - (char*)source->data_contents.data, - source->data_contents.size, - NULL, 0); - if (data_size < 1) - goto error_return; - - data_size *= 2; // convert to size in bytes. - if (!wl_array_add(&data_contents, data_size)) - goto error_return; - - /* convert to UNICODE */ - size_t data_size_in_char = MultiByteToWideChar(CP_UTF8, 0, - (char*)source->data_contents.data, - source->data_contents.size, - (LPWSTR)data_contents.data, - data_size); - assert(data_contents.size == (data_size_in_char * 2)); - } else { - /* Windows to Linux (UNICODE to utf-8) */ - LPWSTR data = (LPWSTR)source->data_contents.data; - size_t data_size_in_char = source->data_contents.size / 2; - - /* Windows's data has trailing chars, which Linux doesn't expect. */ - while(data_size_in_char && - ((data[data_size_in_char-1] == L'\0') || (data[data_size_in_char-1] == L'\n'))) - data_size_in_char -= 1; - if (!data_size_in_char) - goto error_return; - - /* obtain size in utf-8 */ - size_t data_size = WideCharToMultiByte(CP_UTF8, 0, - (LPCWSTR)source->data_contents.data, - data_size_in_char, - NULL, 0, - NULL, NULL); - if (data_size < 1) - goto error_return; - - if (!wl_array_add(&data_contents, data_size)) - goto error_return; - - /* convert to utf-8 */ - data_size = WideCharToMultiByte(CP_UTF8, 0, - (LPCWSTR)source->data_contents.data, - data_size_in_char, - (char*)data_contents.data, - data_size, - NULL, NULL); - assert(data_contents.size == data_size); - } + assert(!source->is_data_processed); - /* swap the data_contents with new one */ - wl_array_release(&source->data_contents); - source->data_contents = data_contents; - source->is_data_processed = TRUE; - } + if (is_send) { + char *data = source->data_contents.data; + size_t data_size, data_size_in_char; + + /* Linux to Windows (convert utf-8 to UNICODE) */ + /* Include terminating NULL in size */ + assert((source->data_contents.size + 1) <= source->data_contents.alloc); + data[source->data_contents.size] = '\0'; + source->data_contents.size++; + + /* obtain size in UNICODE */ + data_size = MultiByteToWideChar(CP_UTF8, 0, + data, + source->data_contents.size, + NULL, 0); + if (data_size < 1) + goto error_return; - rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source), - is_send ? "send" : "receive", (UINT32)source->data_contents.size); + data_size *= 2; /* convert to size in bytes. */ + if (!wl_array_add(&data_contents, data_size)) + goto error_return; - return source->data_contents.data; + /* convert to UNICODE */ + data_size_in_char = MultiByteToWideChar(CP_UTF8, 0, + data, + source->data_contents.size, + data_contents.data, + data_size); + assert(data_contents.size == (data_size_in_char * 2)); + } else { + /* Windows to Linux (UNICODE to utf-8) */ + size_t data_size; + LPWSTR data = source->data_contents.data; + size_t data_size_in_char = source->data_contents.size / 2; + + /* Windows's data has trailing chars, which Linux doesn't expect. */ + while (data_size_in_char && + ((data[data_size_in_char-1] == L'\0') || (data[data_size_in_char-1] == L'\n'))) + data_size_in_char -= 1; + if (!data_size_in_char) + goto error_return; -error_return: + /* obtain size in utf-8 */ + data_size = WideCharToMultiByte(CP_UTF8, 0, + source->data_contents.data, + data_size_in_char, + NULL, 0, + NULL, NULL); + if (data_size < 1) + goto error_return; + + if (!wl_array_add(&data_contents, data_size)) + goto error_return; + + /* convert to utf-8 */ + data_size = WideCharToMultiByte(CP_UTF8, 0, + source->data_contents.data, + data_size_in_char, + data_contents.data, + data_size, + NULL, NULL); + assert(data_contents.size == data_size); + } + /* swap the data_contents with new one */ + wl_array_release(&source->data_contents); + source->data_contents = data_contents; + source->is_data_processed = true; + source->processed_data_start = source->data_contents.data; + source->processed_data_size = source->data_contents.size; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%u bytes)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", + (uint32_t)source->data_contents.size); + + return true; + +error_return: source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s FAILED (%p:%s): %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source), - is_send ? "send" : "receive", (UINT32)source->data_contents.size); + weston_log("RDP %s FAILED (%p:%s): %s (%u bytes)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", + (uint32_t)source->data_contents.size); //rdp_debug_clipboard_verbose(b, "RDP %s FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", // __func__, source, is_send ? "send" : "receive", // (char *)source->data_contents.data, @@ -260,149 +275,157 @@ clipboard_process_text_utf8(struct rdp_clipboard_data_source *source, BOOL is_se wl_array_release(&data_contents); - return NULL; + return false; } -static void * -clipboard_process_text_raw(struct rdp_clipboard_data_source *source, BOOL is_send) +static bool +clipboard_process_text_raw(struct rdp_clipboard_data_source *source, bool is_send) { - freerdp_peer *client = (freerdp_peer*)source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - - if (!source->is_data_processed) { - if (is_send) { - /* Linux to Windows */ - /* Include terminating NULL in size */ - assert((source->data_contents.size + 1) <= source->data_contents.alloc); - assert(((char*)source->data_contents.data)[source->data_contents.size] == '\0'); - source->data_contents.size++; - } else { - /* Windows to Linux */ - char *data = (char*)source->data_contents.data; - size_t data_size = source->data_contents.size; - - /* Windows's data has trailing chars, which Linux doesn't expect. */ - while(data_size && ((data[data_size-1] == '\0') || (data[data_size-1] == '\n'))) - data_size -= 1; - source->data_contents.size = data_size; - } - source->is_data_processed = TRUE; - } + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + char *data = source->data_contents.data; + size_t data_size = source->data_contents.size; + + assert(!source->is_data_processed); - rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s (%d bytes)\n", - __func__, source, is_send ? "send" : "receive", (UINT32)source->data_contents.size); + if (is_send) { + /* Linux to Windows */ + /* Include terminating NULL in size */ + assert(data_size + 1 <= source->data_contents.alloc); + data[data_size] = '\0'; + source->data_contents.size++; + } else { + /* Windows to Linux */ + /* Windows's data has trailing chars, which Linux doesn't expect. */ + while (data_size && ((data[data_size-1] == '\0') || (data[data_size-1] == '\n'))) + data_size -= 1; + source->data_contents.size = data_size; + } + source->is_data_processed = true; + source->processed_data_start = source->data_contents.data; + source->processed_data_size = source->data_contents.size; + rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s (%u bytes)\n", + __func__, source, + is_send ? "send" : "receive", + (uint32_t)source->data_contents.size); //rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s \n\"%s\"\n (%d bytes)\n", // __func__, source, is_send ? "send" : "receive", // (char *)source->data_contents.data, // (UINT32)source->data_contents.size); - return source->data_contents.data; + return true; } /* based off sample code at https://docs.microsoft.com/en-us/troubleshoot/cpp/add-html-code-clipboard But this missing a lot of corner cases, it must be rewritten with use of proper HTML parser */ -/* TODO: This doesn't work for converting HTML from Firefox in Wayland mode to Windows in certain case, +/* TODO: This doesn't work for converting HTML from Firefox in Wayland mode to Windows in certain cases, because Firefox sends "...", thus - here needs to property strip meta header and convert to the Windows clipboard style HTML. */ -static void * -clipboard_process_html(struct rdp_clipboard_data_source *source, BOOL is_send) + this needs to property strip meta header and convert to the Windows clipboard style HTML. */ +static bool +clipboard_process_html(struct rdp_clipboard_data_source *source, bool is_send) { - freerdp_peer *client = (freerdp_peer*)source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; struct wl_array data_contents; + char *cur = source->data_contents.data; + + assert(!source->is_data_processed); + + /* We're treating the contents as a string for now, so null + * terminate it so strstr can't run off the end. However, we + * don't increase data_contents.size because we don't want + * to affect the content. */ + assert(source->data_contents.size + 1 <= source->data_contents.alloc); + ((char *)(source->data_contents.data))[source->data_contents.size] = '\0'; wl_array_init(&data_contents); + cur = strstr(cur, "is_data_processed) { - char *cur = (char *)source->data_contents.data; - cur = strstr(cur, "data_contents.size - + (cur - (char *)source->data_contents.data); - if (!is_send) { - /* Windows to Linux */ - size_t data_size = source->data_contents.size - - (cur - (char *)source->data_contents.data); + /* Windows's data has trailing chars, which Linux doesn't expect. */ + while (data_size && ((cur[data_size-1] == '\0') || (cur[data_size-1] == '\n'))) + data_size -= 1; - /* Windows's data has trailing chars, which Linux doesn't expect. */ - while(data_size && ((cur[data_size-1] == '\0') || (cur[data_size-1] == '\n'))) - data_size -= 1; + if (!data_size) + goto error_return; - if (!data_size) - goto error_return; + if (!wl_array_add(&data_contents, data_size+1)) /* +1 for null */ + goto error_return; - if (!wl_array_add(&data_contents, data_size+1)) // +1 for null - goto error_return; + memcpy(data_contents.data, cur, data_size); + ((char *)(data_contents.data))[data_size] = '\0'; + data_contents.size = data_size; + } else { + /* Linux to Windows */ + char *last, *buf; + uint32_t fragment_start, fragment_end; - memcpy(data_contents.data, cur, data_size); - ((char *)(data_contents.data))[data_size] = '\0'; - data_contents.size = data_size; - } else { - /* Linux to Windows */ - char *last, *buf; - UINT32 fragment_start, fragment_end; - - if (!wl_array_add(&data_contents, source->data_contents.size+200)) - goto error_return; - - buf = (char *)data_contents.data; - strcpy(buf, rdp_clipboard_html_header); - last = cur; - cur = strstr(cur, "' - strncat(buf, last, cur-last); - last = cur; - fragment_start = strlen(buf); - strcat(buf, rdp_clipboard_html_fragment_start); - cur = strstr(cur, "data_contents.size+200)) + goto error_return; - /* swap the data_contents with new one */ - wl_array_release(&source->data_contents); - source->data_contents = data_contents; - source->is_data_processed = TRUE; + buf = data_contents.data; + strcpy(buf, rdp_clipboard_html_header); + last = cur; + cur = strstr(cur, "' */ + strncat(buf, last, cur-last); + last = cur; + fragment_start = strlen(buf); + strcat(buf, rdp_clipboard_html_fragment_start); + cur = strstr(cur, "data_contents.size); + /* swap the data_contents with new one */ + wl_array_release(&source->data_contents); + source->data_contents = data_contents; //rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s \n\"%s\"\n (%d bytes)\n", // __func__, source, is_send ? "send" : "receive", // (char *)source->data_contents.data, // (UINT32)source->data_contents.size); + source->is_data_processed = true; + source->processed_data_start = source->data_contents.data; + source->processed_data_size = source->data_contents.size; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%u bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", (uint32_t)source->data_contents.size); - return source->data_contents.data; + return true; error_return: - source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s FAILED (%p:%s): %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source), - is_send ? "send" : "receive", (UINT32)source->data_contents.size); + weston_log("RDP %s FAILED (%p:%s): %s (%u bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", (uint32_t)source->data_contents.size); //rdp_debug_clipboard_verbose(b, "RDP %s FAILED (%p): %s \n\"%s\"\n (%d bytes)\n", // __func__, source, is_send ? "send" : "receive", // (char *)source->data_contents.data, @@ -410,102 +433,89 @@ clipboard_process_html(struct rdp_clipboard_data_source *source, BOOL is_send) wl_array_release(&data_contents); - return NULL; + return false; } #define DIB_HEADER_MARKER ((WORD) ('M' << 8) | 'B') #define DIB_WIDTH_BYTES(bits) ((((bits) + 31) & ~31) >> 3) -static void * -clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) +static bool +clipboard_process_bmp(struct rdp_clipboard_data_source *source, bool is_send) { - freerdp_peer *client = (freerdp_peer*)source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - void *ret = NULL; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; BITMAPFILEHEADER *bmfh = NULL; BITMAPINFOHEADER *bmih = NULL; - const UINT32 size_of_bmfh = sizeof(*bmfh); - UINT32 color_table_size = 0; - /* size_t original_data_size = source->data_contents.size; */ - /* BOOL was_data_processed = source->is_data_processed; */ + uint32_t color_table_size = 0; struct wl_array data_contents; + assert(!source->is_data_processed); + wl_array_init(&data_contents); if (is_send) { /* Linux to Windows (remove BITMAPFILEHEADER) */ - if (source->data_contents.size <= size_of_bmfh) + if (source->data_contents.size <= sizeof(*bmfh)) goto error_return; - - bmfh = (BITMAPFILEHEADER *)source->data_contents.data; - bmih = (BITMAPINFOHEADER *)(bmfh + 1); - /* size must be adjusted only once */ - if (!source->is_data_processed) { - source->data_contents.size -= size_of_bmfh; - source->is_data_processed = TRUE; - } + bmfh = source->data_contents.data; + bmih = (BITMAPINFOHEADER *)(bmfh + 1); - ret = (void *)bmih; // Skip BITMAPFILEHEADER. + source->is_data_processed = true; + source->processed_data_start = bmih; + source->processed_data_size = source->data_contents.size - sizeof(*bmfh); } else { /* Windows to Linux (insert BITMAPFILEHEADER) */ - if (!source->is_data_processed) { - BITMAPFILEHEADER _bmfh = {}; - bmih = (BITMAPINFOHEADER *)source->data_contents.data; - bmfh = &_bmfh; - if (bmih->biCompression == BI_BITFIELDS) - color_table_size = sizeof(RGBQUAD) * 3; - else - color_table_size = sizeof(RGBQUAD) * bmih->biClrUsed; - - bmfh->bfType = DIB_HEADER_MARKER; - bmfh->bfOffBits = size_of_bmfh + bmih->biSize + color_table_size; - if (bmih->biSizeImage) - bmfh->bfSize = bmfh->bfOffBits + bmih->biSizeImage; - else if (bmih->biCompression == BI_BITFIELDS || bmih->biCompression == BI_RGB) - bmfh->bfSize = bmfh->bfOffBits + - (DIB_WIDTH_BYTES(bmih->biWidth * bmih->biBitCount) * abs(bmih->biHeight)); - else - goto error_return; - - /* source data must have enough size as described at its own bitmap header */ - if (source->data_contents.size < (bmfh->bfSize - size_of_bmfh)) - goto error_return; - - if (!wl_array_add(&data_contents, bmfh->bfSize)) - goto error_return; - assert(data_contents.size == bmfh->bfSize); - - /* copy generated BITMAPFILEHEADER */ - memcpy(data_contents.data, bmfh, size_of_bmfh); - /* copy rest of bitmap data from source */ - memcpy((char *)data_contents.data + size_of_bmfh, - source->data_contents.data, bmfh->bfSize - size_of_bmfh); - - /* swap the data_contents with new one */ - wl_array_release(&source->data_contents); - source->data_contents = data_contents; - source->is_data_processed = TRUE; + BITMAPFILEHEADER _bmfh = {}; - bmfh = (BITMAPFILEHEADER *)source->data_contents.data; - bmih = (BITMAPINFOHEADER *)(bmfh + 1); - } else { - bmfh = (BITMAPFILEHEADER *)source->data_contents.data; - bmih = (BITMAPINFOHEADER *)(bmfh + 1); - } + if (source->data_contents.size <= sizeof(*bmih)) + goto error_return; - ret = source->data_contents.data; - } + bmih = source->data_contents.data; + bmfh = &_bmfh; + if (bmih->biCompression == BI_BITFIELDS) + color_table_size = sizeof(RGBQUAD) * 3; + else + color_table_size = sizeof(RGBQUAD) * bmih->biClrUsed; + + bmfh->bfType = DIB_HEADER_MARKER; + bmfh->bfOffBits = sizeof(*bmfh) + bmih->biSize + color_table_size; + if (bmih->biSizeImage) + bmfh->bfSize = bmfh->bfOffBits + bmih->biSizeImage; + else if (bmih->biCompression == BI_BITFIELDS || bmih->biCompression == BI_RGB) + bmfh->bfSize = bmfh->bfOffBits + + (DIB_WIDTH_BYTES(bmih->biWidth * bmih->biBitCount) * abs(bmih->biHeight)); + else + goto error_return; - assert(ret); - assert(bmfh); - assert(bmih); + /* source data must have enough size as described in its own bitmap header */ + if (source->data_contents.size < (bmfh->bfSize - sizeof(*bmfh))) + goto error_return; + + if (!wl_array_add(&data_contents, bmfh->bfSize)) + goto error_return; + assert(data_contents.size == bmfh->bfSize); + + /* copy generated BITMAPFILEHEADER */ + memcpy(data_contents.data, bmfh, sizeof(*bmfh)); + /* copy rest of bitmap data from source */ + memcpy((char *)data_contents.data + sizeof(*bmfh), + source->data_contents.data, bmfh->bfSize - sizeof(*bmfh)); + + /* swap the data_contents with new one */ + wl_array_release(&source->data_contents); + source->data_contents = data_contents; + source->is_data_processed = true; + source->processed_data_start = source->data_contents.data; + source->processed_data_size = source->data_contents.size; + } rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source), - is_send ? "send" : "receive", - (UINT32)source->data_contents.size); + __func__, source, + clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", + (uint32_t)source->data_contents.size); /* rdp_debug_clipboard_verbose_continue(b, " BITMAPFILEHEADER.bfType:0x%x\n", bmfh->bfType); @@ -560,72 +570,70 @@ clipboard_process_bmp(struct rdp_clipboard_data_source *source, BOOL is_send) rdp_debug_clipboard_verbose_continue(b, " data_processed:%d -> %d\n", was_data_processed, source->is_data_processed); */ - return ret; + return true; error_return: - source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s FAILED (%p:%s): %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source), - is_send ? "send" : "receive", (UINT32)source->data_contents.size); + weston_log("RDP %s FAILED (%p:%s): %s (%d bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", (uint32_t)source->data_contents.size); wl_array_release(&data_contents); - return NULL; + return false; } static char * clipboard_format_id_to_string(UINT32 formatId, bool is_server_format_id) { - switch (formatId) - { - case CF_RAW: - return "CF_RAW"; - case CF_TEXT: - return "CF_TEXT"; - case CF_BITMAP: - return "CF_BITMAP"; - case CF_METAFILEPICT: - return "CF_METAFILEPICT"; - case CF_SYLK: - return "CF_SYLK"; - case CF_DIF: - return "CF_DIF"; - case CF_TIFF: - return "CF_TIFF"; - case CF_OEMTEXT: - return "CF_OEMTEX"; - case CF_DIB: - return "CF_DIB"; - case CF_PALETTE: - return "CF_PALETTE"; - case CF_PENDATA: - return "CF_PENDATA"; - case CF_RIFF: - return "CF_RIFF"; - case CF_WAVE: - return "CF_WAVE"; - case CF_UNICODETEXT: - return "CF_UNICODETEXT"; - case CF_ENHMETAFILE: - return "CF_ENHMETAFILE"; - case CF_HDROP: - return "CF_HDROP"; - case CF_LOCALE: - return "CF_LOCALE"; - case CF_DIBV5: - return "CF_DIBV5"; - - case CF_OWNERDISPLAY: - return "CF_OWNERDISPLAY"; - case CF_DSPTEXT: - return "CF_DSPTEXT"; - case CF_DSPBITMAP: - return "CF_DSPBITMAP"; - case CF_DSPMETAFILEPICT: - return "CF_DSPMETAFILEPICT"; - case CF_DSPENHMETAFILE: - return "CF_DSPENHMETAFILE"; + switch (formatId) { + case CF_RAW: + return "CF_RAW"; + case CF_TEXT: + return "CF_TEXT"; + case CF_BITMAP: + return "CF_BITMAP"; + case CF_METAFILEPICT: + return "CF_METAFILEPICT"; + case CF_SYLK: + return "CF_SYLK"; + case CF_DIF: + return "CF_DIF"; + case CF_TIFF: + return "CF_TIFF"; + case CF_OEMTEXT: + return "CF_OEMTEXT"; + case CF_DIB: + return "CF_DIB"; + case CF_PALETTE: + return "CF_PALETTE"; + case CF_PENDATA: + return "CF_PENDATA"; + case CF_RIFF: + return "CF_RIFF"; + case CF_WAVE: + return "CF_WAVE"; + case CF_UNICODETEXT: + return "CF_UNICODETEXT"; + case CF_ENHMETAFILE: + return "CF_ENHMETAFILE"; + case CF_HDROP: + return "CF_HDROP"; + case CF_LOCALE: + return "CF_LOCALE"; + case CF_DIBV5: + return "CF_DIBV5"; + + case CF_OWNERDISPLAY: + return "CF_OWNERDISPLAY"; + case CF_DSPTEXT: + return "CF_DSPTEXT"; + case CF_DSPBITMAP: + return "CF_DSPBITMAP"; + case CF_DSPMETAFILEPICT: + return "CF_DSPMETAFILEPICT"; + case CF_DSPENHMETAFILE: + return "CF_DSPENHMETAFILE"; } if (formatId >= CF_PRIVATEFIRST && formatId <= CF_PRIVATELAST) @@ -654,12 +662,13 @@ clipboard_format_id_to_string(UINT32 formatId, bool is_server_format_id) static int clipboard_find_supported_format_by_format_id(UINT32 format_id) { - for (UINT i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { + unsigned int i; + + for (i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { struct rdp_clipboard_supported_format *format = &clipboard_supported_formats[i]; - if (format_id == format->format_id) { - assert(i == format->index); - return format->index; - } + + if (format_id == format->format_id) + return i; } return -1; } @@ -668,17 +677,18 @@ clipboard_find_supported_format_by_format_id(UINT32 format_id) static int clipboard_find_supported_format_by_format_id_and_name(UINT32 format_id, const char *format_name) { - for (UINT i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { + unsigned int i; + + for (i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { struct rdp_clipboard_supported_format *format = &clipboard_supported_formats[i]; + /* when our supported format table has format name, only format name must match, format id provided from client is ignored (but it may be saved by caller for future use. When our supported format table doesn't have format name, only format id must match, format name (if provided from client) is ignored */ if ((format->format_name == NULL && format_id == format->format_id) || - (format->format_name && format_name && strcmp(format_name, format->format_name) == 0)) { - assert(i == format->index); - return format->index; - } + (format->format_name && format_name && strcmp(format_name, format->format_name) == 0)) + return i; } return -1; } @@ -687,49 +697,69 @@ clipboard_find_supported_format_by_format_id_and_name(UINT32 format_id, const ch static int clipboard_find_supported_format_by_mime_type(const char *mime_type) { - for (UINT i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { + unsigned int i; + + for (i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { struct rdp_clipboard_supported_format *format = &clipboard_supported_formats[i]; - if (strcmp(mime_type, format->mime_type) == 0) { - assert(i == format->index); - return format->index; - } + + if (strcmp(mime_type, format->mime_type) == 0) + return i; } return -1; } +static bool +clipboard_process_source(struct rdp_clipboard_data_source *source, bool is_send) +{ + if (source->is_data_processed) { + assert(source->processed_data_is_send == is_send); + return true; + } + + source->processed_data_start = NULL; + source->processed_data_size = 0; + + if (clipboard_supported_formats[source->format_index].pfn) + return clipboard_supported_formats[source->format_index].pfn(source, is_send); + + /* No processor, so just set up pointer and length for raw data */ + source->is_data_processed = true; + source->processed_data_start = source->data_contents.data; + source->processed_data_size = source->data_contents.size; + source->processed_data_is_send = is_send; + return true; +} + static void clipboard_data_source_unref(struct rdp_clipboard_data_source *source) { - freerdp_peer *client = (freerdp_peer*)source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; char **p; + assert_compositor_thread(b); + assert(source->refcount); source->refcount--; rdp_debug_clipboard(b, "RDP %s (%p:%s): refcount:%d\n", - __func__, source, clipboard_data_source_state_to_string(source), source->refcount); + __func__, source, + clipboard_data_source_state_to_string(source), + source->refcount); if (source->refcount > 0) return; - if (source->transfer_event_source) { - /* removing event source must be done from wayland display thread */ - ASSERT_COMPOSITOR_THREAD(b); + if (source->transfer_event_source) wl_event_source_remove(source->transfer_event_source); - } - if (source->data_source_fd != -1) { - ASSERT_COMPOSITOR_THREAD(b); + if (source->data_source_fd != -1) close(source->data_source_fd); - } - if (!wl_list_empty(&source->base.destroy_signal.listener_list)) { - ASSERT_COMPOSITOR_THREAD(b); + if (!wl_list_empty(&source->base.destroy_signal.listener_list)) wl_signal_emit(&source->base.destroy_signal, &source->base); - } wl_array_release(&source->data_contents); @@ -746,35 +776,38 @@ clipboard_data_source_unref(struct rdp_clipboard_data_source *source) \******************************************/ /* Inform client data request is succeeded with data */ -static UINT -clipboard_client_send_format_data_response(RdpPeerContext *peerCtx, struct rdp_clipboard_data_source *source, void *data, UINT32 size) +static void +clipboard_client_send_format_data_response(RdpPeerContext *ctx, struct rdp_clipboard_data_source *source) { - struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_backend *b = ctx->rdpBackend; CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; + assert(source->is_data_processed); rdp_debug_clipboard(b, "Client: %s (%p:%s) format_index:%d %s (%d bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source), source->format_index, - clipboard_supported_formats[source->format_index].mime_type, size); + __func__, source, + clipboard_data_source_state_to_string(source), + source->format_index, + clipboard_supported_formats[source->format_index].mime_type, + source->processed_data_size); formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; formatDataResponse.msgFlags = CB_RESPONSE_OK; - formatDataResponse.dataLen = size; - formatDataResponse.requestedFormatData = data; - peerCtx->clipboard_server_context->ServerFormatDataResponse(peerCtx->clipboard_server_context, &formatDataResponse); + formatDataResponse.dataLen = source->processed_data_size; + formatDataResponse.requestedFormatData = source->processed_data_start; + ctx->clipboard_server_context->ServerFormatDataResponse(ctx->clipboard_server_context, &formatDataResponse); /* if here failed to send response, what can we do ? */ - - return 0; } -/* Inform client data request is failed */ -static UINT -clipboard_client_send_format_data_response_fail(RdpPeerContext *peerCtx, struct rdp_clipboard_data_source *source) +/* Inform client data request has failed */ +static void +clipboard_client_send_format_data_response_fail(RdpPeerContext *ctx, struct rdp_clipboard_data_source *source) { - struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_backend *b = ctx->rdpBackend; CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; rdp_debug_clipboard(b, "Client: %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source)); + __func__, source, + clipboard_data_source_state_to_string(source)); if (source) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; @@ -785,10 +818,8 @@ clipboard_client_send_format_data_response_fail(RdpPeerContext *peerCtx, struct formatDataResponse.msgFlags = CB_RESPONSE_FAIL; formatDataResponse.dataLen = 0; formatDataResponse.requestedFormatData = NULL; - peerCtx->clipboard_server_context->ServerFormatDataResponse(peerCtx->clipboard_server_context, &formatDataResponse); + ctx->clipboard_server_context->ServerFormatDataResponse(ctx->clipboard_server_context, &formatDataResponse); /* if here failed to send response, what can we do ? */ - - return 0; } /***************************************\ @@ -800,17 +831,17 @@ static int clipboard_data_source_read(int fd, uint32_t mask, void *arg) { struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; - freerdp_peer *client = (freerdp_peer*)source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - char *data; - int len, size; - void *data_to_send; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + int len; + bool failed = true; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", - __func__, source, clipboard_data_source_state_to_string(source), fd); + __func__, source, + clipboard_data_source_state_to_string(source), fd); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); assert(source->data_source_fd == fd); assert(source->refcount == 1); @@ -820,100 +851,85 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) available for read in pipe. */ assert(source->transfer_event_source); - /* if buffer is less than 1024 bytes remaining, request another 1024 bytes minimum */ - /* but actual reallocated buffer size will be increased by ^2 */ - if (source->data_contents.alloc - source->data_contents.size < 1024) { - if (!wl_array_add(&source->data_contents, 1024)) { - goto error_exit; - } - source->data_contents.size -= 1024; - } - source->state = RDP_CLIPBOARD_SOURCE_TRANSFERING; - data = (char*)source->data_contents.data + source->data_contents.size; - size = source->data_contents.alloc - source->data_contents.size - 1; // -1 leave space for NULL-terminate. - len = read(fd, data, size); - if (len == 0) { - /* all data from source is read, so completed. */ - source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; - rdp_debug_clipboard(b, "RDP %s (%p:%s): read completed (%ld bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source), source->data_contents.size); - if (!source->data_contents.size) - goto error_exit; - /* process data before sending to client */ - if (clipboard_supported_formats[source->format_index].pfn) - data_to_send = clipboard_supported_formats[source->format_index].pfn(source, TRUE); - else - data_to_send = source->data_contents.data; - /* send clipboard data to client */ - if (data_to_send) - clipboard_client_send_format_data_response(peerCtx, source, data_to_send, source->data_contents.size); - else - goto error_exit; - goto send_exit; - } else if (len < 0) { + + len = rdp_wl_array_read_fd(&source->data_contents, fd); + if (len < 0) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s (%p:%s) read failed (%s)\n", - __func__, source, clipboard_data_source_state_to_string(source), strerror(errno)); + weston_log("RDP %s (%p:%s) read failed (%s)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + strerror(errno)); goto error_exit; - } else { - source->data_contents.size += len; - ((char*)source->data_contents.data)[source->data_contents.size] = '\0'; + } + + if (len > 0) { rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) read (%zu bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source), source->data_contents.size); + __func__, source, + clipboard_data_source_state_to_string(source), + source->data_contents.size); /* continue to read next batch */ return 0; } - assert(false); + + /* len == 0, all data from source is read, so completed. */ + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; + rdp_debug_clipboard(b, "RDP %s (%p:%s): read completed (%ld bytes)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->data_contents.size); + if (!source->data_contents.size) + goto error_exit; + /* process data before sending to client */ + if (!clipboard_process_source(source, true)) + goto error_exit; + + clipboard_client_send_format_data_response(ctx, source); + failed = false; error_exit: - clipboard_client_send_format_data_response_fail(peerCtx, source); + if (failed) + clipboard_client_send_format_data_response_fail(ctx, source); -send_exit: /* make sure this is the last reference, so event source is removed at unref */ assert(source->refcount == 1); clipboard_data_source_unref(source); - return 0; + return 0; } -/* client's reply with error for data request, clean up */ +/* client's reply with error for data request, clean up */ static int clipboard_data_source_fail(int fd, uint32_t mask, void *arg) { struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; - freerdp_peer *client = (freerdp_peer *) source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", __func__, - source, clipboard_data_source_state_to_string(source), fd); + source, clipboard_data_source_state_to_string(source), fd); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); assert(source->data_source_fd == fd); /* this data source must be tracked as inflight */ - assert(source == peerCtx->clipboard_inflight_client_data_source); + assert(source == ctx->clipboard_inflight_client_data_source); - /* remove event source now, and if write is failed with EAGAIN, queue back to display loop. */ wl_event_source_remove(source->transfer_event_source); source->transfer_event_source = NULL; - if (source->data_contents.size) { - /* if data is recieved, but failed by other reason, - then keep data and format index for future request, - otherwise data is purged at last reference release. */ - /* wl_array_release(&source->data_contents); */ - /* wl_array_init(&source->data_contents); */ - } else { - /* data has been never recieved, thus must be empty. */ + /* if data was received, but failed for another reason then keep data + * and format index for future request, otherwise data is purged at + * last reference release. */ + if (!source->data_contents.size) { + /* data has been never received, thus must be empty. */ assert(source->data_contents.size == 0); assert(source->data_contents.alloc == 0); assert(source->data_contents.data == NULL); /* clear previous requested format so it can be requested later again. */ source->format_index = -1; } - /* don't clear format id table, so it allows to retry to get data from client. */ - /* memset(source->client_format_id_table, 0, sizeof(source->client_format_id_table)); */ + /* data has never been sent to write(), thus must be no inflight write. */ assert(source->inflight_write_count == 0); assert(source->inflight_data_to_write == NULL); @@ -924,7 +940,7 @@ clipboard_data_source_fail(int fd, uint32_t mask, void *arg) close(source->data_source_fd); source->data_source_fd = -1; /* clear inflight data source from client to server. */ - peerCtx->clipboard_inflight_client_data_source = NULL; + ctx->clipboard_inflight_client_data_source = NULL; clipboard_data_source_unref(source); return 0; @@ -935,89 +951,107 @@ static int clipboard_data_source_write(int fd, uint32_t mask, void *arg) { struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; - freerdp_peer *client = (freerdp_peer *) source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; void *data_to_write; size_t data_size; ssize_t size; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", __func__, - source, clipboard_data_source_state_to_string(source), fd); + source, + clipboard_data_source_state_to_string(source), + fd); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); assert(source->data_source_fd == fd); /* this data source must be tracked as inflight */ - assert(source == peerCtx->clipboard_inflight_client_data_source); + assert(source == ctx->clipboard_inflight_client_data_source); if (source->is_canceled) { /* if source is being canceled, this must be the last reference */ assert(source->refcount == 1); source->state = RDP_CLIPBOARD_SOURCE_CANCELED; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) canceled\n", - __func__, source, clipboard_data_source_state_to_string(source)); - } else if (source->data_contents.data && source->data_contents.size) { + __func__, source, + clipboard_data_source_state_to_string(source)); + goto fail; + } + + if (!source->data_contents.data || !source->data_contents.size) { assert(source->refcount > 1); - if (source->inflight_data_to_write) { - assert(source->inflight_data_size); - rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) transfer in chunck, count:%d\n", - __func__, source, clipboard_data_source_state_to_string(source), source->inflight_write_count); - data_to_write = source->inflight_data_to_write; - data_size = source->inflight_data_size; - } else { - fcntl(source->data_source_fd, F_SETFL, O_WRONLY | O_NONBLOCK); - if (clipboard_supported_formats[source->format_index].pfn) - data_to_write = clipboard_supported_formats[source->format_index].pfn(source, FALSE); - else - data_to_write = source->data_contents.data; - data_size = source->data_contents.size; - } - while (data_to_write && data_size) { - source->state = RDP_CLIPBOARD_SOURCE_TRANSFERING; + weston_log("RDP %s (%p:%s) no data received from client\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + goto fail; + } + + assert(source->refcount > 1); + if (source->inflight_data_to_write) { + assert(source->inflight_data_size); + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) transfer in chunck, count:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->inflight_write_count); + data_to_write = source->inflight_data_to_write; + data_size = source->inflight_data_size; + } else { + fcntl(source->data_source_fd, F_SETFL, O_WRONLY | O_NONBLOCK); + clipboard_process_source(source, false); + data_to_write = source->processed_data_start; + data_size = source->processed_data_size; + } + while (data_to_write && data_size) { + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERING; + do { size = write(source->data_source_fd, data_to_write, data_size); - if (size <= 0) { - if (errno != EAGAIN) { - source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s (%p:%s) write failed %s\n", - __func__, source, clipboard_data_source_state_to_string(source), strerror(errno)); - break; - } - /* buffer is full, wait until data_source_fd is writable again */ - source->inflight_data_to_write = data_to_write; - source->inflight_data_size = data_size; - source->inflight_write_count++; - return 0; - } else { - assert(data_size >= (size_t)size); - data_size -= size; - data_to_write = (char *)data_to_write + size; - rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) wrote %ld bytes, remaining %ld bytes\n", - __func__, source, clipboard_data_source_state_to_string(source), size, data_size); - if (!data_size) { - source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; - rdp_debug_clipboard(b, "RDP %s (%p:%s) write completed (%ld bytes)\n", - __func__, source, clipboard_data_source_state_to_string(source), source->data_contents.size); - } + } while (size == -1 && errno == EINTR); + + if (size <= 0) { + if (errno != EAGAIN) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("RDP %s (%p:%s) write failed %s\n", + __func__, source, + clipboard_data_source_state_to_string(source), + strerror(errno)); + break; + } + /* buffer is full, wait until data_source_fd is writable again */ + source->inflight_data_to_write = data_to_write; + source->inflight_data_size = data_size; + source->inflight_write_count++; + return 0; + } else { + assert(data_size >= (size_t)size); + data_size -= size; + data_to_write = (char *)data_to_write + size; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) wrote %ld bytes, remaining %ld bytes\n", + __func__, source, + clipboard_data_source_state_to_string(source), + size, data_size); + if (!data_size) { + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; + rdp_debug_clipboard(b, "RDP %s (%p:%s) write completed (%ld bytes)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->data_contents.size); } } - } else { - assert(source->refcount > 1); - rdp_debug_clipboard_error(b, "RDP %s (%p:%s) no data received from client\n", - __func__, source, clipboard_data_source_state_to_string(source)); } - /* Here write is either completed, cancelled or failed, thus close pipe. */ +fail: + /* Here write is either completed, canceled or failed, so close the pipe. */ close(source->data_source_fd); source->data_source_fd = -1; - /* and remove event source. */ + /* and remove the event source */ wl_event_source_remove(source->transfer_event_source); source->transfer_event_source = NULL; /* and reset the inflight transfer state. */ source->inflight_write_count = 0; source->inflight_data_to_write = NULL; source->inflight_data_size = 0; - peerCtx->clipboard_inflight_client_data_source = NULL; + ctx->clipboard_inflight_client_data_source = NULL; clipboard_data_source_unref(source); return 0; @@ -1033,47 +1067,52 @@ clipboard_data_source_accept(struct weston_data_source *base, uint32_t time, const char *mime_type) { struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)base; - freerdp_peer *client = (freerdp_peer*)source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; rdp_debug_clipboard(b, "RDP %s (%p:%s) mime-type:\"%s\"\n", - __func__, source, clipboard_data_source_state_to_string(source), mime_type); + __func__, source, + clipboard_data_source_state_to_string(source), + mime_type); } -/* data-device informs the application requested the specified format data in given data_source (= client's clipboard) */ +/* data-device informs the application requested the specified format data + * in given data_source (= client's clipboard) */ static void clipboard_data_source_send(struct weston_data_source *base, const char *mime_type, int32_t fd) { struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)base; - freerdp_peer *client = (freerdp_peer*)source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - struct weston_seat *seat = peerCtx->item.seat; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + struct weston_seat *seat = ctx->item.seat; struct wl_event_loop *loop = wl_display_get_event_loop(seat->compositor->wl_display); CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = {}; int index; rdp_debug_clipboard(b, "RDP %s (%p:%s) fd:%d, mime-type:\"%s\"\n", - __func__, source, clipboard_data_source_state_to_string(source), fd, mime_type); + __func__, source, + clipboard_data_source_state_to_string(source), + fd, mime_type); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); - if (peerCtx->clipboard_inflight_client_data_source) { + if (ctx->clipboard_inflight_client_data_source) { /* Here means server side (Linux application) request clipboard data, but server hasn't completed with previous request yet. If this happens, punt to idle loop and reattempt. */ - rdp_debug_clipboard_error(b, "\n\n\nRDP %s new (%p:%s:fd %d) vs prev (%p:%s:fd %d): outstanding RDP data request (client to server)\n\n\n", - __func__, source, clipboard_data_source_state_to_string(source), fd, - peerCtx->clipboard_inflight_client_data_source, - clipboard_data_source_state_to_string(peerCtx->clipboard_inflight_client_data_source), - peerCtx->clipboard_inflight_client_data_source->data_source_fd); - if (source == peerCtx->clipboard_inflight_client_data_source) { + weston_log("\n\n\nRDP %s new (%p:%s:fd %d) vs prev (%p:%s:fd %d): outstanding RDP data request (client to server)\n\n\n", + __func__, source, clipboard_data_source_state_to_string(source), fd, + ctx->clipboard_inflight_client_data_source, + clipboard_data_source_state_to_string(ctx->clipboard_inflight_client_data_source), + ctx->clipboard_inflight_client_data_source->data_source_fd); + if (source == ctx->clipboard_inflight_client_data_source) { /* when new source and previous source is same, update fd with new one and retry */ source->state = RDP_CLIPBOARD_SOURCE_RETRY; - peerCtx->clipboard_inflight_client_data_source->data_source_fd = fd; - goto exit_return; + ctx->clipboard_inflight_client_data_source->data_source_fd = fd; + return; } else { source->state = RDP_CLIPBOARD_SOURCE_FAILED; goto error_return_close_fd; @@ -1083,39 +1122,48 @@ clipboard_data_source_send(struct weston_data_source *base, if (source->base.mime_types.size == 0) { source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; rdp_debug_clipboard(b, "RDP %s (%p:%s) source has no data\n", - __func__, source, clipboard_data_source_state_to_string(source)); + __func__, source, clipboard_data_source_state_to_string(source)); goto error_return_close_fd; } index = clipboard_find_supported_format_by_mime_type(mime_type); if (index >= 0 && /* check supported by this RDP bridge */ - source->client_format_id_table[index]) { /* check supported by current data source from client */ - peerCtx->clipboard_inflight_client_data_source = source; - source->refcount++; // reference while request inflight. + source->client_format_id_table[index]) { /* check supported by current data source from client */ + ctx->clipboard_inflight_client_data_source = source; + source->refcount++; /* reference while request inflight. */ source->data_source_fd = fd; assert(source->inflight_write_count == 0); assert(source->inflight_data_to_write == NULL); assert(source->inflight_data_size == 0); if (index == source->format_index) { + bool ret; + /* data is already in data_contents, no need to pull from client */ assert(source->transfer_event_source == NULL); source->state = RDP_CLIPBOARD_SOURCE_RECEIVED_DATA; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) data in cache \"%s\" index:%d formatId:%d %s\n", - __func__, source, clipboard_data_source_state_to_string(source), mime_type, index, - source->client_format_id_table[index], - clipboard_format_id_to_string(source->client_format_id_table[index], false)); - if (!rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, - clipboard_data_source_write, source, &source->transfer_event_source)) { + __func__, source, + clipboard_data_source_state_to_string(source), + mime_type, index, + source->client_format_id_table[index], + clipboard_format_id_to_string(source->client_format_id_table[index], + false)); + + ret = rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + clipboard_data_source_write, source, + &source->transfer_event_source); + if (!ret) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s (%p:%s) rdp_event_loop_add_fd failed\n", - __func__, source, clipboard_data_source_state_to_string(source)); + weston_log("RDP %s (%p:%s) rdp_event_loop_add_fd failed\n", + __func__, source, + clipboard_data_source_state_to_string(source)); goto error_return_unref_source; } } else { /* purge cached data */ wl_array_release(&source->data_contents); wl_array_init(&source->data_contents); - source->is_data_processed = FALSE; + source->is_data_processed = false; /* update requesting format property */ source->format_index = index; /* request clipboard data from client */ @@ -1124,21 +1172,20 @@ clipboard_data_source_send(struct weston_data_source *base, formatDataRequest.requestedFormatId = source->client_format_id_table[index]; source->state = RDP_CLIPBOARD_SOURCE_REQUEST_DATA; rdp_debug_clipboard(b, "RDP %s (%p:%s) request data \"%s\" index:%d formatId:%d %s\n", - __func__, source, clipboard_data_source_state_to_string(source), mime_type, index, - formatDataRequest.requestedFormatId, - clipboard_format_id_to_string(formatDataRequest.requestedFormatId, false)); - if (peerCtx->clipboard_server_context->ServerFormatDataRequest(peerCtx->clipboard_server_context, &formatDataRequest) != 0) + __func__, source, clipboard_data_source_state_to_string(source), mime_type, index, + formatDataRequest.requestedFormatId, + clipboard_format_id_to_string(formatDataRequest.requestedFormatId, false)); + if (ctx->clipboard_server_context->ServerFormatDataRequest(ctx->clipboard_server_context, &formatDataRequest) != 0) goto error_return_unref_source; } } else { source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s (%p:%s) specified format \"%s\" index:%d formatId:%d is not supported by client\n", - __func__, source, clipboard_data_source_state_to_string(source), - mime_type, index, source->client_format_id_table[index]); + weston_log("RDP %s (%p:%s) specified format \"%s\" index:%d formatId:%d is not supported by client\n", + __func__, source, clipboard_data_source_state_to_string(source), + mime_type, index, source->client_format_id_table[index]); goto error_return_close_fd; } -exit_return: return; error_return_unref_source: @@ -1146,57 +1193,58 @@ clipboard_data_source_send(struct weston_data_source *base, assert(source->inflight_write_count == 0); assert(source->inflight_data_to_write == NULL); assert(source->inflight_data_size == 0); - assert(peerCtx->clipboard_inflight_client_data_source == source); - peerCtx->clipboard_inflight_client_data_source = NULL; + assert(ctx->clipboard_inflight_client_data_source == source); + ctx->clipboard_inflight_client_data_source = NULL; clipboard_data_source_unref(source); error_return_close_fd: close(fd); - - return; } /* data-device informs the given data source is not longer referenced by compositor */ static void clipboard_data_source_cancel(struct weston_data_source *base) { - struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *) base; - freerdp_peer *client = (freerdp_peer*)source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)base; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; rdp_debug_clipboard(b, "RDP %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source)); + __func__, source, + clipboard_data_source_state_to_string(source)); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); - if (source == peerCtx->clipboard_inflight_client_data_source) { - source->is_canceled = TRUE; + if (source == ctx->clipboard_inflight_client_data_source) { + source->is_canceled = true; source->state = RDP_CLIPBOARD_SOURCE_CANCEL_PENDING; rdp_debug_clipboard(b, "RDP %s (%p:%s): still inflight - refcount:%d\n", - __func__, source, clipboard_data_source_state_to_string(source), - source->refcount); + __func__, source, + clipboard_data_source_state_to_string(source), + source->refcount); assert(source->refcount > 1); - } else { - /* everything outside of the base has to be cleaned up */ - source->state = RDP_CLIPBOARD_SOURCE_CANCELED; - rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) - refcount:%d\n", - __func__, source, clipboard_data_source_state_to_string(source), - source->refcount); - assert(source->refcount == 1); - assert(source->transfer_event_source == NULL); - wl_array_release(&source->data_contents); - wl_array_init(&source->data_contents); - source->is_data_processed = FALSE; - source->format_index = -1; - memset(source->client_format_id_table, 0, sizeof(source->client_format_id_table)); - source->inflight_write_count = 0; - source->inflight_data_to_write = NULL; - source->inflight_data_size = 0; - if (source->data_source_fd != -1) { - close(source->data_source_fd); - source->data_source_fd = -1; - } + return; + } + /* everything outside of the base has to be cleaned up */ + source->state = RDP_CLIPBOARD_SOURCE_CANCELED; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) - refcount:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->refcount); + assert(source->refcount == 1); + assert(source->transfer_event_source == NULL); + wl_array_release(&source->data_contents); + wl_array_init(&source->data_contents); + source->is_data_processed = false; + source->format_index = -1; + memset(source->client_format_id_table, 0, sizeof(source->client_format_id_table)); + source->inflight_write_count = 0; + source->inflight_data_to_write = NULL; + source->inflight_data_size = 0; + if (source->data_source_fd != -1) { + close(source->data_source_fd); + source->data_source_fd = -1; } } @@ -1209,36 +1257,35 @@ static void clipboard_data_source_publish(bool freeOnly, void *arg) { struct rdp_clipboard_data_source *source = wl_container_of(arg, source, task_base); - freerdp_peer *client = (freerdp_peer*)source->context; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; struct rdp_clipboard_data_source *source_prev; rdp_debug_clipboard(b, "RDP %s (%p:%s)\n", - __func__, source, clipboard_data_source_state_to_string(source)); + __func__, source, clipboard_data_source_state_to_string(source)); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); /* here is going to publish new data, if previous data from client is still referenced, unref it after selection */ - source_prev = peerCtx->clipboard_client_data_source; + source_prev = ctx->clipboard_client_data_source; if (!freeOnly) { - peerCtx->clipboard_client_data_source = source; + ctx->clipboard_client_data_source = source; source->transfer_event_source = NULL; source->base.accept = clipboard_data_source_accept; source->base.send = clipboard_data_source_send; source->base.cancel = clipboard_data_source_cancel; source->state = RDP_CLIPBOARD_SOURCE_PUBLISHED; - weston_seat_set_selection(peerCtx->item.seat, &source->base, - wl_display_next_serial(b->compositor->wl_display)); + weston_seat_set_selection(ctx->item.seat, &source->base, + wl_display_next_serial(b->compositor->wl_display)); } else { - peerCtx->clipboard_client_data_source = NULL; + ctx->clipboard_client_data_source = NULL; clipboard_data_source_unref(source); } + if (source_prev) clipboard_data_source_unref(source_prev); - - return; } /* Request the specified clipboard data from data-device at server side */ @@ -1246,18 +1293,19 @@ static void clipboard_data_source_request(bool freeOnly, void *arg) { struct rdp_clipboard_data_request *request = wl_container_of(arg, request, task_base); - RdpPeerContext *peerCtx = request->peerCtx; - struct rdp_backend *b = peerCtx->rdpBackend; - struct weston_seat *seat = peerCtx->item.seat; + RdpPeerContext *ctx = request->ctx; + struct rdp_backend *b = ctx->rdpBackend; + struct weston_seat *seat = ctx->item.seat; struct weston_data_source *selection_data_source = seat->selection_data_source; struct wl_event_loop *loop = wl_display_get_event_loop(seat->compositor->wl_display); struct rdp_clipboard_data_source *source = NULL; int p[2] = {}; const char *requested_mime_type, **mime_type; int index; - BOOL found_requested_format; + bool found_requested_format; + bool ret; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); if (freeOnly) goto error_exit_free_request; @@ -1266,20 +1314,20 @@ clipboard_data_source_request(bool freeOnly, void *arg) assert(index >= 0 && index < (int)RDP_NUM_CLIPBOARD_FORMATS); requested_mime_type = clipboard_supported_formats[index].mime_type; rdp_debug_clipboard(b, "RDP %s (base:%p) requested mime type:\"%s\"\n", - __func__, selection_data_source, requested_mime_type); + __func__, selection_data_source, requested_mime_type); found_requested_format = FALSE; wl_array_for_each(mime_type, &selection_data_source->mime_types) { rdp_debug_clipboard(b, "RDP %s (base:%p) available formats: %s\n", - __func__, selection_data_source, *mime_type); + __func__, selection_data_source, *mime_type); if (strcmp(requested_mime_type, *mime_type) == 0) { - found_requested_format = TRUE; + found_requested_format = true; break; } } if (!found_requested_format) { rdp_debug_clipboard(b, "RDP %s (base:%p) requested format not found format:\"%s\"\n", - __func__, selection_data_source, requested_mime_type); + __func__, selection_data_source, requested_mime_type); goto error_exit_response_fail; } @@ -1291,13 +1339,15 @@ clipboard_data_source_request(bool freeOnly, void *arg) to client by clipboard_set_selection(). */ source->state = RDP_CLIPBOARD_SOURCE_PUBLISHED; rdp_debug_clipboard(b, "RDP %s (%p:%s) for (base:%p)\n", - __func__, source, clipboard_data_source_state_to_string(source), selection_data_source); + __func__, source, + clipboard_data_source_state_to_string(source), + selection_data_source); wl_signal_init(&source->base.destroy_signal); wl_array_init(&source->base.mime_types); wl_array_init(&source->data_contents); - source->is_data_processed = FALSE; - source->context = (void*)peerCtx->item.peer; - source->refcount = 1; // decremented when data sent to client. + source->is_data_processed = false; + source->context = ctx->item.peer; + source->refcount = 1; /* decremented when data sent to client. */ source->data_source_fd = -1; source->format_index = index; @@ -1307,20 +1357,23 @@ clipboard_data_source_request(bool freeOnly, void *arg) source->data_source_fd = p[0]; rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) pipe write:%d -> read:%d\n", - __func__, source, clipboard_data_source_state_to_string(source), - p[1], p[0]); + __func__, source, + clipboard_data_source_state_to_string(source), + p[1], p[0]); /* Request data from data source */ source->state = RDP_CLIPBOARD_SOURCE_REQUEST_DATA; selection_data_source->send(selection_data_source, requested_mime_type, p[1]); /* p[1] should be closed by data source */ - /* wait until data is ready on pipe */ - if (!rdp_event_loop_add_fd(loop, p[0], WL_EVENT_READABLE, - clipboard_data_source_read, source, &source->transfer_event_source)) { + ret = rdp_event_loop_add_fd(loop, p[0], WL_EVENT_READABLE, + clipboard_data_source_read, source, + &source->transfer_event_source); + if (!ret) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "RDP %s (%p:%s) rdp_event_loop_add_fd failed.\n", - __func__, source, clipboard_data_source_state_to_string(source)); + weston_log("RDP %s (%p:%s) rdp_event_loop_add_fd failed.\n", + __func__, source, + clipboard_data_source_state_to_string(source)); goto error_exit_free_source; } @@ -1331,14 +1384,10 @@ clipboard_data_source_request(bool freeOnly, void *arg) error_exit_free_source: assert(source->refcount == 1); clipboard_data_source_unref(source); - error_exit_response_fail: - clipboard_client_send_format_data_response_fail(peerCtx, NULL); - + clipboard_client_send_format_data_response_fail(ctx, NULL); error_exit_free_request: free(request); - - return; } /*************************************\ @@ -1349,12 +1398,12 @@ clipboard_data_source_request(bool freeOnly, void *arg) static void clipboard_set_selection(struct wl_listener *listener, void *data) { - RdpPeerContext *peerCtx = + RdpPeerContext *ctx = container_of(listener, RdpPeerContext, clipboard_selection_listener); - struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_backend *b = ctx->rdpBackend; struct weston_seat *seat = data; struct weston_data_source *selection_data_source = seat->selection_data_source; - struct rdp_clipboard_data_source* data_source; + struct rdp_clipboard_data_source *data_source; CLIPRDR_FORMAT_LIST formatList = {}; CLIPRDR_FORMAT format[RDP_NUM_CLIPBOARD_FORMATS] = {}; const char **mime_type; @@ -1362,26 +1411,28 @@ clipboard_set_selection(struct wl_listener *listener, void *data) rdp_debug_clipboard(b, "RDP %s (base:%p)\n", __func__, selection_data_source); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); if (selection_data_source == NULL) { return; - } else if (selection_data_source->accept == clipboard_data_source_accept) { + } + + if (selection_data_source->accept == clipboard_data_source_accept) { /* Callback for our data source. */ return; } /* another data source (from server side) gets selected, no longer need previous data from client. */ - if (peerCtx->clipboard_client_data_source) { - data_source = peerCtx->clipboard_client_data_source; - peerCtx->clipboard_client_data_source = NULL; + if (ctx->clipboard_client_data_source) { + data_source = ctx->clipboard_client_data_source; + ctx->clipboard_client_data_source = NULL; clipboard_data_source_unref(data_source); } wl_array_for_each(mime_type, &selection_data_source->mime_types) { rdp_debug_clipboard(b, "RDP %s (base:%p) available formats[%d]: %s\n", - __func__, selection_data_source, num_avail_format, *mime_type); + __func__, selection_data_source, num_avail_format, *mime_type); num_avail_format++; } @@ -1389,16 +1440,18 @@ clipboard_set_selection(struct wl_listener *listener, void *data) wl_array_for_each(mime_type, &selection_data_source->mime_types) { index = clipboard_find_supported_format_by_mime_type(*mime_type); if (index >= 0) { - format[num_supported_format].formatId = clipboard_supported_formats[index].format_id; - format[num_supported_format].formatName = clipboard_supported_formats[index].format_name; + CLIPRDR_FORMAT *f = &format[num_supported_format]; + + f->formatId = clipboard_supported_formats[index].format_id; + f->formatName = clipboard_supported_formats[index].format_name; rdp_debug_clipboard(b, "RDP %s (base:%p) supported formats[%d]: %d: %s\n", - __func__, - selection_data_source, - num_supported_format, - format[num_supported_format].formatId, - format[num_supported_format].formatName ? \ - format[num_supported_format].formatName : \ - clipboard_format_id_to_string(format[num_supported_format].formatId, true)); + __func__, + selection_data_source, + num_supported_format, + f->formatId, + f->formatName ? + f->formatName : + clipboard_format_id_to_string(f->formatId, true)); num_supported_format++; } } @@ -1408,12 +1461,10 @@ clipboard_set_selection(struct wl_listener *listener, void *data) formatList.msgType = CB_FORMAT_LIST; formatList.numFormats = num_supported_format; formatList.formats = &format[0]; - peerCtx->clipboard_server_context->ServerFormatList(peerCtx->clipboard_server_context, &formatList); + ctx->clipboard_server_context->ServerFormatList(ctx->clipboard_server_context, &formatList); } else { rdp_debug_clipboard(b, "RDP %s (base:%p) no supported formats\n", __func__, selection_data_source); } - - return; } /*********************\ @@ -1422,44 +1473,45 @@ clipboard_set_selection(struct wl_listener *listener, void *data) /* client reports the path of temp folder */ static UINT -clipboard_client_temp_directory(CliprdrServerContext* context, const CLIPRDR_TEMP_DIRECTORY* tempDirectory) +clipboard_client_temp_directory(CliprdrServerContext *context, const CLIPRDR_TEMP_DIRECTORY *tempDirectory) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + rdp_debug_clipboard(b, "Client: %s %s\n", __func__, tempDirectory->szTempDir); return 0; } /* client reports thier clipboard capabilities */ static UINT -clipboard_client_capabilities(CliprdrServerContext* context, const CLIPRDR_CAPABILITIES* capabilities) +clipboard_client_capabilities(CliprdrServerContext *context, const CLIPRDR_CAPABILITIES *capabilities) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + rdp_debug_clipboard(b, "Client: clipboard capabilities: cCapabilitiesSet:%d\n", capabilities->cCapabilitiesSets); - for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++) { - CLIPRDR_CAPABILITY_SET* capabilitySets = &capabilities->capabilitySets[i]; + for (uint32_t i = 0; i < capabilities->cCapabilitiesSets; i++) { + CLIPRDR_CAPABILITY_SET *capabilitySets = &capabilities->capabilitySets[i]; + CLIPRDR_GENERAL_CAPABILITY_SET *generalCapabilitySet = (CLIPRDR_GENERAL_CAPABILITY_SET *)capabilitySets; + switch (capabilitySets->capabilitySetType) { - case CB_CAPSTYPE_GENERAL: - { - CLIPRDR_GENERAL_CAPABILITY_SET *generalCapabilitySet = (CLIPRDR_GENERAL_CAPABILITY_SET *)capabilitySets; - rdp_debug_clipboard(b, "Client: clipboard capabilities[%d]: General\n", i); - rdp_debug_clipboard(b, " Version:%d\n", generalCapabilitySet->version); - rdp_debug_clipboard(b, " GeneralFlags:0x%x\n", generalCapabilitySet->generalFlags); - if (generalCapabilitySet->generalFlags & CB_USE_LONG_FORMAT_NAMES) - rdp_debug_clipboard(b, " CB_USE_LONG_FORMAT_NAMES\n"); - if (generalCapabilitySet->generalFlags & CB_STREAM_FILECLIP_ENABLED) - rdp_debug_clipboard(b, " CB_STREAM_FILECLIP_ENABLED\n"); - if (generalCapabilitySet->generalFlags & CB_FILECLIP_NO_FILE_PATHS) - rdp_debug_clipboard(b, " CB_FILECLIP_NO_FILE_PATHS\n"); - if (generalCapabilitySet->generalFlags & CB_CAN_LOCK_CLIPDATA) - rdp_debug_clipboard(b, " CB_CAN_LOCK_CLIPDATA\n"); - break; - } - default: - return -1; + case CB_CAPSTYPE_GENERAL: + rdp_debug_clipboard(b, "Client: clipboard capabilities[%d]: General\n", i); + rdp_debug_clipboard(b, " Version:%d\n", generalCapabilitySet->version); + rdp_debug_clipboard(b, " GeneralFlags:0x%x\n", generalCapabilitySet->generalFlags); + if (generalCapabilitySet->generalFlags & CB_USE_LONG_FORMAT_NAMES) + rdp_debug_clipboard(b, " CB_USE_LONG_FORMAT_NAMES\n"); + if (generalCapabilitySet->generalFlags & CB_STREAM_FILECLIP_ENABLED) + rdp_debug_clipboard(b, " CB_STREAM_FILECLIP_ENABLED\n"); + if (generalCapabilitySet->generalFlags & CB_FILECLIP_NO_FILE_PATHS) + rdp_debug_clipboard(b, " CB_FILECLIP_NO_FILE_PATHS\n"); + if (generalCapabilitySet->generalFlags & CB_CAN_LOCK_CLIPDATA) + rdp_debug_clipboard(b, " CB_CAN_LOCK_CLIPDATA\n"); + break; + default: + return -1; } } return 0; @@ -1467,155 +1519,162 @@ clipboard_client_capabilities(CliprdrServerContext* context, const CLIPRDR_CAPAB /* client reports the supported format list in client's clipboard */ static UINT -clipboard_client_format_list(CliprdrServerContext* context, const CLIPRDR_FORMAT_LIST* formatList) +clipboard_client_format_list(CliprdrServerContext *context, const CLIPRDR_FORMAT_LIST *formatList) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = {}; + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; struct rdp_clipboard_data_source *source = NULL; - BOOL isPublished = FALSE; char **p, *s; - ASSERT_NOT_COMPOSITOR_THREAD(b); + assert_not_compositor_thread(b); rdp_debug_clipboard(b, "Client: %s clipboard format list: numFormats:%d\n", __func__, formatList->numFormats); - for (UINT32 i = 0; i < formatList->numFormats; i++) { - CLIPRDR_FORMAT* format = &formatList->formats[i]; + for (uint32_t i = 0; i < formatList->numFormats; i++) { + CLIPRDR_FORMAT *format = &formatList->formats[i]; + rdp_debug_clipboard(b, "Client: %s clipboard formats[%d]: formatId:%d, formatName:%s\n", - __func__, i, format->formatId, - format->formatName ? format->formatName : clipboard_format_id_to_string(format->formatId, false)); + __func__, i, format->formatId, + format->formatName ? format->formatName : clipboard_format_id_to_string(format->formatId, false)); } source = zalloc(sizeof *source); - if (source) { - source->state = RDP_CLIPBOARD_SOURCE_ALLOCATED; - rdp_debug_clipboard(b, "Client: %s (%p:%s) allocated\n", - __func__, source, clipboard_data_source_state_to_string(source)); - wl_signal_init(&source->base.destroy_signal); - wl_array_init(&source->base.mime_types); - wl_array_init(&source->data_contents); - source->context = (void*) client; - source->refcount = 1; // decremented when another source is selected. - source->data_source_fd = -1; - source->format_index = -1; + if (!source) + goto fail; + + source->state = RDP_CLIPBOARD_SOURCE_ALLOCATED; + rdp_debug_clipboard(b, "Client: %s (%p:%s) allocated\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + wl_signal_init(&source->base.destroy_signal); + wl_array_init(&source->base.mime_types); + wl_array_init(&source->data_contents); + source->context = client; + source->refcount = 1; /* decremented when another source is selected. */ + source->data_source_fd = -1; + source->format_index = -1; + + for (uint32_t i = 0; i < formatList->numFormats; i++) { + CLIPRDR_FORMAT *format = &formatList->formats[i]; + int index = clipboard_find_supported_format_by_format_id_and_name(format->formatId, format->formatName); - for (UINT32 i = 0; i < formatList->numFormats; i++) { - CLIPRDR_FORMAT* format = &formatList->formats[i]; - int index = clipboard_find_supported_format_by_format_id_and_name(format->formatId, format->formatName); - if (index >= 0) { - /* save format id given from client, client can handle its own format id for private format. */ - source->client_format_id_table[index] = format->formatId; - s = strdup(clipboard_supported_formats[index].mime_type); - if (s) { - p = wl_array_add(&source->base.mime_types, sizeof *p); - if (p) { - rdp_debug_clipboard(b, "Client: %s (%p:%s) mine_type:\"%s\" index:%d formatId:%d\n", - __func__, source, clipboard_data_source_state_to_string(source), - s, index, format->formatId); - *p = s; - } else { - rdp_debug_clipboard(b, "Client: %s (%p:%s) wl_array_add failed\n", - __func__, source, clipboard_data_source_state_to_string(source)); - free(s); - } + if (index >= 0) { + /* save format id given from client, client can handle its own format id for private format. */ + source->client_format_id_table[index] = format->formatId; + s = strdup(clipboard_supported_formats[index].mime_type); + if (s) { + p = wl_array_add(&source->base.mime_types, sizeof *p); + if (p) { + rdp_debug_clipboard(b, "Client: %s (%p:%s) mine_type:\"%s\" index:%d formatId:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + s, index, format->formatId); + *p = s; } else { - rdp_debug_clipboard(b, "Client: %s (%p:%s) strdup failed\n", - __func__, source, clipboard_data_source_state_to_string(source)); + rdp_debug_clipboard(b, "Client: %s (%p:%s) wl_array_add failed\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + free(s); } + } else { + rdp_debug_clipboard(b, "Client: %s (%p:%s) strdup failed\n", + __func__, source, + clipboard_data_source_state_to_string(source)); } } + } - if (formatList->numFormats != 0 && - source->base.mime_types.size == 0) { - rdp_debug_clipboard(b, "Client: %s (%p:%s) no formats are supported\n", - __func__, source, clipboard_data_source_state_to_string(source)); - } - - source->state = RDP_CLIPBOARD_SOURCE_FORMATLIST_READY; - rdp_dispatch_task_to_display_loop(peerCtx, clipboard_data_source_publish, &source->task_base); - isPublished = TRUE; + if (formatList->numFormats != 0 && + source->base.mime_types.size == 0) { + rdp_debug_clipboard(b, "Client: %s (%p:%s) no formats are supported\n", + __func__, source, + clipboard_data_source_state_to_string(source)); } - CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = {}; + source->state = RDP_CLIPBOARD_SOURCE_FORMATLIST_READY; + rdp_dispatch_task_to_display_loop(ctx, clipboard_data_source_publish, &source->task_base); + +fail: formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE; - formatListResponse.msgFlags = (source && isPublished) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + formatListResponse.msgFlags = source ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; formatListResponse.dataLen = 0; - if (peerCtx->clipboard_server_context->ServerFormatListResponse(peerCtx->clipboard_server_context, &formatListResponse) != 0) { + if (ctx->clipboard_server_context->ServerFormatListResponse(ctx->clipboard_server_context, &formatListResponse) != 0) { source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "Client: %s (%p:%s) ServerFormatListResponse failed\n", - __func__, source, clipboard_data_source_state_to_string(source)); + weston_log("Client: %s (%p:%s) ServerFormatListResponse failed\n", + __func__, source, + clipboard_data_source_state_to_string(source)); return -1; } - - if (!isPublished && source) { - assert(source->refcount == 1); - clipboard_data_source_unref(source); - } - return 0; } /* client responded with clipboard data asked by server */ static UINT -clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +clipboard_client_format_data_response(CliprdrServerContext *context, const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; struct wl_event_loop *loop = wl_display_get_event_loop(b->compositor->wl_display); - struct rdp_clipboard_data_source *source = peerCtx->clipboard_inflight_client_data_source; - BOOL Success = FALSE; + struct rdp_clipboard_data_source *source = ctx->clipboard_inflight_client_data_source; + bool success = false; + bool ret; rdp_debug_clipboard(b, "Client: %s (%p:%s) flags:%d dataLen:%d\n", - __func__, source, clipboard_data_source_state_to_string(source), - formatDataResponse->msgFlags, formatDataResponse->dataLen); + __func__, source, + clipboard_data_source_state_to_string(source), + formatDataResponse->msgFlags, + formatDataResponse->dataLen); - ASSERT_NOT_COMPOSITOR_THREAD(b); + assert_not_compositor_thread(b); - if (source) { - if (source->transfer_event_source || (source->inflight_write_count != 0)) { - /* here means client responded more than once for single data request */ - source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "Client: %s (%p:%s) middle of write loop:%p, %d\n", - __func__, source, clipboard_data_source_state_to_string(source), - source->transfer_event_source, source->inflight_write_count); - return -1; - } + if (!source) { + rdp_debug_clipboard(b, "Client: %s client send data without server asking. protocol error", __func__); + return -1; + } - if (formatDataResponse->msgFlags == CB_RESPONSE_OK) { - /* Recieved data from client, cache to data source */ - if (wl_array_add(&source->data_contents, formatDataResponse->dataLen+1)) { - memcpy(source->data_contents.data, - formatDataResponse->requestedFormatData, - formatDataResponse->dataLen); - source->data_contents.size = formatDataResponse->dataLen; - /* regardless data type, make sure it ends with NULL */ - ((char*)source->data_contents.data)[source->data_contents.size] = '\0'; - /* data is ready, waiting to be written to destination */ - source->state = RDP_CLIPBOARD_SOURCE_RECEIVED_DATA; - Success = TRUE; - } else { - source->state = RDP_CLIPBOARD_SOURCE_FAILED; - } + if (source->transfer_event_source || (source->inflight_write_count != 0)) { + /* here means client responded more than once for single data request */ + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("Client: %s (%p:%s) middle of write loop:%p, %d\n", + __func__, source, clipboard_data_source_state_to_string(source), + source->transfer_event_source, source->inflight_write_count); + return -1; + } + + if (formatDataResponse->msgFlags == CB_RESPONSE_OK) { + /* Recieved data from client, cache to data source */ + if (wl_array_add(&source->data_contents, formatDataResponse->dataLen+1)) { + memcpy(source->data_contents.data, + formatDataResponse->requestedFormatData, + formatDataResponse->dataLen); + source->data_contents.size = formatDataResponse->dataLen; + /* regardless data type, make sure it ends with NULL */ + ((char *)source->data_contents.data)[source->data_contents.size] = '\0'; + /* data is ready, waiting to be written to destination */ + source->state = RDP_CLIPBOARD_SOURCE_RECEIVED_DATA; + success = true; } else { source->state = RDP_CLIPBOARD_SOURCE_FAILED; - source->data_response_fail_count++; - } - rdp_debug_clipboard_verbose(b, "Client: %s (%p:%s) fail count:%d)\n", - __func__, source, - clipboard_data_source_state_to_string(source), - source->data_response_fail_count); - - assert(source->transfer_event_source == NULL); - if (!rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, - Success ? clipboard_data_source_write : clipboard_data_source_fail, source, &source->transfer_event_source)) { - source->state = RDP_CLIPBOARD_SOURCE_FAILED; - rdp_debug_clipboard_error(b, "Client: %s (%p:%s) rdp_event_loop_add_fd failed\n", - __func__, source, clipboard_data_source_state_to_string(source)); - return -1; } } else { - rdp_debug_clipboard(b, "Client: %s client send data without server asking. protocol error", __func__); + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + source->data_response_fail_count++; + } + rdp_debug_clipboard_verbose(b, "Client: %s (%p:%s) fail count:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->data_response_fail_count); + + assert(source->transfer_event_source == NULL); + ret = rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + success ? clipboard_data_source_write : clipboard_data_source_fail, + source, &source->transfer_event_source); + if (!ret) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("Client: %s (%p:%s) rdp_event_loop_add_fd failed\n", + __func__, source, clipboard_data_source_state_to_string(source)); return -1; } @@ -1624,53 +1683,56 @@ clipboard_client_format_data_response(CliprdrServerContext* context, const CLIPR /* client responded on the format list sent by server */ static UINT -clipboard_client_format_list_response(CliprdrServerContext* context, const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +clipboard_client_format_list_response(CliprdrServerContext *context, + const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + rdp_debug_clipboard(b, "Client: %s msgFlags:0x%x\n", __func__, formatListResponse->msgFlags); - ASSERT_NOT_COMPOSITOR_THREAD(b); + assert_not_compositor_thread(b); return 0; } /* client requested the data of specificed format in server clipboard */ static UINT -clipboard_client_format_data_request(CliprdrServerContext* context, const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +clipboard_client_format_data_request(CliprdrServerContext *context, + const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; struct rdp_clipboard_data_request *request; int index; rdp_debug_clipboard(b, "Client: %s requestedFormatId:%d - %s\n", - __func__, formatDataRequest->requestedFormatId, - clipboard_format_id_to_string(formatDataRequest->requestedFormatId, true)); + __func__, formatDataRequest->requestedFormatId, + clipboard_format_id_to_string(formatDataRequest->requestedFormatId, true)); - ASSERT_NOT_COMPOSITOR_THREAD(b); + assert_not_compositor_thread(b); /* Make sure clients requested the format we knew */ index = clipboard_find_supported_format_by_format_id(formatDataRequest->requestedFormatId); - if (index >= 0) { - request = zalloc(sizeof(*request)); - if (!request) { - rdp_debug_clipboard_error(b, "zalloc failed\n", __func__); - goto error_return; - } - request->peerCtx = peerCtx; - request->requested_format_index = index; - rdp_dispatch_task_to_display_loop(peerCtx, clipboard_data_source_request, &request->task_base); - } else { - rdp_debug_clipboard_error(b, "Client: %s client requests data format the server never reported in format list response. protocol error.\n", __func__); + if (index < 0) { + weston_log("Client: %s client requests data format the server never reported in format list response. protocol error.\n", __func__); goto error_return; } + + request = zalloc(sizeof(*request)); + if (!request) { + weston_log("zalloc failed\n"); + goto error_return; + } + request->ctx = ctx; + request->requested_format_index = index; + rdp_dispatch_task_to_display_loop(ctx, clipboard_data_source_request, &request->task_base); + return 0; error_return: /* send FAIL response to client */ - if (clipboard_client_send_format_data_response_fail(peerCtx, NULL) != 0) - return -1; + clipboard_client_send_format_data_response_fail(ctx, NULL); return 0; } @@ -1678,82 +1740,84 @@ clipboard_client_format_data_request(CliprdrServerContext* context, const CLIPRD * Public functions * \********************/ -int -rdp_clipboard_init(freerdp_peer* client) +int +rdp_clipboard_init(freerdp_peer *client) { - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - struct weston_seat *seat = peerCtx->item.seat; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + struct weston_seat *seat = ctx->item.seat; + CliprdrServerContext *clip_ctx; assert(seat); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); - peerCtx->clipboard_server_context = cliprdr_server_context_new(peerCtx->vcm); - if (!peerCtx->clipboard_server_context) + ctx->clipboard_server_context = cliprdr_server_context_new(ctx->vcm); + if (!ctx->clipboard_server_context) goto error; - peerCtx->clipboard_server_context->custom = (void *)client; - peerCtx->clipboard_server_context->TempDirectory = clipboard_client_temp_directory; - peerCtx->clipboard_server_context->ClientCapabilities = clipboard_client_capabilities; - peerCtx->clipboard_server_context->ClientFormatList = clipboard_client_format_list; - peerCtx->clipboard_server_context->ClientFormatListResponse = clipboard_client_format_list_response; - //peerCtx->clipboard_server_context->ClientLockClipboardData - //peerCtx->clipboard_server_context->ClientUnlockClipboardData - peerCtx->clipboard_server_context->ClientFormatDataRequest = clipboard_client_format_data_request; - peerCtx->clipboard_server_context->ClientFormatDataResponse = clipboard_client_format_data_response; - //peerCtx->clipboard_server_context->ClientFileContentsRequest - //peerCtx->clipboard_server_context->ClientFileContentsResponse - peerCtx->clipboard_server_context->useLongFormatNames = FALSE; // ASCII8 format name only (No Windows-style 2 bytes Unicode). - peerCtx->clipboard_server_context->streamFileClipEnabled = FALSE; - peerCtx->clipboard_server_context->fileClipNoFilePaths = FALSE; - peerCtx->clipboard_server_context->canLockClipData = TRUE; - if (peerCtx->clipboard_server_context->Start(peerCtx->clipboard_server_context) != 0) + clip_ctx = ctx->clipboard_server_context; + clip_ctx->custom = (void *)client; + clip_ctx->TempDirectory = clipboard_client_temp_directory; + clip_ctx->ClientCapabilities = clipboard_client_capabilities; + clip_ctx->ClientFormatList = clipboard_client_format_list; + clip_ctx->ClientFormatListResponse = clipboard_client_format_list_response; + /* clip_ctx->ClientLockClipboardData */ + /* clip_ctx->ClientUnlockClipboardData */ + clip_ctx->ClientFormatDataRequest = clipboard_client_format_data_request; + clip_ctx->ClientFormatDataResponse = clipboard_client_format_data_response; + /* clip_ctxClientFileContentsRequest */ + /* clip_ctx->ClientFileContentsResponse */ + clip_ctx->useLongFormatNames = FALSE; /* ASCII8 format name only (No Windows-style 2 bytes Unicode). */ + clip_ctx->streamFileClipEnabled = FALSE; + clip_ctx->fileClipNoFilePaths = FALSE; + clip_ctx->canLockClipData = TRUE; + if (clip_ctx->Start(ctx->clipboard_server_context) != 0) goto error; - peerCtx->clipboard_selection_listener.notify = clipboard_set_selection; + ctx->clipboard_selection_listener.notify = clipboard_set_selection; wl_signal_add(&seat->selection_signal, - &peerCtx->clipboard_selection_listener); + &ctx->clipboard_selection_listener); return 0; error: - if (peerCtx->clipboard_server_context) { - cliprdr_server_context_free(peerCtx->clipboard_server_context); - peerCtx->clipboard_server_context = NULL; + if (ctx->clipboard_server_context) { + cliprdr_server_context_free(ctx->clipboard_server_context); + ctx->clipboard_server_context = NULL; } return -1; } void -rdp_clipboard_destroy(RdpPeerContext *peerCtx) +rdp_clipboard_destroy(RdpPeerContext *ctx) { - struct rdp_clipboard_data_source* data_source; - struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_clipboard_data_source *data_source; + struct rdp_backend *b = ctx->rdpBackend; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); - if (peerCtx->clipboard_selection_listener.notify) { - wl_list_remove(&peerCtx->clipboard_selection_listener.link); - peerCtx->clipboard_selection_listener.notify = NULL; + if (ctx->clipboard_selection_listener.notify) { + wl_list_remove(&ctx->clipboard_selection_listener.link); + ctx->clipboard_selection_listener.notify = NULL; } - if (peerCtx->clipboard_inflight_client_data_source) { - data_source = peerCtx->clipboard_inflight_client_data_source; - peerCtx->clipboard_inflight_client_data_source = NULL; + if (ctx->clipboard_inflight_client_data_source) { + data_source = ctx->clipboard_inflight_client_data_source; + ctx->clipboard_inflight_client_data_source = NULL; clipboard_data_source_unref(data_source); } - if (peerCtx->clipboard_client_data_source) { - data_source = peerCtx->clipboard_client_data_source; - peerCtx->clipboard_client_data_source = NULL; + if (ctx->clipboard_client_data_source) { + data_source = ctx->clipboard_client_data_source; + ctx->clipboard_client_data_source = NULL; clipboard_data_source_unref(data_source); } - if (peerCtx->clipboard_server_context) { - peerCtx->clipboard_server_context->Stop(peerCtx->clipboard_server_context); - cliprdr_server_context_free(peerCtx->clipboard_server_context); - peerCtx->clipboard_server_context = NULL; + if (ctx->clipboard_server_context) { + ctx->clipboard_server_context->Stop(ctx->clipboard_server_context); + cliprdr_server_context_free(ctx->clipboard_server_context); + ctx->clipboard_server_context = NULL; } } diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 8d22a845c..5cf4d9681 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -93,7 +93,7 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); pixman_region32_clear(&peerCtx->regionClientHeads); pixman_region32_clear(&peerCtx->regionWestonHeads); @@ -138,7 +138,7 @@ disp_end_monitor_layout_change(freerdp_peer *client) struct rdp_backend *b = peerCtx->rdpBackend; struct rdp_head *current, *next; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); /* move output to final location */ wl_list_for_each_safe(current, next, &b->head_move_pending_list, link) { @@ -207,7 +207,7 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo struct rdp_head *current; BOOL updateMode = FALSE; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); if (monitorMode->monitorDef.is_primary) { assert(b->head_default); @@ -536,7 +536,7 @@ disp_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MON struct rdp_monitor_mode *monitorMode; MONITOR_DEF *resetMonitorDef; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); rdp_debug(b, "Client: DisplayControl: monitor count:0x%x\n", displayControl->NumMonitors); @@ -618,7 +618,7 @@ disp_monitor_layout_change_callback(bool freeOnly, void* dataIn) freerdp_peer *client = (freerdp_peer*)context->custom; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); + assert_compositor_thread(peerCtx->rdpBackend); if (!freeOnly) disp_monitor_layout_change(context, &data->displayControl); @@ -637,7 +637,7 @@ disp_client_monitor_layout_change(DispServerContext* context, const DISPLAY_CONT struct rdp_backend *b = peerCtx->rdpBackend; struct disp_schedule_monitor_layout_change_data *data; - ASSERT_NOT_COMPOSITOR_THREAD(b); + assert_not_compositor_thread(b); rdp_debug(b, "Client: DisplayLayoutChange: monitor count:0x%x\n", displayControl->NumMonitors); diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 31db61aa0..d2fceded7 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -81,7 +81,7 @@ struct rdp_rail_dispatch_data { struct rdp_rail_dispatch_data *dispatch_data; \ dispatch_data = (struct rdp_rail_dispatch_data *)malloc(sizeof(*dispatch_data)); \ if (dispatch_data) { \ - ASSERT_NOT_COMPOSITOR_THREAD(b); \ + assert_not_compositor_thread(b); \ dispatch_data->client = client; \ dispatch_data->u_##arg_type = *(arg); \ rdp_dispatch_task_to_display_loop(peerCtx, callback, &dispatch_data->task_base); \ @@ -103,7 +103,7 @@ applist_client_Caps_callback(bool freeOnly, void *arg) rdp_debug(b, "Client AppList caps version:%d\n", caps->version); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); if (!freeOnly && b->rdprail_shell_api && @@ -174,7 +174,7 @@ rail_client_Exec_callback(bool freeOnly, void *arg) exec->RemoteApplicationWorkingDir, exec->RemoteApplicationArguments); - ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); + assert_compositor_thread(peerCtx->rdpBackend); if (!freeOnly && exec->RemoteApplicationProgram) { @@ -286,7 +286,7 @@ rail_client_Activate_callback(bool freeOnly, void *arg) rdp_debug_verbose(b, "Client: ClientActivate: WindowId:0x%x, enabled:%d\n", activate->windowId, activate->enabled); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); if (!freeOnly && b->rdprail_shell_api && @@ -330,7 +330,7 @@ rail_client_SnapArrange_callback(bool freeOnly, void *arg) snap->right - snap->left, snap->bottom - snap->top); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); surface = NULL; if (!freeOnly) @@ -396,7 +396,7 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) windowMove->right - windowMove->left, windowMove->bottom - windowMove->top); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); surface = NULL; if (!freeOnly) @@ -449,7 +449,7 @@ rail_client_Syscommand_callback(bool freeOnly, void *arg) struct rdp_backend *b = peerCtx->rdpBackend; struct weston_surface* surface; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); surface = NULL; if (!freeOnly) @@ -529,7 +529,7 @@ rail_client_ClientSysparam_callback(bool freeOnly, void *arg) struct weston_output *base_output; struct weston_head *base_head_iter; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); if (sysparam->params & SPI_MASK_SET_DRAG_FULL_WINDOWS) { rdp_debug(b, "Client: ClientSysparam: dragFullWindows:%d\n", sysparam->dragFullWindows); @@ -659,7 +659,7 @@ rail_client_ClientGetAppidReq_callback(bool freeOnly, void *arg) rdp_debug_verbose(b, "Client: ClientGetAppidReq: WindowId:0x%x\n", getAppidReq->windowId); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); if (!freeOnly && b->rdprail_shell_api && @@ -835,7 +835,7 @@ rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) struct xkb_rule_names xkbRuleNames; char *s; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); switch (languageImeInfo->ProfileType) { @@ -1181,7 +1181,7 @@ rdp_rail_create_cursor(struct weston_surface *surface) RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); rail_state->clientPos.width = 0; /* triggers force update on next update. */ rail_state->clientPos.height = 0; @@ -1211,7 +1211,7 @@ rdp_rail_update_cursor(struct weston_surface *surface) int contentBufferWidth; int contentBufferHeight; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); assert(rail_state); weston_surface_get_content_size(surface, &contentBufferWidth, &contentBufferHeight); @@ -1328,7 +1328,7 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) peerCtx = (RdpPeerContext *)b->rdp_peer->context; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); if (!peerCtx->activationRailCompleted) { rdp_debug_verbose(b, "CreateWindow(): rdp_peer rail is not activated.\n"); @@ -1564,7 +1564,7 @@ rdp_rail_destroy_window(struct wl_listener *listener, void *data) assert(b && b->rdp_peer); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); peerCtx = (RdpPeerContext *)b->rdp_peer->context; if (rail_state->isCursor) { @@ -1669,7 +1669,7 @@ rdp_rail_schedule_update_window(struct wl_listener *listener, void *data) if (!window_id) return; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); /* negative width/height is not allowed */ if (surface->width < 0 || surface->height < 0) { @@ -1726,7 +1726,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter char window_title_mod[256]; char *title = NULL; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); if (!rail_state || rail_state->error) return 0; @@ -2615,7 +2615,7 @@ rdp_rail_sync_window_zorder(struct weston_compositor *compositor) MONITORED_DESKTOP_ORDER monitored_desktop_order = {}; UINT32 iCurrent = 0; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); if (!b->enable_window_zorder_sync) return; @@ -2721,7 +2721,7 @@ rdp_rail_peer_activate(freerdp_peer* client) #endif // HAVE_FREERDP_RDPAPPLIST_H UINT waitRetry; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); /* In RAIL mode, client must not be resized */ assert(b->no_clients_resize == 0); @@ -2930,7 +2930,7 @@ rdp_rail_idle_handler(struct wl_listener *listener, void *data) container_of(listener, RdpPeerContext, idle_listener); struct rdp_backend *b = peerCtx->rdpBackend; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); rdp_debug(b, "%s is called on peerCtx:%p\n", __func__, peerCtx); @@ -2947,7 +2947,7 @@ rdp_rail_wake_handler(struct wl_listener *listener, void *data) container_of(listener, RdpPeerContext, wake_listener); struct rdp_backend *b = peerCtx->rdpBackend; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); rdp_debug(b, "%s is called on peerCtx:%p\n", __func__, peerCtx); @@ -2961,7 +2961,7 @@ rdp_rail_notify_window_proxy_surface(struct weston_surface *proxy_surface) { struct rdp_backend *b = to_rdp_backend(proxy_surface->compositor); - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); b->proxy_surface = proxy_surface; } @@ -2972,7 +2972,7 @@ rdp_rail_notify_window_zorder_change(struct weston_compositor *compositor) struct rdp_backend *b = to_rdp_backend(compositor); RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); /* z order will be sent to client at next repaint */ peerCtx->is_window_zorder_dirty = true; @@ -2985,7 +2985,7 @@ rdp_rail_sync_window_status(freerdp_peer* client) struct rdp_backend *b = peerCtx->rdpBackend; struct weston_view *view; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); { RAIL_SYSPARAM_ORDER sysParamOrder = {}; @@ -3116,7 +3116,7 @@ rdp_rail_start_window_move( return; } - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); assert(rail_state); int posX=0, posY=0; @@ -3217,7 +3217,7 @@ rdp_rail_end_window_move(struct weston_surface* surface) return; } - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); assert(rail_state); peerCtx = (RdpPeerContext *)b->rdp_peer->context; @@ -3349,7 +3349,7 @@ rdp_drdynvc_init(freerdp_peer *client) { RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); + assert_compositor_thread(peerCtx->rdpBackend); /* Open Dynamic virtual channel */ peerCtx->drdynvc_server_context = drdynvc_server_context_new(peerCtx->vcm); @@ -3754,7 +3754,7 @@ rdp_rail_set_window_icon(struct weston_surface *surface, pixman_image_t *icon) if (!b->rdp_peer->settings->HiDefRemoteApp) return; - ASSERT_COMPOSITOR_THREAD(b); + assert_compositor_thread(b); if (!rail_state || rail_state->window_id == 0) { rdp_rail_create_window(NULL, (void *)surface); diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index f02e60602..059879418 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -109,16 +109,15 @@ void rdp_debug_print(struct weston_log_scope *log_scope, bool cont, char *fmt, . } } -#ifdef ENABLE_RDP_THREAD_CHECK void assert_compositor_thread(struct rdp_backend *b) { assert(b->compositor_tid == rdp_get_tid()); } + void assert_not_compositor_thread(struct rdp_backend *b) { assert(b->compositor_tid != rdp_get_tid()); } -#endif // ENABLE_RDP_THREAD_CHECK #ifdef HAVE_FREERDP_GFXREDIR_H BOOL @@ -231,7 +230,7 @@ rdp_free_shared_memory(struct rdp_backend *b, struct weston_rdp_shared_memory *s BOOL rdp_id_manager_init(struct rdp_backend *rdp_backend, struct rdp_id_manager *id_manager, UINT32 low_limit, UINT32 high_limit) { - ASSERT_COMPOSITOR_THREAD(rdp_backend); + assert_compositor_thread(rdp_backend); assert(id_manager->hash_table == NULL); assert(low_limit > 0); @@ -257,7 +256,7 @@ rdp_id_manager_init(struct rdp_backend *rdp_backend, struct rdp_id_manager *id_m void rdp_id_manager_free(struct rdp_id_manager *id_manager) { - ASSERT_COMPOSITOR_THREAD(id_manager->rdp_backend); + assert_compositor_thread(id_manager->rdp_backend); if (id_manager->id_used != 0) rdp_debug_error(id_manager->rdp_backend, "%s: possible id leak: %d\n", __func__, id_manager->id_used); @@ -278,7 +277,7 @@ rdp_id_manager_free(struct rdp_id_manager *id_manager) void rdp_id_manager_lock(struct rdp_id_manager *id_manager) { - ASSERT_NOT_COMPOSITOR_THREAD(id_manager->rdp_backend); + assert_not_compositor_thread(id_manager->rdp_backend); pthread_mutex_lock(&id_manager->mutex); id_manager->mutex_tid = rdp_get_tid(); @@ -287,7 +286,7 @@ rdp_id_manager_lock(struct rdp_id_manager *id_manager) void rdp_id_manager_unlock(struct rdp_id_manager *id_manager) { - ASSERT_NOT_COMPOSITOR_THREAD(id_manager->rdp_backend); + assert_not_compositor_thread(id_manager->rdp_backend); /* At unlock, restore compositor thread as owner */ id_manager->mutex_tid = id_manager->rdp_backend->compositor_tid; @@ -307,7 +306,7 @@ rdp_id_manager_lookup(struct rdp_id_manager *id_manager, UINT32 id) void rdp_id_manager_for_each(struct rdp_id_manager *id_manager, hash_table_iterator_func_t func, void *data) { - ASSERT_COMPOSITOR_THREAD(id_manager->rdp_backend); + assert_compositor_thread(id_manager->rdp_backend); if (!id_manager->hash_table) return; @@ -320,7 +319,7 @@ rdp_id_manager_allocate_id(struct rdp_id_manager *id_manager, void *object, UINT { UINT32 id = 0; - ASSERT_COMPOSITOR_THREAD(id_manager->rdp_backend); + assert_compositor_thread(id_manager->rdp_backend); assert(id_manager->hash_table); for(;id_manager->id_used < id_manager->id_total;) { @@ -343,7 +342,7 @@ rdp_id_manager_allocate_id(struct rdp_id_manager *id_manager, void *object, UINT void rdp_id_manager_free_id(struct rdp_id_manager *id_manager, UINT32 id) { - ASSERT_COMPOSITOR_THREAD(id_manager->rdp_backend); + assert_compositor_thread(id_manager->rdp_backend); assert(id_manager->hash_table); pthread_mutex_lock(&id_manager->mutex); @@ -382,7 +381,7 @@ rdp_dispatch_task_to_display_loop(RdpPeerContext *peerCtx, rdp_loop_task_func_t { /* this function is ONLY used to queue the task from FreeRDP thread, and the task to be processed at wayland display loop thread. */ - ASSERT_NOT_COMPOSITOR_THREAD(peerCtx->rdpBackend); + assert_not_compositor_thread(peerCtx->rdpBackend); task->peerCtx = peerCtx; task->func = func; @@ -403,7 +402,7 @@ rdp_dispatch_task(int fd, uint32_t mask, void *arg) eventfd_t dummy; /* this must be called back at wayland display loop thread */ - ASSERT_COMPOSITOR_THREAD(peerCtx->rdpBackend); + assert_compositor_thread(peerCtx->rdpBackend); eventfd_read(peerCtx->loop_task_event_source_fd, &dummy); @@ -492,3 +491,38 @@ rdp_destroy_dispatch_task_event_source(RdpPeerContext *peerCtx) pthread_mutex_destroy(&peerCtx->loop_task_list_mutex); } +/* This is a little tricky - it makes sure there's always at least + * one spare byte in the array in case the caller needs to add a + * null terminator to it. We can't just null terminate the array + * here, because some callers won't want that - and some won't + * like having an odd number of bytes. + */ +int +rdp_wl_array_read_fd(struct wl_array *array, int fd) +{ + int len, size; + char *data; + + /* Make sure we have at least 1024 bytes of space left */ + if (array->alloc - array->size < 1024) { + if (!wl_array_add(array, 1024)) { + errno = ENOMEM; + return -1; + } + array->size -= 1024; + } + data = (char *)array->data + array->size; + /* Leave one char in case the caller needs space for a + * null terminator */ + size = array->alloc - array->size - 1; + do { + len = read(fd, data, size); + } while (len == -1 && errno == EINTR); + + if (len == -1) + return -1; + + array->size += len; + + return len; +} From 87b1cfd807bd4198dee9877aaa69b5c4c7d7e074 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 27 Jun 2022 16:04:41 -0500 Subject: [PATCH 1532/1642] rdp: Move audio processing to front-end (#83) Move the bulk of the audio related code into the compositor front end, and instead put callbacks to setup functions into the rdp backend config structure. There are some changes to logging because we no longer have access to the backend's logging scopes. Instead we create rdp-audio and rdp-audio-in scopes. Signed-off-by: Derek Foreman --- compositor/main.c | 26 +- compositor/meson.build | 4 + .../backend-rdp => compositor}/rdpaudio.c | 402 +++++++++--------- compositor/rdpaudio.h | 90 ++++ .../backend-rdp => compositor}/rdpaudioin.c | 249 +++++------ include/libweston/backend-rdp.h | 11 +- libweston/backend-rdp/meson.build | 2 - libweston/backend-rdp/rdp.c | 39 +- libweston/backend-rdp/rdp.h | 45 +- 9 files changed, 487 insertions(+), 381 deletions(-) rename {libweston/backend-rdp => compositor}/rdpaudio.c (58%) create mode 100644 compositor/rdpaudio.h rename {libweston/backend-rdp => compositor}/rdpaudioin.c (64%) diff --git a/compositor/main.c b/compositor/main.c index 41a01d987..7b1795172 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -66,6 +66,8 @@ #include "../remoting/remoting-plugin.h" #include "../pipewire/pipewire-plugin.h" +#include + #define WINDOW_TITLE "Weston Compositor" /* flight recorder size (in bytes) */ #define DEFAULT_FLIGHT_REC_SIZE (5 * 1024 * 1024) @@ -2705,8 +2707,10 @@ weston_rdp_backend_config_init(struct weston_rdp_backend_config *config) config->no_clients_resize = 0; config->force_no_compression = 0; config->redirect_clipboard = false; - config->redirect_audio_playback = false; - config->redirect_audio_capture = false; + config->audio_in_setup = NULL; + config->audio_in_teardown = NULL; + config->audio_out_setup = NULL; + config->audio_out_teardown = NULL; config->rdp_monitor_refresh_rate = WESTON_RDP_MODE_FREQ; config->rail_config.use_rdpapplist = false; config->rail_config.use_shared_memory = false; @@ -2763,8 +2767,9 @@ load_rdp_backend(struct weston_compositor *c, { struct weston_rdp_backend_config config = {{ 0, }}; int ret = 0; - struct wet_output_config *parsed_options = wet_init_parsed_options(c); + bool audio_tmp; + if (!parsed_options) return -1; @@ -2788,8 +2793,19 @@ load_rdp_backend(struct weston_compositor *c, /* certain configurations are read from environment variables */ config.redirect_clipboard = read_rdp_config_bool("WESTON_RDP_CLIPBOARD", true); - config.redirect_audio_playback = read_rdp_config_bool("WESTON_RDP_AUDIO_PLAYBACK", true); - config.redirect_audio_capture = read_rdp_config_bool("WESTON_RDP_AUDIO_CAPTURE", true); + + audio_tmp = read_rdp_config_bool("WESTON_RDP_AUDIO_PLAYBACK", true); + if (audio_tmp) { + config.audio_out_setup = rdp_audio_out_init; + config.audio_out_teardown = rdp_audio_out_destroy; + } + + audio_tmp = read_rdp_config_bool("WESTON_RDP_AUDIO_CAPTURE", true); + if (audio_tmp) { + config.audio_in_setup = rdp_audio_in_init; + config.audio_in_teardown = rdp_audio_in_destroy; + } + config.rdp_monitor_refresh_rate = read_rdp_config_int("WESTON_RDP_MONITOR_REFRESH_RATE", WESTON_RDP_MODE_FREQ); config.rail_config.use_rdpapplist = read_rdp_config_bool("WESTON_RDP_APPLIST", true); diff --git a/compositor/meson.build b/compositor/meson.build index 9dc95f3fc..1e32cf30e 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -1,6 +1,8 @@ srcs_weston = [ git_version_h, 'main.c', + 'rdpaudio.c', + 'rdpaudioin.c', 'testsuite-util.c', 'text-backend.c', 'weston-screenshooter.c', @@ -18,6 +20,8 @@ deps_weston = [ dep_libevdev, dep_libdl, dep_threads, + dep_frdp, + dep_frdp_server, ] if get_option('xwayland') diff --git a/libweston/backend-rdp/rdpaudio.c b/compositor/rdpaudio.c similarity index 58% rename from libweston/backend-rdp/rdpaudio.c rename to compositor/rdpaudio.c index 34ef926cb..0f7cb919d 100644 --- a/libweston/backend-rdp/rdpaudio.c +++ b/compositor/rdpaudio.c @@ -37,7 +37,9 @@ #include #include #include -#include "rdp.h" +#include "rdpaudio.h" +#include +#include static AUDIO_FORMAT rdp_audio_supported_audio_formats[] = { { WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0, NULL }, @@ -287,9 +289,8 @@ AUDIO_FORMAT_to_String(UINT16 format) } static int -rdp_audio_setup_listener(RdpPeerContext *peerCtx) +rdp_audio_setup_listener(void) { - struct rdp_backend *b = peerCtx->rdpBackend; char *sink_socket_path; int fd; struct sockaddr_un s; @@ -298,14 +299,14 @@ rdp_audio_setup_listener(RdpPeerContext *peerCtx) fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) { - rdp_debug_error(b, "Couldn't create listener socket.\n"); + weston_log("Couldn't create listener socket.\n"); return -1; } sink_socket_path = getenv("PULSE_AUDIO_RDP_SINK"); if (sink_socket_path == NULL || sink_socket_path[0] == '\0') { close(fd); - rdp_debug_error(b, "Environment variable PULSE_AUDIO_RDP_SINK not set.\n"); + weston_log("Environment variable PULSE_AUDIO_RDP_SINK not set.\n"); return -1; } @@ -316,11 +317,11 @@ rdp_audio_setup_listener(RdpPeerContext *peerCtx) remove(s.sun_path); - rdp_debug(b, "Pulse Audio Sink listener socket on %s\n", s.sun_path); + weston_log("Pulse Audio Sink listener socket on %s\n", s.sun_path); error = bind(fd, (struct sockaddr *)&s, sizeof(struct sockaddr_un)); if (error != 0) { close(fd); - rdp_debug_error(b, "Failed to bind to listener socket (%d).\n", error); + weston_log("Failed to bind to listener socket (%d).\n", error); return -1; } @@ -342,39 +343,38 @@ rdp_audio_client_confirm_block( BYTE confirmBlockNum, UINT16 wtimestamp) { - RdpPeerContext *peerCtx = (RdpPeerContext*)context->data; - struct rdp_backend *b = peerCtx->rdpBackend; + struct audio_out_private *priv = context->data; - if (peerCtx->blockInfo[confirmBlockNum].ackReceivedTime != 0) { - assert(peerCtx->blockInfo[confirmBlockNum].ackPlayedTime == 0); - peerCtx->blockInfo[confirmBlockNum].ackPlayedTime = rdp_audio_timestamp(); + if (priv->blockInfo[confirmBlockNum].ackReceivedTime != 0) { + assert(priv->blockInfo[confirmBlockNum].ackPlayedTime == 0); + priv->blockInfo[confirmBlockNum].ackPlayedTime = rdp_audio_timestamp(); /* * Sum up all of the latency, we'll compute an average for the last period * requested by the sink. */ - if (peerCtx->nextValidBlock == -1 || peerCtx->nextValidBlock == confirmBlockNum) { - peerCtx->nextValidBlock = -1; + if (priv->nextValidBlock == -1 || priv->nextValidBlock == confirmBlockNum) { + priv->nextValidBlock = -1; - peerCtx->accumulatedRenderedLatency += - peerCtx->blockInfo[confirmBlockNum].ackPlayedTime - - peerCtx->blockInfo[confirmBlockNum].submissionTime; - peerCtx->accumulatedRenderedLatencyCount++; + priv->accumulatedRenderedLatency += + priv->blockInfo[confirmBlockNum].ackPlayedTime - + priv->blockInfo[confirmBlockNum].submissionTime; + priv->accumulatedRenderedLatencyCount++; } uint64_t one = 1; - if (write(peerCtx->audioInSem, &one, sizeof(one)) != sizeof(uint64_t)) { - rdp_debug_error(b, "RDP Audio error at confirm_block while writing to audioInSem (%s)\n", strerror(errno)); + if (write(priv->audioSem, &one, sizeof(one)) != sizeof(uint64_t)) { + weston_log("RDP Audio error at confirm_block while writing to audioSem (%s)\n", strerror(errno)); return ERROR_INTERNAL_ERROR; } } else { - peerCtx->blockInfo[confirmBlockNum].ackReceivedTime = rdp_audio_timestamp(); + priv->blockInfo[confirmBlockNum].ackReceivedTime = rdp_audio_timestamp(); - peerCtx->accumulatedNetworkLatency += - peerCtx->blockInfo[confirmBlockNum].ackReceivedTime - - peerCtx->blockInfo[confirmBlockNum].submissionTime; - peerCtx->accumulatedNetworkLatencyCount++; + priv->accumulatedNetworkLatency += + priv->blockInfo[confirmBlockNum].ackReceivedTime - + priv->blockInfo[confirmBlockNum].submissionTime; + priv->accumulatedNetworkLatencyCount++; } return 0; @@ -382,21 +382,20 @@ rdp_audio_client_confirm_block( static int rdp_audio_handle_version( - RdpPeerContext *peerCtx, + struct audio_out_private *priv, UINT PAVersion) { - struct rdp_backend *b = peerCtx->rdpBackend; uint32_t version = RDP_SINK_INTERFACE_VERSION; ssize_t sizeSent; - peerCtx->PAVersion = PAVersion; + priv->PAVersion = PAVersion; - rdp_debug(b, "RDP Sink version (%d - %d)\n", PAVersion, version); + weston_log("RDP Sink version (%d - %d)\n", PAVersion, version); - sizeSent = send(peerCtx->pulseAudioSinkFd, &version, + sizeSent = send(priv->pulseAudioSinkFd, &version, sizeof(version), MSG_DONTWAIT); if (sizeSent != sizeof(version)) { - rdp_debug_error(b, "RDP audio error responding to version request sent:%ld. %s\n", + weston_log("RDP audio error responding to version request sent:%ld. %s\n", sizeSent, strerror(errno)); return -1; } @@ -406,44 +405,43 @@ rdp_audio_handle_version( static int rdp_audio_handle_transfer( - RdpPeerContext *peerCtx, + struct audio_out_private *priv, UINT bytesLeft, UINT64 timestamp) { - struct rdp_backend *b = peerCtx->rdpBackend; - int nbFrames = bytesLeft / peerCtx->bytesPerFrame; + int nbFrames = bytesLeft / priv->bytesPerFrame; UINT bytesRead = 0; ssize_t sizeRead = 0; - if (bytesLeft > peerCtx->audioBufferSize) { - if(peerCtx->audioBuffer) - free(peerCtx->audioBuffer); + if (bytesLeft > priv->audioBufferSize) { + if(priv->audioBuffer) + free(priv->audioBuffer); - peerCtx->audioBuffer = zalloc(bytesLeft); - if (!peerCtx->audioBuffer) { - rdp_debug_error(b, "RDP Audio error zalloc(%d) failed.\n", bytesLeft); + priv->audioBuffer = zalloc(bytesLeft); + if (!priv->audioBuffer) { + weston_log("RDP Audio error zalloc(%d) failed.\n", bytesLeft); return -1; } - peerCtx->audioBufferSize = bytesLeft; + priv->audioBufferSize = bytesLeft; } - assert((bytesLeft % peerCtx->bytesPerFrame) == 0); + assert((bytesLeft % priv->bytesPerFrame) == 0); /* * Read the expected amount of data over the sink before sending it to RDP */ while (bytesLeft > 0) { - sizeRead = read(peerCtx->pulseAudioSinkFd, - peerCtx->audioBuffer + bytesRead, bytesLeft); + sizeRead = read(priv->pulseAudioSinkFd, + priv->audioBuffer + bytesRead, bytesLeft); if (sizeRead <= 0) { - rdp_debug_error(b, "RDP Audio error while reading data from sink socket sizeRead:%ld. %s\n", sizeRead, strerror(errno)); + weston_log("RDP Audio error while reading data from sink socket sizeRead:%ld. %s\n", sizeRead, strerror(errno)); return -1; } bytesRead += sizeRead; bytesLeft -= sizeRead; } - BYTE* audioBuffer = peerCtx->audioBuffer; + BYTE* audioBuffer = priv->audioBuffer; while (nbFrames > 0) { /* * Ensure we don't overrun our audio buffers. @@ -454,8 +452,8 @@ rdp_audio_handle_transfer( * of our incoming audio packet from pulse. */ uint64_t dummy; - if (read(peerCtx->audioInSem, &dummy, sizeof(dummy)) != sizeof(uint64_t)) { - rdp_debug_error(b, "RDP Audio error at handle_transfer while reading from audioInSem (%s)\n", strerror(errno)); + if (read(priv->audioSem, &dummy, sizeof(dummy)) != sizeof(uint64_t)) { + weston_log("RDP Audio error at handle_transfer while reading from audioSem (%s)\n", strerror(errno)); return -1; } @@ -465,36 +463,36 @@ rdp_audio_handle_transfer( * * Set 0 to timestamp to disable A/V sync at client side. */ - BYTE block_no = peerCtx->rdpsnd_server_context->block_no; - peerCtx->blockInfo[block_no].submissionTime = timestamp; - peerCtx->blockInfo[block_no].ackReceivedTime = 0; - peerCtx->blockInfo[block_no].ackPlayedTime = 0; - if (peerCtx->rdpsnd_server_context->SendSamples(peerCtx->rdpsnd_server_context, + BYTE block_no = priv->rdpsnd_server_context->block_no; + priv->blockInfo[block_no].submissionTime = timestamp; + priv->blockInfo[block_no].ackReceivedTime = 0; + priv->blockInfo[block_no].ackPlayedTime = 0; + if (priv->rdpsnd_server_context->SendSamples(priv->rdpsnd_server_context, audioBuffer, MIN(nbFrames, AUDIO_FRAMES_PER_RDP_PACKET), 0) != 0) { - rdp_debug_error(b, "RDP Audio error while SendSamples\n"); + weston_log("RDP Audio error while SendSamples\n"); return -1; } - if (block_no == peerCtx->rdpsnd_server_context->block_no) { + if (block_no == priv->rdpsnd_server_context->block_no) { /* * Didn't submit any audio this time around, adjust our semaphore. */ uint64_t one = 1; - if (write(peerCtx->audioInSem, &one, sizeof(one)) != sizeof(uint64_t)) { - rdp_debug_error(b, "RDP Audio error at handle_transfer while writing to audioInSem (%s)\n", strerror(errno)); + if (write(priv->audioSem, &one, sizeof(one)) != sizeof(uint64_t)) { + weston_log("RDP Audio error at handle_transfer while writing to audioSem (%s)\n", strerror(errno)); return -1; } } else { /* * There shouldn't be more than one packet of audio sent by RDP. */ - assert((block_no > peerCtx->rdpsnd_server_context->block_no) || (block_no+1) == peerCtx->rdpsnd_server_context->block_no); - assert((block_no < peerCtx->rdpsnd_server_context->block_no) || (block_no==255 && peerCtx->rdpsnd_server_context->block_no==0)); + assert((block_no > priv->rdpsnd_server_context->block_no) || (block_no+1) == priv->rdpsnd_server_context->block_no); + assert((block_no < priv->rdpsnd_server_context->block_no) || (block_no==255 && priv->rdpsnd_server_context->block_no==0)); } - audioBuffer += AUDIO_FRAMES_PER_RDP_PACKET * peerCtx->bytesPerFrame; + audioBuffer += AUDIO_FRAMES_PER_RDP_PACKET * priv->bytesPerFrame; nbFrames -= AUDIO_FRAMES_PER_RDP_PACKET; } @@ -502,39 +500,37 @@ rdp_audio_handle_transfer( } static int -rdp_audio_handle_get_latency( - RdpPeerContext *peerCtx) +rdp_audio_handle_get_latency(struct audio_out_private *priv) { - struct rdp_backend *b = peerCtx->rdpBackend; UINT networkLatency; UINT renderedLatency; ssize_t sizeSent; - if (peerCtx->accumulatedNetworkLatencyCount > 0) { - networkLatency = peerCtx->accumulatedNetworkLatency / peerCtx->accumulatedNetworkLatencyCount; - peerCtx->lastNetworkLatency = networkLatency; - peerCtx->accumulatedNetworkLatency = 0; - peerCtx->accumulatedNetworkLatencyCount = 0; + if (priv->accumulatedNetworkLatencyCount > 0) { + networkLatency = priv->accumulatedNetworkLatency / priv->accumulatedNetworkLatencyCount; + priv->lastNetworkLatency = networkLatency; + priv->accumulatedNetworkLatency = 0; + priv->accumulatedNetworkLatencyCount = 0; } else { - networkLatency = peerCtx->lastNetworkLatency; + networkLatency = priv->lastNetworkLatency; } - if (peerCtx->accumulatedRenderedLatencyCount > 0) { - renderedLatency = peerCtx->accumulatedRenderedLatency / peerCtx->accumulatedRenderedLatencyCount; - peerCtx->lastRenderedLatency = renderedLatency; - peerCtx->accumulatedRenderedLatency = 0; - peerCtx->accumulatedRenderedLatencyCount = 0; + if (priv->accumulatedRenderedLatencyCount > 0) { + renderedLatency = priv->accumulatedRenderedLatency / priv->accumulatedRenderedLatencyCount; + priv->lastRenderedLatency = renderedLatency; + priv->accumulatedRenderedLatency = 0; + priv->accumulatedRenderedLatencyCount = 0; } else { - renderedLatency = peerCtx->lastRenderedLatency; + renderedLatency = priv->lastRenderedLatency; } if (renderedLatency > networkLatency) renderedLatency -= networkLatency; - sizeSent = send(peerCtx->pulseAudioSinkFd, &renderedLatency, + sizeSent = send(priv->pulseAudioSinkFd, &renderedLatency, sizeof(renderedLatency), MSG_DONTWAIT); if (sizeSent != sizeof(renderedLatency)) { - rdp_debug_error(b, "RDP audio error responding to latency request sent:%ld. %s\n", + weston_log("RDP audio error responding to latency request sent:%ld. %s\n", sizeSent, strerror(errno)); return -1; } @@ -550,52 +546,51 @@ static void signalhandler(int sig) { static void* rdp_audio_pulse_audio_sink_thread(void *context) { - RdpPeerContext *peerCtx = (RdpPeerContext*)context; - struct rdp_backend *b = peerCtx->rdpBackend; + struct audio_out_private *priv = context; struct sigaction act; sigset_t set; sigemptyset(&set); if (sigaddset(&set, SIGUSR2) == -1) { - rdp_debug_error(b, "Audio sink thread: sigaddset(SIGUSR2) failed.\n"); + weston_log("Audio sink thread: sigaddset(SIGUSR2) failed.\n"); return NULL; } if (pthread_sigmask(SIG_UNBLOCK, &set, NULL) != 0) { - rdp_debug_error(b, "Audio sink thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); + weston_log("Audio sink thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); return NULL; } act.sa_flags = 0; act.sa_mask = set; act.sa_handler = &signalhandler; if (sigaction(SIGUSR2, &act, NULL) == -1) { - rdp_debug_error(b, "Audio sink thread: sigaction(SIGUSR2) failed.\n"); + weston_log("Audio sink thread: sigaction(SIGUSR2) failed.\n"); return NULL; } - assert(peerCtx->pulseAudioSinkListenerFd != 0); + assert(priv->pulseAudioSinkListenerFd != 0); for (;;) { - rdp_debug(b, "Audio sink thread: Listening for audio connection.\n"); + rdp_audio_debug(priv, "Audio sink thread: Listening for audio connection.\n"); - if (peerCtx->audioExitSignal) { - rdp_debug(b, "Audio sink thread is asked to exit (accept loop)\n"); + if (priv->audioExitSignal) { + rdp_audio_debug(priv, "Audio sink thread is asked to exit (accept loop)\n"); break; } /* * Wait for a connection on our listening socket */ - assert(peerCtx->pulseAudioSinkFd < 0); - peerCtx->pulseAudioSinkFd = accept(peerCtx->pulseAudioSinkListenerFd, + assert(priv->pulseAudioSinkFd < 0); + priv->pulseAudioSinkFd = accept(priv->pulseAudioSinkListenerFd, NULL, NULL); - if (peerCtx->pulseAudioSinkFd < 0) { - rdp_debug_error(b, "Audio sink thread: Listener connection error (%s)\n", strerror(errno)); + if (priv->pulseAudioSinkFd < 0) { + weston_log("Audio sink thread: Listener connection error (%s)\n", strerror(errno)); continue; } else { - rdp_debug(b, "Audio sink thread: connection successful on socket (%d).\n", - peerCtx->pulseAudioSinkFd); + rdp_audio_debug(priv, "Audio sink thread: connection successful on socket (%d).\n", + priv->pulseAudioSinkFd); } - + /* * Read audio from the socket and stream to the RDP Client. */ @@ -603,44 +598,44 @@ rdp_audio_pulse_audio_sink_thread(void *context) rdp_audio_cmd_header header; ssize_t sizeRead; - sizeRead = read(peerCtx->pulseAudioSinkFd, &header, sizeof(header)); + sizeRead = read(priv->pulseAudioSinkFd, &header, sizeof(header)); /* pulseaudio RDP sink always send sizeof(header) regardless command type. */ if (sizeRead != sizeof(header)) { - rdp_debug_error(b, "Audio sink thread: error while reading from sink socket sizeRead:%ld. %s\n", + weston_log("Audio sink thread: error while reading from sink socket sizeRead:%ld. %s\n", sizeRead, strerror(errno)); break; } else if (header.cmd == RDP_AUDIO_CMD_VERSION) { - rdp_debug_verbose(b, "Audio sink command RDP_AUDIO_CMD_VERSION: %d\n", header.version); - if (rdp_audio_handle_version(peerCtx, header.version) < 0) + rdp_audio_debug(priv, "Audio sink command RDP_AUDIO_CMD_VERSION: %d\n", header.version); + if (rdp_audio_handle_version(priv, header.version) < 0) break; } else if (header.cmd == RDP_AUDIO_CMD_TRANSFER) { - rdp_debug_verbose(b, "Audio sink command RDP_AUDIO_CMD_TRANSFER: %d\n", header.transfer.bytes); - if (rdp_audio_handle_transfer(peerCtx, header.transfer.bytes, header.transfer.timestamp) < 0) + rdp_audio_debug(priv, "Audio sink command RDP_AUDIO_CMD_TRANSFER: %d\n", header.transfer.bytes); + if (rdp_audio_handle_transfer(priv, header.transfer.bytes, header.transfer.timestamp) < 0) break; } else if (header.cmd == RDP_AUDIO_CMD_GET_LATENCY) { - rdp_debug_verbose(b, "Audio sink command RDP_AUDIO_CMD_GET_LATENCY\n"); - if (rdp_audio_handle_get_latency(peerCtx) < 0) + rdp_audio_debug(priv, "Audio sink command RDP_AUDIO_CMD_GET_LATENCY\n"); + if (rdp_audio_handle_get_latency(priv) < 0) break; } else if (header.cmd == RDP_AUDIO_CMD_RESET_LATENCY) { - rdp_debug_verbose(b, "Audio sink command RDP_AUDIO_CMD_RESET_LATENCY\n"); - peerCtx->nextValidBlock = peerCtx->rdpsnd_server_context->block_no; - peerCtx->lastNetworkLatency = 0; - peerCtx->accumulatedNetworkLatency = 0; - peerCtx->accumulatedNetworkLatencyCount = 0; - peerCtx->lastRenderedLatency = 0; - peerCtx->accumulatedRenderedLatency = 0; - peerCtx->accumulatedRenderedLatencyCount = 0; + rdp_audio_debug(priv, "Audio sink command RDP_AUDIO_CMD_RESET_LATENCY\n"); + priv->nextValidBlock = priv->rdpsnd_server_context->block_no; + priv->lastNetworkLatency = 0; + priv->accumulatedNetworkLatency = 0; + priv->accumulatedNetworkLatencyCount = 0; + priv->lastRenderedLatency = 0; + priv->accumulatedRenderedLatency = 0; + priv->accumulatedRenderedLatencyCount = 0; } else { - rdp_debug_error(b, "Audio sink thread: unknown command from sink.\n"); + weston_log("Audio sink thread: unknown command from sink.\n"); break; } } - close(peerCtx->pulseAudioSinkFd); - peerCtx->pulseAudioSinkFd = -1; + close(priv->pulseAudioSinkFd); + priv->pulseAudioSinkFd = -1; } - assert(peerCtx->pulseAudioSinkFd < 0); + assert(priv->pulseAudioSinkFd < 0); return NULL; } @@ -648,27 +643,26 @@ rdp_audio_pulse_audio_sink_thread(void *context) static void rdp_audio_client_activated(RdpsndServerContext* context) { - RdpPeerContext *peerCtx = (RdpPeerContext*)context->data; - struct rdp_backend *b = peerCtx->rdpBackend; + struct audio_out_private *priv = context->data; int format = -1; int i, j; - - rdp_debug(b, "rdp_audio_server_activated: %d audio formats supported.\n", + + rdp_audio_debug(priv, "rdp_audio_server_activated: %d audio formats supported.\n", context->num_client_formats); for (i = 0; i < context->num_client_formats; i++) { - rdp_debug(b, "\t[%d] - Format(%s) - Bits(%d), Channels(%d), Frequency(%d)\n", - i, - AUDIO_FORMAT_to_String(context->client_formats[i].wFormatTag), - context->client_formats[i].wBitsPerSample, - context->client_formats[i].nChannels, - context->client_formats[i].nSamplesPerSec); + rdp_audio_debug(priv, "\t[%d] - Format(%s) - Bits(%d), Channels(%d), Frequency(%d)\n", + i, + AUDIO_FORMAT_to_String(context->client_formats[i].wFormatTag), + context->client_formats[i].wBitsPerSample, + context->client_formats[i].nChannels, + context->client_formats[i].nSamplesPerSec); for (j = 0; j < (int)context->num_server_formats; j++) { if ((context->client_formats[i].wFormatTag == context->server_formats[j].wFormatTag) && (context->client_formats[i].nChannels == context->server_formats[j].nChannels) && (context->client_formats[i].nSamplesPerSec == context->server_formats[j].nSamplesPerSec)) { - rdp_debug(b, "RDPAudio - Agreed on format %d.\n", i); + rdp_audio_debug(priv, "RDPAudio - Agreed on format %d.\n", i); format = i; break; } @@ -676,139 +670,151 @@ rdp_audio_client_activated(RdpsndServerContext* context) } if (format != -1) { - peerCtx->nextValidBlock = -1; - peerCtx->bytesPerFrame = (context->client_formats[format].wBitsPerSample / 8) * context->client_formats[format].nChannels; + priv->nextValidBlock = -1; + priv->bytesPerFrame = (context->client_formats[format].wBitsPerSample / 8) * context->client_formats[format].nChannels; context->latency = AUDIO_LATENCY; - rdp_debug(b, "rdp_audio_server_activated: bytesPerFrame:%d, latency:%d\n", - peerCtx->bytesPerFrame, context->latency); + rdp_audio_debug(priv, "rdp_audio_server_activated: bytesPerFrame:%d, latency:%d\n", + priv->bytesPerFrame, context->latency); context->SelectFormat(context, format); context->SetVolume(context, 0x7FFF, 0x7FFF); - peerCtx->pulseAudioSinkListenerFd = rdp_audio_setup_listener(peerCtx); - if (peerCtx->pulseAudioSinkListenerFd < 0) { - rdp_debug_error(b, "RDPAudio - Failed to create listener socket\n"); - } else if (pthread_create(&peerCtx->pulseAudioSinkThread, NULL, rdp_audio_pulse_audio_sink_thread, (void*)peerCtx) < 0) { - rdp_debug_error(b, "RDPAudio - Failed to start Pulse Audio Sink Thread. No audio will be available.\n"); + priv->pulseAudioSinkListenerFd = rdp_audio_setup_listener(); + if (priv->pulseAudioSinkListenerFd < 0) { + weston_log("RDPAudio - Failed to create listener socket\n"); + } else if (pthread_create(&priv->pulseAudioSinkThread, NULL, rdp_audio_pulse_audio_sink_thread, (void*)priv) < 0) { + weston_log("RDPAudio - Failed to start Pulse Audio Sink Thread. No audio will be available.\n"); } } else { - rdp_debug_error(b, "RDPAudio - No agreeded format.\n"); + weston_log("RDPAudio - No agreeded format.\n"); } } -int -rdp_audio_init(RdpPeerContext *peerCtx) +void * +rdp_audio_out_init(struct weston_compositor *c, HANDLE vcm) { - struct rdp_backend *b = peerCtx->rdpBackend; + struct audio_out_private *priv; char *s; - peerCtx->rdpsnd_server_context = rdpsnd_server_context_new(peerCtx->vcm); - if (!peerCtx->rdpsnd_server_context) { - rdp_debug_error(b, "RDPAudio - Couldn't initialize audio virtual channel.\n"); - return 0; // Continue without audio + priv = xzalloc(sizeof *priv); + priv->rdpsnd_server_context = rdpsnd_server_context_new(vcm); + if (!priv->rdpsnd_server_context) { + weston_log("RDPAudio - Couldn't initialize audio virtual channel.\n"); + return NULL; } - peerCtx->audioExitSignal = FALSE; - peerCtx->pulseAudioSinkThread = 0; - peerCtx->pulseAudioSinkListenerFd = -1; - peerCtx->pulseAudioSinkFd = -1; - peerCtx->audioBuffer = NULL; + priv->debug = weston_compositor_add_log_scope(c, "rdp-audio", + "Debug messages for RDP audio output\n", + NULL, NULL, NULL); + + priv->audioExitSignal = FALSE; + priv->pulseAudioSinkThread = 0; + priv->pulseAudioSinkListenerFd = -1; + priv->pulseAudioSinkFd = -1; + priv->audioBuffer = NULL; - peerCtx->audioInSem = eventfd(256, EFD_SEMAPHORE | EFD_CLOEXEC); - if (!peerCtx->audioInSem) { - rdp_debug_error(b, "RDPAudio - Couldn't initialize event semaphore.\n"); + priv->audioSem = eventfd(256, EFD_SEMAPHORE | EFD_CLOEXEC); + if (!priv->audioSem) { + weston_log("RDPAudio - Couldn't initialize event semaphore.\n"); goto Error_Exit; } /* this will be freed by FreeRDP at rdpsnd_server_context_free. */ AUDIO_FORMAT *audio_formats = malloc(sizeof rdp_audio_supported_audio_formats); if (!audio_formats) { - rdp_debug_error(b, "RDPAudio - Couldn't allocate memory for audio formats.\n"); + weston_log("RDPAudio - Couldn't allocate memory for audio formats.\n"); goto Error_Exit; } memcpy(audio_formats, rdp_audio_supported_audio_formats, sizeof rdp_audio_supported_audio_formats); - peerCtx->rdpsnd_server_context->data = (void*)peerCtx; - peerCtx->rdpsnd_server_context->Activated = rdp_audio_client_activated; - peerCtx->rdpsnd_server_context->ConfirmBlock = rdp_audio_client_confirm_block; - peerCtx->rdpsnd_server_context->num_server_formats = ARRAYSIZE(rdp_audio_supported_audio_formats); - peerCtx->rdpsnd_server_context->server_formats = audio_formats; - peerCtx->rdpsnd_server_context->src_format = &rdp_audio_supported_audio_formats[0]; + priv->rdpsnd_server_context->data = (void*)priv; + priv->rdpsnd_server_context->Activated = rdp_audio_client_activated; + priv->rdpsnd_server_context->ConfirmBlock = rdp_audio_client_confirm_block; + priv->rdpsnd_server_context->num_server_formats = ARRAYSIZE(rdp_audio_supported_audio_formats); + priv->rdpsnd_server_context->server_formats = audio_formats; + priv->rdpsnd_server_context->src_format = &rdp_audio_supported_audio_formats[0]; #if HAVE_RDPSND_DYNAMIC_VIRTUAL_CHANNEL - peerCtx->rdpsnd_server_context->use_dynamic_virtual_channel = TRUE; + priv->rdpsnd_server_context->use_dynamic_virtual_channel = TRUE; s = getenv("WESTON_RDP_DISABLE_AUDIO_PLAYBACK_DYNAMIC_VIRTUAL_CHANNEL"); if (s) { if (strcmp(s, "true") == 0) { - peerCtx->rdpsnd_server_context->use_dynamic_virtual_channel = FALSE; - rdp_debug_error(b, "RDPAudio - force static channel.\n"); + priv->rdpsnd_server_context->use_dynamic_virtual_channel = FALSE; + weston_log("RDPAudio - force static channel.\n"); } } #endif // HAVE_RDPAUDIO_DYNAMIC_VIRTUAL_CHANNEL /* Calling Initialize does Start as well */ - if (peerCtx->rdpsnd_server_context->Initialize(peerCtx->rdpsnd_server_context, TRUE) != 0) + if (priv->rdpsnd_server_context->Initialize(priv->rdpsnd_server_context, TRUE) != 0) goto Error_Exit; - return 0; + return priv; Error_Exit: - if (peerCtx->audioInSem != -1) { - close(peerCtx->audioInSem); - peerCtx->audioInSem = -1; + if (priv->debug) + weston_log_scope_destroy(priv->debug); + + if (priv->audioSem != -1) { + close(priv->audioSem); + priv->audioSem = -1; } - if (peerCtx->rdpsnd_server_context) { - rdpsnd_server_context_free(peerCtx->rdpsnd_server_context); - peerCtx->rdpsnd_server_context = NULL; + if (priv->rdpsnd_server_context) { + rdpsnd_server_context_free(priv->rdpsnd_server_context); + priv->rdpsnd_server_context = NULL; } - return 0; // Continue without audio + free(priv); + return NULL; } void -rdp_audio_destroy(RdpPeerContext *peerCtx) +rdp_audio_out_destroy(void *audio_out_private) { - if (peerCtx->rdpsnd_server_context) { - - if (peerCtx->pulseAudioSinkThread) { - peerCtx->audioExitSignal = TRUE; - shutdown(peerCtx->pulseAudioSinkListenerFd, SHUT_RDWR); - shutdown(peerCtx->pulseAudioSinkFd, SHUT_RDWR); - pthread_kill(peerCtx->pulseAudioSinkThread, SIGUSR2); - pthread_join(peerCtx->pulseAudioSinkThread, NULL); - - if (peerCtx->pulseAudioSinkListenerFd != -1) { - close(peerCtx->pulseAudioSinkListenerFd); - peerCtx->pulseAudioSinkListenerFd = -1; + struct audio_out_private *priv = audio_out_private; + + if (priv->rdpsnd_server_context) { + + if (priv->pulseAudioSinkThread) { + priv->audioExitSignal = TRUE; + shutdown(priv->pulseAudioSinkListenerFd, SHUT_RDWR); + shutdown(priv->pulseAudioSinkFd, SHUT_RDWR); + pthread_kill(priv->pulseAudioSinkThread, SIGUSR2); + pthread_join(priv->pulseAudioSinkThread, NULL); + + if (priv->pulseAudioSinkListenerFd != -1) { + close(priv->pulseAudioSinkListenerFd); + priv->pulseAudioSinkListenerFd = -1; } - if (peerCtx->pulseAudioSinkFd != -1) { - close(peerCtx->pulseAudioSinkFd); - peerCtx->pulseAudioSinkFd = -1; + if (priv->pulseAudioSinkFd != -1) { + close(priv->pulseAudioSinkFd); + priv->pulseAudioSinkFd = -1; } - if (peerCtx->audioBuffer) { - free(peerCtx->audioBuffer); - peerCtx->audioBuffer = NULL; + if (priv->audioBuffer) { + free(priv->audioBuffer); + priv->audioBuffer = NULL; } - peerCtx->pulseAudioSinkThread = 0; + priv->pulseAudioSinkThread = 0; } - assert(peerCtx->pulseAudioSinkListenerFd < 0); - assert(peerCtx->pulseAudioSinkFd < 0); - assert(peerCtx->audioBuffer == NULL); + assert(priv->pulseAudioSinkListenerFd < 0); + assert(priv->pulseAudioSinkFd < 0); + assert(priv->audioBuffer == NULL); - peerCtx->rdpsnd_server_context->Close(peerCtx->rdpsnd_server_context); - peerCtx->rdpsnd_server_context->Stop(peerCtx->rdpsnd_server_context); + priv->rdpsnd_server_context->Close(priv->rdpsnd_server_context); + priv->rdpsnd_server_context->Stop(priv->rdpsnd_server_context); - if (peerCtx->audioInSem != -1) { - close(peerCtx->audioInSem); - peerCtx->audioInSem = -1; + if (priv->audioSem != -1) { + close(priv->audioSem); + priv->audioSem = -1; } - rdpsnd_server_context_free(peerCtx->rdpsnd_server_context); - peerCtx->rdpsnd_server_context = NULL; + rdpsnd_server_context_free(priv->rdpsnd_server_context); + priv->rdpsnd_server_context = NULL; } + free(priv); } diff --git a/compositor/rdpaudio.h b/compositor/rdpaudio.h new file mode 100644 index 000000000..c09fc271d --- /dev/null +++ b/compositor/rdpaudio.h @@ -0,0 +1,90 @@ +/* + * Copyright © 2022 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. + */ + +#ifndef RDP_AUDIO_H +#define RDP_AUDIO_H + +#include +#include +#include +#include + +#define rdp_audio_debug(p, ...) \ + weston_log_scope_printf((p)->debug, __VA_ARGS__) + +typedef struct _rdp_audio_block_info { + UINT64 submissionTime; + UINT64 ackReceivedTime; + UINT64 ackPlayedTime; +} rdp_audio_block_info; + +struct audio_out_private { + RdpsndServerContext* rdpsnd_server_context; + struct weston_log_scope *debug; + BOOL audioExitSignal; + int pulseAudioSinkListenerFd; + int pulseAudioSinkFd; + pthread_t pulseAudioSinkThread; + int bytesPerFrame; + UINT audioBufferSize; + BYTE* audioBuffer; + BYTE lastBlockSent; + UINT64 lastNetworkLatency; + UINT64 accumulatedNetworkLatency; + UINT accumulatedNetworkLatencyCount; + UINT64 lastRenderedLatency; + UINT64 accumulatedRenderedLatency; + UINT accumulatedRenderedLatencyCount; + rdp_audio_block_info blockInfo[256]; + int nextValidBlock; + UINT PAVersion; + int audioSem; +}; + +struct audio_in_private { + audin_server_context* audin_server_context; + struct weston_log_scope *debug; + BOOL audioInExitSignal; + int pulseAudioSourceListenerFd; + int pulseAudioSourceFd; + int closeAudioSourceFd; + pthread_t pulseAudioSourceThread; + BOOL isAudioInStreamOpened; +}; + +void * +rdp_audio_out_init(struct weston_compositor *c, HANDLE vcm); + +void +rdp_audio_out_destroy(void *audio_out_private); + +void * +rdp_audio_in_init(struct weston_compositor *c, HANDLE vcm); + +void +rdp_audio_in_destroy(void *audio_in_private); + + +#endif diff --git a/libweston/backend-rdp/rdpaudioin.c b/compositor/rdpaudioin.c similarity index 64% rename from libweston/backend-rdp/rdpaudioin.c rename to compositor/rdpaudioin.c index 2b654aba5..ceda28ae2 100644 --- a/libweston/backend-rdp/rdpaudioin.c +++ b/compositor/rdpaudioin.c @@ -38,7 +38,9 @@ #include #include #include -#include "rdp.h" +#include "rdpaudio.h" +#include +#include static AUDIO_FORMAT rdp_audioin_supported_audio_formats[] = { { WAVE_FORMAT_PCM, 1, 44100, 88200, 2, 16, 0, NULL }, @@ -266,9 +268,8 @@ AUDIO_FORMAT_to_String(UINT16 format) } static int -rdp_audioin_setup_listener(RdpPeerContext *peerCtx) +rdp_audioin_setup_listener(struct audio_in_private *priv) { - struct rdp_backend *b = peerCtx->rdpBackend; char *source_socket_path; int fd; struct sockaddr_un s; @@ -277,14 +278,14 @@ rdp_audioin_setup_listener(RdpPeerContext *peerCtx) fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) { - rdp_debug_error(b, "Couldn't create audioin listener socket.\n"); + weston_log("Couldn't create audioin listener socket.\n"); return -1; } source_socket_path = getenv("PULSE_AUDIO_RDP_SOURCE"); if (source_socket_path == NULL || source_socket_path[0] == '\0') { close(fd); - rdp_debug_error(b, "Environment variable PULSE_AUDIO_RDP_SOURCE not set.\n"); + weston_log("Environment variable PULSE_AUDIO_RDP_SOURCE not set.\n"); return -1; } @@ -295,11 +296,11 @@ rdp_audioin_setup_listener(RdpPeerContext *peerCtx) remove(s.sun_path); - rdp_debug(b, "Pulse Audio source listener socket on %s\n", s.sun_path); + rdp_audio_debug(priv, "Pulse Audio source listener socket on %s\n", s.sun_path); error = bind(fd, (struct sockaddr *)&s, sizeof(struct sockaddr_un)); if (error != 0) { close(fd); - rdp_debug_error(b, "Failed to bind to listener socket for audioin (%d).\n", error); + weston_log("Failed to bind to listener socket for audioin (%d).\n", error); return -1; } @@ -310,27 +311,26 @@ rdp_audioin_setup_listener(RdpPeerContext *peerCtx) static UINT rdp_audioin_client_opening(audin_server_context* context) { - RdpPeerContext *peerCtx = (RdpPeerContext*)context->data; - struct rdp_backend *b = peerCtx->rdpBackend; + struct audio_in_private *priv = context->data; int format = -1; int i, j; - rdp_debug(b, "RDP Audio Open: %d audio formats supported.\n", + rdp_audio_debug(priv, "RDP Audio Open: %d audio formats supported.\n", (int)context->num_client_formats); for (i = 0; i < (int)context->num_client_formats; i++) { - rdp_debug(b, "\t[%d] - Format(%s) - Bits(%d), Channels(%d), Frequency(%d)\n", - i, - AUDIO_FORMAT_to_String(context->client_formats[i].wFormatTag), - context->client_formats[i].wBitsPerSample, - context->client_formats[i].nChannels, - context->client_formats[i].nSamplesPerSec); + rdp_audio_debug(priv, "\t[%d] - Format(%s) - Bits(%d), Channels(%d), Frequency(%d)\n", + i, + AUDIO_FORMAT_to_String(context->client_formats[i].wFormatTag), + context->client_formats[i].wBitsPerSample, + context->client_formats[i].nChannels, + context->client_formats[i].nSamplesPerSec); for (j = 0; j < (int)context->num_server_formats; j++) { if ((context->client_formats[i].wFormatTag == context->server_formats[j].wFormatTag) && (context->client_formats[i].nChannels == context->server_formats[j].nChannels) && (context->client_formats[i].nSamplesPerSec == context->server_formats[j].nSamplesPerSec)) { - rdp_debug(b, "RDPAudioIn - Agreed on format %d.\n", i); + rdp_audio_debug(priv, "RDPAudioIn - Agreed on format %d.\n", i); format = i; break; } @@ -338,12 +338,12 @@ rdp_audioin_client_opening(audin_server_context* context) } if (format == -1) { - rdp_debug_error(b, "RDPAudioIn - No agreeded format.\n"); + weston_log("RDPAudioIn - No agreeded format.\n"); return ERROR_INVALID_DATA; } context->SelectFormat(context, format); - peerCtx->isAudioInStreamOpened = TRUE; + priv->isAudioInStreamOpened = TRUE; return 0; } @@ -353,10 +353,9 @@ rdp_audioin_client_open_result( audin_server_context* context, UINT32 result) { - RdpPeerContext *peerCtx = (RdpPeerContext*)context->data; - struct rdp_backend *b = peerCtx->rdpBackend; + struct audio_in_private *priv = context->data; - rdp_debug(b, "RDP AudioIn Open Result (%d)\n", result); + rdp_audio_debug(priv, "RDP AudioIn Open Result (%d)\n", result); return 0; } @@ -367,11 +366,10 @@ rdp_audioin_client_receive_samples( wStream* buf, size_t nframes) { - RdpPeerContext *peerCtx = (RdpPeerContext*)context->data; - struct rdp_backend *b = peerCtx->rdpBackend; + struct audio_in_private *priv = context->data; - if (!peerCtx->isAudioInStreamOpened || peerCtx->pulseAudioSourceFd == -1) { - rdp_debug_error(b, "RDPAudioIn - audio stream is not opened.\n"); + if (!priv->isAudioInStreamOpened || priv->pulseAudioSourceFd == -1) { + weston_log("RDPAudioIn - audio stream is not opened.\n"); return 0; } @@ -383,15 +381,15 @@ rdp_audioin_client_receive_samples( assert(buf != NULL); int bytes = nframes * format->wBitsPerSample / 8; - int sent = send(peerCtx->pulseAudioSourceFd, buf->buffer, bytes, 0); + int sent = send(priv->pulseAudioSourceFd, buf->buffer, bytes, 0); if (sent != bytes) { - rdp_debug(b, "RDP AudioIn source send failed (sent:%d, bytes:%d) %s\n", + rdp_audio_debug(priv, "RDP AudioIn source send failed (sent:%d, bytes:%d) %s\n", sent, bytes, strerror(errno)); /* Unblock worker thread to close pipe to pulseaudio */ uint64_t one=1; - if (write(peerCtx->closeAudioSourceFd, &one, sizeof(one)) != sizeof(uint64_t)) { - rdp_debug_error(b, "RDP AudioIn error at receive_samples while writing to closeAudioSourceFd (%s)\n", strerror(errno)); + if (write(priv->closeAudioSourceFd, &one, sizeof(one)) != sizeof(uint64_t)) { + weston_log("RDP AudioIn error at receive_samples while writing to closeAudioSourceFd (%s)\n", strerror(errno)); return ERROR_INTERNAL_ERROR; } @@ -413,182 +411,191 @@ static void signalhandler(int sig) { static void* rdp_audioin_source_thread(void *context) { - RdpPeerContext *peerCtx = (RdpPeerContext*)context; - struct rdp_backend *b = peerCtx->rdpBackend; + struct audio_in_private *priv = context; struct sigaction act; sigset_t set; sigemptyset(&set); if (sigaddset(&set, SIGUSR2) == -1) { - rdp_debug_error(b, "AudioIn source thread: sigaddset(SIGUSR2) failed.\n"); + weston_log("AudioIn source thread: sigaddset(SIGUSR2) failed.\n"); return NULL; } if (pthread_sigmask(SIG_UNBLOCK, &set, NULL) != 0) { - rdp_debug_error(b, "AudioIn source thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); + weston_log("AudioIn source thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); return NULL; } act.sa_flags = 0; act.sa_mask = set; act.sa_handler = &signalhandler; if (sigaction(SIGUSR2, &act, NULL) == -1) { - rdp_debug_error(b, "AudioIn source thread: sigaction(SIGUSR2) failed.\n"); + weston_log("AudioIn source thread: sigaction(SIGUSR2) failed.\n"); return NULL; } - assert(peerCtx->closeAudioSourceFd != -1); - assert(peerCtx->pulseAudioSourceListenerFd != -1); + assert(priv->closeAudioSourceFd != -1); + assert(priv->pulseAudioSourceListenerFd != -1); for (;;) { - rdp_debug(b, "AudioIn source_thread: Listening for audio in connection.\n"); + rdp_audio_debug(priv, "AudioIn source_thread: Listening for audio in connection.\n"); - if (peerCtx->audioInExitSignal) { - rdp_debug(b, "AudioIn source_thread is asked to exit (accept loop)\n"); + if (priv->audioInExitSignal) { + rdp_audio_debug(priv, "AudioIn source_thread is asked to exit (accept loop)\n"); break; } /* * Wait for a connection on our listening socket */ - peerCtx->pulseAudioSourceFd = accept(peerCtx->pulseAudioSourceListenerFd, NULL, NULL); - if (peerCtx->pulseAudioSourceFd < 0) { - rdp_debug_error(b, "AudioIn source thread: Listener connection error (%s)\n", strerror(errno)); + priv->pulseAudioSourceFd = accept(priv->pulseAudioSourceListenerFd, NULL, NULL); + if (priv->pulseAudioSourceFd < 0) { + weston_log("AudioIn source thread: Listener connection error (%s)\n", strerror(errno)); continue; } else { - rdp_debug(b, "AudioIn connection successful on socket (%d).\n", peerCtx->pulseAudioSourceFd); - if (peerCtx->audin_server_context->Open(peerCtx->audin_server_context)) { - rdp_debug(b, "RDP AudioIn opened.\n"); + rdp_audio_debug(priv, "AudioIn connection successful on socket (%d).\n", priv->pulseAudioSourceFd); + if (priv->audin_server_context->Open(priv->audin_server_context)) { + rdp_audio_debug(priv, "RDP AudioIn opened.\n"); /* * Wait for the connection to be closed */ uint64_t dummy; - if (read(peerCtx->closeAudioSourceFd, &dummy, sizeof(dummy)) != sizeof(uint64_t)) { - rdp_debug_error(b, "RDP AudioIn wait on eventfd failed. thread exiting. %s\n", strerror(errno)); + if (read(priv->closeAudioSourceFd, &dummy, sizeof(dummy)) != sizeof(uint64_t)) { + weston_log("RDP AudioIn wait on eventfd failed. thread exiting. %s\n", strerror(errno)); break; } - peerCtx->audin_server_context->Close(peerCtx->audin_server_context); - rdp_debug(b, "RDP AudioIn closed.\n"); + priv->audin_server_context->Close(priv->audin_server_context); + rdp_audio_debug(priv, "RDP AudioIn closed.\n"); } else { - rdp_debug_error(b, "Failed to open audio in connection with RDP client.\n"); + weston_log("Failed to open audio in connection with RDP client.\n"); } - close(peerCtx->pulseAudioSourceFd); - peerCtx->pulseAudioSourceFd = -1; + close(priv->pulseAudioSourceFd); + priv->pulseAudioSourceFd = -1; } } - if (peerCtx->audin_server_context->IsOpen(peerCtx->audin_server_context)) - peerCtx->audin_server_context->Close(peerCtx->audin_server_context); + if (priv->audin_server_context->IsOpen(priv->audin_server_context)) + priv->audin_server_context->Close(priv->audin_server_context); - if (peerCtx->pulseAudioSourceFd != -1) { - close(peerCtx->pulseAudioSourceFd); - peerCtx->pulseAudioSourceFd = -1; + if (priv->pulseAudioSourceFd != -1) { + close(priv->pulseAudioSourceFd); + priv->pulseAudioSourceFd = -1; } return NULL; } -int -rdp_audioin_init(RdpPeerContext *peerCtx) +void * +rdp_audio_in_init(struct weston_compositor *c, HANDLE vcm) { - struct rdp_backend *b = peerCtx->rdpBackend; + struct audio_in_private *priv; - peerCtx->audin_server_context = audin_server_context_new(peerCtx->vcm); - if (!peerCtx->audin_server_context) { - rdp_debug_error(b, "RDPAudioIn - Couldn't initialize audio virtual channel.\n"); - return 0; // Continue without audio + priv = xzalloc(sizeof *priv); + priv->audin_server_context = audin_server_context_new(vcm); + if (!priv->audin_server_context) { + weston_log("RDPAudioIn - Couldn't initialize audio virtual channel.\n"); + return NULL; } + priv->debug = weston_compositor_add_log_scope(c, "rdp-audio-in", + "Debug messages for RDP audio input\n", + NULL, NULL, NULL); - peerCtx->audioInExitSignal = FALSE; - peerCtx->pulseAudioSourceThread = 0; - peerCtx->pulseAudioSourceListenerFd = -1; - peerCtx->pulseAudioSourceFd = -1; - peerCtx->closeAudioSourceFd = -1; + priv->audioInExitSignal = FALSE; + priv->pulseAudioSourceThread = 0; + priv->pulseAudioSourceListenerFd = -1; + priv->pulseAudioSourceFd = -1; + priv->closeAudioSourceFd = -1; // this will be freed by FreeRDP at audin_server_context_free. AUDIO_FORMAT *audio_formats = malloc(sizeof rdp_audioin_supported_audio_formats); if (!audio_formats) { - rdp_debug_error(b, "RDPAudioIn - Couldn't allocate memory for audio formats.\n"); + weston_log("RDPAudioIn - Couldn't allocate memory for audio formats.\n"); goto Error_Exit; } memcpy(audio_formats, rdp_audioin_supported_audio_formats, sizeof rdp_audioin_supported_audio_formats); - peerCtx->audin_server_context->data = (void*)peerCtx; - peerCtx->audin_server_context->Opening = rdp_audioin_client_opening; - peerCtx->audin_server_context->OpenResult = rdp_audioin_client_open_result; - peerCtx->audin_server_context->ReceiveSamples = rdp_audioin_client_receive_samples; - peerCtx->audin_server_context->num_server_formats = ARRAYSIZE(rdp_audioin_supported_audio_formats); - peerCtx->audin_server_context->server_formats = audio_formats; - peerCtx->audin_server_context->dst_format = &rdp_audioin_supported_audio_formats[0]; - peerCtx->audin_server_context->frames_per_packet = rdp_audioin_supported_audio_formats[0].nSamplesPerSec / 100; // 10ms per packet - - peerCtx->closeAudioSourceFd = eventfd(0, EFD_CLOEXEC); - if (peerCtx->closeAudioSourceFd < 0) { - rdp_debug_error(b, "RDPAudioIn - Couldn't initialize eventfd.\n"); + priv->audin_server_context->data = (void*)priv; + priv->audin_server_context->Opening = rdp_audioin_client_opening; + priv->audin_server_context->OpenResult = rdp_audioin_client_open_result; + priv->audin_server_context->ReceiveSamples = rdp_audioin_client_receive_samples; + priv->audin_server_context->num_server_formats = ARRAYSIZE(rdp_audioin_supported_audio_formats); + priv->audin_server_context->server_formats = audio_formats; + priv->audin_server_context->dst_format = &rdp_audioin_supported_audio_formats[0]; + priv->audin_server_context->frames_per_packet = rdp_audioin_supported_audio_formats[0].nSamplesPerSec / 100; // 10ms per packet + + priv->closeAudioSourceFd = eventfd(0, EFD_CLOEXEC); + if (priv->closeAudioSourceFd < 0) { + weston_log("RDPAudioIn - Couldn't initialize eventfd.\n"); goto Error_Exit; } - peerCtx->pulseAudioSourceListenerFd = rdp_audioin_setup_listener(peerCtx); - if (peerCtx->pulseAudioSourceListenerFd < 0) { - rdp_debug_error(b, "RDPAudioIn - rdp_audioin_setup_listener failed.\n"); + priv->pulseAudioSourceListenerFd = rdp_audioin_setup_listener(priv); + if (priv->pulseAudioSourceListenerFd < 0) { + weston_log("RDPAudioIn - rdp_audioin_setup_listener failed.\n"); goto Error_Exit; } - if (pthread_create(&peerCtx->pulseAudioSourceThread, NULL, rdp_audioin_source_thread, (void*)peerCtx) < 0) { - rdp_debug_error(b, "RDPAudioIn - Failed to start Pulse Audio Source Thread. No audio in will be available.\n"); + if (pthread_create(&priv->pulseAudioSourceThread, NULL, rdp_audioin_source_thread, (void*)priv) < 0) { + weston_log("RDPAudioIn - Failed to start Pulse Audio Source Thread. No audio in will be available.\n"); goto Error_Exit; } - return 0; + return priv; Error_Exit: - if (peerCtx->pulseAudioSourceListenerFd != -1) { - close(peerCtx->pulseAudioSourceListenerFd); - peerCtx->pulseAudioSourceListenerFd = -1; + if (priv->debug) + weston_log_scope_destroy(priv->debug); + + if (priv->pulseAudioSourceListenerFd != -1) { + close(priv->pulseAudioSourceListenerFd); + priv->pulseAudioSourceListenerFd = -1; } - if (peerCtx->closeAudioSourceFd != -1) { - close(peerCtx->closeAudioSourceFd); - peerCtx->closeAudioSourceFd = -1; + if (priv->closeAudioSourceFd != -1) { + close(priv->closeAudioSourceFd); + priv->closeAudioSourceFd = -1; } - if (peerCtx->audin_server_context) { - audin_server_context_free(peerCtx->audin_server_context); - peerCtx->audin_server_context = NULL; + if (priv->audin_server_context) { + audin_server_context_free(priv->audin_server_context); + priv->audin_server_context = NULL; } + free(priv); - return 0; // Continue without audio + return NULL; // Continue without audio } void -rdp_audioin_destroy(RdpPeerContext *peerCtx) +rdp_audio_in_destroy(void *audio_in_private) { - if (peerCtx->audin_server_context) { - - if (peerCtx->pulseAudioSourceThread) { - peerCtx->audioInExitSignal = TRUE; - shutdown(peerCtx->pulseAudioSourceListenerFd, SHUT_RDWR); - shutdown(peerCtx->closeAudioSourceFd, SHUT_RDWR); - pthread_kill(peerCtx->pulseAudioSourceThread, SIGUSR2); - pthread_join(peerCtx->pulseAudioSourceThread, NULL); - - if (peerCtx->pulseAudioSourceListenerFd != -1) { - close(peerCtx->pulseAudioSourceListenerFd); - peerCtx->pulseAudioSourceListenerFd = -1; + struct audio_in_private *priv = audio_in_private; + if (priv->audin_server_context) { + + if (priv->pulseAudioSourceThread) { + priv->audioInExitSignal = TRUE; + shutdown(priv->pulseAudioSourceListenerFd, SHUT_RDWR); + shutdown(priv->closeAudioSourceFd, SHUT_RDWR); + pthread_kill(priv->pulseAudioSourceThread, SIGUSR2); + pthread_join(priv->pulseAudioSourceThread, NULL); + + if (priv->pulseAudioSourceListenerFd != -1) { + close(priv->pulseAudioSourceListenerFd); + priv->pulseAudioSourceListenerFd = -1; } - if (peerCtx->closeAudioSourceFd != -1) { - close(peerCtx->closeAudioSourceFd); - peerCtx->closeAudioSourceFd = -1; + if (priv->closeAudioSourceFd != -1) { + close(priv->closeAudioSourceFd); + priv->closeAudioSourceFd = -1; } - peerCtx->pulseAudioSourceThread = 0; + priv->pulseAudioSourceThread = 0; } - assert(peerCtx->pulseAudioSourceListenerFd < 0); - assert(peerCtx->closeAudioSourceFd < 0); + assert(priv->pulseAudioSourceListenerFd < 0); + assert(priv->closeAudioSourceFd < 0); - assert(!peerCtx->audin_server_context->IsOpen(peerCtx->audin_server_context)); - audin_server_context_free(peerCtx->audin_server_context); - peerCtx->audin_server_context = NULL; + assert(!priv->audin_server_context->IsOpen(priv->audin_server_context)); + audin_server_context_free(priv->audin_server_context); + priv->audin_server_context = NULL; } + free(priv); } diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index dc1a812e6..d47aef201 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -245,6 +245,11 @@ struct weston_surface_rail_state { #define WESTON_RDP_BACKEND_CONFIG_VERSION 3 +typedef void *(*rdp_audio_in_setup)(struct weston_compositor *c, void *vcm); +typedef void (*rdp_audio_in_teardown)(void *audio_private); +typedef void *(*rdp_audio_out_setup)(struct weston_compositor *c, void *vcm); +typedef void (*rdp_audio_out_teardown)(void *audio_private); + struct weston_rdp_backend_config { struct weston_backend_config base; char *bind_address; @@ -256,8 +261,10 @@ struct weston_rdp_backend_config { int no_clients_resize; int force_no_compression; bool redirect_clipboard; - bool redirect_audio_playback; - bool redirect_audio_capture; + rdp_audio_in_setup audio_in_setup; + rdp_audio_in_teardown audio_in_teardown; + rdp_audio_out_setup audio_out_setup; + rdp_audio_out_teardown audio_out_teardown; int rdp_monitor_refresh_rate; struct { bool use_rdpapplist; diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index fd2884c72..1443a671b 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -64,8 +64,6 @@ endif srcs_rdp = [ 'hash.c', 'rdp.c', - 'rdpaudio.c', - 'rdpaudioin.c', 'rdpdisp.c', 'rdpclip.c', 'rdprail.c', diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index f90afc59e..134e9b5ef 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -882,10 +882,14 @@ rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) static void rdp_peer_context_free(freerdp_peer* client, RdpPeerContext* context) { + struct rdp_backend *b; unsigned i; + if (!context) return; + b = context->rdpBackend; + wl_list_remove(&context->item.link); for (i = 0; i < ARRAY_LENGTH(context->events); i++) { @@ -893,9 +897,11 @@ rdp_peer_context_free(freerdp_peer* client, RdpPeerContext* context) wl_event_source_remove(context->events[i]); } - rdp_audioin_destroy(context); + if (context->audio_in_private) + b->audio_in_teardown(context->audio_in_private); - rdp_audio_destroy(context); + if (context->audio_out_private) + b->audio_out_teardown(context->audio_out_private); rdp_clipboard_destroy(context); @@ -1197,8 +1203,8 @@ xf_peer_activate(freerdp_peer* client) /* override settings by env variables */ settings->RedirectClipboard = b->redirect_clipboard; - settings->AudioPlayback = b->redirect_audio_playback; - settings->AudioCapture = b->redirect_audio_capture; + settings->AudioPlayback = b->audio_out_setup && b->audio_out_teardown; + settings->AudioCapture = b->audio_in_setup && b->audio_in_teardown; if (settings->RemoteApplicationMode || settings->RedirectClipboard || @@ -1218,13 +1224,12 @@ xf_peer_activate(freerdp_peer* client) if (!rdp_rail_peer_activate(client)) goto error_exit; + /* Audio setup will return NULL on failure, and we'll proceed without audio */ if (settings->AudioPlayback) - if (rdp_audio_init(peerCtx) != 0) - goto error_exit; + peerCtx->audio_out_private = b->audio_out_setup(b->compositor, peerCtx->vcm); if (settings->AudioCapture) - if (rdp_audioin_init(peerCtx) != 0) - goto error_exit; + peerCtx->audio_in_private = b->audio_in_setup(b->compositor, peerCtx->vcm); } if (settings->HiDefRemoteApp) { @@ -1364,8 +1369,10 @@ xf_peer_activate(freerdp_peer* client) error_exit: rdp_clipboard_destroy(peerCtx); - rdp_audioin_destroy(peerCtx); - rdp_audio_destroy(peerCtx); + if (settings->AudioPlayback && peerCtx->audio_out_private) + b->audio_out_teardown(peerCtx->audio_out_private); + if (settings->AudioCapture && peerCtx->audio_in_private) + b->audio_in_teardown(peerCtx->audio_in_private); rdp_rail_peer_context_free(client, peerCtx); rdp_drdynvc_destroy(peerCtx); @@ -2153,9 +2160,11 @@ rdp_backend_create(struct weston_compositor *compositor, b->no_clients_resize = config->no_clients_resize; b->force_no_compression = config->force_no_compression; b->redirect_clipboard = config->redirect_clipboard; - b->redirect_audio_playback = config->redirect_audio_playback; - b->redirect_audio_capture = config->redirect_audio_capture; b->rdp_monitor_refresh_rate = config->rdp_monitor_refresh_rate * 1000; + b->audio_in_setup = config->audio_in_setup; + b->audio_in_teardown = config->audio_in_teardown; + b->audio_out_setup = config->audio_out_setup; + b->audio_out_teardown = config->audio_out_teardown; wl_list_init(&b->output_list); wl_list_init(&b->head_list); @@ -2338,8 +2347,6 @@ config_init_to_defaults(struct weston_rdp_backend_config *config) config->no_clients_resize = 0; config->force_no_compression = 0; config->redirect_clipboard = false; - config->redirect_audio_playback = false; - config->redirect_audio_capture = false; config->rdp_monitor_refresh_rate = WESTON_RDP_MODE_FREQ; config->rail_config.use_rdpapplist = false; config->rail_config.use_shared_memory = false; @@ -2353,6 +2360,10 @@ config_init_to_defaults(struct weston_rdp_backend_config *config) config->rail_config.enable_distro_name_title = false; config->rail_config.enable_copy_warning_title = false; config->rail_config.enable_display_power_by_screenupdate = false; + config->audio_in_setup = NULL; + config->audio_in_teardown = NULL; + config->audio_out_setup = NULL; + config->audio_out_teardown = NULL; } WL_EXPORT int diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 05e3714e8..ffecdcbe8 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -110,8 +110,10 @@ struct rdp_backend { int no_clients_resize; int force_no_compression; bool redirect_clipboard; - bool redirect_audio_playback; - bool redirect_audio_capture; + rdp_audio_in_setup audio_in_setup; + rdp_audio_in_teardown audio_in_teardown; + rdp_audio_out_setup audio_out_setup; + rdp_audio_out_teardown audio_out_teardown; const struct weston_rdprail_shell_api *rdprail_shell_api; void *rdprail_shell_context; @@ -277,35 +279,8 @@ struct rdp_peer_context { pixman_region32_t regionClientHeads; pixman_region32_t regionWestonHeads; - // Audio support - RdpsndServerContext* rdpsnd_server_context; - BOOL audioExitSignal; - int pulseAudioSinkListenerFd; - int pulseAudioSinkFd; - pthread_t pulseAudioSinkThread; - int bytesPerFrame; - UINT audioBufferSize; - BYTE* audioBuffer; - BYTE lastBlockSent; - UINT64 lastNetworkLatency; - UINT64 accumulatedNetworkLatency; - UINT accumulatedNetworkLatencyCount; - UINT64 lastRenderedLatency; - UINT64 accumulatedRenderedLatency; - UINT accumulatedRenderedLatencyCount; - rdp_audio_block_info blockInfo[256]; - int nextValidBlock; - UINT PAVersion; - - // AudioIn support - audin_server_context* audin_server_context; - BOOL audioInExitSignal; - int pulseAudioSourceListenerFd; - int pulseAudioSourceFd; - int closeAudioSourceFd; - int audioInSem; - pthread_t pulseAudioSourceThread; - BOOL isAudioInStreamOpened; + void *audio_in_private; + void *audio_out_private; // Clipboard support CliprdrServerContext* clipboard_server_context; @@ -431,14 +406,6 @@ void rdp_rail_end_window_move(struct weston_surface* surface); UINT disp_client_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MONITOR_LAYOUT_PDU* displayControl); BOOL xf_peer_adjust_monitor_layout(freerdp_peer* client); -// rdpaudio.c -int rdp_audio_init(RdpPeerContext *peerCtx); -void rdp_audio_destroy(RdpPeerContext *peerCtx); - -// rdpaudioin.c -int rdp_audioin_init(RdpPeerContext *peerCtx); -void rdp_audioin_destroy(RdpPeerContext *peerCtx); - // rdpclip.c int rdp_clipboard_init(freerdp_peer* client); void rdp_clipboard_destroy(RdpPeerContext *peerCtx); From 2bc5043f1c01c358cc6f5eef45358cdf982ad5b2 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 28 Jun 2022 09:03:24 -0700 Subject: [PATCH 1533/1642] xwayland: Support _NET_FRAME_EXTENTS (#86) Co-authored-by: Hideyuki Nagase --- shared/cairo-util.h | 4 ++++ shared/frame.c | 41 +++++++++++++++++++++++------------ xwayland/window-manager.c | 45 ++++++++++++++++++++++++++++++++++++++- xwayland/xwayland.h | 1 + 4 files changed, 76 insertions(+), 15 deletions(-) diff --git a/shared/cairo-util.h b/shared/cairo-util.h index 6fd11f6bf..17f7b4f3a 100644 --- a/shared/cairo-util.h +++ b/shared/cairo-util.h @@ -161,6 +161,10 @@ frame_width(struct frame *frame); int32_t frame_height(struct frame *frame); +void +frame_decoration_sizes(struct frame *frame, int32_t *top, int32_t *bottom, + int32_t *left, int32_t *right); + void frame_interior(struct frame *frame, int32_t *x, int32_t *y, int32_t *width, int32_t *height); diff --git a/shared/frame.c b/shared/frame.c index e8a5cad62..cf58d66b7 100644 --- a/shared/frame.c +++ b/shared/frame.c @@ -493,27 +493,40 @@ frame_resize(struct frame *frame, int32_t width, int32_t height) } void -frame_resize_inside(struct frame *frame, int32_t width, int32_t height) +frame_decoration_sizes(struct frame *frame, int32_t *top, int32_t *bottom, + int32_t *left, int32_t *right) { struct theme *t = frame->theme; - int decoration_width, decoration_height, titlebar_height; + /* Top may have a titlebar */ if (frame->title || !wl_list_empty(&frame->buttons)) - titlebar_height = t->titlebar_height; + *top = t->titlebar_height; else - titlebar_height = t->width; + *top = t->width; - if (frame->flags & FRAME_FLAG_MAXIMIZED) { - decoration_width = t->width * 2; - decoration_height = t->width + titlebar_height; - } else { - decoration_width = (t->width + t->margin) * 2; - decoration_height = t->width + - titlebar_height + t->margin * 2; - } + /* All other sides have the basic frame thickness */ + *bottom = t->width; + *right = t->width; + *left = t->width; + + if (frame->flags & FRAME_FLAG_MAXIMIZED) + return; + + /* Not maximized, add shadows */ + *top += t->margin; + *bottom += t->margin; + *left += t->margin; + *right += t->margin; +} + +void +frame_resize_inside(struct frame *frame, int32_t width, int32_t height) +{ + int32_t top, bottom, left, right; - frame_resize(frame, width + decoration_width, - height + decoration_height); + frame_decoration_sizes(frame, &top, &bottom, &left, &right); + frame_resize(frame, width + left + right, + height + top + bottom); } int32_t diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 43652f39a..b93ccdb7a 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -177,6 +177,10 @@ struct weston_wm_window { struct wm_size_hints size_hints; struct motif_wm_hints motif_hints; struct wl_list link; + int decor_top; + int decor_bottom; + int decor_left; + int decor_right; }; static int @@ -1248,6 +1252,38 @@ weston_wm_window_set_wm_state(struct weston_wm_window *window, int32_t state) 2, property); } +static void +weston_wm_window_set_net_frame_extents(struct weston_wm_window *window) +{ + struct weston_wm *wm = window->wm; + uint32_t property[4]; + int top = 0, bottom = 0, left = 0, right = 0; + + if (!window->fullscreen) + frame_decoration_sizes(window->frame, &top, &bottom, &left, &right); + + if (window->decor_top == top && window->decor_bottom == bottom && + window->decor_left == left && window->decor_right == right) + return; + + window->decor_top = top; + window->decor_bottom = bottom; + window->decor_left = left; + window->decor_right = right; + + property[0] = left; + property[1] = right; + property[2] = top; + property[3] = bottom; + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + window->id, + wm->atom.net_frame_extents, + XCB_ATOM_CARDINAL, + 32, /* format */ + 4, property); +} + static void weston_wm_window_set_net_wm_state(struct weston_wm_window *window) { @@ -1594,6 +1630,7 @@ weston_wm_window_do_repaint(void *data) weston_wm_window_read_properties(window); weston_wm_window_draw_decoration(window); + weston_wm_window_set_net_frame_extents(window); weston_wm_window_set_pending_state(window); weston_wm_window_set_allow_commits(window, true); } @@ -1794,6 +1831,10 @@ weston_wm_window_create(struct weston_wm *wm, window->frame_y = INT_MIN; window->map_request_x = INT_MIN; /* out of range for valid positions */ window->map_request_y = INT_MIN; /* out of range for valid positions */ + window->decor_top = -1; + window->decor_bottom = -1; + window->decor_left = -1; + window->decor_right = -1; weston_output_weak_ref_init(&window->legacy_fullscreen_output); geometry_reply = xcb_get_geometry_reply(wm->conn, geometry_cookie, NULL); @@ -2703,6 +2744,7 @@ weston_wm_get_resources(struct weston_wm *wm) { "WM_S0", F(atom.wm_s0) }, { "WM_CLIENT_MACHINE", F(atom.wm_client_machine) }, { "WM_CHANGE_STATE", F(atom.wm_change_state) }, + { "_NET_FRAME_EXTENTS", F(atom.net_frame_extents) }, { "_NET_WM_CM_S0", F(atom.net_wm_cm_s0) }, { "_NET_WM_NAME", F(atom.net_wm_name) }, { "_NET_WM_PID", F(atom.net_wm_pid) }, @@ -2891,7 +2933,7 @@ weston_wm_create(struct weston_xserver *wxs, int fd) struct wl_event_loop *loop; xcb_screen_iterator_t s; uint32_t values[1]; - xcb_atom_t supported[6]; + xcb_atom_t supported[7]; wm = zalloc(sizeof *wm); if (wm == NULL) @@ -2945,6 +2987,7 @@ weston_wm_create(struct weston_xserver *wxs, int fd) supported[3] = wm->atom.net_wm_state_maximized_vert; supported[4] = wm->atom.net_wm_state_maximized_horz; supported[5] = wm->atom.net_active_window; + supported[6] = wm->atom.net_frame_extents; xcb_change_property(wm->conn, XCB_PROP_MODE_REPLACE, wm->screen->root, diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index 2203cfcab..0b0811721 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -104,6 +104,7 @@ struct weston_wm { xcb_atom_t wm_s0; xcb_atom_t wm_client_machine; xcb_atom_t wm_change_state; + xcb_atom_t net_frame_extents; xcb_atom_t net_wm_cm_s0; xcb_atom_t net_wm_name; xcb_atom_t net_wm_pid; From 808dcb58526e99c8601483187f633382033a7c0f Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 30 Jun 2022 15:46:54 -0700 Subject: [PATCH 1534/1642] libweston-desktop/xwayland: Use correct geometry (#88) Co-authored-by: Hideyuki Nagase --- libweston-desktop/xwayland.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index 3542ce5c8..836e235b2 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -151,7 +151,7 @@ weston_desktop_xwayland_surface_committed(struct weston_desktop_surface *dsurfac if (surface->has_next_geometry) { oldgeom = weston_desktop_surface_get_geometry(surface->surface); sx -= surface->next_geometry.x - oldgeom.x; - sy -= surface->next_geometry.y - oldgeom.x; + sy -= surface->next_geometry.y - oldgeom.y; surface->has_next_geometry = false; weston_desktop_surface_set_geometry(surface->surface, From bc07a916bf82b7eb87e62bae52b4507d5e41764d Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 30 Jun 2022 15:47:09 -0700 Subject: [PATCH 1535/1642] xwm: Generate more synthetic ConfigureNotify events (#87) Co-authored-by: Hideyuki Nagase --- desktop-shell/shell.c | 12 ++ include/libweston-desktop/libweston-desktop.h | 3 + kiosk-shell/kiosk-shell.c | 12 ++ libweston-desktop/internal.h | 5 + libweston-desktop/libweston-desktop.c | 11 ++ libweston-desktop/xwayland.c | 13 ++ rdprail-shell/shell.c | 12 ++ xwayland/window-manager.c | 142 ++++-------------- xwayland/xwayland-internal-interface.h | 4 +- 9 files changed, 103 insertions(+), 111 deletions(-) diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 03d3e0f61..e102ee7e9 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -2965,6 +2965,17 @@ desktop_surface_set_xwayland_position(struct weston_desktop_surface *surface, shsurf->xwayland.is_set = true; } +static void +desktop_surface_get_position(struct weston_desktop_surface *surface, + int32_t *x, int32_t *y, + void *shell_) +{ + struct shell_surface *shsurf = weston_desktop_surface_get_user_data(surface); + + *x = shsurf->view->geometry.x; + *y = shsurf->view->geometry.y; +} + static const struct weston_desktop_api shell_desktop_api = { .struct_size = sizeof(struct weston_desktop_api), .surface_added = desktop_surface_added, @@ -2979,6 +2990,7 @@ static const struct weston_desktop_api shell_desktop_api = { .ping_timeout = desktop_surface_ping_timeout, .pong = desktop_surface_pong, .set_xwayland_position = desktop_surface_set_xwayland_position, + .get_position = desktop_surface_get_position, }; /* ************************ * diff --git a/include/libweston-desktop/libweston-desktop.h b/include/libweston-desktop/libweston-desktop.h index 47295cf16..9c4450718 100644 --- a/include/libweston-desktop/libweston-desktop.h +++ b/include/libweston-desktop/libweston-desktop.h @@ -117,6 +117,9 @@ struct weston_desktop_api { */ void (*set_xwayland_position)(struct weston_desktop_surface *surface, int32_t x, int32_t y, void *user_data); + void (*get_position)(struct weston_desktop_surface *surface, + int32_t *x, int32_t *y, + void *user_data); /* * In contrast to above set_xwayland_position(), move_xwayland_position() * to be used to move window after mapped. diff --git a/kiosk-shell/kiosk-shell.c b/kiosk-shell/kiosk-shell.c index ac9c86842..52ce0e956 100644 --- a/kiosk-shell/kiosk-shell.c +++ b/kiosk-shell/kiosk-shell.c @@ -812,6 +812,17 @@ desktop_surface_set_xwayland_position(struct weston_desktop_surface *desktop_sur shsurf->xwayland.is_set = true; } +static void +desktop_surface_get_position(struct weston_desktop_surface *desktop_surface, + int32_t *x, int32_t *y, void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + *x = shsurf->view->geometry.x; + *y = shsurf->view->geometry.y; +} + static const struct weston_desktop_api kiosk_shell_desktop_api = { .struct_size = sizeof(struct weston_desktop_api), .surface_added = desktop_surface_added, @@ -826,6 +837,7 @@ static const struct weston_desktop_api kiosk_shell_desktop_api = { .ping_timeout = desktop_surface_ping_timeout, .pong = desktop_surface_pong, .set_xwayland_position = desktop_surface_set_xwayland_position, + .get_position = desktop_surface_get_position, }; /* diff --git a/libweston-desktop/internal.h b/libweston-desktop/internal.h index 26ff30c93..c7224a54a 100644 --- a/libweston-desktop/internal.h +++ b/libweston-desktop/internal.h @@ -91,6 +91,11 @@ weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, struct weston_desktop_surface *surface, int32_t x, int32_t y); +void +weston_desktop_api_get_position(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t *x, int32_t *y); + void weston_desktop_api_move_xwayland_position(struct weston_desktop *desktop, struct weston_desktop_surface *surface, diff --git a/libweston-desktop/libweston-desktop.c b/libweston-desktop/libweston-desktop.c index f2fff1b3a..23d38fc40 100644 --- a/libweston-desktop/libweston-desktop.c +++ b/libweston-desktop/libweston-desktop.c @@ -263,6 +263,17 @@ weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, desktop->user_data); } +void +weston_desktop_api_get_position(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t *x, int32_t *y) +{ + if (!desktop->api.get_position) + return; + + desktop->api.get_position(surface, x, y, desktop->user_data); +} + void weston_desktop_api_move_xwayland_position(struct weston_desktop *desktop, struct weston_desktop_surface *surface, diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index 836e235b2..4fbc318be 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -428,6 +428,18 @@ set_pid(struct weston_desktop_xwayland_surface *surface, pid_t pid) weston_desktop_surface_set_pid(surface->surface, pid); } +static void +get_position(struct weston_desktop_xwayland_surface *surface, + int32_t *x, int32_t *y) +{ + if (!surface->surface) { + *x = 0; + *y = 0; + return; + } + weston_desktop_api_get_position(surface->desktop, surface->surface, x, y); +} + static const struct weston_desktop_xwayland_interface weston_desktop_xwayland_interface = { .create_surface = create_surface, .set_toplevel = set_toplevel, @@ -444,6 +456,7 @@ static const struct weston_desktop_xwayland_interface weston_desktop_xwayland_in .set_maximized = set_maximized, .set_minimized = set_minimized, .set_pid = set_pid, + .get_position = get_position, .set_window_icon = set_window_icon, }; diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 3d17be4bd..861e02e11 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -2959,6 +2959,17 @@ desktop_surface_set_xwayland_position(struct weston_desktop_surface *surface, shsurf->xwayland.is_set = true; } +static void +desktop_surface_get_position(struct weston_desktop_surface *surface, + int32_t *x, int32_t *y, + void *shell_) +{ + struct shell_surface *shsurf = weston_desktop_surface_get_user_data(surface); + + *x = shsurf->view->geometry.x; + *y = shsurf->view->geometry.y; +} + static void desktop_surface_move_xwayland_position(struct weston_desktop_surface *desktop_surface, int32_t x, int32_t y, void *shell_) @@ -3008,6 +3019,7 @@ static const struct weston_desktop_api shell_desktop_api = { .ping_timeout = desktop_surface_ping_timeout, .pong = desktop_surface_pong, .set_xwayland_position = desktop_surface_set_xwayland_position, + .get_position = desktop_surface_get_position, .move_xwayland_position = desktop_surface_move_xwayland_position, .set_window_icon = desktop_surface_set_window_icon, }; diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index b93ccdb7a..d39ec95c8 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -157,8 +157,6 @@ struct weston_wm_window { int x; int y; bool pos_dirty; - int frame_x; - int frame_y; int map_request_x; int map_request_y; struct weston_output_weak_ref legacy_fullscreen_output; @@ -765,15 +763,22 @@ weston_wm_window_send_configure_notify(struct weston_wm_window *window) struct weston_wm *wm = window->wm; bool is_our_resource = our_resource(wm, window->id); int x, y; + int32_t dx = 0, dy = 0; + const struct weston_desktop_xwayland_interface *xwayland_api = + wm->server->compositor->xwayland_interface; weston_wm_window_get_child_position(window, &x, &y); + /* Synthetic ConfigureNotify events must be relative to the root + * window, so get our offset if we're mapped. */ + if (window->shsurf) + xwayland_api->get_position(window->shsurf, &dx, &dy); configure_notify.response_type = XCB_CONFIGURE_NOTIFY; configure_notify.pad0 = 0; configure_notify.event = window->id; configure_notify.window = window->id; configure_notify.above_sibling = XCB_WINDOW_NONE; - configure_notify.x = x; - configure_notify.y = y; + configure_notify.x = x + dx; + configure_notify.y = y + dy; configure_notify.width = window->width; configure_notify.height = window->height; configure_notify.border_width = 0; @@ -789,35 +794,6 @@ weston_wm_window_send_configure_notify(struct weston_wm_window *window) is_our_resource ? ", ours" : ""); } -static void -weston_wm_window_send_event_configure_notify_with_position(struct weston_wm_window *window, int x, int y) -{ - xcb_configure_notify_event_t configure_notify; - struct weston_wm *wm = window->wm; - bool is_our_resource = our_resource(wm, window->id); - - configure_notify.response_type = XCB_CONFIGURE_NOTIFY; - configure_notify.pad0 = 0; - configure_notify.event = window->id; - configure_notify.window = window->id; - configure_notify.above_sibling = XCB_NONE; - configure_notify.x = x; - configure_notify.y = y; - configure_notify.width = window->width; - configure_notify.height = window->height; - configure_notify.border_width = 0; - configure_notify.override_redirect = window->override_redirect; - configure_notify.pad1 = 0; - - xcb_send_event(wm->conn, 0, window->id, - XCB_EVENT_MASK_STRUCTURE_NOTIFY, - (char *) &configure_notify); - - wm_printf(wm, "XWM: send_event_configure_notify_window_position (window %d) %d,%d @ %dx%d%s\n", - window->id, x, y, window->width, window->height, - is_our_resource ? ", ours" : ""); -} - static void weston_wm_configure_window(struct weston_wm *wm, xcb_window_t window_id, uint16_t mask, const uint32_t *values) @@ -878,28 +854,6 @@ weston_wm_window_configure_frame(struct weston_wm_window *window) weston_wm_configure_window(window->wm, window->frame_id, mask, values); } -static void -weston_wm_window_configure_frame_with_position(struct weston_wm_window *window, int x, int y) -{ - uint16_t mask; - uint32_t values[4]; - int width, height; - - if (!window->frame_id) - return; - - weston_wm_window_get_frame_size(window, &width, &height); - values[0] = x; // x = position of frame, not child - values[1] = y; // y = position of frame, not child - values[2] = width; - values[3] = height; - mask = XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT; - weston_wm_configure_window(window->wm, window->frame_id, mask, values); -} - static void weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *event) { @@ -908,10 +862,9 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev struct weston_wm_window *window; uint32_t values[16]; uint16_t mask = 0; - int x, y, configure_x, configure_y; + int x, y; int i = 0; bool is_our_resource = our_resource(wm, configure_request->window); - bool configure_frame_position = false; wm_printf(wm, "XCB_CONFIGURE_REQUEST (window %d) %d,%d @ %dx%d mask 0x%x%s\n", configure_request->window, @@ -939,33 +892,20 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev if (configure_request->value_mask & XCB_CONFIG_WINDOW_HEIGHT) window->height = configure_request->height; - if (configure_request->value_mask & XCB_CONFIG_WINDOW_X) - configure_x = configure_request->x; - else - configure_x = window->x; - if (configure_request->value_mask & XCB_CONFIG_WINDOW_Y) - configure_y = configure_request->y; - else - configure_y = window->y; - if (window->frame) { weston_wm_window_set_allow_commits(window, false); frame_resize_inside(window->frame, window->width, window->height); } - weston_wm_window_get_child_position(window, &x, &y); /* don't send x/y when frame (parent window) is not created yet, unless this is frame itself. Since only after frame is created, the app's window position will become relative to parent (frame). */ if (window->frame || window->override_redirect) { + weston_wm_window_get_child_position(window, &x, &y); /* window is app's window has frame as parent, or override. */ values[i++] = x; // relative from frame. values[i++] = y; // relative from frame. mask |= XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; - /* and if configure has differnt position than current position, - move its frame (if it has frame), so app contents will move, too */ - if (window->frame && ((configure_x != window->x) || (configure_y != window->y))) - configure_frame_position = true; } values[i++] = window->width; values[i++] = window->height; @@ -989,16 +929,13 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev save window position, so it's captured at map, weston_wm_handle_map_request() for map_request_x/y. This over-writes the position given at create_notify. */ - window->x = configure_x; - window->y = configure_y; - } else if (configure_frame_position) { - weston_wm_window_configure_frame_with_position(window, - configure_x - x, - configure_y - y); - } else { - weston_wm_window_configure_frame(window); + if (configure_request->value_mask & XCB_CONFIG_WINDOW_X) + window->x = configure_request->x; + if (configure_request->value_mask & XCB_CONFIG_WINDOW_Y) + window->y = configure_request->y; } - + weston_wm_window_configure_frame(window); + weston_wm_window_send_configure_notify(window); weston_wm_window_schedule_repaint(window); } @@ -1050,16 +987,8 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve if (!wm_lookup_window(wm, configure_notify->window, &window)) return; - if (window->override_redirect || is_our_resource) { - /* override or frame */ - window->frame_x = configure_notify->x; - window->frame_y = configure_notify->y; - } - if (window->override_redirect || !is_our_resource) { - /* override or not frame */ - window->x = configure_notify->x; - window->y = configure_notify->y; - } + window->x = configure_notify->x; + window->y = configure_notify->y; window->pos_dirty = false; if (window->override_redirect) { @@ -1078,7 +1007,7 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve } else if (is_our_resource) { if (window->shsurf) xwayland_api->move_position(window->shsurf, - window->frame_x, window->frame_y); + window->x, window->y); } } @@ -1375,6 +1304,7 @@ weston_wm_window_create_frame(struct weston_wm_window *window) width, height); hash_table_insert(wm->window_hash, window->frame_id, window); + weston_wm_window_send_configure_notify(window); } /* @@ -1827,8 +1757,6 @@ weston_wm_window_create(struct weston_wm *wm, window->x = x; window->y = y; window->pos_dirty = false; - window->frame_x = INT_MIN; - window->frame_y = INT_MIN; window->map_request_x = INT_MIN; /* out of range for valid positions */ window->map_request_y = INT_MIN; /* out of range for valid positions */ window->decor_top = -1; @@ -3090,6 +3018,7 @@ weston_wm_window_configure(void *data) values); weston_wm_window_configure_frame(window); + weston_wm_window_send_configure_notify(window); weston_wm_window_schedule_repaint(window); } @@ -3148,16 +3077,16 @@ send_position(struct weston_surface *surface, int32_t x, int32_t y) { struct weston_wm_window *window = get_wm_window(surface); struct weston_wm *wm; - int dx, dy; + uint32_t values[2]; + uint16_t mask; if (!window || !window->wm) return; wm = window->wm; - wm_printf(wm, "XWM: send_position (window %d) input %d,%d frame %d,%d window %d,%d%s\n", + wm_printf(wm, "XWM: send_position (window %d) input %d,%d window %d,%d%s\n", window->id, x, y, - window->frame_x, window->frame_y, window->x, window->y, window->override_redirect ? ", override" : ""); @@ -3165,22 +3094,15 @@ send_position(struct weston_surface *surface, int32_t x, int32_t y) * This is needed in case we send two configure events in a very * short time, since window->x/y is set in after a roundtrip, hence * we cannot just check if the current x and y are different. */ - if (window->frame_x != x || window->frame_y != y || window->pos_dirty) { + if (window->x != x || window->y != y || window->pos_dirty) { window->pos_dirty = true; - weston_wm_window_configure_frame_with_position(window, x, y); - - // !!! need further investigation !!! - /* Xwayland reparents app's window with our own frame window - as new parent, so when shell moves frame window, with intent to - move app's window, the app (child) window position which is - relative to parent, doesn't change. But it seems certain application - (Qt based or PyCharm) isn't aware of this reparenting done by us at - weston_wm_window_create_frame(), thus, here sends XCB_CONFIGURE_NOTIFY - event to let application knows actual app's window position (rather - than offset from parent/frame window */ - weston_wm_window_get_child_position(window, &dx, &dy); - weston_wm_window_send_event_configure_notify_with_position(window, x + dx, y + dy); + values[0] = x; + values[1] = y; + mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; + + weston_wm_configure_window(wm, window->frame_id, mask, values); + weston_wm_window_send_configure_notify(window); xcb_flush(wm->conn); } } diff --git a/xwayland/xwayland-internal-interface.h b/xwayland/xwayland-internal-interface.h index 88d153fd5..884d76687 100644 --- a/xwayland/xwayland-internal-interface.h +++ b/xwayland/xwayland-internal-interface.h @@ -62,8 +62,10 @@ struct weston_desktop_xwayland_interface { void (*set_maximized)(struct weston_desktop_xwayland_surface *shsurf); void (*set_minimized)(struct weston_desktop_xwayland_surface *shsurf); void (*set_pid)(struct weston_desktop_xwayland_surface *shsurf, pid_t pid); + void (*get_position)(struct weston_desktop_xwayland_surface *surface, + int32_t *x, int32_t *y); void (*set_window_icon)(struct weston_desktop_xwayland_surface *surface, - int32_t width, int32_t height, int32_t bpp, void *bits); + int32_t width, int32_t height, int32_t bpp, void *bits); }; #endif From 0812e17a2677b5e42223fef990c651e9e53091af Mon Sep 17 00:00:00 2001 From: Tiago Koji Castro Shibata Date: Tue, 5 Jul 2022 16:16:20 -0700 Subject: [PATCH 1536/1642] Hide copy warning on ARM --- compositor/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index 7b1795172..857757bb3 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2848,7 +2848,11 @@ load_rdp_backend(struct weston_compositor *c, read_rdp_config_bool("WESTON_RDP_DISPLAY_POWER_BY_SCREENUPDATE", false); config.rail_config.enable_distro_name_title = read_rdp_config_bool("WESTON_RDP_APPEND_DISTRONAME_TITLE", true); +#if defined(__arm__) || defined(__aarch64__) + config.rail_config.enable_copy_warning_title = read_rdp_config_bool("WESTON_RDP_COPY_WARNING_TITLE", false); +#else config.rail_config.enable_copy_warning_title = read_rdp_config_bool("WESTON_RDP_COPY_WARNING_TITLE", true); +#endif wet_set_simple_head_configurator(c, rdp_backend_output_configure); From c2bdfbae7b52575eaa2633945060b68fbba794d1 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 11 Jul 2022 09:25:17 -0700 Subject: [PATCH 1537/1642] xwm: fix window geometry for non-decorated window (#90) * xwm: fix window geometry for non-decorated window * when not decorated, use theme and bare window size as window geometry Co-authored-by: Hideyuki Nagase --- xwayland/window-manager.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index d39ec95c8..eae73bcae 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -1503,6 +1503,7 @@ weston_wm_window_set_pending_state(struct weston_wm_window *window) int32_t input_x, input_y, input_w, input_h; const struct weston_desktop_xwayland_interface *xwayland_interface = window->wm->server->compositor->xwayland_interface; + struct theme *t = window->wm->theme; if (!window->surface) return; @@ -1523,14 +1524,19 @@ weston_wm_window_set_pending_state(struct weston_wm_window *window) window->height + 2); } - if (window->decorate && !window->fullscreen) { - frame_input_rect(window->frame, &input_x, &input_y, - &input_w, &input_h); - } else { + if (window->fullscreen) { input_x = x; input_y = y; input_w = width; input_h = height; + } else if (window->decorate && window->frame) { + frame_input_rect(window->frame, &input_x, &input_y, + &input_w, &input_h); + } else { + input_x = t->margin; + input_y = t->margin; + input_w = window->width; + input_h = window->height; } wm_printf(window->wm, "XWM: win %d geometry: %d,%d %dx%d\n", From 34989f76bbe011d64715c14a9901d34af111d58a Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 13 Jul 2022 14:27:14 -0700 Subject: [PATCH 1538/1642] rdp-backend: add window resize margin when window shadow remoting is disabled (#91) * add window resize margin when window shadow remoting is disabled * compute window margin based on surface input extents Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 5 ++ libweston/backend-rdp/rdp.h | 16 ++++ libweston/backend-rdp/rdprail.c | 136 +++++++++++++++++++++++++++----- rdprail-shell/shell.c | 8 +- 4 files changed, 142 insertions(+), 23 deletions(-) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index d47aef201..d1d02d60b 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -233,6 +233,11 @@ struct weston_surface_rail_state { void *get_label; int taskbarButton; + uint32_t window_margin_top; + uint32_t window_margin_left; + uint32_t window_margin_right; + uint32_t window_margin_bottom; + /* gfxredir shared memory */ uint32_t pool_id; uint32_t buffer_id; diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index ffecdcbe8..b69dfd9e8 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -538,4 +538,20 @@ to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, } } +#define RDP_RAIL_WINDOW_RESIZE_MARGIN 8 + +static inline bool +is_window_shadow_remoting_disabled(RdpPeerContext *peerCtx) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + + /* When shadow is not remoted, window geometry must be able to queried from shell to clip + shadow area, and resize margin must be supported by client. When remoting window shadow, + the shadow area is used as resize margin, but without it, window can't be resizable, + thus window margin must be added by client side. */ + return (!b->enable_window_shadow_remoting && + b->rdprail_shell_api && b->rdprail_shell_api->get_window_geometry && + (peerCtx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED)); +} + #endif diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index d2fceded7..5bb72aef8 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -49,6 +49,9 @@ #define RAIL_WINDOW_FULLSCREEN_STYLE (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_GROUP | WS_TABSTOP) #define RAIL_WINDOW_NORMAL_STYLE (RAIL_WINDOW_FULLSCREEN_STYLE | WS_THICKFRAME | WS_CAPTION) +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) > (b)) ? (b) : (a)) + extern PWtsApiFunctionTable FreeRDP_InitWtsApi(void); static void rdp_rail_destroy_window(struct wl_listener *listener, void *data); @@ -346,9 +349,8 @@ rail_client_SnapArrange_callback(bool freeOnly, void *arg) to_weston_coordinate(peerCtx, &snapArrangeRect.x, &snapArrangeRect.y, &snapArrangeRect.width, &snapArrangeRect.height); - if (!b->enable_window_shadow_remoting && - b->rdprail_shell_api && - b->rdprail_shell_api->get_window_geometry) { + if (is_window_shadow_remoting_disabled(peerCtx)) { + /* offset window shadow area */ /* window_geometry here is last commited geometry */ b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); snapArrangeRect.x -= windowGeometry.x; @@ -411,9 +413,8 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) to_weston_coordinate(peerCtx, &windowMoveRect.x, &windowMoveRect.y, &windowMoveRect.width, &windowMoveRect.height); - if (!b->enable_window_shadow_remoting && - b->rdprail_shell_api && - b->rdprail_shell_api->get_window_geometry) { + if (is_window_shadow_remoting_disabled(peerCtx)) { + /* offset window shadow area */ /* window_geometry here is last commited geometry */ b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); windowMoveRect.x -= windowGeometry.x; @@ -1302,6 +1303,7 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) struct weston_geometry windowGeometry = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; RECTANGLE_16 window_rect = { 0, 0, surface->width, surface->height }; RECTANGLE_16 window_vis = { 0, 0, surface->width, surface->height }; + uint32_t window_margin_top = 0, window_margin_left = 0, window_margin_right = 0, window_margin_bottom = 0; int numViews; struct weston_view *view; UINT32 window_id; @@ -1394,10 +1396,28 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); } - if (!b->enable_window_shadow_remoting && - b->rdprail_shell_api && - b->rdprail_shell_api->get_window_geometry) { + if (is_window_shadow_remoting_disabled(peerCtx)) { + /* drop window shadow area */ b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + + /* calculate window margin from input extents */ + if (windowGeometry.x > max(0, surface->input.extents.x1)) + window_margin_left = windowGeometry.x - max(0, surface->input.extents.x1); + window_margin_left = max(window_margin_left, RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (windowGeometry.y > max(0, surface->input.extents.y1)) + window_margin_top = windowGeometry.y - max(0, surface->input.extents.y1); + window_margin_top = max(window_margin_top, RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (min(surface->input.extents.x2, surface->width) > (windowGeometry.x + windowGeometry.width)) + window_margin_right = min(surface->input.extents.x2, surface->width) - (windowGeometry.x + windowGeometry.width); + window_margin_right = max(window_margin_right, RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (min(surface->input.extents.y2, surface->height) > (windowGeometry.y + windowGeometry.height)) + window_margin_bottom = min(surface->input.extents.y2, surface->height) - (windowGeometry.y + windowGeometry.height); + window_margin_bottom = max(window_margin_bottom, RDP_RAIL_WINDOW_RESIZE_MARGIN); + + /* offset window origin by window geometry */ clientPos.x += windowGeometry.x; clientPos.y += windowGeometry.y; clientPos.width = windowGeometry.width; @@ -1405,10 +1425,18 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) } /* apply global to output transform, and translate to client coordinate */ - if (surface->output) + if (surface->output) { to_client_coordinate(peerCtx, surface->output, &clientPos.x, &clientPos.y, &clientPos.width, &clientPos.height); + if (is_window_shadow_remoting_disabled(peerCtx)) { + to_client_coordinate(peerCtx, surface->output, + &window_margin_left, &window_margin_top, NULL, NULL); + to_client_coordinate(peerCtx, surface->output, + &window_margin_right, &window_margin_bottom, NULL, NULL); + } + } + window_rect.top = window_vis.top = clientPos.y; window_rect.left = window_vis.left = clientPos.x; window_rect.right = window_vis.right = clientPos.x + clientPos.width; @@ -1471,6 +1499,16 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) window_state_order.numVisibilityRects = 1; window_state_order.visibilityRects = &window_vis; + if (is_window_shadow_remoting_disabled(peerCtx)) { + /* add resize margin area */ + window_order_info.fieldFlags |= + WINDOW_ORDER_FIELD_RESIZE_MARGIN_X | WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y; + window_state_order.resizeMarginLeft = window_margin_left; + window_state_order.resizeMarginTop = window_margin_top; + window_state_order.resizeMarginRight = window_margin_right; + window_state_order.resizeMarginBottom = window_margin_bottom; + } + /*window_state_order.titleInfo = NULL; */ /*window_state_order.OverlayDescription = 0;*/ @@ -1483,6 +1521,10 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) rail_state->parent_window_id = window_state_order.ownerWindowId; rail_state->pos = pos; rail_state->clientPos = clientPos; + rail_state->window_margin_left = window_margin_left; + rail_state->window_margin_top = window_margin_top; + rail_state->window_margin_right = window_margin_right; + rail_state->window_margin_bottom = window_margin_bottom; rail_state->isWindowCreated = TRUE; rail_state->get_label = (void *)-1; // label to be re-checked at update. rail_state->taskbarButton = window_state_order.TaskbarButton; @@ -1715,6 +1757,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter struct weston_geometry contentBufferWindowGeometry; RECTANGLE_16 window_rect; RECTANGLE_16 window_vis; + uint32_t window_margin_top = 0, window_margin_left = 0, window_margin_right = 0, window_margin_bottom = 0; int numViews; struct weston_view *view; UINT32 window_id; @@ -1837,10 +1880,28 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); } - if (!b->enable_window_shadow_remoting && - b->rdprail_shell_api && - b->rdprail_shell_api->get_window_geometry) { + if (is_window_shadow_remoting_disabled(peerCtx)) { + /* drop window shadow area */ b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + + /* calculate window margin from input extents */ + if (windowGeometry.x > max(0, surface->input.extents.x1)) + window_margin_left = windowGeometry.x - max(0, surface->input.extents.x1); + window_margin_left = max(window_margin_left, RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (windowGeometry.y > max(0, surface->input.extents.y1)) + window_margin_top = windowGeometry.y - max(0, surface->input.extents.y1); + window_margin_top = max(window_margin_top, RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (min(surface->input.extents.x2, surface->width) > (windowGeometry.x + windowGeometry.width)) + window_margin_right = min(surface->input.extents.x2, surface->width) - (windowGeometry.x + windowGeometry.width); + window_margin_right = max(window_margin_right, RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (min(surface->input.extents.y2, surface->height) > (windowGeometry.y + windowGeometry.height)) + window_margin_bottom = min(surface->input.extents.y2, surface->height) - (windowGeometry.y + windowGeometry.height); + window_margin_bottom = max(window_margin_bottom, RDP_RAIL_WINDOW_RESIZE_MARGIN); + + /* offset window origin by window geometry */ newClientPos.x += windowGeometry.x; newClientPos.y += windowGeometry.y; newClientPos.width = windowGeometry.width; @@ -1849,10 +1910,18 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter contentBufferWindowGeometry = windowGeometry; /* apply global to output transform, and translate to client coordinate */ - if (surface->output) + if (surface->output) { to_client_coordinate(peerCtx, surface->output, &newClientPos.x, &newClientPos.y, &newClientPos.width, &newClientPos.height); + if (is_window_shadow_remoting_disabled(peerCtx)) { + to_client_coordinate(peerCtx, surface->output, + &window_margin_left, &window_margin_top, NULL, NULL); + to_client_coordinate(peerCtx, surface->output, + &window_margin_right, &window_margin_bottom, NULL, NULL); + } + } + /* when window move to new output with different scale, refresh all state to client. */ if (rail_state->output_scale != surface->output->current_scale) { rail_state->forceUpdateWindowState = true; @@ -1993,6 +2062,30 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter window_state_order.TaskbarButton = (BYTE) rail_state->taskbarButton; } + if (is_window_shadow_remoting_disabled(peerCtx)) { + if (rail_state->forceUpdateWindowState || + rail_state->window_margin_left != window_margin_left || + rail_state->window_margin_top != window_margin_top || + rail_state->window_margin_right != window_margin_right || + rail_state->window_margin_bottom != window_margin_bottom) { + /* add resize margin area */ + window_order_info.fieldFlags |= + WINDOW_ORDER_FIELD_RESIZE_MARGIN_X | WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y; + window_state_order.resizeMarginLeft = window_margin_left; + window_state_order.resizeMarginTop = window_margin_top; + window_state_order.resizeMarginRight = window_margin_right; + window_state_order.resizeMarginBottom = window_margin_bottom; + + rail_state->window_margin_left = window_margin_left; + rail_state->window_margin_top = window_margin_top; + rail_state->window_margin_right = window_margin_right; + rail_state->window_margin_bottom = window_margin_bottom; + + rdp_debug_verbose(b, "WindowUpdate(0x%x - window margin left:%d, top:%d, right:%d, bottom:%d\n", + window_id, window_margin_left, window_margin_top, window_margin_right, window_margin_bottom); + } + } + if (rail_state->forceUpdateWindowState || rail_state->clientPos.width != newClientPos.width || rail_state->clientPos.height != newClientPos.height || @@ -2254,7 +2347,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter } /* damageBox represents damaged area in contentBuffer */ /* if it's not remoting window shadow, exclude the area from damageBox */ - if (!b->enable_window_shadow_remoting) { + if (is_window_shadow_remoting_disabled(peerCtx)) { if (damageBox.x1 < contentBufferWindowGeometry.x) damageBox.x1 = contentBufferWindowGeometry.x; if (damageBox.x2 > contentBufferWindowGeometry.x + contentBufferWindowGeometry.width) @@ -3133,9 +3226,8 @@ rdp_rail_start_window_move( rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); } - if (!b->enable_window_shadow_remoting && - b->rdprail_shell_api && - b->rdprail_shell_api->get_window_geometry) { + if (is_window_shadow_remoting_disabled(peerCtx)) { + /* offset window shadow area */ b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); posX += windowGeometry.x; posY += windowGeometry.y; @@ -3236,9 +3328,8 @@ rdp_rail_end_window_move(struct weston_surface* surface) rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); } - if (!b->enable_window_shadow_remoting && - b->rdprail_shell_api && - b->rdprail_shell_api->get_window_geometry) { + if (is_window_shadow_remoting_disabled(peerCtx)) { + /* offset window shadow area */ b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); posX += windowGeometry.x; posY += windowGeometry.y; @@ -3589,6 +3680,9 @@ rdp_rail_dump_window_iter(void *element, void *data) fprintf(fp," RDP client position x:%d, y:%d width:%d height:%d\n", rail_state->clientPos.x, rail_state->clientPos.y, rail_state->clientPos.width, rail_state->clientPos.height); + fprintf(fp," Window margin left:%d, top:%d, right:%d bottom:%d\n", + rail_state->window_margin_left, rail_state->window_margin_top, + rail_state->window_margin_right, rail_state->window_margin_bottom); fprintf(fp," Window geometry x:%d, y:%d, width:%d height:%d\n", windowGeometry.x, windowGeometry.y, windowGeometry.width, windowGeometry.height); diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 861e02e11..b2ece61f4 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -4456,9 +4456,13 @@ shell_backend_get_window_geometry(struct weston_surface *surface, struct weston_ geometry->x = 0; if (geometry->y < 0) geometry->y = 0; - if (geometry->width > (geometry->x + surface->width)) + if (geometry->width == 0) + geometry->width = surface->width; + else if (geometry->width > (geometry->x + surface->width)) geometry->width = (geometry->x + surface->width); - if (geometry->height > (geometry->y + surface->height)) + if (geometry->height == 0) + geometry->height = surface->height; + else if (geometry->height > (geometry->y + surface->height)) geometry->height = (geometry->y + surface->height); } else { geometry->x = 0; From 7e24eca90125eacb19ac2b09b8d5a6e59cb6c186 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 18 Jul 2022 14:25:09 -0700 Subject: [PATCH 1539/1642] use pthread_cancel instead of pthread_kill (#93) Co-authored-by: Hideyuki Nagase --- compositor/rdpaudio.c | 27 +++------------------------ compositor/rdpaudioin.c | 27 +++------------------------ 2 files changed, 6 insertions(+), 48 deletions(-) diff --git a/compositor/rdpaudio.c b/compositor/rdpaudio.c index 0f7cb919d..bfb7638a2 100644 --- a/compositor/rdpaudio.c +++ b/compositor/rdpaudio.c @@ -538,34 +538,13 @@ rdp_audio_handle_get_latency(struct audio_out_private *priv) return 0; } -static void signalhandler(int sig) { - weston_log("RDP Audio: %s(%d)\n", __func__, sig); - return; -} - static void* rdp_audio_pulse_audio_sink_thread(void *context) { struct audio_out_private *priv = context; - struct sigaction act; - sigset_t set; - sigemptyset(&set); - if (sigaddset(&set, SIGUSR2) == -1) { - weston_log("Audio sink thread: sigaddset(SIGUSR2) failed.\n"); - return NULL; - } - if (pthread_sigmask(SIG_UNBLOCK, &set, NULL) != 0) { - weston_log("Audio sink thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); - return NULL; - } - act.sa_flags = 0; - act.sa_mask = set; - act.sa_handler = &signalhandler; - if (sigaction(SIGUSR2, &act, NULL) == -1) { - weston_log("Audio sink thread: sigaction(SIGUSR2) failed.\n"); - return NULL; - } + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); assert(priv->pulseAudioSinkListenerFd != 0); @@ -780,7 +759,7 @@ rdp_audio_out_destroy(void *audio_out_private) priv->audioExitSignal = TRUE; shutdown(priv->pulseAudioSinkListenerFd, SHUT_RDWR); shutdown(priv->pulseAudioSinkFd, SHUT_RDWR); - pthread_kill(priv->pulseAudioSinkThread, SIGUSR2); + pthread_cancel(priv->pulseAudioSinkThread); pthread_join(priv->pulseAudioSinkThread, NULL); if (priv->pulseAudioSinkListenerFd != -1) { diff --git a/compositor/rdpaudioin.c b/compositor/rdpaudioin.c index ceda28ae2..af367705f 100644 --- a/compositor/rdpaudioin.c +++ b/compositor/rdpaudioin.c @@ -403,34 +403,13 @@ rdp_audioin_client_receive_samples( return 0; } -static void signalhandler(int sig) { - weston_log("RDP AudioIn: %s(%d)\n", __func__, sig); - return; -} - static void* rdp_audioin_source_thread(void *context) { struct audio_in_private *priv = context; - struct sigaction act; - sigset_t set; - sigemptyset(&set); - if (sigaddset(&set, SIGUSR2) == -1) { - weston_log("AudioIn source thread: sigaddset(SIGUSR2) failed.\n"); - return NULL; - } - if (pthread_sigmask(SIG_UNBLOCK, &set, NULL) != 0) { - weston_log("AudioIn source thread: pthread_sigmask(SIG_UNBLOCK,SIGUSR2) failed.\n"); - return NULL; - } - act.sa_flags = 0; - act.sa_mask = set; - act.sa_handler = &signalhandler; - if (sigaction(SIGUSR2, &act, NULL) == -1) { - weston_log("AudioIn source thread: sigaction(SIGUSR2) failed.\n"); - return NULL; - } + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); assert(priv->closeAudioSourceFd != -1); assert(priv->pulseAudioSourceListenerFd != -1); @@ -574,7 +553,7 @@ rdp_audio_in_destroy(void *audio_in_private) priv->audioInExitSignal = TRUE; shutdown(priv->pulseAudioSourceListenerFd, SHUT_RDWR); shutdown(priv->closeAudioSourceFd, SHUT_RDWR); - pthread_kill(priv->pulseAudioSourceThread, SIGUSR2); + pthread_cancel(priv->pulseAudioSourceThread); pthread_join(priv->pulseAudioSourceThread, NULL); if (priv->pulseAudioSourceListenerFd != -1) { From e95242f80ef3cdc562d71665bb0f8bbff969bdcf Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 19 Jul 2022 06:57:06 -0700 Subject: [PATCH 1540/1642] compositor: Stop trapping SIGINT (#94) Co-authored-by: Hideyuki Nagase --- compositor/main.c | 49 +++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 857757bb3..027d5f4dd 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -440,6 +440,13 @@ weston_client_launch(struct weston_compositor *compositor, } if (pid == 0) { + /* Put the client in a new session so it won't catch signals + * intended for the parent. Sharing a session can be + * confusing when launching weston under gdb, as the ctrl-c + * intended for gdb will pass to the child, and weston + * will cleanly shut down when the child exits. + */ + setsid(); child_client_exec(sv[1], path); _exit(-1); } @@ -3248,13 +3255,19 @@ weston_log_subscribe_to_scopes(struct weston_log_context *log_ctx, } } +static void +sigint_helper(int sig) +{ + raise(SIGUSR2); +} + WL_EXPORT int wet_main(int argc, char *argv[]) { int ret = EXIT_FAILURE; char *cmdline; struct wl_display *display; - struct wl_event_source *signals[4]; + struct wl_event_source *signals[3]; struct wl_event_loop *loop; int i, fd; char *backend = NULL; @@ -3287,9 +3300,9 @@ wet_main(int argc, char *argv[]) struct weston_log_subscriber *logger = NULL; struct weston_log_subscriber *flight_rec = NULL; sigset_t mask; + struct sigaction action; bool wait_for_debugger = false; - bool disable_terminate_on_sigint = false; struct wl_protocol_logger *protologger = NULL; const struct weston_option core_options[] = { @@ -3307,7 +3320,6 @@ wet_main(int argc, char *argv[]) { WESTON_OPTION_BOOLEAN, "no-config", 0, &noconfig }, { WESTON_OPTION_STRING, "config", 'c', &config_file }, { WESTON_OPTION_BOOLEAN, "wait-for-debugger", 0, &wait_for_debugger }, - { WESTON_OPTION_BOOLEAN, "disable-terminate-on-sigint", 0, &disable_terminate_on_sigint }, { WESTON_OPTION_BOOLEAN, "debug", 0, &debug_protocol }, { WESTON_OPTION_STRING, "logger-scopes", 'l', &log_scopes }, { WESTON_OPTION_STRING, "flight-rec-scopes", 'f', &flight_rec_scopes }, @@ -3384,25 +3396,28 @@ wet_main(int argc, char *argv[]) loop = wl_display_get_event_loop(display); signals[0] = wl_event_loop_add_signal(loop, SIGTERM, on_term_signal, display); - - /* vs-code 'pause' button raise SIGINT, disable to terminate if requested */ - if (!disable_terminate_on_sigint) - weston_config_section_get_bool(section, "disable-terminate-on-sigint", - &disable_terminate_on_sigint, false); - if (!disable_terminate_on_sigint) - signals[1] = wl_event_loop_add_signal(loop, SIGINT, on_term_signal, - display); - else - signals[1] = NULL; - - signals[2] = wl_event_loop_add_signal(loop, SIGQUIT, on_term_signal, + signals[1] = wl_event_loop_add_signal(loop, SIGUSR2, on_term_signal, display); wl_list_init(&child_process_list); - signals[3] = wl_event_loop_add_signal(loop, SIGCHLD, sigchld_handler, + signals[2] = wl_event_loop_add_signal(loop, SIGCHLD, sigchld_handler, NULL); - if (!signals[0] || (!signals[1] && !disable_terminate_on_sigint) || !signals[2] || !signals[3]) + /* When debugging weston, if use wl_event_loop_add_signal() to catch + * SIGINT, the debugger can't catch it, and attempting to stop + * weston from within the debugger results in weston exiting + * cleanly. + * + * Instead, use the sigaction() function, which sets up the signal + * in a way that gdb can successfully catch, but have the handler + * for SIGINT send SIGUSR2 (xwayland uses SIGUSR1), which we catch + * via wl_event_loop_add_signal(). + */ + action.sa_handler = sigint_helper; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + sigaction(SIGINT, &action, NULL); + if (!signals[0] || !signals[1] || !signals[2]) goto out_signals; /* Xwayland uses SIGUSR1 for communicating with weston. Since some From b697455f83a32674cd7e0b242461ac0421f7f16d Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 19 Jul 2022 13:10:32 -0700 Subject: [PATCH 1541/1642] xwayland: give Xwayland its own session (#95) --- compositor/xwayland.c | 1 + 1 file changed, 1 insertion(+) diff --git a/compositor/xwayland.c b/compositor/xwayland.c index d9f0eec90..042c202b1 100644 --- a/compositor/xwayland.c +++ b/compositor/xwayland.c @@ -86,6 +86,7 @@ spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd pid = fork(); switch (pid) { case 0: + setsid(); /* SOCK_CLOEXEC closes both ends, so we need to unset * the flag on the client fd. */ fd = dup(sv[1]); From 56beef719b2dd44fdde56fa372875fa155e3e4e7 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 26 Jul 2022 10:58:19 -0700 Subject: [PATCH 1542/1642] rdp: resize margin adjustment for MoveWindow/SnapArrange PDU (#98) * rdp: resize margin adjustment for MoveWindow/SnapArrange PDU * fix comment typo * match code format with upstream-style Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdprail.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 5bb72aef8..5507b87d7 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -339,13 +339,23 @@ rail_client_SnapArrange_callback(bool freeOnly, void *arg) if (!freeOnly) surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, snap->windowId); if (surface) { - rail_state = (struct weston_surface_rail_state *)surface->backend_state; + rail_state = surface->backend_state; if (b->rdprail_shell_api && b->rdprail_shell_api->request_window_snap) { snapArrangeRect.x = snap->left; snapArrangeRect.y = snap->top; snapArrangeRect.width = snap->right - snap->left; snapArrangeRect.height = snap->bottom - snap->top; + /* SnapArrange PDU include window resize margin */ + /* [MS-RDPERP] - v20200304 - 3.2.5.1.6 Processing Window Information Orders + However, the Client Window Move PDU (section 2.2.2.7.4) and Client Window Snap PDU + (section 2.2.2.7.5) do include resize margins in the window boundaries. */ + snapArrangeRect.x += rail_state->window_margin_left; + snapArrangeRect.y += rail_state->window_margin_top; + snapArrangeRect.width -= rail_state->window_margin_left + + rail_state->window_margin_right; + snapArrangeRect.height -= rail_state->window_margin_top + + rail_state->window_margin_bottom; to_weston_coordinate(peerCtx, &snapArrangeRect.x, &snapArrangeRect.y, &snapArrangeRect.width, &snapArrangeRect.height); @@ -363,10 +373,9 @@ rail_client_SnapArrange_callback(bool freeOnly, void *arg) snapArrangeRect.y, snapArrangeRect.width, snapArrangeRect.height); + rail_state->forceUpdateWindowState = true; + rdp_rail_schedule_update_window(NULL, surface); } - - rail_state->forceUpdateWindowState = true; - rdp_rail_schedule_update_window(NULL, (void*)surface); } free(data); @@ -388,6 +397,7 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; struct weston_surface *surface; + struct weston_surface_rail_state *rail_state; pixman_rectangle32_t windowMoveRect; struct weston_geometry windowGeometry; @@ -404,12 +414,23 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) if (!freeOnly) surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, windowMove->windowId); if (surface) { + rail_state = surface->backend_state; if (b->rdprail_shell_api && b->rdprail_shell_api->request_window_move) { windowMoveRect.x = windowMove->left; windowMoveRect.y = windowMove->top; windowMoveRect.width = windowMove->right - windowMove->left; windowMoveRect.height = windowMove->bottom - windowMove->top; + /* WindowMove PDU include window resize margin */ + /* [MS-RDPERP] - v20200304 - 3.2.5.1.6 Processing Window Information Orders + However, the Client Window Move PDU (section 2.2.2.7.4) and Client Window Snap PDU + (section 2.2.2.7.5) do include resize margins in the window boundaries. */ + windowMoveRect.x += rail_state->window_margin_left; + windowMoveRect.y += rail_state->window_margin_top; + windowMoveRect.width -= rail_state->window_margin_left + + rail_state->window_margin_right; + windowMoveRect.height -= rail_state->window_margin_top + + rail_state->window_margin_bottom; to_weston_coordinate(peerCtx, &windowMoveRect.x, &windowMoveRect.y, &windowMoveRect.width, &windowMoveRect.height); @@ -427,6 +448,8 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) windowMoveRect.y, windowMoveRect.width, windowMoveRect.height); + rail_state->forceUpdateWindowState = true; + rdp_rail_schedule_update_window(NULL, surface); } } From db7b0c47b24aed171a1cddf567e30e8a31c4bd95 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 26 Jul 2022 12:00:00 -0700 Subject: [PATCH 1543/1642] add wslgd-notify (#92) --- compositor/meson.build | 13 +++++++ compositor/wslgd-notify.c | 77 +++++++++++++++++++++++++++++++++++++++ meson_options.txt | 7 ++++ 3 files changed, 97 insertions(+) create mode 100644 compositor/wslgd-notify.c diff --git a/compositor/meson.build b/compositor/meson.build index 1e32cf30e..70baffae2 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -181,6 +181,19 @@ if get_option('systemd') env_modmap += 'systemd-notify.so=@0@;'.format(plugin_systemd_notify.full_path()) endif +if get_option('wslgd') + plugin_wslgd_notify = shared_library( + 'wslgd-notify', + 'wslgd-notify.c', + include_directories: common_inc, + dependencies: [ dep_libweston_public ], + name_prefix: '', + install: true, + install_dir: dir_module_weston + ) + env_modmap += 'wslgd-notify.so=@0@;'.format(plugin_wslgd_notify.full_path()) +endif + weston_ini_config = configuration_data() weston_ini_config.set('bindir', dir_bin) weston_ini_config.set('libexecdir', dir_libexec) diff --git a/compositor/wslgd-notify.c b/compositor/wslgd-notify.c new file mode 100644 index 000000000..61a2eccfd --- /dev/null +++ b/compositor/wslgd-notify.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 Microsoft. All rights reservied. + * + * 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 "shared/helpers.h" +#include "shared/string-helpers.h" +#include +#include +#include "weston.h" + +WL_EXPORT int +wet_module_init(struct weston_compositor *compositor, + int *argc, char *argv[]) +{ + struct sockaddr_un addr = {}; + socklen_t size, name_size; + char *socket_path = getenv("WSLGD_NOTIFY_SOCKET"); + if (!socket_path) { + weston_log("%s: socket path is not specified\n", __func__); + return 0; + } + + int socket_fd = socket(PF_LOCAL, SOCK_SEQPACKET, 0); + if (socket_fd < 0) { + weston_log("%s: socket failed\n", __func__); + return -1; + } + + addr.sun_family = AF_LOCAL; + name_size = snprintf(addr.sun_path, sizeof addr.sun_path, + "%s", socket_path) + 1; + size = offsetof(struct sockaddr_un, sun_path) + name_size; + + int fd = connect(socket_fd, &addr, size); + if (fd < 0) { + weston_log("%s: connect(%s) failed %s\n", __func__, addr.sun_path, strerror(errno)); + goto close_socket_fd; + } + + weston_log("%s: socket connected\n", __FILE__); + + close(fd); + +close_socket_fd: + close(socket_fd); + + return 0; +} diff --git a/meson_options.txt b/meson_options.txt index 701b22fd5..250d655d9 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -92,6 +92,13 @@ option( description: 'systemd service plugin: state notify, watchdog, socket activation' ) +option( + 'wslgd', + type: 'boolean', + value: false, + description: 'wslgd plugin: state notify' +) + option( 'remoting', type: 'boolean', From 079a4350d4ae30e632703e38a0a32242edb38fce Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 27 Jul 2022 08:27:56 -0700 Subject: [PATCH 1544/1642] compositor: load xwayland module last (#99) Co-authored-by: Hideyuki Nagase --- compositor/main.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 027d5f4dd..4c4d7964c 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -963,7 +963,7 @@ wet_get_bindir_path(const char *name) static int load_modules(struct weston_compositor *ec, const char *modules, - int *argc, char *argv[], bool *xwayland) + int *argc, char *argv[]) { const char *p, *end; char buffer[256]; @@ -977,11 +977,11 @@ load_modules(struct weston_compositor *ec, const char *modules, snprintf(buffer, sizeof buffer, "%.*s", (int) (end - p), p); if (strstr(buffer, "xwayland.so")) { - weston_log("Old Xwayland module loading detected: " + weston_log("fatal: Old Xwayland module loading detected: " "Please use --xwayland command line option " "or set xwayland=true in the [core] section " "in weston.ini\n"); - *xwayland = true; + return -1; } else { if (wet_load_module(ec, buffer, argc, argv) < 0) return -1; @@ -3532,13 +3532,10 @@ wet_main(int argc, char *argv[]) if (wet_load_shell(wet.compositor, shell, &argc, argv) < 0) goto out; - weston_config_section_get_string(section, "modules", &modules, ""); - if (load_modules(wet.compositor, modules, &argc, argv, &xwayland) < 0) - goto out; - - if (load_modules(wet.compositor, option_modules, &argc, argv, &xwayland) < 0) - goto out; - + /* Load xwayland before other modules - this way if we're using + * the systemd-notify module it will notify after we're ready + * to receive xwayland connections. + */ if (!xwayland) { weston_config_section_get_bool(section, "xwayland", &xwayland, false); @@ -3548,6 +3545,13 @@ wet_main(int argc, char *argv[]) goto out; } + weston_config_section_get_string(section, "modules", &modules, ""); + if (load_modules(wet.compositor, modules, &argc, argv) < 0) + goto out; + + if (load_modules(wet.compositor, option_modules, &argc, argv) < 0) + goto out; + section = weston_config_get_section(config, "keyboard", NULL, NULL); weston_config_section_get_bool(section, "numlock-on", &numlock_on, false); if (numlock_on) { From 7fcd480f048cf3abfb92332580073bdd64f8fe32 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 1 Aug 2022 09:55:29 -0700 Subject: [PATCH 1545/1642] xwayland: Don't track focus for override redirect windows (#100) Co-authored-by: Hideyuki Nagase --- xwayland/window-manager.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index eae73bcae..06ec12cee 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2531,6 +2531,7 @@ weston_wm_handle_leave(struct weston_wm *wm, xcb_generic_event_t *event) static void weston_wm_handle_focus_in(struct weston_wm *wm, xcb_generic_event_t *event) { + struct weston_wm_window *window; xcb_focus_in_event_t *focus = (xcb_focus_in_event_t *) event; /* Do not interfere with grabs */ @@ -2538,6 +2539,17 @@ weston_wm_handle_focus_in(struct weston_wm *wm, xcb_generic_event_t *event) focus->mode == XCB_NOTIFY_MODE_UNGRAB) return; + if (!wm_lookup_window(wm, focus->event, &window)) + return; + + /* Sometimes apps like to focus their own windows, and we don't + * want to prevent that - but we'd like to at least prevent any + * attempt to focus a toplevel that isn't the currently activated + * toplevel. + */ + if (!window->frame) + return; + /* Do not let X clients change the focus behind the compositor's * back. Reset the focus to the old one if it changed. */ if (!wm->focus_window || focus->event != wm->focus_window->id) From bf26340394f1a20e8efcb94e7d90a4bbdbdb1c5d Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 24 Jun 2022 11:15:22 -0500 Subject: [PATCH 1546/1642] rdp: Update to new FreeRDP structure layout In an upcoming release the old style will be deprecated, so let's update now. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 52 +++++++++++++-------------- libweston/backend-rdp/rdpdisp.c | 8 ++--- libweston/backend-rdp/rdprail.c | 64 ++++++++++++++++----------------- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 134e9b5ef..eb7f720fb 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -82,7 +82,7 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p pixman_box32_t *region, *rects; uint32_t *ptr; RFX_RECT *rfxRect; - rdpUpdate *update = peer->update; + rdpUpdate *update = peer->context->update; SURFACE_BITS_COMMAND cmd = { 0 }; RdpPeerContext *context = (RdpPeerContext *)peer->context; @@ -99,7 +99,7 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p cmd.destRight = damage->extents.x2; cmd.destBottom = damage->extents.y2; cmd.bmp.bpp = 32; - cmd.bmp.codecID = peer->settings->RemoteFxCodecId; + cmd.bmp.codecID = peer->context->settings->RemoteFxCodecId; cmd.bmp.width = width; cmd.bmp.height = height; @@ -136,7 +136,7 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p { int width, height; uint32_t *ptr; - rdpUpdate *update = peer->update; + rdpUpdate *update = peer->context->update; SURFACE_BITS_COMMAND cmd = { 0 }; RdpPeerContext *context = (RdpPeerContext *)peer->context; @@ -153,7 +153,7 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p cmd.destRight = damage->extents.x2; cmd.destBottom = damage->extents.y2; cmd.bmp.bpp = 32; - cmd.bmp.codecID = peer->settings->NSCodecId; + cmd.bmp.codecID = peer->context->settings->NSCodecId; cmd.bmp.width = width; cmd.bmp.height = height; @@ -187,7 +187,7 @@ pixman_image_flipped_subrect(const pixman_box32_t *rect, pixman_image_t *img, BY static void rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_peer *peer) { - rdpUpdate *update = peer->update; + rdpUpdate *update = peer->context->update; SURFACE_BITS_COMMAND cmd = { 0 }; SURFACE_FRAME_MARKER marker; pixman_box32_t *rect, subrect; @@ -212,7 +212,7 @@ rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_p cmd.destRight = rect->x2; cmd.bmp.width = rect->x2 - rect->x1; - heightIncrement = peer->settings->MultifragMaxRequestSize / (16 + cmd.bmp.width * 4); + heightIncrement = peer->context->settings->MultifragMaxRequestSize / (16 + cmd.bmp.width * 4); remainingHeight = rect->y2 - rect->y1; top = rect->y1; @@ -249,7 +249,7 @@ rdp_peer_refresh_region(pixman_region32_t *region, freerdp_peer *peer) { RdpPeerContext *context = (RdpPeerContext *)peer->context; struct rdp_output *output = context->rdpBackend->output_default; - rdpSettings *settings = peer->settings; + rdpSettings *settings = peer->context->settings; if (settings->RemoteFxCodec) rdp_peer_refresh_rfx(region, output->shadow_surface, peer); @@ -295,7 +295,7 @@ rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, } if (b->rdp_peer && - b->rdp_peer->settings->HiDefRemoteApp) { + b->rdp_peer->context->settings->HiDefRemoteApp) { /* RAIL mode, repaint RAIL window */ rdp_rail_output_repaint(output_base, damage); } else if (output_base->renderer_state) { @@ -382,7 +382,7 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) const struct pixman_renderer_output_options options = { .use_shadow = true, }; bool HiDefRemoteApp = false; - if (rdpBackend->rdp_peer && rdpBackend->rdp_peer->settings->HiDefRemoteApp) + if (rdpBackend->rdp_peer && rdpBackend->rdp_peer->context->settings->HiDefRemoteApp) HiDefRemoteApp = true; local_mode = ensure_matching_mode(output, target_mode); @@ -424,7 +424,7 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) rdpOutput->shadow_surface = new_shadow_buffer; wl_list_for_each(rdpPeer, &rdpBackend->output_default->peers, link) { - settings = rdpPeer->peer->settings; + settings = rdpPeer->peer->context->settings; if (settings->DesktopWidth == (UINT32)target_mode->width && settings->DesktopHeight == (UINT32)target_mode->height) continue; @@ -436,7 +436,7 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) } else { settings->DesktopWidth = target_mode->width; settings->DesktopHeight = target_mode->height; - rdpPeer->peer->update->DesktopResize(rdpPeer->peer->context); + rdpPeer->peer->context->update->DesktopResize(rdpPeer->peer->context); } } } @@ -462,7 +462,7 @@ rdp_output_get_config(struct weston_output *base, h->monitorMode.monitorDef.width, h->monitorMode.monitorDef.height); /* In HiDef RAIL mode, get monitor resolution from RDP client if provided. */ - if (client && client->settings->HiDefRemoteApp) { + if (client && client->context->settings->HiDefRemoteApp) { if (h->monitorMode.monitorDef.width && h->monitorMode.monitorDef.height) { /* Return true client resolution (not adjusted by DPI) */ *width = h->monitorMode.monitorDef.width; @@ -509,7 +509,7 @@ rdp_output_set_size(struct weston_output *base, h->monitorMode.monitorDef.attributes.physicalHeight); /* In HiDef RAIL mode, set this mode as preferred mode */ - if (client && client->settings->HiDefRemoteApp) { + if (client && client->context->settings->HiDefRemoteApp) { if (h->monitorMode.monitorDef.width && h->monitorMode.monitorDef.height) { /* given width/height must match with monitor's if provided */ assert(width == h->monitorMode.monitorDef.width); @@ -559,7 +559,7 @@ rdp_output_enable(struct weston_output *base) }; bool HiDefRemoteApp = false; - if (b->rdp_peer && b->rdp_peer->settings->HiDefRemoteApp) + if (b->rdp_peer && b->rdp_peer->context->settings->HiDefRemoteApp) HiDefRemoteApp = true; if (HiDefRemoteApp) { @@ -764,7 +764,7 @@ rdp_destroy(struct weston_compositor *ec) } } else if (b->rdp_peer) { freerdp_peer* client = b->rdp_peer; - assert(client->settings->HiDefRemoteApp); + assert(client->context->settings->HiDefRemoteApp); client->Disconnect(client); freerdp_peer_context_free(client); @@ -857,8 +857,8 @@ rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) return FALSE; context->rfx_context->mode = RLGR3; - context->rfx_context->width = client->settings->DesktopWidth; - context->rfx_context->height = client->settings->DesktopHeight; + context->rfx_context->width = client->context->settings->DesktopWidth; + context->rfx_context->height = client->context->settings->DesktopHeight; rfx_context_set_pixel_format(context->rfx_context, DEFAULT_PIXEL_FORMAT); context->nsc_context = nsc_context_new(); @@ -1169,7 +1169,7 @@ xf_peer_activate(freerdp_peer* client) peerCtx = (RdpPeerContext *)client->context; b = peerCtx->rdpBackend; peersItem = &peerCtx->item; - settings = client->settings; + settings = client->context->settings; if (!settings->SurfaceCommandsEnabled) { rdp_debug_error(b, "client doesn't support required SurfaceCommands\n"); @@ -1254,7 +1254,7 @@ xf_peer_activate(freerdp_peer* client) } else { settings->DesktopWidth = output->base.width; settings->DesktopHeight = output->base.height; - client->update->DesktopResize(client->context); + client->context->update->DesktopResize(client->context); } } else { /* ask weston to adjust size */ @@ -1348,7 +1348,7 @@ xf_peer_activate(freerdp_peer* client) if (!settings->HiDefRemoteApp && output) { /* disable pointer on the client side */ - pointer = client->update->pointer; + pointer = client->context->update->pointer; pointer_system.type = SYSPTR_NULL; pointer->PointerSystem(client->context, &pointer_system); @@ -1672,7 +1672,7 @@ xf_input_synchronize_event(rdpInput *input, UINT32 flags) value); } - if (!client->settings->HiDefRemoteApp && output) { + if (!client->context->settings->HiDefRemoteApp && output) { /* sends a full refresh */ box.x1 = 0; box.y1 = 0; @@ -1725,8 +1725,8 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) /* From Linux's keyboard driver at drivers/input/keyboard/atkbd.c */ #define ATKBD_RET_HANJA 0xf1 #define ATKBD_RET_HANGEUL 0xf2 - if (client->settings->KeyboardType == 8 && - client->settings->KeyboardSubType == 6 && + if (client->context->settings->KeyboardType == 8 && + client->context->settings->KeyboardSubType == 6 && ((full_code == (KBD_FLAGS_EXTENDED | ATKBD_RET_HANJA)) || (full_code == (KBD_FLAGS_EXTENDED | ATKBD_RET_HANGEUL)))) { if (full_code == (KBD_FLAGS_EXTENDED | ATKBD_RET_HANJA)) @@ -1743,7 +1743,7 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) assert(keyState == WL_KEYBOARD_KEY_STATE_PRESSED); send_release_key = true; } else { - vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, client->settings->KeyboardType); + vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, client->context->settings->KeyboardType); } /* Korean keyboard support */ /* WinPR's GetKeycodeFromVirtualKeyCode() expects no extended bit for VK_HANGUL and VK_HANJA */ @@ -1847,7 +1847,7 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) peerCtx = (RdpPeerContext *) client->context; peerCtx->rdpBackend = b; - settings = client->settings; + settings = client->context->settings; /* configure security settings */ if (b->rdp_key) settings->RdpKeyFile = strdup(b->rdp_key); @@ -1895,7 +1895,7 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) client->Activate = xf_peer_activate; client->AdjustMonitorsLayout = xf_peer_adjust_monitor_layout; - client->update->SuppressOutput = (pSuppressOutput)xf_suppress_output; + client->context->update->SuppressOutput = (pSuppressOutput)xf_suppress_output; #if FREERDP_VERSION_MAJOR >= 3 input = client->context->input; diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 5cf4d9681..7349a2b22 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -201,7 +201,7 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo { RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - rdpSettings *settings = client->settings; + rdpSettings *settings = client->context->settings; struct weston_output *output = NULL; struct weston_head *head = NULL; struct rdp_head *current; @@ -530,7 +530,7 @@ disp_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MON { freerdp_peer *client = (freerdp_peer*)context->custom; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - rdpSettings *settings = client->settings; + rdpSettings *settings = client->context->settings; struct rdp_backend *b = peerCtx->rdpBackend; DISPLAY_CONTROL_MONITOR_LAYOUT *monitorLayout = displayControl->Monitors; struct rdp_monitor_mode *monitorMode; @@ -633,7 +633,7 @@ disp_client_monitor_layout_change(DispServerContext* context, const DISPLAY_CONT { freerdp_peer *client = (freerdp_peer*)context->custom; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - rdpSettings *settings = client->settings; + rdpSettings *settings = client->context->settings; struct rdp_backend *b = peerCtx->rdpBackend; struct disp_schedule_monitor_layout_change_data *data; @@ -663,7 +663,7 @@ xf_peer_adjust_monitor_layout(freerdp_peer* client) { RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - rdpSettings *settings = client->settings; + rdpSettings *settings = client->context->settings; BOOL success = TRUE; rdp_debug(b, "%s:\n", __func__); diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 5507b87d7..156368ebe 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -851,7 +851,7 @@ rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); const RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo = &data->u_languageImeInfo; freerdp_peer *client = data->client; - rdpSettings *settings = client->settings; + rdpSettings *settings = client->context->settings; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; UINT32 new_keyboard_layout = 0; @@ -1266,9 +1266,9 @@ rdp_rail_update_cursor(struct weston_surface *surface) /* hide pointer */ POINTER_SYSTEM_UPDATE pointerSystem = {}; pointerSystem.type = SYSPTR_NULL; - b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); - b->rdp_peer->update->pointer->PointerSystem(b->rdp_peer->update->context, &pointerSystem); - b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); + b->rdp_peer->context->update->pointer->PointerSystem(b->rdp_peer->context->update->context, &pointerSystem); + b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); } else if (isCursorResized || isCursorDamanged) { POINTER_LARGE_UPDATE pointerUpdate = {}; int cursorBpp = 4; // Bytes Per Pixel. @@ -1302,9 +1302,9 @@ rdp_rail_update_cursor(struct weston_surface *surface) pointerUpdate.andMaskData = NULL; rdp_debug_verbose(b, "CursorUpdate(width %d, height %d)\n", newPos.width, newPos.height); - b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); - b->rdp_peer->update->pointer->PointerLarge(b->rdp_peer->update->context, &pointerUpdate); - b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); + b->rdp_peer->context->update->pointer->PointerLarge(b->rdp_peer->context->update->context, &pointerUpdate); + b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); free(pointerBits); } @@ -1343,7 +1343,7 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) return; } - if (!b->rdp_peer->settings->HiDefRemoteApp) + if (!b->rdp_peer->context->settings->HiDefRemoteApp) return; if (!b->rdp_peer->context) { @@ -1537,9 +1537,9 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) rdp_debug_verbose(b, "WindowCreate(0x%x - (%d, %d, %d, %d)\n", window_id, clientPos.x, clientPos.y, clientPos.width, clientPos.height); - b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); - b->rdp_peer->update->window->WindowCreate(b->rdp_peer->update->context, &window_order_info, &window_state_order); - b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); + b->rdp_peer->context->update->window->WindowCreate(b->rdp_peer->context->update->context, &window_order_info, &window_state_order); + b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); rail_state->parent_window_id = window_state_order.ownerWindowId; rail_state->pos = pos; @@ -1634,9 +1634,9 @@ rdp_rail_destroy_window(struct wl_listener *listener, void *data) peerCtx = (RdpPeerContext *)b->rdp_peer->context; if (rail_state->isCursor) { pointerSystem.type = SYSPTR_NULL; - b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); - b->rdp_peer->update->pointer->PointerSystem(b->rdp_peer->update->context, &pointerSystem); - b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); + b->rdp_peer->context->update->pointer->PointerSystem(b->rdp_peer->context->update->context, &pointerSystem); + b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); if (peerCtx->cursorSurface == surface) peerCtx->cursorSurface = NULL; rail_state->isCursor = false; @@ -1671,9 +1671,9 @@ rdp_rail_destroy_window(struct wl_listener *listener, void *data) window_order_info.fieldFlags = WINDOW_ORDER_TYPE_WINDOW | WINDOW_ORDER_STATE_DELETED; rdp_debug_verbose(b, "WindowDestroy(0x%x)\n", window_id); - b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); - b->rdp_peer->update->window->WindowDelete(b->rdp_peer->update->context, &window_order_info); - b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); + b->rdp_peer->context->update->window->WindowDelete(b->rdp_peer->context->update->context, &window_order_info); + b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); if (rail_state->surface_id) { @@ -2210,9 +2210,9 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter window_id, newPos.x, newPos.y, newClientPos.x, newClientPos.y); } - b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); - b->rdp_peer->update->window->WindowUpdate(b->rdp_peer->update->context, &window_order_info, &window_state_order); - b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); + b->rdp_peer->context->update->window->WindowUpdate(b->rdp_peer->context->update->context, &window_order_info, &window_state_order); + b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); if (rail_window_title_string.string) free(rail_window_title_string.string); @@ -2770,7 +2770,7 @@ rdp_rail_sync_window_zorder(struct weston_compositor *compositor) monitored_desktop_order.numWindowIds = iCurrent; monitored_desktop_order.windowIds = windowIdArray; - client->update->window->MonitoredDesktop(client->context, &window_order_info, &monitored_desktop_order); + client->context->update->window->MonitoredDesktop(client->context, &window_order_info, &monitored_desktop_order); client->DrainOutputBuffer(client); Exit: @@ -2824,7 +2824,7 @@ rdp_rail_peer_activate(freerdp_peer* client) { RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - rdpSettings *settings = client->settings; + rdpSettings *settings = client->context->settings; BOOL rail_server_started = FALSE; BOOL disp_server_opened = FALSE; BOOL rail_grfx_server_opened = FALSE; @@ -3133,7 +3133,7 @@ rdp_rail_sync_window_status(freerdp_peer* client) window_order_info.windowId = RDP_RAIL_MARKER_WINDOW_ID; window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_HOOKED | WINDOW_ORDER_FIELD_DESKTOP_ARC_BEGAN; - client->update->window->MonitoredDesktop(client->update->context, &window_order_info, &monitored_desktop_order); + client->context->update->window->MonitoredDesktop(client->context->update->context, &window_order_info, &monitored_desktop_order); client->DrainOutputBuffer(client); } @@ -3150,7 +3150,7 @@ rdp_rail_sync_window_status(freerdp_peer* client) windowsIdArray[0] = RDP_RAIL_MARKER_WINDOW_ID; monitored_desktop_order.windowIds = (UINT*)&windowsIdArray; - client->update->window->MonitoredDesktop(client->update->context, &window_order_info, &monitored_desktop_order); + client->context->update->window->MonitoredDesktop(client->context->update->context, &window_order_info, &monitored_desktop_order); client->DrainOutputBuffer(client); } @@ -3161,7 +3161,7 @@ rdp_rail_sync_window_status(freerdp_peer* client) window_order_info.windowId = RDP_RAIL_MARKER_WINDOW_ID; window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED; - client->update->window->MonitoredDesktop(client->update->context, &window_order_info, &monitored_desktop_order); + client->context->update->window->MonitoredDesktop(client->context->update->context, &window_order_info, &monitored_desktop_order); client->DrainOutputBuffer(client); } @@ -3228,7 +3228,7 @@ rdp_rail_start_window_move( RAIL_MINMAXINFO_ORDER minmax_order; RAIL_LOCALMOVESIZE_ORDER move_order; - if (!b->rdp_peer || !b->rdp_peer->settings->HiDefRemoteApp) { + if (!b->rdp_peer || !b->rdp_peer->context->settings->HiDefRemoteApp) { return; } @@ -3328,7 +3328,7 @@ rdp_rail_end_window_move(struct weston_surface* surface) RdpPeerContext *peerCtx = NULL; RAIL_LOCALMOVESIZE_ORDER move_order; - if (!b->rdp_peer || !b->rdp_peer->settings->HiDefRemoteApp) { + if (!b->rdp_peer || !b->rdp_peer->context->settings->HiDefRemoteApp) { return; } @@ -3868,7 +3868,7 @@ rdp_rail_set_window_icon(struct weston_surface *surface, pixman_image_t *icon) peerCtx = (RdpPeerContext *)b->rdp_peer->context; - if (!b->rdp_peer->settings->HiDefRemoteApp) + if (!b->rdp_peer->context->settings->HiDefRemoteApp) return; assert_compositor_thread(b); @@ -4011,9 +4011,9 @@ rdp_rail_set_window_icon(struct weston_surface *surface, pixman_image_t *icon) iconInfo.bitsColor = bitsColor; iconOrder.iconInfo = &iconInfo; - b->rdp_peer->update->BeginPaint(b->rdp_peer->update->context); - b->rdp_peer->update->window->WindowIcon(b->rdp_peer->update->context, &orderInfo, &iconOrder); - b->rdp_peer->update->EndPaint(b->rdp_peer->update->context); + b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); + b->rdp_peer->context->update->window->WindowIcon(b->rdp_peer->context->update->context, &orderInfo, &iconOrder); + b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); exit: if (bitsMask) @@ -4038,7 +4038,7 @@ rdp_rail_notify_app_list(void *rdp_backend, struct weston_rdprail_app_list_data return false; // return false only when peer is not ready for possible re-send. } - if (!b->rdp_peer->settings->HiDefRemoteApp) + if (!b->rdp_peer->context->settings->HiDefRemoteApp) return true; peerCtx = (RdpPeerContext *)b->rdp_peer->context; From fd7bf2af27140bee0b68f03b8e35ab4c67e9e3ed Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 7 Jun 2022 11:50:28 -0500 Subject: [PATCH 1547/1642] rdp: Stop using deprecated functions The get file descriptor functions are being deprecated and a two step process of getting handles and then getting the descriptors for the handles is being used instead. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index eb7f720fb..af34aa46f 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -820,18 +820,19 @@ static int rdp_implant_listener(struct rdp_backend *b, freerdp_listener* instance) { int i, fd; - int rcount = 0; - void* rfds[MAX_FREERDP_FDS]; + int handle_count = 0; + HANDLE handles[MAX_FREERDP_FDS]; struct wl_event_loop *loop; - if (!instance->GetFileDescriptor(instance, rfds, &rcount)) { - weston_log("Failed to get FreeRDP file descriptor\n"); + handle_count = instance->GetEventHandles(instance, handles, MAX_FREERDP_FDS); + if (!handle_count) { + weston_log("Failed to get FreeRDP handles\n"); return -1; } loop = wl_display_get_event_loop(b->compositor->wl_display); - for (i = 0; i < rcount; i++) { - fd = (int)(long)(rfds[i]); + for (i = 0; i < handle_count; i++) { + fd = GetEventFileDescriptor(handles[i]); b->listener_events[i] = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, rdp_listener_activity, instance); } @@ -1831,9 +1832,9 @@ is_tls_enabled(struct rdp_backend *b) static int rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) { - unsigned i, rcount = 0; - void *rfds[MAX_FREERDP_FDS+1]; // +1 for WTSVirtualChannelManagerGetFileDescriptor. - int fd; + int i, fd; + int handle_count = 0; + HANDLE handles[MAX_FREERDP_FDS + 1]; /* +1 for virtual channel */ struct wl_event_loop *loop; rdpSettings *settings; rdpInput *input; @@ -1908,8 +1909,9 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) input->KeyboardEvent = xf_input_keyboard_event; input->UnicodeKeyboardEvent = xf_input_unicode_keyboard_event; - if (!client->GetFileDescriptor(client, rfds, &rcount)) { - rdp_debug_error(b, "unable to retrieve client fds\n"); + handle_count = client->GetEventHandles(client, handles, MAX_FREERDP_FDS); + if (!handle_count) { + rdp_debug_error(b, "unable to retrieve client handles\n"); goto error_initialize; } @@ -1917,19 +1919,19 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) WTSRegisterWtsApiFunctionTable(fn); peerCtx->vcm = WTSOpenServerA((LPSTR)peerCtx); if (peerCtx->vcm) { - WTSVirtualChannelManagerGetFileDescriptor(peerCtx->vcm, rfds, &rcount); + handles[handle_count++] = WTSVirtualChannelManagerGetEventHandle(peerCtx->vcm); } else { rdp_debug_error(b, "WTSOpenServer is failed! continue without virtual channel.\n"); } loop = wl_display_get_event_loop(b->compositor->wl_display); - for (i = 0; i < rcount; i++) { - fd = (int)(long)(rfds[i]); + for (i = 0; i < handle_count; i++) { + fd = GetEventFileDescriptor(handles[i]); peerCtx->events[i] = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, rdp_client_activity, client); } - for ( ; i < ARRAY_LENGTH(peerCtx->events); i++) + for ( ; i < (int)ARRAY_LENGTH(peerCtx->events); i++) peerCtx->events[i] = 0; if (!rdp_initialize_dispatch_task_event_source(peerCtx)) @@ -1954,7 +1956,7 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) rdp_destroy_dispatch_task_event_source(peerCtx); error_dispatch_initialize: - for (i = 0; i < ARRAY_LENGTH(peerCtx->events); i++) { + for (i = 0; i < (int)ARRAY_LENGTH(peerCtx->events); i++) { if (peerCtx->events[i]) { wl_event_source_remove(peerCtx->events[i]); peerCtx->events[i] = NULL; From 762a2130d49e381b0ca7efc8a90ee00b1415a499 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 27 Jun 2022 13:33:42 -0500 Subject: [PATCH 1548/1642] shared: Make xalloc.h stand alone Make fail_on_null static inline and put it in xalloc.h so we can use the header exclusively instead of having to link with the library for it. This is so we can use xalloc in places (like the RDP backend) without having to bring in libshared. Signed-off-by: Derek Foreman --- shared/meson.build | 1 - shared/xalloc.c | 50 ---------------------------------------------- shared/xalloc.h | 20 +++++++++++++++++-- 3 files changed, 18 insertions(+), 53 deletions(-) delete mode 100644 shared/xalloc.c diff --git a/shared/meson.build b/shared/meson.build index 8073dcdb3..8a740125f 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -3,7 +3,6 @@ srcs_libshared = [ 'option-parser.c', 'file-util.c', 'os-compatibility.c', - 'xalloc.c', ] deps_libshared = dep_wayland_client diff --git a/shared/xalloc.c b/shared/xalloc.c deleted file mode 100644 index 1cc5c12a0..000000000 --- a/shared/xalloc.c +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright © 2008 Kristian Høgsberg - * - * 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 "xalloc.h" - -void * -fail_on_null(void *p, size_t size, char *file, int32_t line) -{ - if (p == NULL) { - fprintf(stderr, "[%s] ", program_invocation_short_name); - if (file) - fprintf(stderr, "%s:%d: ", file, line); - fprintf(stderr, "out of memory"); - if (size) - fprintf(stderr, " (%zd)", size); - fprintf(stderr, "\n"); - exit(EXIT_FAILURE); - } - - return p; -} diff --git a/shared/xalloc.h b/shared/xalloc.h index cd39dd8b3..15ad1fadf 100644 --- a/shared/xalloc.h +++ b/shared/xalloc.h @@ -30,14 +30,30 @@ extern "C" { #endif +#include #include #include #include #include -void * -fail_on_null(void *p, size_t size, char *file, int32_t line); + +static inline void * +fail_on_null(void *p, size_t size, char *file, int32_t line) +{ + if (p == NULL) { + fprintf(stderr, "[%s] ", program_invocation_short_name); + if (file) + fprintf(stderr, "%s:%d: ", file, line); + fprintf(stderr, "out of memory"); + if (size) + fprintf(stderr, " (%zd)", size); + fprintf(stderr, "\n"); + exit(EXIT_FAILURE); + } + + return p; +} #define xmalloc(s) (fail_on_null(malloc(s), (s), __FILE__, __LINE__)) #define xzalloc(s) (fail_on_null(zalloc(s), (s), __FILE__, __LINE__)) From 59117987a753c983a3630402f34b82bcac5d6ce2 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 27 Jun 2022 14:24:27 -0500 Subject: [PATCH 1549/1642] rdp: Refactor display management code Split this into front/back end bits, with the front end parts putting together a monitor array in a common form, and the back end bits in rdpdisp.c doing the final processing. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 74 ++++++++++++ libweston/backend-rdp/rdp.h | 7 +- libweston/backend-rdp/rdpdisp.c | 193 +++++++------------------------- libweston/backend-rdp/rdprail.c | 71 ++++++++++++ 4 files changed, 191 insertions(+), 154 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index af34aa46f..8d9b91fb5 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -56,6 +56,7 @@ #include #include #include "pixman-renderer.h" +#include "shared/xalloc.h" #if HAVE_OPENSSL /* for session certificate generation */ @@ -69,6 +70,9 @@ extern PWtsApiFunctionTable FreeRDP_InitWtsApi(void); +static BOOL +xf_peer_adjust_monitor_layout(freerdp_peer *client); + static void rdp_peer_seat_led_update(struct weston_seat *seat_base, enum weston_led leds) { @@ -1817,6 +1821,76 @@ 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; + 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); + + /* these settings must have no impact in RAIL mode */ + /* In RAIL mode, it must mirror client's monitor settings */ + /* If not in RAIL mode, or RAIL-shell is not used, only signle mon is allowed */ + if (!settings->HiDefRemoteApp || b->rdprail_shell_api == NULL) { + if (settings->MonitorCount > 1) { + rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: multiple monitor is not supported in non HiDef RAIL mode\nWARNING\nWARNING\n"); + return FALSE; + } + } + if (settings->MonitorCount > RDP_MAX_MONITOR) { + rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: client reports more monitors then expected:(%d)\nWARNING\nWARNING\n", + settings->MonitorCount); + return FALSE; + } + + if (settings->MonitorCount > 0 && settings->MonitorDefArray) { + 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; // settings->DesktopPosX; + monitors[0].y = 0; // settings->DesktopPosY; + 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; + } + success = handle_adjust_monitor_layout(client, monitor_count, monitors); + + free(monitors); + return success; +} + static BOOL using_session_tls(struct rdp_backend *b) { diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index b69dfd9e8..6c2a3aea4 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -403,8 +403,11 @@ void rdp_rail_start_window_move(struct weston_surface* surface, int pointerGrabX void rdp_rail_end_window_move(struct weston_surface* surface); // rdpdisp.c -UINT disp_client_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MONITOR_LAYOUT_PDU* displayControl); -BOOL xf_peer_adjust_monitor_layout(freerdp_peer* client); +void +disp_monitor_layout_change(DispServerContext* context, int monitor_count, rdpMonitor *monitors); + +bool +handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor *monitors); // rdpclip.c int rdp_clipboard_init(freerdp_peer* client); diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 7349a2b22..334f39d1b 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -39,6 +39,8 @@ #include "rdp.h" +#include "shared/xalloc.h" + static BOOL is_line_intersected(int l1, int l2, int r1, int r2) { @@ -525,60 +527,53 @@ disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_mon return TRUE; } -static void -disp_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MONITOR_LAYOUT_PDU* displayControl) +void +disp_monitor_layout_change(DispServerContext* context, int monitor_count, rdpMonitor *monitors) { freerdp_peer *client = (freerdp_peer*)context->custom; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; rdpSettings *settings = client->context->settings; struct rdp_backend *b = peerCtx->rdpBackend; - DISPLAY_CONTROL_MONITOR_LAYOUT *monitorLayout = displayControl->Monitors; struct rdp_monitor_mode *monitorMode; MONITOR_DEF *resetMonitorDef; assert_compositor_thread(b); - rdp_debug(b, "Client: DisplayControl: monitor count:0x%x\n", displayControl->NumMonitors); + rdp_debug(b, "Client: DisplayControl: monitor count:0x%x\n", monitor_count); assert(settings->HiDefRemoteApp); - if (displayControl->NumMonitors > RDP_MAX_MONITOR) { + if (monitor_count > RDP_MAX_MONITOR) { rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: client reports more monitors then expected:(%d)\nWARNING\nWARNING\n", - displayControl->NumMonitors); + monitor_count); return; } - monitorMode = malloc(sizeof(struct rdp_monitor_mode) * displayControl->NumMonitors); - if (!monitorMode) - return; - resetMonitorDef = malloc(sizeof(MONITOR_DEF) * displayControl->NumMonitors); + monitorMode = xmalloc(sizeof(struct rdp_monitor_mode) * monitor_count); + resetMonitorDef = malloc(sizeof(MONITOR_DEF) * monitor_count); if (!resetMonitorDef) { free(monitorMode); return; } - for (UINT i = 0; i < displayControl->NumMonitors; i++, monitorLayout++) { - monitorMode[i].monitorDef.x = resetMonitorDef[i].left = monitorLayout->Left; - monitorMode[i].monitorDef.y = resetMonitorDef[i].top = monitorLayout->Top; - monitorMode[i].monitorDef.width = resetMonitorDef[i].right = monitorLayout->Width; - monitorMode[i].monitorDef.height = resetMonitorDef[i].bottom = monitorLayout->Height; - monitorMode[i].monitorDef.is_primary = resetMonitorDef[i].flags = monitorLayout->Flags & DISPLAY_CONTROL_MONITOR_PRIMARY ? 1 : 0; + for (int i = 0; i < monitor_count; i++) { + monitorMode[i].monitorDef = monitors[i]; + resetMonitorDef[i].left = monitors[i].x; + resetMonitorDef[i].top = monitors[i].y; + resetMonitorDef[i].right = monitors[i].width; + resetMonitorDef[i].bottom = monitors[i].height; + resetMonitorDef[i].flags = monitors[i].is_primary; monitorMode[i].monitorDef.orig_screen = 0; - monitorMode[i].monitorDef.attributes.physicalWidth = monitorLayout->PhysicalWidth; - monitorMode[i].monitorDef.attributes.physicalHeight = monitorLayout->PhysicalHeight; - monitorMode[i].monitorDef.attributes.orientation = monitorLayout->Orientation; - monitorMode[i].monitorDef.attributes.desktopScaleFactor = monitorLayout->DesktopScaleFactor; - monitorMode[i].monitorDef.attributes.deviceScaleFactor = monitorLayout->DeviceScaleFactor; monitorMode[i].scale = disp_get_output_scale_from_monitor(peerCtx, &monitorMode[i]); monitorMode[i].clientScale = disp_get_client_scale_from_monitor(peerCtx, &monitorMode[i]); } - if (!disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, displayControl->NumMonitors)) + if (!disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitor_count)) goto Exit; int doneIndex = 0; - disp_start_monitor_layout_change(client, monitorMode, displayControl->NumMonitors, &doneIndex); - for (UINT i = 0; i < displayControl->NumMonitors; i++) { + disp_start_monitor_layout_change(client, monitorMode, monitor_count, &doneIndex); + for (int i = 0; i < monitor_count; i++) { if ((doneIndex & (1 << i)) == 0) { if (disp_set_monitor_layout_change(client, &monitorMode[i]) != 0) goto Exit; @@ -590,7 +585,7 @@ disp_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MON RDPGFX_RESET_GRAPHICS_PDU resetGraphics = {}; resetGraphics.width = peerCtx->regionClientHeads.extents.x2 - peerCtx->regionClientHeads.extents.x1; resetGraphics.height = peerCtx->regionClientHeads.extents.y2 - peerCtx->regionClientHeads.extents.x1; - resetGraphics.monitorCount = displayControl->NumMonitors; + resetGraphics.monitorCount = monitor_count; resetGraphics.monitorDefArray = resetMonitorDef; peerCtx->rail_grfx_server_context->ResetGraphics(peerCtx->rail_grfx_server_context, &resetGraphics); @@ -604,148 +599,42 @@ disp_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MON return; } -struct disp_schedule_monitor_layout_change_data { - struct rdp_loop_task _base; - DispServerContext* context; - DISPLAY_CONTROL_MONITOR_LAYOUT_PDU displayControl; -}; - -static void -disp_monitor_layout_change_callback(bool freeOnly, void* dataIn) +bool +handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor *monitors) { - struct disp_schedule_monitor_layout_change_data *data = wl_container_of(dataIn, data, _base); - DispServerContext* context = data->context; - freerdp_peer *client = (freerdp_peer*)context->custom; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + bool success = true; + struct rdp_monitor_mode *monitorMode = NULL; + int i; - assert_compositor_thread(peerCtx->rdpBackend); - - if (!freeOnly) - disp_monitor_layout_change(context, &data->displayControl); - - free(data); - - return; -} - -UINT -disp_client_monitor_layout_change(DispServerContext* context, const DISPLAY_CONTROL_MONITOR_LAYOUT_PDU* displayControl) -{ - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - rdpSettings *settings = client->context->settings; - struct rdp_backend *b = peerCtx->rdpBackend; - struct disp_schedule_monitor_layout_change_data *data; - - assert_not_compositor_thread(b); - - rdp_debug(b, "Client: DisplayLayoutChange: monitor count:0x%x\n", displayControl->NumMonitors); - - assert(settings->HiDefRemoteApp); - - data = malloc(sizeof(*data) + (sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT) * displayControl->NumMonitors)); - if (!data) - return ERROR_INTERNAL_ERROR; - - data->context = context; - data->displayControl = *displayControl; - data->displayControl.Monitors = (DISPLAY_CONTROL_MONITOR_LAYOUT*)(data+1); - memcpy(data->displayControl.Monitors, displayControl->Monitors, - sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT) * displayControl->NumMonitors); - - rdp_dispatch_task_to_display_loop(peerCtx, disp_monitor_layout_change_callback, &data->_base); - - return 0; -} + monitorMode = malloc(sizeof(struct rdp_monitor_mode) * monitor_count); + if (!monitorMode) + return true; -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; - BOOL success = TRUE; - - 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); - - /* these settings must have no impact in RAIL mode */ - /* In RAIL mode, it must mirror client's monitor settings */ - /* If not in RAIL mode, or RAIL-shell is not used, only signle mon is allowed */ - if (!settings->HiDefRemoteApp || b->rdprail_shell_api == NULL) { - if (settings->MonitorCount > 1) { - rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: multiple monitor is not supported in non HiDef RAIL mode\nWARNING\nWARNING\n"); - return FALSE; - } - } - if (settings->MonitorCount > RDP_MAX_MONITOR) { - rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: client reports more monitors then expected:(%d)\nWARNING\nWARNING\n", - settings->MonitorCount); - return FALSE; - } - struct rdp_monitor_mode _monitorMode = {}; - struct rdp_monitor_mode *monitorMode = NULL; - UINT32 monitorCount; - if (settings->MonitorCount > 0 && settings->MonitorDefArray) { - rdpMonitor *rdp_monitor = settings->MonitorDefArray; - monitorCount = settings->MonitorCount; - monitorMode = malloc(sizeof(struct rdp_monitor_mode) * monitorCount); - if (!monitorMode) - return FALSE; - for (UINT32 i = 0; i < monitorCount; i++) { - monitorMode[i].monitorDef = rdp_monitor[i]; - if (!settings->HasMonitorAttributes) { - monitorMode[i].monitorDef.attributes.physicalWidth = 0; - monitorMode[i].monitorDef.attributes.physicalHeight = 0; - monitorMode[i].monitorDef.attributes.orientation = ORIENTATION_LANDSCAPE; - monitorMode[i].monitorDef.attributes.desktopScaleFactor = 100; - monitorMode[i].monitorDef.attributes.deviceScaleFactor = 100; - } - monitorMode[i].scale = disp_get_output_scale_from_monitor(peerCtx, &monitorMode[i]); - monitorMode[i].clientScale = disp_get_client_scale_from_monitor(peerCtx, &monitorMode[i]); - } - } else { - /* when no monitor array provided, generate from desktop settings */ - _monitorMode.monitorDef.x = 0; // settings->DesktopPosX; - _monitorMode.monitorDef.y = 0; // settings->DesktopPosY; - _monitorMode.monitorDef.width = settings->DesktopWidth; - _monitorMode.monitorDef.height = settings->DesktopHeight; - _monitorMode.monitorDef.is_primary = 1; - _monitorMode.monitorDef.attributes.physicalWidth = settings->DesktopPhysicalWidth; - _monitorMode.monitorDef.attributes.physicalHeight = settings->DesktopPhysicalHeight; - _monitorMode.monitorDef.attributes.orientation = settings->DesktopOrientation; - _monitorMode.monitorDef.attributes.desktopScaleFactor = settings->DesktopScaleFactor; - _monitorMode.monitorDef.attributes.deviceScaleFactor = settings->DeviceScaleFactor; - _monitorMode.scale = disp_get_output_scale_from_monitor(peerCtx, &_monitorMode); - _monitorMode.clientScale = disp_get_client_scale_from_monitor(peerCtx, &_monitorMode); - monitorCount = 1; - monitorMode = &_monitorMode; + for (i = 0; i < monitor_count; i++) { + monitorMode[i].monitorDef = monitors[i]; + monitorMode[i].scale = disp_get_output_scale_from_monitor(peerCtx, &monitorMode[i]); + monitorMode[i].clientScale = disp_get_client_scale_from_monitor(peerCtx, &monitorMode[i]); } - if (!disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitorCount)) { - success = FALSE; - goto Exit; + if (!disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitor_count)) { + success = true; + goto exit; } int doneIndex = 0; - disp_start_monitor_layout_change(client, monitorMode, monitorCount, &doneIndex); - for (UINT32 i = 0; i < monitorCount; i++) { + disp_start_monitor_layout_change(client, monitorMode, monitor_count, &doneIndex); + for (int i = 0; i < monitor_count; i++) { if ((doneIndex & (1 << i)) == 0) if (disp_set_monitor_layout_change(client, &monitorMode[i]) != 0) { - success = FALSE; - goto Exit; + success = true; + goto exit; } } disp_end_monitor_layout_change(client); -Exit: - if (monitorMode != &_monitorMode) - free(monitorMode); +exit: + free(monitorMode); return success; } diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 156368ebe..3c6ab80f3 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -45,6 +45,7 @@ #include "rdp.h" #include "libweston-internal.h" +#include "shared/xalloc.h" #define RAIL_WINDOW_FULLSCREEN_STYLE (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_GROUP | WS_TABSTOP) #define RAIL_WINDOW_NORMAL_STYLE (RAIL_WINDOW_FULLSCREEN_STYLE | WS_THICKFRAME | WS_CAPTION) @@ -2819,6 +2820,76 @@ rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage) return; } +struct disp_schedule_monitor_layout_change_data { + struct rdp_loop_task _base; + DispServerContext *context; + int count; + rdpMonitor *monitors; +}; + +static void +disp_monitor_layout_change_callback(bool freeOnly, void *dataIn) +{ + struct disp_schedule_monitor_layout_change_data *data = wl_container_of(dataIn, data, _base); + DispServerContext *context = data->context; + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + + assert_compositor_thread(b); + + if (freeOnly) + goto out; + + disp_monitor_layout_change(context, data->count, data->monitors); + +out: + free(data); + return; +} + +static unsigned int +disp_client_monitor_layout_change(DispServerContext *context, const DISPLAY_CONTROL_MONITOR_LAYOUT_PDU *display_control) +{ + freerdp_peer *client = (freerdp_peer*)context->custom; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + rdpSettings *settings = client->context->settings; + struct rdp_backend *b = peerCtx->rdpBackend; + struct disp_schedule_monitor_layout_change_data *data; + unsigned int i; + + assert_not_compositor_thread(b); + + rdp_debug(b, "Client: DisplayLayoutChange: monitor count:0x%x\n", display_control->NumMonitors); + + assert(settings->HiDefRemoteApp); + + data = xmalloc(sizeof(*data) + (sizeof(rdpMonitor) * display_control->NumMonitors)); + + data->context = context; + data->monitors = (rdpMonitor *)(data + 1); + data->count = display_control->NumMonitors; + for (i = 0; i < display_control->NumMonitors; i++) { + DISPLAY_CONTROL_MONITOR_LAYOUT *ml = &display_control->Monitors[i]; + + data->monitors[i].x = ml->Left; + data->monitors[i].y = ml->Top; + data->monitors[i].width = ml->Width; + data->monitors[i].height = ml->Height; + data->monitors[i].is_primary = !!(ml->Flags & DISPLAY_CONTROL_MONITOR_PRIMARY); + data->monitors[i].attributes.physicalWidth = ml->PhysicalWidth; + data->monitors[i].attributes.physicalHeight = ml->PhysicalHeight; + data->monitors[i].attributes.orientation = ml->Orientation; + data->monitors[i].attributes.desktopScaleFactor = ml->DesktopScaleFactor; + data->monitors[i].attributes.deviceScaleFactor = ml->DeviceScaleFactor; + data->monitors[i].orig_screen = 0; + } + + rdp_dispatch_task_to_display_loop(peerCtx, disp_monitor_layout_change_callback, &data->_base); + + return CHANNEL_RC_OK; +} + BOOL rdp_rail_peer_activate(freerdp_peer* client) { From 312cf2d5f801c16285397c61aa572e3df99886f4 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 28 Jun 2022 11:30:21 -0500 Subject: [PATCH 1550/1642] rdp: Move RAIL ResetGraphics handling Pulling backend handling code out to aid further refactoring. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.h | 2 +- libweston/backend-rdp/rdpdisp.c | 49 +++++++-------------------------- libweston/backend-rdp/rdprail.c | 37 ++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 6c2a3aea4..08ef11916 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -403,7 +403,7 @@ void rdp_rail_start_window_move(struct weston_surface* surface, int pointerGrabX void rdp_rail_end_window_move(struct weston_surface* surface); // rdpdisp.c -void +bool disp_monitor_layout_change(DispServerContext* context, int monitor_count, rdpMonitor *monitors); bool diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 334f39d1b..b0cc1d6a3 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -336,15 +336,6 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo return 0; } -static void -disp_force_recreate_iter(void *element, void *data) -{ - struct weston_surface *surface = (struct weston_surface *)element; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - rail_state->forceRecreateSurface = TRUE; - rail_state->forceUpdateWindowState = TRUE; -} - static BOOL disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode, UINT32 monitorCount) { @@ -527,7 +518,7 @@ disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_mon return TRUE; } -void +bool disp_monitor_layout_change(DispServerContext* context, int monitor_count, rdpMonitor *monitors) { freerdp_peer *client = (freerdp_peer*)context->custom; @@ -535,7 +526,7 @@ disp_monitor_layout_change(DispServerContext* context, int monitor_count, rdpMon rdpSettings *settings = client->context->settings; struct rdp_backend *b = peerCtx->rdpBackend; struct rdp_monitor_mode *monitorMode; - MONITOR_DEF *resetMonitorDef; + bool success = true; assert_compositor_thread(b); @@ -546,57 +537,37 @@ disp_monitor_layout_change(DispServerContext* context, int monitor_count, rdpMon if (monitor_count > RDP_MAX_MONITOR) { rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: client reports more monitors then expected:(%d)\nWARNING\nWARNING\n", monitor_count); - return; + return false; } monitorMode = xmalloc(sizeof(struct rdp_monitor_mode) * monitor_count); - resetMonitorDef = malloc(sizeof(MONITOR_DEF) * monitor_count); - if (!resetMonitorDef) { - free(monitorMode); - return; - } for (int i = 0; i < monitor_count; i++) { monitorMode[i].monitorDef = monitors[i]; - resetMonitorDef[i].left = monitors[i].x; - resetMonitorDef[i].top = monitors[i].y; - resetMonitorDef[i].right = monitors[i].width; - resetMonitorDef[i].bottom = monitors[i].height; - resetMonitorDef[i].flags = monitors[i].is_primary; monitorMode[i].monitorDef.orig_screen = 0; monitorMode[i].scale = disp_get_output_scale_from_monitor(peerCtx, &monitorMode[i]); monitorMode[i].clientScale = disp_get_client_scale_from_monitor(peerCtx, &monitorMode[i]); } - if (!disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitor_count)) + if (!disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitor_count)) { + success = false; goto Exit; - + } int doneIndex = 0; disp_start_monitor_layout_change(client, monitorMode, monitor_count, &doneIndex); for (int i = 0; i < monitor_count; i++) { if ((doneIndex & (1 << i)) == 0) { - if (disp_set_monitor_layout_change(client, &monitorMode[i]) != 0) + if (disp_set_monitor_layout_change(client, &monitorMode[i]) != 0) { + success = false; goto Exit; + } } } disp_end_monitor_layout_change(client); - /* tell client the server updated the monitor layout */ - RDPGFX_RESET_GRAPHICS_PDU resetGraphics = {}; - resetGraphics.width = peerCtx->regionClientHeads.extents.x2 - peerCtx->regionClientHeads.extents.x1; - resetGraphics.height = peerCtx->regionClientHeads.extents.y2 - peerCtx->regionClientHeads.extents.x1; - resetGraphics.monitorCount = monitor_count; - resetGraphics.monitorDefArray = resetMonitorDef; - peerCtx->rail_grfx_server_context->ResetGraphics(peerCtx->rail_grfx_server_context, &resetGraphics); - - /* force recreate all surface and redraw. */ - rdp_id_manager_for_each(&peerCtx->windowId, disp_force_recreate_iter, NULL); - weston_compositor_damage_all(b->compositor); - Exit: free(monitorMode); - free(resetMonitorDef); - return; + return success; } bool diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 3c6ab80f3..221225624 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2820,6 +2820,16 @@ rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage) return; } +static void +disp_force_recreate_iter(void *element, void *data) +{ + struct weston_surface *surface = element; + struct weston_surface_rail_state *rail_state = surface->backend_state; + + rail_state->forceRecreateSurface = TRUE; + rail_state->forceUpdateWindowState = TRUE; +} + struct disp_schedule_monitor_layout_change_data { struct rdp_loop_task _base; DispServerContext *context; @@ -2835,15 +2845,40 @@ disp_monitor_layout_change_callback(bool freeOnly, void *dataIn) freerdp_peer *client = (freerdp_peer *)context->custom; RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; + RDPGFX_RESET_GRAPHICS_PDU reset_graphics = {}; + MONITOR_DEF *reset_monitor_def; assert_compositor_thread(b); if (freeOnly) goto out; - disp_monitor_layout_change(context, data->count, data->monitors); + /* Skip reset graphics on failure */ + if (!disp_monitor_layout_change(context, data->count, data->monitors)) + goto out; + reset_monitor_def = xmalloc(sizeof(MONITOR_DEF) * data->count); + + for (int i = 0; i < data->count; i++) { + reset_monitor_def[i].left = data->monitors[i].x; + reset_monitor_def[i].top = data->monitors[i].y; + reset_monitor_def[i].right = data->monitors[i].width; + reset_monitor_def[i].bottom = data->monitors[i].height; + reset_monitor_def[i].flags = data->monitors[i].is_primary; + } + + /* tell client the server updated the monitor layout */ + reset_graphics.width = peerCtx->regionClientHeads.extents.x2 - peerCtx->regionClientHeads.extents.x1; + reset_graphics.height = peerCtx->regionClientHeads.extents.y2 - peerCtx->regionClientHeads.extents.x1; + reset_graphics.monitorCount = data->count; + reset_graphics.monitorDefArray = reset_monitor_def; + peerCtx->rail_grfx_server_context->ResetGraphics(peerCtx->rail_grfx_server_context, &reset_graphics); + + /* force recreate all surface and redraw. */ + rdp_id_manager_for_each(&peerCtx->windowId, disp_force_recreate_iter, NULL); + weston_compositor_damage_all(b->compositor); out: + free(reset_monitor_def); free(data); return; } From 8310975b78a9f56ce6579f5d8c173266df1769e0 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 28 Jun 2022 12:01:28 -0500 Subject: [PATCH 1551/1642] rdp: Consolidate monitor layout change functions Now that the ResetGraphics handling is pulled out, these are essentially the same function. Mash them together into one. This will help later when we push monitor layouting to the front end, as only one callback will be required. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.h | 3 -- libweston/backend-rdp/rdpdisp.c | 57 ++------------------------------- libweston/backend-rdp/rdprail.c | 2 +- 3 files changed, 3 insertions(+), 59 deletions(-) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 08ef11916..30eea48ea 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -403,9 +403,6 @@ void rdp_rail_start_window_move(struct weston_surface* surface, int pointerGrabX void rdp_rail_end_window_move(struct weston_surface* surface); // rdpdisp.c -bool -disp_monitor_layout_change(DispServerContext* context, int monitor_count, rdpMonitor *monitors); - bool handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor *monitors); diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index b0cc1d6a3..67d922ff6 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -518,58 +518,6 @@ disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_mon return TRUE; } -bool -disp_monitor_layout_change(DispServerContext* context, int monitor_count, rdpMonitor *monitors) -{ - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - rdpSettings *settings = client->context->settings; - struct rdp_backend *b = peerCtx->rdpBackend; - struct rdp_monitor_mode *monitorMode; - bool success = true; - - assert_compositor_thread(b); - - rdp_debug(b, "Client: DisplayControl: monitor count:0x%x\n", monitor_count); - - assert(settings->HiDefRemoteApp); - - if (monitor_count > RDP_MAX_MONITOR) { - rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: client reports more monitors then expected:(%d)\nWARNING\nWARNING\n", - monitor_count); - return false; - } - - monitorMode = xmalloc(sizeof(struct rdp_monitor_mode) * monitor_count); - - for (int i = 0; i < monitor_count; i++) { - monitorMode[i].monitorDef = monitors[i]; - monitorMode[i].monitorDef.orig_screen = 0; - monitorMode[i].scale = disp_get_output_scale_from_monitor(peerCtx, &monitorMode[i]); - monitorMode[i].clientScale = disp_get_client_scale_from_monitor(peerCtx, &monitorMode[i]); - } - - if (!disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitor_count)) { - success = false; - goto Exit; - } - int doneIndex = 0; - disp_start_monitor_layout_change(client, monitorMode, monitor_count, &doneIndex); - for (int i = 0; i < monitor_count; i++) { - if ((doneIndex & (1 << i)) == 0) { - if (disp_set_monitor_layout_change(client, &monitorMode[i]) != 0) { - success = false; - goto Exit; - } - } - } - disp_end_monitor_layout_change(client); - -Exit: - free(monitorMode); - return success; -} - bool handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor *monitors) { @@ -578,12 +526,11 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor struct rdp_monitor_mode *monitorMode = NULL; int i; - monitorMode = malloc(sizeof(struct rdp_monitor_mode) * monitor_count); - if (!monitorMode) - return true; + monitorMode = xmalloc(sizeof(struct rdp_monitor_mode) * monitor_count); for (i = 0; i < monitor_count; i++) { monitorMode[i].monitorDef = monitors[i]; + monitorMode[i].monitorDef.orig_screen = 0; monitorMode[i].scale = disp_get_output_scale_from_monitor(peerCtx, &monitorMode[i]); monitorMode[i].clientScale = disp_get_client_scale_from_monitor(peerCtx, &monitorMode[i]); } diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 221225624..b3c844a51 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2854,7 +2854,7 @@ disp_monitor_layout_change_callback(bool freeOnly, void *dataIn) goto out; /* Skip reset graphics on failure */ - if (!disp_monitor_layout_change(context, data->count, data->monitors)) + if (!handle_adjust_monitor_layout(client, data->count, data->monitors)) goto out; reset_monitor_def = xmalloc(sizeof(MONITOR_DEF) * data->count); From 4748d9354c173ef6078a3ca0b02ec4d7591ac873 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 28 Jun 2022 14:04:57 -0500 Subject: [PATCH 1552/1642] rdp: Move input translation code to rdpdisp.c This will end up in the front end soon, so pull it out of the backend include files. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.h | 86 +++------------------------------ libweston/backend-rdp/rdpdisp.c | 84 ++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 80 deletions(-) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 30eea48ea..cc81c5ecb 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -406,6 +406,12 @@ void rdp_rail_end_window_move(struct weston_surface* surface); 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, uint32_t *width, uint32_t *height); + +void +to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, int32_t *x, int32_t *y, uint32_t *width, uint32_t *height); + // rdpclip.c int rdp_clipboard_init(freerdp_peer* client); void rdp_clipboard_destroy(RdpPeerContext *peerCtx); @@ -458,86 +464,6 @@ rdp_matrix_transform_scale(struct weston_matrix *matrix, int *sx, int *sy) } } -static inline void -to_weston_scale_only(RdpPeerContext *peer, struct weston_output *output, float scale, int *x, int *y) -{ - //rdp_matrix_transform_scale(&output->inverse_matrix, x, y); - /* TODO: built-in to matrix */ - *x = (float)(*x) * scale; - *y = (float)(*y) * scale; -} - -/* Input x/y in client space, output x/y in weston space */ -static inline struct weston_output * -to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32_t *width, uint32_t *height) -{ - struct rdp_backend *b = peerContext->rdpBackend; - int sx = *x, sy = *y; - /* First, find which monitor contains this x/y. */ - struct rdp_head *head_iter; - wl_list_for_each(head_iter, &b->head_list, link) { - if (pixman_region32_contains_point(&head_iter->regionClient, sx, sy, NULL)) { - struct weston_output *output = head_iter->base.output; - float scale = 1.0f / head_iter->monitorMode.clientScale; - /* translate x/y to offset from this output on client space. */ - sx -= head_iter->monitorMode.monitorDef.x; - sy -= head_iter->monitorMode.monitorDef.y; - /* scale x/y to client output space. */ - to_weston_scale_only(peerContext, output, scale, &sx, &sy); - if (width && height) - to_weston_scale_only(peerContext, output, scale, width, height); - /* translate x/y to offset from this output on weston space. */ - sx += head_iter->monitorMode.rectWeston.x; - sy += head_iter->monitorMode.rectWeston.y; - rdp_debug_verbose(b, "%s: (x:%d, y:%d) -> (sx:%d, sy:%d) at head:%s\n", - __func__, *x, *y, sx, sy, head_iter->base.name); - *x = sx; - *y = sy; - return output; // must be only 1 head per output. - } - } - /* x/y is outside of any monitors. */ - return NULL; -} - -static inline void -to_client_scale_only(RdpPeerContext *peer, struct weston_output *output, float scale, int *x, int *y) -{ - //rdp_matrix_transform_scale(&output->matrix, x, y); - /* TODO: built-in to matrix */ - *x = (float)(*x) * scale; - *y = (float)(*y) * scale; -} - -/* Input x/y in weston space, output x/y in client space */ -static inline void -to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, int32_t *x, int32_t *y, uint32_t *width, uint32_t *height) -{ - struct rdp_backend *b = peerContext->rdpBackend; - int sx = *x, sy = *y; - /* Pick first head from output. */ - struct weston_head *head_iter; - wl_list_for_each(head_iter, &output->head_list, output_link) { - struct rdp_head *head = to_rdp_head(head_iter); - float scale = head->monitorMode.clientScale; - /* translate x/y to offset from this output on weston space. */ - sx -= head->monitorMode.rectWeston.x; - sy -= head->monitorMode.rectWeston.y; - /* scale x/y to client output space. */ - to_client_scale_only(peerContext, output, scale, &sx, &sy); - if (width && height) - to_client_scale_only(peerContext, output, scale, width, height); - /* translate x/y to offset from this output on client space. */ - sx += head->monitorMode.monitorDef.x; - sy += head->monitorMode.monitorDef.y; - rdp_debug_verbose(b, "%s: (x:%d, y:%d) -> (sx:%d, sy:%d) at head:%s\n", - __func__, *x, *y, sx, sy, head_iter->name); - *x = sx; - *y = sy; - return; // must be only 1 head per output. - } -} - #define RDP_RAIL_WINDOW_RESIZE_MARGIN 8 static inline bool diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 67d922ff6..cc014c5be 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -556,3 +556,87 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor return success; } + +static inline void +to_weston_scale_only(RdpPeerContext *peer, struct weston_output *output, float scale, int *x, int *y) +{ + //rdp_matrix_transform_scale(&output->inverse_matrix, x, y); + /* TODO: built-in to matrix */ + *x = (float)(*x) * scale; + *y = (float)(*y) * scale; +} + +/* 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, uint32_t *width, uint32_t *height) +{ + struct rdp_backend *b = peerContext->rdpBackend; + int sx = *x, sy = *y; + struct rdp_head *head_iter; + + /* First, find which monitor contains this x/y. */ + wl_list_for_each(head_iter, &b->head_list, link) { + if (pixman_region32_contains_point(&head_iter->regionClient, sx, sy, NULL)) { + struct weston_output *output = head_iter->base.output; + float scale = 1.0f / head_iter->monitorMode.clientScale; + + /* translate x/y to offset from this output on client space. */ + sx -= head_iter->monitorMode.monitorDef.x; + sy -= head_iter->monitorMode.monitorDef.y; + /* scale x/y to client output space. */ + to_weston_scale_only(peerContext, output, scale, &sx, &sy); + if (width && height) + to_weston_scale_only(peerContext, output, scale, width, height); + /* translate x/y to offset from this output on weston space. */ + sx += head_iter->monitorMode.rectWeston.x; + sy += head_iter->monitorMode.rectWeston.y; + rdp_debug_verbose(b, "%s: (x:%d, y:%d) -> (sx:%d, sy:%d) at head:%s\n", + __func__, *x, *y, sx, sy, head_iter->base.name); + *x = sx; + *y = sy; + return output; // must be only 1 head per output. + } + } + /* x/y is outside of any monitors. */ + return NULL; +} + +static inline void +to_client_scale_only(RdpPeerContext *peer, struct weston_output *output, float scale, int *x, int *y) +{ + //rdp_matrix_transform_scale(&output->matrix, x, y); + /* TODO: built-in to matrix */ + *x = (float)(*x) * scale; + *y = (float)(*y) * scale; +} + +/* Input x/y in weston space, output x/y in client space */ +void +to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, int32_t *x, int32_t *y, uint32_t *width, uint32_t *height) +{ + struct rdp_backend *b = peerContext->rdpBackend; + int sx = *x, sy = *y; + struct weston_head *head_iter; + + /* Pick first head from output. */ + wl_list_for_each(head_iter, &output->head_list, output_link) { + struct rdp_head *head = to_rdp_head(head_iter); + float scale = head->monitorMode.clientScale; + + /* translate x/y to offset from this output on weston space. */ + sx -= head->monitorMode.rectWeston.x; + sy -= head->monitorMode.rectWeston.y; + /* scale x/y to client output space. */ + to_client_scale_only(peerContext, output, scale, &sx, &sy); + if (width && height) + to_client_scale_only(peerContext, output, scale, width, height); + /* translate x/y to offset from this output on client space. */ + sx += head->monitorMode.monitorDef.x; + sy += head->monitorMode.monitorDef.y; + rdp_debug_verbose(b, "%s: (x:%d, y:%d) -> (sx:%d, sy:%d) at head:%s\n", + __func__, *x, *y, sx, sy, head_iter->name); + *x = sx; + *y = sy; + return; // must be only 1 head per output. + } +} From 0d67cf752bdbd08b23a85ed1c8237736f311929c Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 5 Jul 2022 13:08:54 -0500 Subject: [PATCH 1553/1642] rdp: Use wl_list_for_each_safe for monitor layout change code We can't safely remove items from a list when using wl_list_for_each, instead we should use wl_list_for_each_safe. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdpdisp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index cc014c5be..95ae9582b 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -108,8 +108,8 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * /* clear head list */ wl_list_init(&b->head_list); for (UINT32 i = 0; i < monitorCount; i++, monitorMode++) { - struct rdp_head *current; - wl_list_for_each(current, &b->head_pending_list, link) { + struct rdp_head *current, *tmp; + wl_list_for_each_safe(current, tmp, &b->head_pending_list, link) { if (memcmp(¤t->monitorMode, monitorMode, sizeof(*monitorMode)) == 0) { rdp_debug_verbose(b, "Head mode exact match:%s, x:%d, y:%d, width:%d, height:%d, is_primary: %d\n", current->base.name, From 1e6ceb7abfa40f4454b191ea19f90f10f553bd3d Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 6 Jul 2022 10:26:58 -0500 Subject: [PATCH 1554/1642] rdp: Always set the preferred mode flag This missing from non-RAIL cases is really a bug, so we can just always set it here. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 8d9b91fb5..b5299e56d 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -489,7 +489,6 @@ rdp_output_set_size(struct weston_output *base, struct weston_head *head; struct weston_mode *currentMode; struct weston_mode initMode; - BOOL is_preferred_mode = false; /* We can only be called once. */ assert(!output->base.current_mode); @@ -514,12 +513,6 @@ rdp_output_set_size(struct weston_output *base, /* In HiDef RAIL mode, set this mode as preferred mode */ if (client && client->context->settings->HiDefRemoteApp) { - if (h->monitorMode.monitorDef.width && h->monitorMode.monitorDef.height) { - /* given width/height must match with monitor's if provided */ - assert(width == h->monitorMode.monitorDef.width); - assert(height == h->monitorMode.monitorDef.height); - is_preferred_mode = true; - } break; // only one head per output in HiDef. } } @@ -535,8 +528,7 @@ rdp_output_set_size(struct weston_output *base, return -1; currentMode->flags |= WL_OUTPUT_MODE_CURRENT; - if (is_preferred_mode) - currentMode->flags |= WL_OUTPUT_MODE_PREFERRED; + currentMode->flags |= WL_OUTPUT_MODE_PREFERRED; output->base.current_mode = currentMode; output->base.native_mode = currentMode; From ed32d0c03245d86660881a59a1d5921e76673c69 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 13 Jul 2022 11:05:20 -0500 Subject: [PATCH 1555/1642] rdp: Remove regionWestonHeads This is only used for printing some debug text, so let's just not keep it. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 4 ---- libweston/backend-rdp/rdp.h | 1 - libweston/backend-rdp/rdpdisp.c | 10 ---------- libweston/backend-rdp/rdprail.c | 2 -- 4 files changed, 17 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index b5299e56d..9f5508200 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1284,10 +1284,6 @@ xf_peer_activate(freerdp_peer* client) rdp_debug(b, "%s: OutputWidth:%d, OutputHeight:%d, OutputScaleFactor:%d\n", __FUNCTION__, weston_output->width, weston_output->height, weston_output->scale); - pixman_region32_clear(&peerCtx->regionWestonHeads); - pixman_region32_init_rect(&peerCtx->regionWestonHeads, - 0, 0, weston_output->width, weston_output->height); - pixman_region32_clear(&b->head_default->regionWeston); pixman_region32_init_rect(&b->head_default->regionWeston, 0, 0, weston_output->width, weston_output->height); diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index cc81c5ecb..8b0420d7e 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -277,7 +277,6 @@ struct rdp_peer_context { // Multiple monitor support (monitor topology) pixman_region32_t regionClientHeads; - pixman_region32_t regionWestonHeads; void *audio_in_private; void *audio_out_private; diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 95ae9582b..94caf8b2f 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -98,7 +98,6 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * assert_compositor_thread(b); pixman_region32_clear(&peerCtx->regionClientHeads); - pixman_region32_clear(&peerCtx->regionWestonHeads); /* move all heads to pending list */ b->head_pending_list = b->head_list; b->head_pending_list.next->prev = &b->head_pending_list; @@ -123,9 +122,6 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * pixman_region32_union_rect(&peerCtx->regionClientHeads, &peerCtx->regionClientHeads, current->monitorMode.monitorDef.x, current->monitorMode.monitorDef.y, current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height); - pixman_region32_union_rect(&peerCtx->regionWestonHeads, &peerCtx->regionWestonHeads, - current->monitorMode.rectWeston.x, current->monitorMode.rectWeston.y, - current->monitorMode.rectWeston.width, current->monitorMode.rectWeston.height); *doneIndex |= (1 << i); break; } @@ -193,9 +189,6 @@ disp_end_monitor_layout_change(freerdp_peer *client) rdp_debug(b, "client virtual desktop is (%d,%d) - (%d,%d)\n", peerCtx->regionClientHeads.extents.x1, peerCtx->regionClientHeads.extents.y1, peerCtx->regionClientHeads.extents.x2, peerCtx->regionClientHeads.extents.y2); - rdp_debug(b, "weston virtual desktop is (%d,%d) - (%d,%d)\n", - peerCtx->regionWestonHeads.extents.x1, peerCtx->regionWestonHeads.extents.y1, - peerCtx->regionWestonHeads.extents.x2, peerCtx->regionWestonHeads.extents.y2); } static UINT @@ -329,9 +322,6 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo pixman_region32_union_rect(&peerCtx->regionClientHeads, &peerCtx->regionClientHeads, monitorMode->monitorDef.x, monitorMode->monitorDef.y, monitorMode->monitorDef.width, monitorMode->monitorDef.height); - pixman_region32_union_rect(&peerCtx->regionWestonHeads, &peerCtx->regionWestonHeads, - monitorMode->rectWeston.x, monitorMode->rectWeston.y, - monitorMode->rectWeston.width, monitorMode->rectWeston.height); return 0; } diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index b3c844a51..ec5fd36ec 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -3561,7 +3561,6 @@ rdp_rail_peer_context_free(freerdp_peer* client, RdpPeerContext* context) rdp_id_manager_free(&context->windowId); pixman_region32_fini(&context->regionClientHeads); - pixman_region32_fini(&context->regionWestonHeads); } BOOL @@ -3641,7 +3640,6 @@ rdp_rail_peer_init(freerdp_peer *client, RdpPeerContext *peerCtx) peerCtx->acknowledgedFrameId = 0; pixman_region32_init(&peerCtx->regionClientHeads); - pixman_region32_init(&peerCtx->regionWestonHeads); return TRUE; From 378e7e3ccc6d3bb91e9a404b6a91aa1b56c62f04 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 5 Jul 2022 12:59:43 -0500 Subject: [PATCH 1556/1642] rdp: Move heads to pending list properly wl_list_insert_list() should be used for this, otherwise an empty list becomes a loop. Currently we never see an empty list here, so it won't break yet, but some upcoming changes will hit this. Also gratuitously move the head_list list_init because it's conceptually part of "moving" all the elements out of it into the pending list. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdpdisp.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 94caf8b2f..53abc7557 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -99,13 +99,12 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * pixman_region32_clear(&peerCtx->regionClientHeads); /* move all heads to pending list */ - b->head_pending_list = b->head_list; - b->head_pending_list.next->prev = &b->head_pending_list; - b->head_pending_list.prev->next = &b->head_pending_list; + wl_list_init(&b->head_pending_list); + wl_list_insert_list(&b->head_pending_list, &b->head_list); + wl_list_init(&b->head_list); + /* init move pending list */ wl_list_init(&b->head_move_pending_list); - /* clear head list */ - wl_list_init(&b->head_list); for (UINT32 i = 0; i < monitorCount; i++, monitorMode++) { struct rdp_head *current, *tmp; wl_list_for_each_safe(current, tmp, &b->head_pending_list, link) { From f227c48f5f95101f28b159d6d26d9cc14d782412 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 4 Jul 2022 16:35:30 -0500 Subject: [PATCH 1557/1642] rdp: Move peerlist from output to backend Having the peer list attached to a monitor structure makes less sense now that we have multi-head support. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 24 +++++++++--------------- libweston/backend-rdp/rdp.h | 3 ++- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 9f5508200..2a57ffe03 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -280,7 +280,7 @@ rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, { struct rdp_output *output = container_of(output_base, struct rdp_output, base); struct weston_compositor *ec = output->base.compositor; - struct rdp_peers_item *outputPeer; + struct rdp_peers_item *peer; struct rdp_backend *b = to_rdp_backend(ec); /* Calculate the time we should complete this frame such that frames @@ -315,13 +315,10 @@ rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, output_base->transform, output_base->current_scale, damage, &transformed_damage); - /* note: if this code really need to walk peers in HiDef mode, */ - /* it must walk from output_default in backend, in non-HiDef */ - /* there must be only one default output, so doesn't matter. */ - wl_list_for_each(outputPeer, &output->peers, link) { - if ((outputPeer->flags & RDP_PEER_ACTIVATED) && - (outputPeer->flags & RDP_PEER_OUTPUT_ENABLED)) - rdp_peer_refresh_region(&transformed_damage, outputPeer->peer); + wl_list_for_each(peer, &b->peers, link) { + if ((peer->flags & RDP_PEER_ACTIVATED) && + (peer->flags & RDP_PEER_OUTPUT_ENABLED)) + rdp_peer_refresh_region(&transformed_damage, peer->peer); } pixman_region32_fini(&transformed_damage); } @@ -427,7 +424,7 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) pixman_image_unref(rdpOutput->shadow_surface); rdpOutput->shadow_surface = new_shadow_buffer; - wl_list_for_each(rdpPeer, &rdpBackend->output_default->peers, link) { + wl_list_for_each(rdpPeer, &rdpBackend->peers, link) { settings = rdpPeer->peer->context->settings; if (settings->DesktopWidth == (UINT32)target_mode->width && settings->DesktopHeight == (UINT32)target_mode->height) @@ -517,8 +514,6 @@ rdp_output_set_size(struct weston_output *base, } } - wl_list_init(&output->peers); - initMode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; initMode.width = width; initMode.height = height; @@ -751,7 +746,7 @@ rdp_destroy(struct weston_compositor *ec) int i; if (b->output_default) { - wl_list_for_each_safe(rdp_peer, tmp, &b->output_default->peers, link) { + wl_list_for_each_safe(rdp_peer, tmp, &b->peers, link) { freerdp_peer* client = rdp_peer->peer; client->Disconnect(client); @@ -2009,9 +2004,7 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) if (!b->rdp_peer) b->rdp_peer = client; - /* chain peers at default_output */ - if (b->output_default) - wl_list_insert(&b->output_default->peers, &peerCtx->item.link); + wl_list_insert(&b->peers, &peerCtx->item.link); return 0; error_rail_initialize: @@ -2231,6 +2224,7 @@ rdp_backend_create(struct weston_compositor *compositor, b->audio_out_teardown = config->audio_out_teardown; wl_list_init(&b->output_list); + wl_list_init(&b->peers); wl_list_init(&b->head_list); b->head_index = 0; diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 8b0420d7e..cd065f582 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -102,6 +102,8 @@ struct rdp_backend { struct weston_log_scope *debugClipboard; uint32_t debugClipboardLevel; + struct wl_list peers; + char *server_cert; char *server_key; char *server_cert_content; @@ -204,7 +206,6 @@ struct rdp_output { pixman_image_t *shadow_surface; uint32_t index; - struct wl_list peers; struct wl_list link; // rdp_backend::output_list }; From d8ce062fa3b0bfbb82e6251dfc595a4bb79a9c7b Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 13 Jul 2022 12:44:13 -0500 Subject: [PATCH 1558/1642] rdp: remove regionClientHeads We only need the desktop's bounds, we don't need to store the regions. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 11 ++++++----- libweston/backend-rdp/rdp.h | 2 +- libweston/backend-rdp/rdpdisp.c | 27 ++++++++++++++------------- libweston/backend-rdp/rdprail.c | 11 ++--------- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 2a57ffe03..4f5a79176 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1266,9 +1266,10 @@ xf_peer_activate(freerdp_peer* client) settings->DesktopPhysicalHeight); } } - pixman_region32_clear(&peerCtx->regionClientHeads); - pixman_region32_init_rect(&peerCtx->regionClientHeads, - 0, 0, settings->DesktopWidth, settings->DesktopHeight); + peerCtx->desktop_top = 0; + peerCtx->desktop_left = 0; + peerCtx->desktop_width = settings->DesktopWidth; + peerCtx->desktop_height = settings->DesktopHeight; pixman_region32_clear(&b->head_default->regionClient); pixman_region32_init_rect(&b->head_default->regionClient, @@ -1385,8 +1386,8 @@ rdp_translate_and_notify_mouse_position(RdpPeerContext *peerContext, UINT16 x, U /* (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 to the coordinate based on primary monitor's upper-left as (0,0) */ - sx = x + peerContext->regionClientHeads.extents.x1; - sy = y + peerContext->regionClientHeads.extents.y1; + 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, diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index cd065f582..957cd68f0 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -277,7 +277,7 @@ struct rdp_peer_context { bool is_window_zorder_dirty; // Multiple monitor support (monitor topology) - pixman_region32_t regionClientHeads; + int32_t desktop_top, desktop_left, desktop_width, desktop_height; void *audio_in_private; void *audio_out_private; diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 53abc7557..617017274 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -94,10 +94,11 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * { RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; + pixman_region32_t desktop; assert_compositor_thread(b); - pixman_region32_clear(&peerCtx->regionClientHeads); + pixman_region32_init(&desktop); /* move all heads to pending list */ wl_list_init(&b->head_pending_list); wl_list_insert_list(&b->head_pending_list, &b->head_list); @@ -107,6 +108,13 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * wl_list_init(&b->head_move_pending_list); for (UINT32 i = 0; i < monitorCount; i++, monitorMode++) { struct rdp_head *current, *tmp; + /* accumulate monitor layout */ + pixman_region32_union_rect(&desktop, &desktop, + monitorMode->monitorDef.x, + monitorMode->monitorDef.y, + monitorMode->monitorDef.width, + monitorMode->monitorDef.height); + wl_list_for_each_safe(current, tmp, &b->head_pending_list, link) { if (memcmp(¤t->monitorMode, monitorMode, sizeof(*monitorMode)) == 0) { rdp_debug_verbose(b, "Head mode exact match:%s, x:%d, y:%d, width:%d, height:%d, is_primary: %d\n", @@ -117,15 +125,16 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * /* move from pending list to move pending list */ wl_list_remove(¤t->link); wl_list_insert(&b->head_move_pending_list, ¤t->link); - /* accumulate monitor layout */ - pixman_region32_union_rect(&peerCtx->regionClientHeads, &peerCtx->regionClientHeads, - current->monitorMode.monitorDef.x, current->monitorMode.monitorDef.y, - current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height); *doneIndex |= (1 << i); break; } } } + 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 void @@ -185,9 +194,6 @@ disp_end_monitor_layout_change(freerdp_peer *client) is_primary_found = TRUE; } } - rdp_debug(b, "client virtual desktop is (%d,%d) - (%d,%d)\n", - peerCtx->regionClientHeads.extents.x1, peerCtx->regionClientHeads.extents.y1, - peerCtx->regionClientHeads.extents.x2, peerCtx->regionClientHeads.extents.y2); } static UINT @@ -317,11 +323,6 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo } } - /* accumulate monitor layout */ - pixman_region32_union_rect(&peerCtx->regionClientHeads, &peerCtx->regionClientHeads, - monitorMode->monitorDef.x, monitorMode->monitorDef.y, - monitorMode->monitorDef.width, monitorMode->monitorDef.height); - return 0; } diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index ec5fd36ec..9ebb6446b 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2868,8 +2868,8 @@ disp_monitor_layout_change_callback(bool freeOnly, void *dataIn) } /* tell client the server updated the monitor layout */ - reset_graphics.width = peerCtx->regionClientHeads.extents.x2 - peerCtx->regionClientHeads.extents.x1; - reset_graphics.height = peerCtx->regionClientHeads.extents.y2 - peerCtx->regionClientHeads.extents.x1; + reset_graphics.width = peerCtx->desktop_width; + reset_graphics.height = peerCtx->desktop_height; reset_graphics.monitorCount = data->count; reset_graphics.monitorDefArray = reset_monitor_def; peerCtx->rail_grfx_server_context->ResetGraphics(peerCtx->rail_grfx_server_context, &reset_graphics); @@ -3559,8 +3559,6 @@ rdp_rail_peer_context_free(freerdp_peer* client, RdpPeerContext* context) #endif // HAVE_FREERDP_GFXREDIR_H rdp_id_manager_free(&context->surfaceId); rdp_id_manager_free(&context->windowId); - - pixman_region32_fini(&context->regionClientHeads); } BOOL @@ -3639,8 +3637,6 @@ rdp_rail_peer_init(freerdp_peer *client, RdpPeerContext *peerCtx) peerCtx->currentFrameId = 0; peerCtx->acknowledgedFrameId = 0; - pixman_region32_init(&peerCtx->regionClientHeads); - return TRUE; error_return: @@ -3707,9 +3703,6 @@ print_rdp_head(FILE *fp, const struct rdp_head *current) current->monitorMode.monitorDef.attributes.deviceScaleFactor); fprintf(fp," scale:%d, client scale :%3.2f\n", current->monitorMode.scale, current->monitorMode.clientScale); - fprintf(fp," regionClient: x1:%d, y1:%d, x2:%d, y2:%d\n", - current->regionClient.extents.x1, current->regionClient.extents.y1, - current->regionClient.extents.x2, current->regionClient.extents.y2); fprintf(fp," regionWeston: x1:%d, y1:%d, x2:%d, y2:%d\n", current->regionWeston.extents.x1, current->regionWeston.extents.y1, current->regionWeston.extents.x2, current->regionWeston.extents.y2); From d74ef150729a4077474fd1844a4274b98ef886b1 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 13 Jul 2022 13:13:26 -0500 Subject: [PATCH 1559/1642] rdp: Remove regionWeston from rdp_head This is only used to print some debug text. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 9 --------- libweston/backend-rdp/rdp.h | 1 - libweston/backend-rdp/rdpdisp.c | 4 ---- libweston/backend-rdp/rdprail.c | 3 --- 4 files changed, 17 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 4f5a79176..adc9b4f7b 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -698,14 +698,10 @@ rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp pixman_region32_init_rect(&head->regionClient, monitorMode->monitorDef.x, monitorMode->monitorDef.y, monitorMode->monitorDef.width, monitorMode->monitorDef.height); - pixman_region32_init_rect(&head->regionWeston, - monitorMode->rectWeston.x, monitorMode->rectWeston.y, - monitorMode->rectWeston.width, monitorMode->rectWeston.height); } else { head->monitorMode.scale = 1.0f; head->monitorMode.clientScale = 1; pixman_region32_init(&head->regionClient); - pixman_region32_init(&head->regionWeston); } if (isPrimary) { rdp_debug(b, "Default head is being added\n"); @@ -728,7 +724,6 @@ rdp_head_destroy(struct weston_compositor *compositor, struct rdp_head *head) struct rdp_backend *b = to_rdp_backend(compositor); weston_head_release(&head->base); wl_list_remove(&head->link); - pixman_region32_fini(&head->regionWeston); pixman_region32_fini(&head->regionClient); if (b->head_default == head) { rdp_debug(b, "Default head is being removed\n"); @@ -1280,10 +1275,6 @@ xf_peer_activate(freerdp_peer* client) rdp_debug(b, "%s: OutputWidth:%d, OutputHeight:%d, OutputScaleFactor:%d\n", __FUNCTION__, weston_output->width, weston_output->height, weston_output->scale); - pixman_region32_clear(&b->head_default->regionWeston); - pixman_region32_init_rect(&b->head_default->regionWeston, - 0, 0, weston_output->width, weston_output->height); - rfx_context_reset(peerCtx->rfx_context, weston_output->width, weston_output->height); nsc_context_reset(peerCtx->nsc_context, weston_output->width, weston_output->height); } diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 957cd68f0..3a4d8285a 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -193,7 +193,6 @@ struct rdp_head { struct rdp_monitor_mode monitorMode; /*TODO: these region/rectangles can be moved to rdp_output */ pixman_region32_t regionClient; // in client coordnate. - pixman_region32_t regionWeston; // in weston coordnate. pixman_rectangle32_t workareaClient; // in client coordinate. pixman_rectangle32_t workarea; // in weston coordinate. diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 617017274..89d0edb65 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -272,10 +272,6 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo pixman_region32_init_rect(¤t->regionClient, monitorMode->monitorDef.x, monitorMode->monitorDef.y, monitorMode->monitorDef.width, monitorMode->monitorDef.height); - pixman_region32_clear(¤t->regionWeston); - pixman_region32_init_rect(¤t->regionWeston, - monitorMode->rectWeston.x, monitorMode->rectWeston.y, - monitorMode->rectWeston.width, monitorMode->rectWeston.height); /* move from pending list to move pending list */ wl_list_remove(¤t->link); wl_list_insert(&b->head_move_pending_list, &to_rdp_head(head)->link); diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 9ebb6446b..87b53a229 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -3703,9 +3703,6 @@ print_rdp_head(FILE *fp, const struct rdp_head *current) current->monitorMode.monitorDef.attributes.deviceScaleFactor); fprintf(fp," scale:%d, client scale :%3.2f\n", current->monitorMode.scale, current->monitorMode.clientScale); - fprintf(fp," regionWeston: x1:%d, y1:%d, x2:%d, y2:%d\n", - current->regionWeston.extents.x1, current->regionWeston.extents.y1, - current->regionWeston.extents.x2, current->regionWeston.extents.y2); fprintf(fp," workarea: x:%d, y:%d, width:%d, height:%d\n", current->workarea.x, current->workarea.y, current->workarea.width, current->workarea.height); From 8deb0ebf1d84ce50903290b5799bfd5f5b756b75 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 13 Jul 2022 14:42:58 -0500 Subject: [PATCH 1560/1642] rdp: Remove head_default from struct rdp_backend We still need to set up regions for our single head, so push it all through xf_peer_adjust_monitor_layout. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 39 +++++---------------------------- libweston/backend-rdp/rdp.h | 1 - libweston/backend-rdp/rdpdisp.c | 13 ++++++++--- 3 files changed, 16 insertions(+), 37 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index adc9b4f7b..a635984dd 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -703,10 +703,9 @@ rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp head->monitorMode.clientScale = 1; pixman_region32_init(&head->regionClient); } - if (isPrimary) { + if (isPrimary) rdp_debug(b, "Default head is being added\n"); - b->head_default = head; - } + head->monitorMode.monitorDef.is_primary = isPrimary; wl_list_insert(&b->head_list, &head->link); sprintf(name, "rdp-%x", head->index); @@ -721,14 +720,9 @@ rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp void rdp_head_destroy(struct weston_compositor *compositor, struct rdp_head *head) { - struct rdp_backend *b = to_rdp_backend(compositor); weston_head_release(&head->base); wl_list_remove(&head->link); pixman_region32_fini(&head->regionClient); - if (b->head_default == head) { - rdp_debug(b, "Default head is being removed\n"); - b->head_default = NULL; - } free(head); } @@ -1244,31 +1238,9 @@ xf_peer_activate(freerdp_peer* client) 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_matching_mode(&output->base, &new_mode); - if (!target_mode) { - rdp_debug_error(b, "client mode not found\n"); - goto error_exit; - } - weston_output_mode_set_native(&output->base, target_mode, - output->base.scale ? output->base.scale : 1); - weston_head_set_physical_size(&b->head_default->base, - settings->DesktopPhysicalWidth, - settings->DesktopPhysicalHeight); + xf_peer_adjust_monitor_layout(client); } } - peerCtx->desktop_top = 0; - peerCtx->desktop_left = 0; - peerCtx->desktop_width = settings->DesktopWidth; - peerCtx->desktop_height = settings->DesktopHeight; - - pixman_region32_clear(&b->head_default->regionClient); - pixman_region32_init_rect(&b->head_default->regionClient, - 0, 0, settings->DesktopWidth, settings->DesktopHeight); weston_output = &output->base; @@ -1805,6 +1777,7 @@ xf_peer_adjust_monitor_layout(freerdp_peer *client) rdpMonitor *monitors; unsigned int monitor_count; BOOL success; + bool fallback = false; unsigned int i; rdp_debug(b, "%s:\n", __func__); @@ -1821,7 +1794,7 @@ xf_peer_adjust_monitor_layout(freerdp_peer *client) if (!settings->HiDefRemoteApp || b->rdprail_shell_api == NULL) { if (settings->MonitorCount > 1) { rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: multiple monitor is not supported in non HiDef RAIL mode\nWARNING\nWARNING\n"); - return FALSE; + fallback = true; } } if (settings->MonitorCount > RDP_MAX_MONITOR) { @@ -1830,7 +1803,7 @@ xf_peer_adjust_monitor_layout(freerdp_peer *client) return FALSE; } - if (settings->MonitorCount > 0 && settings->MonitorDefArray) { + if ((settings->MonitorCount > 0 && settings->MonitorDefArray) && !fallback) { rdpMonitor *rdp_monitor = settings->MonitorDefArray; monitor_count = settings->MonitorCount; monitors = xmalloc(sizeof(*monitors) * monitor_count); diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 3a4d8285a..9f897cab1 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -91,7 +91,6 @@ struct rdp_backend { freerdp_listener *listener; struct wl_event_source *listener_events[MAX_FREERDP_FDS]; struct rdp_output *output_default; // default output created at backend initialize - struct rdp_head *head_default; // default head created at backend initialize struct wl_list output_list; // rdp_output::link struct wl_list head_list; // rdp_head::link struct wl_list head_pending_list; // used during monitor layout change. diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 89d0edb65..216392ba8 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -69,6 +69,10 @@ static float disp_get_client_scale_from_monitor(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode) { struct rdp_backend *b = peerCtx->rdpBackend; + + if (monitorMode->monitorDef.attributes.desktopScaleFactor == 0.0) + return 1.0f; + if (b->enable_hi_dpi_support) { if (b->debug_desktop_scaling_factor) return (float)b->debug_desktop_scaling_factor / 100.f; @@ -210,13 +214,16 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo assert_compositor_thread(b); if (monitorMode->monitorDef.is_primary) { - assert(b->head_default); assert(b->output_default); /* use default output and head for primary */ output = &b->output_default->base; - head = &b->head_default->base; - current = to_rdp_head(head); + wl_list_for_each(current, &b->head_pending_list, link) + if (current->monitorMode.monitorDef.is_primary) + break; + + assert(current->monitorMode.monitorDef.is_primary); + head = ¤t->base; if (current->monitorMode.monitorDef.width != monitorMode->monitorDef.width || current->monitorMode.monitorDef.height != monitorMode->monitorDef.height || From 7672daa8fb6135eed5a16d48f7dd1d91719da3f6 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 15 Jul 2022 12:39:54 -0500 Subject: [PATCH 1561/1642] rdp: Don't depend on default_output in xf_input_synchronize_event Use the first monitor in the list instead of default_monitor. In this particular case, there will only be a single monitor, so it would be the same. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index a635984dd..238920dfc 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1601,8 +1601,7 @@ xf_input_synchronize_event(rdpInput *input, UINT32 flags) freerdp_peer *client = input->context->peer; RdpPeerContext *peerCtx = (RdpPeerContext *)input->context; struct rdp_backend *b = peerCtx->rdpBackend; - struct rdp_output *output = b->output_default; - pixman_box32_t box; + struct weston_output *output; pixman_region32_t damage; rdp_debug_verbose(b, "RDP backend: %s ScrLk:%d, NumLk:%d, CapsLk:%d, KanaLk:%d\n", @@ -1624,18 +1623,23 @@ xf_input_synchronize_event(rdpInput *input, UINT32 flags) value); } - if (!client->context->settings->HiDefRemoteApp && output) { - /* sends a full refresh */ - box.x1 = 0; - box.y1 = 0; - box.x2 = output->base.width; - box.y2 = output->base.height; - pixman_region32_init_with_extents(&damage, &box); + if (client->context->settings->HiDefRemoteApp) + return TRUE; + + /* sends a full refresh */ + pixman_region32_init(&damage); + wl_list_for_each(output, &b->compositor->output_list, link) { + pixman_region32_union_rect(&damage, &damage, + output->x, output->y, + output->width, output->height); + /* we're limited to one output for now */ + break; + } + if (output) rdp_peer_refresh_region(&damage, client); - pixman_region32_fini(&damage); - } + pixman_region32_fini(&damage); return TRUE; } From bf36b3871c90160c7fde3979b0aecb8a458945e9 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 15 Jul 2022 12:48:15 -0500 Subject: [PATCH 1562/1642] rdp: Use the first monitor in rdp_peer_refresh_region output_default would have been this anyway. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 238920dfc..7de7d79b5 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -79,6 +79,17 @@ rdp_peer_seat_led_update(struct weston_seat *seat_base, enum weston_led leds) /*TODO: if Caps/Num lock change is triggered by server side, here can forward to client */ } +static struct rdp_output * +rdp_get_first_output(struct rdp_backend *b) +{ + struct weston_output *output; + + wl_list_for_each(output, &b->compositor->output_list, link) { + return to_rdp_output(output); + } + return NULL; +} + static void rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_peer *peer) { @@ -252,7 +263,7 @@ static void rdp_peer_refresh_region(pixman_region32_t *region, freerdp_peer *peer) { RdpPeerContext *context = (RdpPeerContext *)peer->context; - struct rdp_output *output = context->rdpBackend->output_default; + struct rdp_output *output = rdp_get_first_output(context->rdpBackend); rdpSettings *settings = peer->context->settings; if (settings->RemoteFxCodec) From 5c9117b124c624117f49be0fef2fd0c87c33efd4 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 15 Jul 2022 12:51:32 -0500 Subject: [PATCH 1563/1642] rdp: always walk the peer list in rdp_destroy Since the peer list has been moved to the backend, we don't have to check b->output_default to find it. The singleton rdp_peer for RAIL is also always in this list, so we don't need to special case it. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 7de7d79b5..06ccbfb96 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -745,17 +745,8 @@ rdp_destroy(struct weston_compositor *ec) struct rdp_peers_item *rdp_peer, *tmp; int i; - if (b->output_default) { - wl_list_for_each_safe(rdp_peer, tmp, &b->peers, link) { - freerdp_peer* client = rdp_peer->peer; - - client->Disconnect(client); - freerdp_peer_context_free(client); - freerdp_peer_free(client); - } - } else if (b->rdp_peer) { - freerdp_peer* client = b->rdp_peer; - assert(client->context->settings->HiDefRemoteApp); + wl_list_for_each_safe(rdp_peer, tmp, &b->peers, link) { + freerdp_peer* client = rdp_peer->peer; client->Disconnect(client); freerdp_peer_context_free(client); From b03254c2ca3b502056d7a01be8592c9be44d46ab Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 15 Jul 2022 12:55:22 -0500 Subject: [PATCH 1564/1642] rdp: Remove dependency on output_default We can find the primary head and its output in the main loop instead of special casing output_default. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdpdisp.c | 65 ++++++++++++--------------------- 1 file changed, 24 insertions(+), 41 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 216392ba8..293b148db 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -213,55 +213,38 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo assert_compositor_thread(b); - if (monitorMode->monitorDef.is_primary) { - assert(b->output_default); - - /* use default output and head for primary */ - output = &b->output_default->base; - wl_list_for_each(current, &b->head_pending_list, link) - if (current->monitorMode.monitorDef.is_primary) - break; - - assert(current->monitorMode.monitorDef.is_primary); - head = ¤t->base; - - if (current->monitorMode.monitorDef.width != monitorMode->monitorDef.width || - current->monitorMode.monitorDef.height != monitorMode->monitorDef.height || - current->monitorMode.scale != monitorMode->scale) + /* search head match configuration from pending list */ + wl_list_for_each(current, &b->head_pending_list, link) { + if (current->monitorMode.monitorDef.is_primary != monitorMode->monitorDef.is_primary) + continue; + + if (current->monitorMode.monitorDef.width == monitorMode->monitorDef.width && + current->monitorMode.monitorDef.height == monitorMode->monitorDef.height && + current->monitorMode.scale == monitorMode->scale) { + /* size mode (width/height/scale) */ + head = ¤t->base; + output = head->output; + break; + } else if (current->monitorMode.monitorDef.x == monitorMode->monitorDef.x && + current->monitorMode.monitorDef.y == monitorMode->monitorDef.y) { + /* position match in client space */ + head = ¤t->base; + output = head->output; updateMode = TRUE; - } else { - /* search head match configuration from pending list */ + break; + } + } + if (!head) { + /* just pick first one to change mode */ wl_list_for_each(current, &b->head_pending_list, link) { - if (current->monitorMode.monitorDef.is_primary) { - /* primary is only re-used for primary */ - } else if (current->monitorMode.monitorDef.width == monitorMode->monitorDef.width && - current->monitorMode.monitorDef.height == monitorMode->monitorDef.height && - current->monitorMode.scale == monitorMode->scale) { - /* size mode (width/height/scale) */ - head = ¤t->base; - output = head->output; - break; - } else if (current->monitorMode.monitorDef.x == monitorMode->monitorDef.x && - current->monitorMode.monitorDef.y == monitorMode->monitorDef.y) { - /* position match in client space */ + /* primary is only re-used for primary */ + if (!current->monitorMode.monitorDef.is_primary) { head = ¤t->base; output = head->output; updateMode = TRUE; break; } } - if (!head) { - /* just pick first one to change mode */ - wl_list_for_each(current, &b->head_pending_list, link) { - /* primary is only re-used for primary */ - if (!current->monitorMode.monitorDef.is_primary) { - head = ¤t->base; - output = head->output; - updateMode = TRUE; - break; - } - } - } } if (head) { From ab43ed7634547928a35a652b7f192e4a8f65d5d7 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 15 Jul 2022 13:00:47 -0500 Subject: [PATCH 1565/1642] rdp: Remove output_default We no longer need this Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 11 +---------- libweston/backend-rdp/rdp.h | 1 - 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 06ccbfb96..c9a512678 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -646,10 +646,6 @@ rdp_output_attach_head(struct weston_output *output_base, return -1; } o->index = h->index; - if (h->monitorMode.monitorDef.is_primary) { - assert(b->output_default == NULL); - b->output_default = o; - } return 0; } @@ -661,10 +657,6 @@ rdp_output_detach_head(struct weston_output *output_base, struct rdp_head *h = to_rdp_head(head_base); rdp_debug(b, "Head detaching: %s, index:%d, is_primary: %d\n", head_base->name, h->index, h->monitorMode.monitorDef.is_primary); - if (h->monitorMode.monitorDef.is_primary) { - assert(b->output_default == to_rdp_output(output_base)); - b->output_default = NULL; - } } static struct weston_output * @@ -1221,9 +1213,8 @@ xf_peer_activate(freerdp_peer* client) output = NULL; weston_output = NULL; } else { + output = rdp_get_first_output(b); /* multiple monitor is not supported in non-HiDef */ - assert(b->output_default); - output = b->output_default; rdp_debug_error(b, "%s: DesktopWidth:%d, DesktopHeigh:%d, DesktopScaleFactor:%d\n", __FUNCTION__, settings->DesktopWidth, settings->DesktopHeight, settings->DesktopScaleFactor); if (output->base.width != (int)settings->DesktopWidth || diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 9f897cab1..288eae4af 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -90,7 +90,6 @@ struct rdp_backend { freerdp_listener *listener; struct wl_event_source *listener_events[MAX_FREERDP_FDS]; - struct rdp_output *output_default; // default output created at backend initialize struct wl_list output_list; // rdp_output::link struct wl_list head_list; // rdp_head::link struct wl_list head_pending_list; // used during monitor layout change. From 5dfe933e49e3e24fb44e93457d02c377bbb6ea8e Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 18 Jul 2022 08:51:33 -0500 Subject: [PATCH 1566/1642] rdp: Don't use rectWeston in input translation We've set up the outputs to use these same co-ordinates, so picking them from there puts us one step closer to eliminating head walks from translations. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdpdisp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 293b148db..78a0ad173 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -564,8 +564,8 @@ to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32 if (width && height) to_weston_scale_only(peerContext, output, scale, width, height); /* translate x/y to offset from this output on weston space. */ - sx += head_iter->monitorMode.rectWeston.x; - sy += head_iter->monitorMode.rectWeston.y; + 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_iter->base.name); *x = sx; @@ -600,8 +600,8 @@ to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, float scale = head->monitorMode.clientScale; /* translate x/y to offset from this output on weston space. */ - sx -= head->monitorMode.rectWeston.x; - sy -= head->monitorMode.rectWeston.y; + sx -= output->x; + sy -= output->y; /* scale x/y to client output space. */ to_client_scale_only(peerContext, output, scale, &sx, &sy); if (width && height) From d3c8ce9f8a251d431bd9d11e10958e390ed2ccab Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 18 Jul 2022 14:27:53 -0500 Subject: [PATCH 1567/1642] rdp: Break monitor checks into two steps Put the basic sanity checks into one function, as these will remain in the backend, and put the layout checks in another which will migrate to the frontend. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdpdisp.c | 40 ++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 78a0ad173..8c304c648 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -312,22 +312,16 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo return 0; } -static BOOL -disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode, UINT32 monitorCount) +static bool +disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode, uint32_t count) { struct rdp_backend *b = peerCtx->rdpBackend; - bool isConnected_H = false; - bool isConnected_V = false; - bool isScalingUsed = false; - bool isScalingSupported = true; uint32_t primaryCount = 0; - int upperLeftX = 0; - int upperLeftY = 0; uint32_t i; /* dump client monitor topology */ rdp_debug(b, "%s:---INPUT---\n", __func__); - for (i = 0; i < monitorCount; i++) { + for (i = 0; i < count; i++) { rdp_debug(b, " rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", i, monitorMode[i].monitorDef.x, monitorMode[i].monitorDef.y, monitorMode[i].monitorDef.width, monitorMode[i].monitorDef.height, @@ -343,22 +337,38 @@ disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_mon i, monitorMode[i].scale, monitorMode[i].clientScale); } - for (i = 0; i < monitorCount; i++) { + for (i = 0; i < count; i++) { /* make sure there is only one primary and its position at client */ if (monitorMode[i].monitorDef.is_primary) { /* count number of primary */ if (++primaryCount > 1) { rdp_debug_error(b, "%s: RDP client reported unexpected primary count (%d)\n",__func__, primaryCount); - return FALSE; + return false; } /* primary must be at (0,0) in client space */ if (monitorMode[i].monitorDef.x != 0 || monitorMode[i].monitorDef.y != 0) { rdp_debug_error(b, "%s: RDP client reported primary is not at (0,0) but (%d,%d).\n", __func__, monitorMode[i].monitorDef.x, monitorMode[i].monitorDef.y); - return FALSE; + return false; } } + } + return true; +} +static void +disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode, uint32_t monitorCount) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + bool isConnected_H = false; + bool isConnected_V = false; + bool isScalingUsed = false; + bool isScalingSupported = true; + int upperLeftX = 0; + int upperLeftY = 0; + uint32_t i; + + for (i = 0; i < monitorCount; i++) { /* check if any monitor has scaling enabled */ if (monitorMode[i].clientScale != 1.0f) isScalingUsed = true; @@ -490,8 +500,6 @@ disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_mon rdp_debug(b, " rdpMonitor[%d]: scale:%d, clientScale:%3.2f\n", i, monitorMode[i].scale, monitorMode[i].clientScale); } - - return TRUE; } bool @@ -511,11 +519,13 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor monitorMode[i].clientScale = disp_get_client_scale_from_monitor(peerCtx, &monitorMode[i]); } - if (!disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitor_count)) { + if (!disp_monitor_sanity_check_layout(peerCtx, monitorMode, monitor_count)) { success = true; goto exit; } + disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitor_count); + int doneIndex = 0; disp_start_monitor_layout_change(client, monitorMode, monitor_count, &doneIndex); for (int i = 0; i < monitor_count; i++) { From f0a195616dca90ec9706361b899b1829a6c8b6f8 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Mon, 18 Jul 2022 15:50:25 -0500 Subject: [PATCH 1568/1642] rdp: Remove head_list from backend The compositor already has a head list, so let's just use that instead. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 5 --- libweston/backend-rdp/rdp.h | 3 -- libweston/backend-rdp/rdpdisp.c | 69 ++++++++++++++++++++------------- libweston/backend-rdp/rdprail.c | 19 +++++---- 4 files changed, 53 insertions(+), 43 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index c9a512678..cb009332f 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -710,7 +710,6 @@ rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp rdp_debug(b, "Default head is being added\n"); head->monitorMode.monitorDef.is_primary = isPrimary; - wl_list_insert(&b->head_list, &head->link); sprintf(name, "rdp-%x", head->index); weston_head_init(&head->base, name); @@ -724,7 +723,6 @@ void rdp_head_destroy(struct weston_compositor *compositor, struct rdp_head *head) { weston_head_release(&head->base); - wl_list_remove(&head->link); pixman_region32_fini(&head->regionClient); free(head); } @@ -766,8 +764,6 @@ rdp_destroy(struct weston_compositor *ec) wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) rdp_head_destroy(ec, to_rdp_head(base)); - assert(wl_list_empty(&b->head_list)); - freerdp_listener_free(b->listener); free(b->server_cert); @@ -2187,7 +2183,6 @@ rdp_backend_create(struct weston_compositor *compositor, wl_list_init(&b->output_list); wl_list_init(&b->peers); - wl_list_init(&b->head_list); b->head_index = 0; b->debug = weston_log_ctx_add_log_scope(compositor->weston_log_ctx, diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 288eae4af..0e1cbcd5b 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -91,7 +91,6 @@ struct rdp_backend { freerdp_listener *listener; struct wl_event_source *listener_events[MAX_FREERDP_FDS]; struct wl_list output_list; // rdp_output::link - struct wl_list head_list; // rdp_head::link struct wl_list head_pending_list; // used during monitor layout change. struct wl_list head_move_pending_list; // used during monitor layout change. uint32_t head_index; @@ -193,8 +192,6 @@ struct rdp_head { pixman_region32_t regionClient; // in client coordnate. pixman_rectangle32_t workareaClient; // in client coordinate. pixman_rectangle32_t workarea; // in weston coordinate. - - struct wl_list link; // rdp_backend::head_list }; struct rdp_output { diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 8c304c648..8108ea6ad 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -105,13 +105,13 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * pixman_region32_init(&desktop); /* move all heads to pending list */ wl_list_init(&b->head_pending_list); - wl_list_insert_list(&b->head_pending_list, &b->head_list); - wl_list_init(&b->head_list); + wl_list_insert_list(&b->head_pending_list, &b->compositor->head_list); + wl_list_init(&b->compositor->head_list); /* init move pending list */ wl_list_init(&b->head_move_pending_list); for (UINT32 i = 0; i < monitorCount; i++, monitorMode++) { - struct rdp_head *current, *tmp; + struct weston_head *iter, *tmp; /* accumulate monitor layout */ pixman_region32_union_rect(&desktop, &desktop, monitorMode->monitorDef.x, @@ -119,7 +119,9 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * monitorMode->monitorDef.width, monitorMode->monitorDef.height); - wl_list_for_each_safe(current, tmp, &b->head_pending_list, link) { + wl_list_for_each_safe(iter, tmp, &b->head_pending_list, compositor_link) { + struct rdp_head *current = to_rdp_head(iter); + if (memcmp(¤t->monitorMode, monitorMode, sizeof(*monitorMode)) == 0) { rdp_debug_verbose(b, "Head mode exact match:%s, x:%d, y:%d, width:%d, height:%d, is_primary: %d\n", current->base.name, @@ -127,8 +129,8 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height, current->monitorMode.monitorDef.is_primary); /* move from pending list to move pending list */ - wl_list_remove(¤t->link); - wl_list_insert(&b->head_move_pending_list, ¤t->link); + wl_list_remove(&iter->compositor_link); + wl_list_insert(&b->head_move_pending_list, &iter->compositor_link); *doneIndex |= (1 << i); break; } @@ -146,15 +148,17 @@ disp_end_monitor_layout_change(freerdp_peer *client) { RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - struct rdp_head *current, *next; + struct weston_head *iter, *next; assert_compositor_thread(b); /* move output to final location */ - wl_list_for_each_safe(current, next, &b->head_move_pending_list, link) { + wl_list_for_each_safe(iter, next, &b->head_move_pending_list, compositor_link) { + struct rdp_head *current = to_rdp_head(iter); + /* move from move pending list to current list */ - wl_list_remove(¤t->link); - wl_list_insert(&b->head_list, ¤t->link); + wl_list_remove(&iter->compositor_link); + wl_list_insert(&b->compositor->head_list, &iter->compositor_link); if (current->base.output) { rdp_debug(b, "move head/output %s (%d,%d) -> (%d,%d)\n", current->base.name, @@ -175,17 +179,19 @@ disp_end_monitor_layout_change(freerdp_peer *client) wl_list_init(&b->head_move_pending_list); /* remove all unsed head from pending list */ if (!wl_list_empty(&b->head_pending_list)) { - wl_list_for_each_safe(current, next, &b->head_pending_list, link) - rdp_head_destroy(b->compositor, current); + wl_list_for_each_safe(iter, next, &b->head_pending_list, compositor_link) + rdp_head_destroy(b->compositor, to_rdp_head(iter)); /* make sure nothing left in pending list */ assert(wl_list_empty(&b->head_pending_list)); wl_list_init(&b->head_pending_list); } /* make sure head list is not empty */ - assert(!wl_list_empty(&b->head_list)); + assert(!wl_list_empty(&b->compositor->head_list)); BOOL is_primary_found = FALSE; - wl_list_for_each(current, &b->head_list, link) { + wl_list_for_each(iter, &b->compositor->head_list, compositor_link) { + struct rdp_head *current = to_rdp_head(iter); + if (current->monitorMode.monitorDef.is_primary) { rdp_debug(b, "client origin (0,0) is (%d,%d) in Weston space\n", current->monitorMode.rectWeston.x, @@ -208,13 +214,15 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo rdpSettings *settings = client->context->settings; struct weston_output *output = NULL; struct weston_head *head = NULL; - struct rdp_head *current; + struct weston_head *iter; BOOL updateMode = FALSE; assert_compositor_thread(b); /* search head match configuration from pending list */ - wl_list_for_each(current, &b->head_pending_list, link) { + wl_list_for_each(iter, &b->head_pending_list, compositor_link) { + struct rdp_head *current = to_rdp_head(iter); + if (current->monitorMode.monitorDef.is_primary != monitorMode->monitorDef.is_primary) continue; @@ -236,7 +244,9 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo } if (!head) { /* just pick first one to change mode */ - wl_list_for_each(current, &b->head_pending_list, link) { + wl_list_for_each(iter, &b->head_pending_list, compositor_link) { + struct rdp_head *current = to_rdp_head(iter); + /* primary is only re-used for primary */ if (!current->monitorMode.monitorDef.is_primary) { head = ¤t->base; @@ -248,8 +258,9 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo } if (head) { + struct rdp_head *current = to_rdp_head(head); + assert(output); - assert(to_rdp_head(head) == current); rdp_debug(b, "Head mode change:%s OLD width:%d, height:%d, scale:%d, clientScale:%f\n", output->name, current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height, @@ -263,8 +274,8 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo monitorMode->monitorDef.x, monitorMode->monitorDef.y, monitorMode->monitorDef.width, monitorMode->monitorDef.height); /* move from pending list to move pending list */ - wl_list_remove(¤t->link); - wl_list_insert(&b->head_move_pending_list, &to_rdp_head(head)->link); + wl_list_remove(&head->compositor_link); + wl_list_insert(&b->head_move_pending_list, &head->compositor_link); } else { /* no head found, create one */ if (rdp_head_create(b->compositor, monitorMode->monitorDef.is_primary, monitorMode) == NULL) @@ -558,17 +569,19 @@ to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32 { struct rdp_backend *b = peerContext->rdpBackend; int sx = *x, sy = *y; - struct rdp_head *head_iter; + struct weston_head *head_iter; /* First, find which monitor contains this x/y. */ - wl_list_for_each(head_iter, &b->head_list, link) { - if (pixman_region32_contains_point(&head_iter->regionClient, sx, sy, NULL)) { - struct weston_output *output = head_iter->base.output; - float scale = 1.0f / head_iter->monitorMode.clientScale; + wl_list_for_each(head_iter, &b->compositor->head_list, compositor_link) { + struct rdp_head *head = to_rdp_head(head_iter); + + if (pixman_region32_contains_point(&head->regionClient, sx, sy, NULL)) { + struct weston_output *output = head->base.output; + float scale = 1.0f / head->monitorMode.clientScale; /* translate x/y to offset from this output on client space. */ - sx -= head_iter->monitorMode.monitorDef.x; - sy -= head_iter->monitorMode.monitorDef.y; + sx -= head->monitorMode.monitorDef.x; + sy -= head->monitorMode.monitorDef.y; /* scale x/y to client output space. */ to_weston_scale_only(peerContext, output, scale, &sx, &sy); if (width && height) @@ -577,7 +590,7 @@ to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32 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_iter->base.name); + __func__, *x, *y, sx, sy, head->base.name); *x = sx; *y = sy; return output; // must be only 1 head per output. diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 87b53a229..0b034e7bd 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -3730,15 +3730,17 @@ rdp_rail_dump_monitor_binding(struct weston_keyboard *keyboard, { struct rdp_backend *b = (struct rdp_backend *)data; if (b) { - struct rdp_head *current; + struct weston_head *current; int err; char *str; size_t len; FILE *fp = open_memstream(&str, &len); assert(fp); fprintf(fp,"\nrdp debug binding 'M' - dump all monitor.\n"); - wl_list_for_each(current, &b->head_list, link) { - print_rdp_head(fp, current); + wl_list_for_each(current, &b->compositor->head_list, compositor_link) { + struct rdp_head *head = to_rdp_head(current); + + print_rdp_head(fp, head); fprintf(fp,"\n"); } err = fclose(fp); @@ -4263,10 +4265,13 @@ static struct weston_output * rdp_rail_get_primary_output(void *rdp_backend) { struct rdp_backend *b = (struct rdp_backend*)rdp_backend; - struct rdp_head *current; - wl_list_for_each(current, &b->head_list, link) { - if (current->monitorMode.monitorDef.is_primary) - return current->base.output; + struct weston_head *current; + + wl_list_for_each(current, &b->compositor->head_list, compositor_link) { + struct rdp_head *head = to_rdp_head(current); + + if (head->monitorMode.monitorDef.is_primary) + return current->output; } return NULL; } From 5762f2eb143ac5e4bb0e0aed9b7576dbda0f4fee Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 19 Jul 2022 09:51:36 -0500 Subject: [PATCH 1569/1642] rdp: Remove the move pending list weston_output_move is idempotent, so it doesn't matter if we move a head that's already in the right place. So let's just move all the heads instead of having a special move pending list at all. This will make things easier later when the frontend and backend code do different parts of monitor setup later. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.h | 1 - libweston/backend-rdp/rdpdisp.c | 15 +++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 0e1cbcd5b..be28f603c 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -92,7 +92,6 @@ struct rdp_backend { struct wl_event_source *listener_events[MAX_FREERDP_FDS]; struct wl_list output_list; // rdp_output::link struct wl_list head_pending_list; // used during monitor layout change. - struct wl_list head_move_pending_list; // used during monitor layout change. uint32_t head_index; struct weston_log_scope *debug; uint32_t debugLevel; diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 8108ea6ad..a9a8ff405 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -108,8 +108,6 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * wl_list_insert_list(&b->head_pending_list, &b->compositor->head_list); wl_list_init(&b->compositor->head_list); - /* init move pending list */ - wl_list_init(&b->head_move_pending_list); for (UINT32 i = 0; i < monitorCount; i++, monitorMode++) { struct weston_head *iter, *tmp; /* accumulate monitor layout */ @@ -128,9 +126,8 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * current->monitorMode.monitorDef.x, current->monitorMode.monitorDef.y, current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height, current->monitorMode.monitorDef.is_primary); - /* move from pending list to move pending list */ wl_list_remove(&iter->compositor_link); - wl_list_insert(&b->head_move_pending_list, &iter->compositor_link); + wl_list_insert(&b->compositor->head_list, &iter->compositor_link); *doneIndex |= (1 << i); break; } @@ -153,12 +150,9 @@ disp_end_monitor_layout_change(freerdp_peer *client) assert_compositor_thread(b); /* move output to final location */ - wl_list_for_each_safe(iter, next, &b->head_move_pending_list, compositor_link) { + wl_list_for_each_safe(iter, next, &b->compositor->head_list, compositor_link) { struct rdp_head *current = to_rdp_head(iter); - /* move from move pending list to current list */ - wl_list_remove(&iter->compositor_link); - wl_list_insert(&b->compositor->head_list, &iter->compositor_link); if (current->base.output) { rdp_debug(b, "move head/output %s (%d,%d) -> (%d,%d)\n", current->base.name, @@ -175,8 +169,6 @@ disp_end_monitor_layout_change(freerdp_peer *client) /* position will be set at rdp_output_enable */ } } - assert(wl_list_empty(&b->head_move_pending_list)); - wl_list_init(&b->head_move_pending_list); /* remove all unsed head from pending list */ if (!wl_list_empty(&b->head_pending_list)) { wl_list_for_each_safe(iter, next, &b->head_pending_list, compositor_link) @@ -273,9 +265,8 @@ disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *mo pixman_region32_init_rect(¤t->regionClient, monitorMode->monitorDef.x, monitorMode->monitorDef.y, monitorMode->monitorDef.width, monitorMode->monitorDef.height); - /* move from pending list to move pending list */ wl_list_remove(&head->compositor_link); - wl_list_insert(&b->head_move_pending_list, &head->compositor_link); + wl_list_insert(&b->compositor->head_list, &head->compositor_link); } else { /* no head found, create one */ if (rdp_head_create(b->compositor, monitorMode->monitorDef.is_primary, monitorMode) == NULL) From 2719eee940244e4aec0789b184bdf0581b70d939 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Mon, 13 Jun 2022 15:38:13 +0200 Subject: [PATCH 1570/1642] compositor: only reflow the outputs if the shell did not move them weston_compositor_reflow_outputs() assumes that all output are positioned from left to right with no gaps in the same order in which they where created. If the shell moves an output with weston_output_move() then this assumption is no longer true. So stop reflowing the outputs in the case. The shell is now responsible for positioning all outputs as needed. Signed-off-by: Michael Olbrich --- include/libweston/libweston.h | 1 + libweston/compositor.c | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index ca0bcba56..7bbe38722 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -1085,6 +1085,7 @@ struct weston_compositor { struct wl_list plugin_api_list; /* struct weston_plugin_api::link */ uint32_t output_id_pool; + bool output_flow_dirty; struct xkb_rule_names xkb_names; struct xkb_context *xkb_context; diff --git a/libweston/compositor.c b/libweston/compositor.c index 72c7407c1..1feb2c95a 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5839,6 +5839,9 @@ weston_head_get_destroy_listener(struct weston_head *head, return wl_signal_get(&head->destroy_signal, notify); } +static void +weston_output_set_position(struct weston_output *output, int x, int y); + /* Move other outputs when one is resized so the space remains contiguous. */ static void weston_compositor_reflow_outputs(struct weston_compositor *compositor, @@ -5847,6 +5850,9 @@ weston_compositor_reflow_outputs(struct weston_compositor *compositor, struct weston_output *output; bool start_resizing = false; + if (compositor->output_flow_dirty) + return; + if (!delta_width) return; @@ -5857,7 +5863,7 @@ weston_compositor_reflow_outputs(struct weston_compositor *compositor, } if (start_resizing) { - weston_output_move(output, output->x + delta_width, output->y); + weston_output_set_position(output, output->x + delta_width, output->y); output->dirty = 1; } } @@ -5951,8 +5957,8 @@ weston_output_init_geometry(struct weston_output *output, int x, int y) /** * \ingroup output */ -WL_EXPORT void -weston_output_move(struct weston_output *output, int x, int y) +static void +weston_output_set_position(struct weston_output *output, int x, int y) { struct weston_head *head; struct wl_resource *resource; @@ -5998,6 +6004,16 @@ weston_output_move(struct weston_output *output, int x, int y) } } +/** + * \ingroup output + */ +WL_EXPORT void +weston_output_move(struct weston_output *output, int x, int y) +{ + output->compositor->output_flow_dirty = true; + weston_output_set_position(output, x, y); +} + /** Signal that a pending output is taken into use. * * Removes the output from the pending list and adds it to the compositor's From bbd84eb2df6187c3e0f4ddde6fe8e8eb97aae49d Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 20 Jul 2022 11:53:09 -0500 Subject: [PATCH 1571/1642] rdp: Pass rdp_backend to client_scale getters We only need the backend, and we'll be calling this in places where we don't have the peer context soon. While we're at it, make it take an rdpMonitor as its second parameter, as this will also help in the future. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdpdisp.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index a9a8ff405..6e55de354 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -66,31 +66,29 @@ compare_monitors_y(const void *p1, const void *p2) } static float -disp_get_client_scale_from_monitor(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode) +disp_get_client_scale_from_monitor(struct rdp_backend *b, const rdpMonitor *config) { - struct rdp_backend *b = peerCtx->rdpBackend; - - if (monitorMode->monitorDef.attributes.desktopScaleFactor == 0.0) + if (config->attributes.desktopScaleFactor == 0.0) return 1.0f; if (b->enable_hi_dpi_support) { if (b->debug_desktop_scaling_factor) return (float)b->debug_desktop_scaling_factor / 100.f; else if (b->enable_fractional_hi_dpi_support) - return (float)monitorMode->monitorDef.attributes.desktopScaleFactor / 100.0f; + return (float)config->attributes.desktopScaleFactor / 100.0f; else if (b->enable_fractional_hi_dpi_roundup) - return (float)(int)((monitorMode->monitorDef.attributes.desktopScaleFactor + 50) / 100); + return (float)(int)((config->attributes.desktopScaleFactor + 50) / 100); else - return (float)(int)(monitorMode->monitorDef.attributes.desktopScaleFactor / 100); + return (float)(int)(config->attributes.desktopScaleFactor / 100); } else { return 1.0f; } } static int -disp_get_output_scale_from_monitor(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode) +disp_get_output_scale_from_monitor(struct rdp_backend *b, rdpMonitor *config) { - return (int) disp_get_client_scale_from_monitor(peerCtx, monitorMode); + return (int) disp_get_client_scale_from_monitor(b, config); } static void @@ -508,6 +506,7 @@ bool handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor *monitors) { RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; bool success = true; struct rdp_monitor_mode *monitorMode = NULL; int i; @@ -517,8 +516,8 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor for (i = 0; i < monitor_count; i++) { monitorMode[i].monitorDef = monitors[i]; monitorMode[i].monitorDef.orig_screen = 0; - monitorMode[i].scale = disp_get_output_scale_from_monitor(peerCtx, &monitorMode[i]); - monitorMode[i].clientScale = disp_get_client_scale_from_monitor(peerCtx, &monitorMode[i]); + monitorMode[i].scale = disp_get_output_scale_from_monitor(b, &monitorMode[i].monitorDef); + monitorMode[i].clientScale = disp_get_client_scale_from_monitor(b, &monitorMode[i].monitorDef); } if (!disp_monitor_sanity_check_layout(peerCtx, monitorMode, monitor_count)) { From 2938284b6f03d37cf6b9e3dfb8c4b6aea101d680 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 20 Jul 2022 10:24:47 -0500 Subject: [PATCH 1572/1642] libweston: Make weston_head_set_device_changed public Some backends have special head specific state that doesn't fit into the existing generic head setter functions, and is too specific to make more functions for. RDP's primary output flag is an example. Signed-off-by: Derek Foreman --- include/libweston/libweston.h | 3 +++ libweston/compositor.c | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 7bbe38722..745f378d1 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -1960,6 +1960,9 @@ weston_head_is_device_changed(struct weston_head *head); bool weston_head_is_non_desktop(struct weston_head *head); +void +weston_head_set_device_changed(struct weston_head *head); + void weston_head_reset_device_changed(struct weston_head *head); diff --git a/libweston/compositor.c b/libweston/compositor.c index 1feb2c95a..6b0c23865 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5415,7 +5415,23 @@ weston_head_release(struct weston_head *head) wl_list_remove(&head->compositor_link); } -static void +/** Propagate device information changes + * + * \param head The head that changed. + * + * The information about the connected display device, e.g. a monitor, may + * change without being disconnected in between. Changing information + * causes a call to the heads_changed hook. + * + * Normally this is handled automatically by the generic setters, but if + * a backend has + * specific head properties it may have to call this directly. + * + * \sa weston_head_reset_device_changed, weston_compositor_set_heads_changed_cb, + * weston_head_is_device_changed + * \ingroup head + */ +WL_EXPORT void weston_head_set_device_changed(struct weston_head *head) { head->device_changed = true; From 0ca0e14a497b82b797937f8e44e865f98ee4507a Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 19 Jul 2022 14:10:28 -0500 Subject: [PATCH 1573/1642] rdp: Rework head layout changes Primary goal here was to have a clean split between the code that creates and deletes heads, and the code the validates and sets modes. These correspond to the front and and back end responsibilities respectively. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 4 +- libweston/backend-rdp/rdp.h | 2 +- libweston/backend-rdp/rdpdisp.c | 351 +++++++++++++++++--------------- 3 files changed, 193 insertions(+), 164 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index cb009332f..9f89cf7c0 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -691,9 +691,7 @@ rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp struct rdp_head *head; char name[13] = {}; // 'rdp-' + 8 chars for hex uint32_t + NULL. - head = zalloc(sizeof *head); - if (!head) - return NULL; + head = xzalloc(sizeof *head); head->index = b->head_index++; if (monitorMode) { diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index be28f603c..bf352ad5c 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -91,7 +91,6 @@ struct rdp_backend { freerdp_listener *listener; struct wl_event_source *listener_events[MAX_FREERDP_FDS]; struct wl_list output_list; // rdp_output::link - struct wl_list head_pending_list; // used during monitor layout change. uint32_t head_index; struct weston_log_scope *debug; uint32_t debugLevel; @@ -187,6 +186,7 @@ struct rdp_head { struct weston_head base; uint32_t index; struct rdp_monitor_mode monitorMode; + bool matched; /*TODO: these region/rectangles can be moved to rdp_output */ pixman_region32_t regionClient; // in client coordnate. pixman_rectangle32_t workareaClient; // in client coordinate. diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 6e55de354..41c1ee85f 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -91,45 +91,171 @@ disp_get_output_scale_from_monitor(struct rdp_backend *b, rdpMonitor *config) return (int) disp_get_client_scale_from_monitor(b, config); } +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 = disp_get_output_scale_from_monitor(rdp, a); + int scale_b = disp_get_output_scale_from_monitor(rdp, b); + + 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_any(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) +{ + return true; +} + +static void +update_head(struct rdp_backend *rdp, struct rdp_head *head, struct rdp_monitor_mode *mm) +{ + struct weston_mode mode = {}; + int scale; + bool changed = false; + + head->matched = true; + scale = disp_get_output_scale_from_monitor(rdp, &mm->monitorDef); + + if (!match_position(rdp, &head->monitorMode.monitorDef, &mm->monitorDef)) + changed = true; + + if (!match_dimensions(rdp, &head->monitorMode.monitorDef, &mm->monitorDef)) { + mode.flags = WL_OUTPUT_MODE_PREFERRED; + mode.width = mm->monitorDef.width; + mode.height = mm->monitorDef.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->monitorMode = *mm; + /* update monitor region in client */ + pixman_region32_clear(&head->regionClient); + pixman_region32_init_rect(&head->regionClient, + mm->monitorDef.x, + mm->monitorDef.y, + mm->monitorDef.width, + mm->monitorDef.height); +} + static void -disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *monitorMode, UINT32 monitorCount, int *doneIndex) +match_heads(struct rdp_backend *rdp, struct rdp_monitor_mode *mm, 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->monitorMode.monitorDef, &mm[i].monitorDef)) { + *done |= 1 << i; + update_head(rdp, current, &mm[i]); + break; + } + } + } +} + +static void +disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *monitorMode, UINT32 monitorCount) { RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + rdpSettings *settings = client->context->settings; 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); - /* move all heads to pending list */ - wl_list_init(&b->head_pending_list); - wl_list_insert_list(&b->head_pending_list, &b->compositor->head_list); - wl_list_init(&b->compositor->head_list); - for (UINT32 i = 0; i < monitorCount; i++, monitorMode++) { - struct weston_head *iter, *tmp; + /* 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(b->compositor, to_rdp_head(iter)); + continue; + } + current->matched = false; + } + + /* We want the primary head to remain primary - it + * should always be rdp-0. + */ + match_heads(b, monitorMode, monitorCount, &done, match_primary); + + /* Match first head with the same dimensions */ + match_heads(b, monitorMode, monitorCount, &done, match_dimensions); + + /* Match head with the same position */ + match_heads(b, monitorMode, monitorCount, &done, match_position); + + /* Pick any available head */ + match_heads(b, monitorMode, 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(b->compositor, to_rdp_head(iter)); + } + + + for (uint32_t i = 0; i < monitorCount; i++) { /* accumulate monitor layout */ - pixman_region32_union_rect(&desktop, &desktop, - monitorMode->monitorDef.x, - monitorMode->monitorDef.y, - monitorMode->monitorDef.width, - monitorMode->monitorDef.height); - - wl_list_for_each_safe(iter, tmp, &b->head_pending_list, compositor_link) { - struct rdp_head *current = to_rdp_head(iter); - - if (memcmp(¤t->monitorMode, monitorMode, sizeof(*monitorMode)) == 0) { - rdp_debug_verbose(b, "Head mode exact match:%s, x:%d, y:%d, width:%d, height:%d, is_primary: %d\n", - current->base.name, - current->monitorMode.monitorDef.x, current->monitorMode.monitorDef.y, - current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height, - current->monitorMode.monitorDef.is_primary); - wl_list_remove(&iter->compositor_link); - wl_list_insert(&b->compositor->head_list, &iter->compositor_link); - *doneIndex |= (1 << i); - break; - } + if (monitorMode[i].monitorDef.is_primary) { + /* it looks settings's desktopWidth/Height only represents primary */ + settings->DesktopWidth = monitorMode[i].monitorDef.width; + settings->DesktopHeight = monitorMode[i].monitorDef.height; } + pixman_region32_union_rect(&desktop, &desktop, + monitorMode[i].monitorDef.x, + monitorMode[i].monitorDef.y, + monitorMode[i].monitorDef.width, + monitorMode[i].monitorDef.height); + + /* Create new heads for any without matches */ + if (!(done & (1 << i))) + rdp_head_create(b->compositor, monitorMode[i].monitorDef.is_primary, &monitorMode[i]); } peerCtx->desktop_left = desktop.extents.x1; peerCtx->desktop_top = desktop.extents.y1; @@ -147,6 +273,42 @@ disp_end_monitor_layout_change(freerdp_peer *client) assert_compositor_thread(b); + wl_list_for_each_safe(iter, next, &b->compositor->head_list, compositor_link) { + struct rdp_head *current = to_rdp_head(iter); + struct weston_output *output = iter->output; + + if (output) { + /* ask weston to adjust size */ + struct weston_mode new_mode = {}; + + new_mode.width = current->monitorMode.monitorDef.width; + new_mode.height = current->monitorMode.monitorDef.height; + rdp_debug(b, "Head mode change:%s NEW width:%d, height:%d, scale:%d, clientScale:%f\n", + output->name, current->monitorMode.monitorDef.width, + current->monitorMode.monitorDef.height, + current->monitorMode.scale, + current->monitorMode.clientScale); + if (output->scale != current->monitorMode.scale) { + weston_output_disable(output); + output->scale = 0; /* reset scale first, otherwise assert */ + weston_output_set_scale(output, current->monitorMode.scale); + weston_output_enable(output); + } + weston_output_mode_set_native(iter->output, &new_mode, current->monitorMode.scale); + weston_head_set_physical_size(iter, + current->monitorMode.monitorDef.attributes.physicalWidth, + current->monitorMode.monitorDef.attributes.physicalHeight); + /* Notify clients for updated resolution/scale. */ + weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); + /* output size must match with monitor's rect in weston space */ + assert(output->width == (int32_t)current->monitorMode.rectWeston.width); + assert(output->height == (int32_t)current->monitorMode.rectWeston.height); + } else { + /* if head doesn't have output yet, mode is set at rdp_output_set_size */ + rdp_debug(b, "output doesn't exist for head %s\n", iter->name); + } + } + /* move output to final location */ wl_list_for_each_safe(iter, next, &b->compositor->head_list, compositor_link) { struct rdp_head *current = to_rdp_head(iter); @@ -167,14 +329,7 @@ disp_end_monitor_layout_change(freerdp_peer *client) /* position will be set at rdp_output_enable */ } } - /* remove all unsed head from pending list */ - if (!wl_list_empty(&b->head_pending_list)) { - wl_list_for_each_safe(iter, next, &b->head_pending_list, compositor_link) - rdp_head_destroy(b->compositor, to_rdp_head(iter)); - /* make sure nothing left in pending list */ - assert(wl_list_empty(&b->head_pending_list)); - wl_list_init(&b->head_pending_list); - } + /* make sure head list is not empty */ assert(!wl_list_empty(&b->compositor->head_list)); @@ -196,122 +351,6 @@ disp_end_monitor_layout_change(freerdp_peer *client) } } -static UINT -disp_set_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *monitorMode) -{ - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - rdpSettings *settings = client->context->settings; - struct weston_output *output = NULL; - struct weston_head *head = NULL; - struct weston_head *iter; - BOOL updateMode = FALSE; - - assert_compositor_thread(b); - - /* search head match configuration from pending list */ - wl_list_for_each(iter, &b->head_pending_list, compositor_link) { - struct rdp_head *current = to_rdp_head(iter); - - if (current->monitorMode.monitorDef.is_primary != monitorMode->monitorDef.is_primary) - continue; - - if (current->monitorMode.monitorDef.width == monitorMode->monitorDef.width && - current->monitorMode.monitorDef.height == monitorMode->monitorDef.height && - current->monitorMode.scale == monitorMode->scale) { - /* size mode (width/height/scale) */ - head = ¤t->base; - output = head->output; - break; - } else if (current->monitorMode.monitorDef.x == monitorMode->monitorDef.x && - current->monitorMode.monitorDef.y == monitorMode->monitorDef.y) { - /* position match in client space */ - head = ¤t->base; - output = head->output; - updateMode = TRUE; - break; - } - } - if (!head) { - /* just pick first one to change mode */ - wl_list_for_each(iter, &b->head_pending_list, compositor_link) { - struct rdp_head *current = to_rdp_head(iter); - - /* primary is only re-used for primary */ - if (!current->monitorMode.monitorDef.is_primary) { - head = ¤t->base; - output = head->output; - updateMode = TRUE; - break; - } - } - } - - if (head) { - struct rdp_head *current = to_rdp_head(head); - - assert(output); - rdp_debug(b, "Head mode change:%s OLD width:%d, height:%d, scale:%d, clientScale:%f\n", - output->name, current->monitorMode.monitorDef.width, - current->monitorMode.monitorDef.height, - current->monitorMode.scale, - current->monitorMode.clientScale); - /* reusing exising head */ - current->monitorMode = *monitorMode; - /* update monitor region in client */ - pixman_region32_clear(¤t->regionClient); - pixman_region32_init_rect(¤t->regionClient, - monitorMode->monitorDef.x, monitorMode->monitorDef.y, - monitorMode->monitorDef.width, monitorMode->monitorDef.height); - wl_list_remove(&head->compositor_link); - wl_list_insert(&b->compositor->head_list, &head->compositor_link); - } else { - /* no head found, create one */ - if (rdp_head_create(b->compositor, monitorMode->monitorDef.is_primary, monitorMode) == NULL) - return ERROR_INTERNAL_ERROR; - } - - if (updateMode) { - if (output) { - assert(head); - /* ask weston to adjust size */ - struct weston_mode new_mode = {}; - new_mode.width = monitorMode->monitorDef.width; - new_mode.height = monitorMode->monitorDef.height; - if (monitorMode->monitorDef.is_primary) { - /* it looks settings's desktopWidth/Height only represents primary */ - settings->DesktopWidth = new_mode.width; - settings->DesktopHeight = new_mode.height; - } - rdp_debug(b, "Head mode change:%s NEW width:%d, height:%d, scale:%d, clientScale:%f\n", - output->name, monitorMode->monitorDef.width, - monitorMode->monitorDef.height, - monitorMode->scale, - monitorMode->clientScale); - if (output->scale != monitorMode->scale) { - weston_output_disable(output); - output->scale = 0; /* reset scale first, otherwise assert */ - weston_output_set_scale(output, monitorMode->scale); - weston_output_enable(output); - } - weston_output_mode_set_native(output, &new_mode, monitorMode->scale); - weston_head_set_physical_size(head, - monitorMode->monitorDef.attributes.physicalWidth, - monitorMode->monitorDef.attributes.physicalHeight); - /* Notify clients for updated resolution/scale. */ - weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); - /* output size must match with monitor's rect in weston space */ - assert(output->width == (int32_t) monitorMode->rectWeston.width); - assert(output->height == (int32_t) monitorMode->rectWeston.height); - } else { - /* if head doesn't have output yet, mode is set at rdp_output_set_size */ - rdp_debug(b, "output doesn't exist for head %s\n", head->name); - } - } - - return 0; -} - static bool disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode, uint32_t count) { @@ -527,15 +566,7 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitor_count); - int doneIndex = 0; - disp_start_monitor_layout_change(client, monitorMode, monitor_count, &doneIndex); - for (int i = 0; i < monitor_count; i++) { - if ((doneIndex & (1 << i)) == 0) - if (disp_set_monitor_layout_change(client, &monitorMode[i]) != 0) { - success = true; - goto exit; - } - } + disp_start_monitor_layout_change(client, monitorMode, monitor_count); disp_end_monitor_layout_change(client); exit: From 54ba4225268d8735ec893bba120f390466663bf5 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 19 Jul 2022 14:24:20 -0500 Subject: [PATCH 1574/1642] rdp: Only pass compositor to end_monitor_layout_change When this is part of the front end, it will only have access to the compositor structure. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdpdisp.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 41c1ee85f..a7a2a623a 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -265,15 +265,11 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * } static void -disp_end_monitor_layout_change(freerdp_peer *client) +disp_end_monitor_layout_change(struct weston_compositor *ec) { - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; struct weston_head *iter, *next; - assert_compositor_thread(b); - - wl_list_for_each_safe(iter, next, &b->compositor->head_list, compositor_link) { + wl_list_for_each_safe(iter, next, &ec->head_list, compositor_link) { struct rdp_head *current = to_rdp_head(iter); struct weston_output *output = iter->output; @@ -283,7 +279,7 @@ disp_end_monitor_layout_change(freerdp_peer *client) new_mode.width = current->monitorMode.monitorDef.width; new_mode.height = current->monitorMode.monitorDef.height; - rdp_debug(b, "Head mode change:%s NEW width:%d, height:%d, scale:%d, clientScale:%f\n", + weston_log("Head mode change:%s NEW width:%d, height:%d, scale:%d, clientScale:%f\n", output->name, current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height, current->monitorMode.scale, @@ -305,16 +301,16 @@ disp_end_monitor_layout_change(freerdp_peer *client) assert(output->height == (int32_t)current->monitorMode.rectWeston.height); } else { /* if head doesn't have output yet, mode is set at rdp_output_set_size */ - rdp_debug(b, "output doesn't exist for head %s\n", iter->name); + weston_log("output doesn't exist for head %s\n", iter->name); } } /* move output to final location */ - wl_list_for_each_safe(iter, next, &b->compositor->head_list, compositor_link) { + wl_list_for_each_safe(iter, next, &ec->head_list, compositor_link) { struct rdp_head *current = to_rdp_head(iter); if (current->base.output) { - rdp_debug(b, "move head/output %s (%d,%d) -> (%d,%d)\n", + weston_log("move head/output %s (%d,%d) -> (%d,%d)\n", current->base.name, current->base.output->x, current->base.output->y, @@ -331,14 +327,14 @@ disp_end_monitor_layout_change(freerdp_peer *client) } /* make sure head list is not empty */ - assert(!wl_list_empty(&b->compositor->head_list)); + assert(!wl_list_empty(&ec->head_list)); BOOL is_primary_found = FALSE; - wl_list_for_each(iter, &b->compositor->head_list, compositor_link) { + wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *current = to_rdp_head(iter); if (current->monitorMode.monitorDef.is_primary) { - rdp_debug(b, "client origin (0,0) is (%d,%d) in Weston space\n", + weston_log("client origin (0,0) is (%d,%d) in Weston space\n", current->monitorMode.rectWeston.x, current->monitorMode.rectWeston.y); /* primary must be at (0,0) in client space */ @@ -567,7 +563,7 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitor_count); disp_start_monitor_layout_change(client, monitorMode, monitor_count); - disp_end_monitor_layout_change(client); + disp_end_monitor_layout_change(b->compositor); exit: free(monitorMode); From d2ac9b46938bb142bf94fb48d427a42fcfd79e82 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 20 Jul 2022 07:39:24 -0500 Subject: [PATCH 1575/1642] rdp: rework head layout validation Perform this on the compositor head_list instead of on the monitor array, which is how it needs to work to be in the front end code, where it will only have access to the compositor list, and only after the heads have been created. This also moves the validation code to after the heads have been created and removed - it validates output configuration and scale parameters, but wasn't really changing head allocation/deallocation anyway. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdpdisp.c | 244 +++++++++++++++++++++----------- 1 file changed, 158 insertions(+), 86 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index a7a2a623a..e7d19715e 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -52,17 +52,23 @@ is_line_intersected(int l1, int l2, int r1, int r2) static int compare_monitors_x(const void *p1, const void *p2) { - const struct rdp_monitor_mode *l = p1; - const struct rdp_monitor_mode *r = p2; - return l->monitorDef.x > r->monitorDef.x; + const struct weston_head *whl = *(const struct weston_head **)p1; + const struct weston_head *whr = *(const struct weston_head **)p2; + const struct rdp_head *l = to_rdp_head((void *)whl); + const struct rdp_head *r = to_rdp_head((void *)whr); + + return l->monitorMode.monitorDef.x > r->monitorMode.monitorDef.x; } static int compare_monitors_y(const void *p1, const void *p2) { - const struct rdp_monitor_mode *l = p1; - const struct rdp_monitor_mode *r = p2; - return l->monitorDef.y > r->monitorDef.y; + const struct weston_head *whl = *(const struct weston_head **)p1; + const struct weston_head *whr = *(const struct weston_head **)p2; + const struct rdp_head *l = to_rdp_head((void *)whl); + const struct rdp_head *r = to_rdp_head((void *)whr); + + return l->monitorMode.monitorDef.y > r->monitorMode.monitorDef.y; } static float @@ -91,6 +97,17 @@ disp_get_output_scale_from_monitor(struct rdp_backend *b, rdpMonitor *config) return (int) disp_get_client_scale_from_monitor(b, config); } +static struct rdp_head * +get_first_head(struct weston_compositor *ec) +{ + struct weston_head *head; + + wl_list_for_each(head, &ec->head_list, compositor_link) + return to_rdp_head(head); + + return NULL; +} + static bool match_primary(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) { @@ -392,84 +409,131 @@ disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mod } static void -disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode, uint32_t monitorCount) +sort_head_list(struct weston_compositor *ec, int (*compar)(const void *, const void *)) { - struct rdp_backend *b = peerCtx->rdpBackend; + int count = wl_list_length(&ec->head_list); + struct weston_head *head_array[count]; + struct weston_head *iter, *tmp; + int i = 0; + + wl_list_for_each_safe(iter, tmp, &ec->head_list, compositor_link) { + head_array[i++] = iter; + wl_list_remove(&iter->compositor_link); + } + + qsort(head_array, count, sizeof(struct weston_head *), compar); + + wl_list_init(&ec->head_list); + for (i = 0; i < count; i++) + wl_list_insert(ec->head_list.prev, &head_array[i]->compositor_link); +} + +static void +disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) +{ + struct weston_head *iter; bool isConnected_H = false; bool isConnected_V = false; bool isScalingUsed = false; bool isScalingSupported = true; int upperLeftX = 0; int upperLeftY = 0; - uint32_t i; + int i; + int count; + + wl_list_for_each(iter, &ec->head_list, compositor_link) { + struct rdp_head *head = to_rdp_head(iter); - for (i = 0; i < monitorCount; i++) { /* check if any monitor has scaling enabled */ - if (monitorMode[i].clientScale != 1.0f) + if (head->monitorMode.clientScale != 1.0f) isScalingUsed = true; /* find upper-left corner of combined monitors in client space */ - if (upperLeftX > monitorMode[i].monitorDef.x) - upperLeftX = monitorMode[i].monitorDef.x; - if (upperLeftY > monitorMode[i].monitorDef.y) - upperLeftY = monitorMode[i].monitorDef.y; + if (upperLeftX > head->monitorMode.monitorDef.x) + upperLeftX = head->monitorMode.monitorDef.x; + if (upperLeftY > head->monitorMode.monitorDef.y) + upperLeftY = head->monitorMode.monitorDef.y; } assert(upperLeftX <= 0); assert(upperLeftY <= 0); - rdp_debug(b, "Client desktop upper left coordinate (%d,%d)\n", upperLeftX, upperLeftY); + weston_log("Client desktop upper left coordinate (%d,%d)\n", upperLeftX, upperLeftY); - if (monitorCount > 1) { + count = wl_list_length(&ec->head_list); + + if (count > 1) { + struct rdp_head *head, *last; int32_t offsetFromOriginClient; /* first, sort monitors horizontally */ - qsort(monitorMode, monitorCount, sizeof(*monitorMode), compare_monitors_x); - assert(upperLeftX == monitorMode[0].monitorDef.x); + sort_head_list(ec, compare_monitors_x); + head = get_first_head(ec); + last = head; + assert(upperLeftX == head->monitorMode.monitorDef.x); /* check if monitors are horizontally connected each other */ - offsetFromOriginClient = monitorMode[0].monitorDef.x + monitorMode[0].monitorDef.width; - for (i = 1; i < monitorCount; i++) { - if (offsetFromOriginClient != monitorMode[i].monitorDef.x) { - rdp_debug(b, "\tRDP client reported monitors not horizontally connected each other at %d (x check)\n", i); + offsetFromOriginClient = head->monitorMode.monitorDef.x + head->monitorMode.monitorDef.width; + i = 0; + wl_list_for_each(iter, &ec->head_list, compositor_link) { + struct rdp_head *cur = to_rdp_head(iter); + + i++; + if (i == 1) + continue; + + if (offsetFromOriginClient != cur->monitorMode.monitorDef.x) { + weston_log("\tRDP client reported monitors not horizontally connected each other at %d (x check)\n", i); break; } - offsetFromOriginClient += monitorMode[i].monitorDef.width; + offsetFromOriginClient += cur->monitorMode.monitorDef.width; - if (!is_line_intersected(monitorMode[i-1].monitorDef.y, - monitorMode[i-1].monitorDef.y + monitorMode[i-1].monitorDef.height, - monitorMode[i].monitorDef.y, - monitorMode[i].monitorDef.y + monitorMode[i].monitorDef.height)) { - rdp_debug(b, "\tRDP client reported monitors not horizontally connected each other at %d (y check)\n\n", i); + if (!is_line_intersected(last->monitorMode.monitorDef.y, + last->monitorMode.monitorDef.y + last->monitorMode.monitorDef.height, + cur->monitorMode.monitorDef.y, + cur->monitorMode.monitorDef.y + cur->monitorMode.monitorDef.height)) { + weston_log("\tRDP client reported monitors not horizontally connected each other at %d (y check)\n\n", i); break; } + last = cur; } - if (i == monitorCount) { - rdp_debug(b, "\tAll monitors are horizontally placed\n"); + if (i == count) { + weston_log("\tAll monitors are horizontally placed\n"); isConnected_H = true; } else { + struct rdp_head *head, *last; /* next, trying sort monitors vertically */ - qsort(monitorMode, monitorCount, sizeof(*monitorMode), compare_monitors_y); - assert(upperLeftY == monitorMode[0].monitorDef.y); + sort_head_list(ec, compare_monitors_y); + head = get_first_head(ec); + last = head; + assert(upperLeftY == head->monitorMode.monitorDef.y); /* make sure monitors are horizontally connected each other */ - offsetFromOriginClient = monitorMode[0].monitorDef.y + monitorMode[0].monitorDef.height; - for (i = 1; i < monitorCount; i++) { - if (offsetFromOriginClient != monitorMode[i].monitorDef.y) { - rdp_debug(b, "\tRDP client reported monitors not vertically connected each other at %d (y check)\n", i); + offsetFromOriginClient = head->monitorMode.monitorDef.y + head->monitorMode.monitorDef.height; + i = 0; + wl_list_for_each(iter, &ec->head_list, compositor_link) { + struct rdp_head *cur = to_rdp_head(iter); + + i++; + if (i == 1) + continue; + + if (offsetFromOriginClient != cur->monitorMode.monitorDef.y) { + weston_log("\tRDP client reported monitors not vertically connected each other at %d (y check)\n", i); break; } - offsetFromOriginClient += monitorMode[i].monitorDef.height; + offsetFromOriginClient += cur->monitorMode.monitorDef.height; - if (!is_line_intersected(monitorMode[i-1].monitorDef.x, - monitorMode[i-1].monitorDef.x + monitorMode[i-1].monitorDef.width, - monitorMode[i].monitorDef.x, - monitorMode[i].monitorDef.x + monitorMode[i].monitorDef.width)) { - rdp_debug(b, "\tRDP client reported monitors not horizontally connected each other at %d (x check)\n\n", i); + if (!is_line_intersected(last->monitorMode.monitorDef.x, + last->monitorMode.monitorDef.x + last->monitorMode.monitorDef.width, + cur->monitorMode.monitorDef.x, + cur->monitorMode.monitorDef.x + cur->monitorMode.monitorDef.width)) { + weston_log("\tRDP client reported monitors not horizontally connected each other at %d (x check)\n\n", i); break; } + last = cur; } - if (i == monitorCount) { - rdp_debug(b, "\tAll monitors are vertically placed\n"); + if (i == count) { + weston_log("\tAll monitors are vertically placed\n"); isConnected_V = true; } } @@ -479,61 +543,69 @@ disp_monitor_validate_and_compute_layout(RdpPeerContext *peerCtx, struct rdp_mon if (isScalingUsed && (!isConnected_H && !isConnected_V)) { /* scaling can't be supported in complex monitor placement */ - rdp_debug_error(b, "\nWARNING\nWARNING\nWARNING: Scaling is used, but can't be supported in complex monitor placement\nWARNING\nWARNING\n"); + weston_log("\nWARNING\nWARNING\nWARNING: Scaling is used, but can't be supported in complex monitor placement\nWARNING\nWARNING\n"); isScalingSupported = false; } if (isScalingUsed && isScalingSupported) { uint32_t offsetFromOriginWeston = 0; - for (i = 0; i < monitorCount; i++) { - monitorMode[i].rectWeston.width = monitorMode[i].monitorDef.width / monitorMode[i].scale; - monitorMode[i].rectWeston.height = monitorMode[i].monitorDef.height / monitorMode[i].scale; + + wl_list_for_each(iter, &ec->head_list, compositor_link) { + struct rdp_head *head = to_rdp_head(iter); + + head->monitorMode.rectWeston.width = head->monitorMode.monitorDef.width / head->monitorMode.scale; + head->monitorMode.rectWeston.height = head->monitorMode.monitorDef.height / head->monitorMode.scale; if (isConnected_H) { assert(isConnected_V == false); - monitorMode[i].rectWeston.x = offsetFromOriginWeston; - monitorMode[i].rectWeston.y = abs((upperLeftY - monitorMode[i].monitorDef.y) / monitorMode[i].scale); - offsetFromOriginWeston += monitorMode[i].rectWeston.width; + head->monitorMode.rectWeston.x = offsetFromOriginWeston; + head->monitorMode.rectWeston.y = abs((upperLeftY - head->monitorMode.monitorDef.y) / head->monitorMode.scale); + offsetFromOriginWeston += head->monitorMode.rectWeston.width; } else { assert(isConnected_V == true); - monitorMode[i].rectWeston.x = abs((upperLeftX - monitorMode[i].monitorDef.x) / monitorMode[i].scale); - monitorMode[i].rectWeston.y = offsetFromOriginWeston; - offsetFromOriginWeston += monitorMode[i].rectWeston.height; + head->monitorMode.rectWeston.x = abs((upperLeftX - head->monitorMode.monitorDef.x) / head->monitorMode.scale); + head->monitorMode.rectWeston.y = offsetFromOriginWeston; + offsetFromOriginWeston += head->monitorMode.rectWeston.height; } - assert(monitorMode[i].rectWeston.x >= 0); - assert(monitorMode[i].rectWeston.y >= 0); + assert(head->monitorMode.rectWeston.x >= 0); + assert(head->monitorMode.rectWeston.y >= 0); } } else { /* no scaling is used or monitor placement is too complex to scale in weston space, fallback to 1.0f */ - for (i = 0; i < monitorCount; i++) { - monitorMode[i].rectWeston.width = monitorMode[i].monitorDef.width; - monitorMode[i].rectWeston.height = monitorMode[i].monitorDef.height; - monitorMode[i].rectWeston.x = monitorMode[i].monitorDef.x + abs(upperLeftX); - monitorMode[i].rectWeston.y = monitorMode[i].monitorDef.y + abs(upperLeftY); - assert(monitorMode[i].rectWeston.x >= 0); - assert(monitorMode[i].rectWeston.y >= 0); - monitorMode[i].scale = 1; - monitorMode[i].clientScale = 1.0f; + wl_list_for_each(iter, &ec->head_list, compositor_link) { + struct rdp_head *head = to_rdp_head(iter); + head->monitorMode.rectWeston.width = head->monitorMode.monitorDef.width; + head->monitorMode.rectWeston.height = head->monitorMode.monitorDef.height; + head->monitorMode.rectWeston.x = head->monitorMode.monitorDef.x + abs(upperLeftX); + head->monitorMode.rectWeston.y = head->monitorMode.monitorDef.y + abs(upperLeftY); + assert(head->monitorMode.rectWeston.x >= 0); + assert(head->monitorMode.rectWeston.y >= 0); + head->monitorMode.scale = 1; + head->monitorMode.clientScale = 1.0f; } } - rdp_debug(b, "%s:---OUTPUT---\n", __func__); - for (UINT32 i = 0; i < monitorCount; i++) { - rdp_debug(b, " rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", - i, monitorMode[i].monitorDef.x, monitorMode[i].monitorDef.y, - monitorMode[i].monitorDef.width, monitorMode[i].monitorDef.height, - monitorMode[i].monitorDef.is_primary); - rdp_debug(b, " rdpMonitor[%d]: weston x:%d, y:%d, width:%d, height:%d\n", - i, monitorMode[i].rectWeston.x, monitorMode[i].rectWeston.y, - monitorMode[i].rectWeston.width, monitorMode[i].rectWeston.height); - rdp_debug(b, " rdpMonitor[%d]: physicalWidth:%d, physicalHeight:%d, orientation:%d\n", - i, monitorMode[i].monitorDef.attributes.physicalWidth, - monitorMode[i].monitorDef.attributes.physicalHeight, - monitorMode[i].monitorDef.attributes.orientation); - rdp_debug(b, " rdpMonitor[%d]: desktopScaleFactor:%d, deviceScaleFactor:%d\n", - i, monitorMode[i].monitorDef.attributes.desktopScaleFactor, - monitorMode[i].monitorDef.attributes.deviceScaleFactor); - rdp_debug(b, " rdpMonitor[%d]: scale:%d, clientScale:%3.2f\n", - i, monitorMode[i].scale, monitorMode[i].clientScale); + weston_log("%s:---OUTPUT---\n", __func__); + i = 0; + wl_list_for_each(iter, &ec->head_list, compositor_link) { + struct rdp_head *head = to_rdp_head(iter); + + weston_log(" rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", + i, head->monitorMode.monitorDef.x, head->monitorMode.monitorDef.y, + head->monitorMode.monitorDef.width, head->monitorMode.monitorDef.height, + head->monitorMode.monitorDef.is_primary); + weston_log(" rdpMonitor[%d]: weston x:%d, y:%d, width:%d, height:%d\n", + i, head->monitorMode.rectWeston.x, head->monitorMode.rectWeston.y, + head->monitorMode.rectWeston.width, head->monitorMode.rectWeston.height); + weston_log(" rdpMonitor[%d]: physicalWidth:%d, physicalHeight:%d, orientation:%d\n", + i, head->monitorMode.monitorDef.attributes.physicalWidth, + head->monitorMode.monitorDef.attributes.physicalHeight, + head->monitorMode.monitorDef.attributes.orientation); + weston_log(" rdpMonitor[%d]: desktopScaleFactor:%d, deviceScaleFactor:%d\n", + i, head->monitorMode.monitorDef.attributes.desktopScaleFactor, + head->monitorMode.monitorDef.attributes.deviceScaleFactor); + weston_log(" rdpMonitor[%d]: scale:%d, clientScale:%3.2f\n", + i, head->monitorMode.scale, head->monitorMode.clientScale); + i++; } } @@ -560,9 +632,9 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor goto exit; } - disp_monitor_validate_and_compute_layout(peerCtx, monitorMode, monitor_count); - disp_start_monitor_layout_change(client, monitorMode, monitor_count); + + disp_monitor_validate_and_compute_layout(b->compositor); disp_end_monitor_layout_change(b->compositor); exit: From ebb9da47ab82552ec958f8c7c35c0837c76b93a7 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 20 Jul 2022 07:58:17 -0500 Subject: [PATCH 1576/1642] rdp: Combine the two front-end output setup functions Since these take the same parameters and always run back to back, combine them. At the same time convert a couple of wl_list_for_each_safe call sites that don't remove list elements to wl_list_for_each. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdpdisp.c | 161 +++++++++++++++----------------- 1 file changed, 77 insertions(+), 84 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index e7d19715e..7fd8ae7c7 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -281,89 +281,6 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * pixman_region32_fini(&desktop); } -static void -disp_end_monitor_layout_change(struct weston_compositor *ec) -{ - struct weston_head *iter, *next; - - wl_list_for_each_safe(iter, next, &ec->head_list, compositor_link) { - struct rdp_head *current = to_rdp_head(iter); - struct weston_output *output = iter->output; - - if (output) { - /* ask weston to adjust size */ - struct weston_mode new_mode = {}; - - new_mode.width = current->monitorMode.monitorDef.width; - new_mode.height = current->monitorMode.monitorDef.height; - weston_log("Head mode change:%s NEW width:%d, height:%d, scale:%d, clientScale:%f\n", - output->name, current->monitorMode.monitorDef.width, - current->monitorMode.monitorDef.height, - current->monitorMode.scale, - current->monitorMode.clientScale); - if (output->scale != current->monitorMode.scale) { - weston_output_disable(output); - output->scale = 0; /* reset scale first, otherwise assert */ - weston_output_set_scale(output, current->monitorMode.scale); - weston_output_enable(output); - } - weston_output_mode_set_native(iter->output, &new_mode, current->monitorMode.scale); - weston_head_set_physical_size(iter, - current->monitorMode.monitorDef.attributes.physicalWidth, - current->monitorMode.monitorDef.attributes.physicalHeight); - /* Notify clients for updated resolution/scale. */ - weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); - /* output size must match with monitor's rect in weston space */ - assert(output->width == (int32_t)current->monitorMode.rectWeston.width); - assert(output->height == (int32_t)current->monitorMode.rectWeston.height); - } else { - /* if head doesn't have output yet, mode is set at rdp_output_set_size */ - weston_log("output doesn't exist for head %s\n", iter->name); - } - } - - /* move output to final location */ - wl_list_for_each_safe(iter, next, &ec->head_list, compositor_link) { - struct rdp_head *current = to_rdp_head(iter); - - if (current->base.output) { - weston_log("move head/output %s (%d,%d) -> (%d,%d)\n", - current->base.name, - current->base.output->x, - current->base.output->y, - current->monitorMode.rectWeston.x, - current->monitorMode.rectWeston.y); - /* Notify clients for updated output position. */ - weston_output_move(current->base.output, - current->monitorMode.rectWeston.x, - current->monitorMode.rectWeston.y); - } else { - /* newly created head doesn't have output yet */ - /* position will be set at rdp_output_enable */ - } - } - - /* make sure head list is not empty */ - assert(!wl_list_empty(&ec->head_list)); - - BOOL is_primary_found = FALSE; - wl_list_for_each(iter, &ec->head_list, compositor_link) { - struct rdp_head *current = to_rdp_head(iter); - - if (current->monitorMode.monitorDef.is_primary) { - weston_log("client origin (0,0) is (%d,%d) in Weston space\n", - current->monitorMode.rectWeston.x, - current->monitorMode.rectWeston.y); - /* primary must be at (0,0) in client space */ - assert(current->monitorMode.monitorDef.x == 0); - assert(current->monitorMode.monitorDef.y == 0); - /* there must be only one primary */ - assert(is_primary_found == FALSE); - is_primary_found = TRUE; - } - } -} - static bool disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode, uint32_t count) { @@ -607,6 +524,83 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) i, head->monitorMode.scale, head->monitorMode.clientScale); i++; } + + wl_list_for_each(iter, &ec->head_list, compositor_link) { + struct rdp_head *current = to_rdp_head(iter); + struct weston_output *output = iter->output; + + if (output) { + /* ask weston to adjust size */ + struct weston_mode new_mode = {}; + + new_mode.width = current->monitorMode.monitorDef.width; + new_mode.height = current->monitorMode.monitorDef.height; + weston_log("Head mode change:%s NEW width:%d, height:%d, scale:%d, clientScale:%f\n", + output->name, current->monitorMode.monitorDef.width, + current->monitorMode.monitorDef.height, + current->monitorMode.scale, + current->monitorMode.clientScale); + if (output->scale != current->monitorMode.scale) { + weston_output_disable(output); + output->scale = 0; /* reset scale first, otherwise assert */ + weston_output_set_scale(output, current->monitorMode.scale); + weston_output_enable(output); + } + weston_output_mode_set_native(iter->output, &new_mode, current->monitorMode.scale); + weston_head_set_physical_size(iter, + current->monitorMode.monitorDef.attributes.physicalWidth, + current->monitorMode.monitorDef.attributes.physicalHeight); + /* Notify clients for updated resolution/scale. */ + weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); + /* output size must match with monitor's rect in weston space */ + assert(output->width == (int32_t)current->monitorMode.rectWeston.width); + assert(output->height == (int32_t)current->monitorMode.rectWeston.height); + } else { + /* if head doesn't have output yet, mode is set at rdp_output_set_size */ + weston_log("output doesn't exist for head %s\n", iter->name); + } + } + + /* move output to final location */ + wl_list_for_each(iter, &ec->head_list, compositor_link) { + struct rdp_head *current = to_rdp_head(iter); + + if (current->base.output) { + weston_log("move head/output %s (%d,%d) -> (%d,%d)\n", + current->base.name, + current->base.output->x, + current->base.output->y, + current->monitorMode.rectWeston.x, + current->monitorMode.rectWeston.y); + /* Notify clients for updated output position. */ + weston_output_move(current->base.output, + current->monitorMode.rectWeston.x, + current->monitorMode.rectWeston.y); + } else { + /* newly created head doesn't have output yet */ + /* position will be set at rdp_output_enable */ + } + } + + /* make sure head list is not empty */ + assert(!wl_list_empty(&ec->head_list)); + + BOOL is_primary_found = FALSE; + wl_list_for_each(iter, &ec->head_list, compositor_link) { + struct rdp_head *current = to_rdp_head(iter); + + if (current->monitorMode.monitorDef.is_primary) { + weston_log("client origin (0,0) is (%d,%d) in Weston space\n", + current->monitorMode.rectWeston.x, + current->monitorMode.rectWeston.y); + /* primary must be at (0,0) in client space */ + assert(current->monitorMode.monitorDef.x == 0); + assert(current->monitorMode.monitorDef.y == 0); + /* there must be only one primary */ + assert(is_primary_found == FALSE); + is_primary_found = TRUE; + } + } } bool @@ -635,7 +629,6 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor disp_start_monitor_layout_change(client, monitorMode, monitor_count); disp_monitor_validate_and_compute_layout(b->compositor); - disp_end_monitor_layout_change(b->compositor); exit: free(monitorMode); From 9c3e1d65fa825c9484e6e25efc4038ff88dcbc40 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 20 Jul 2022 10:21:41 -0500 Subject: [PATCH 1577/1642] rdp: Remove westonRect from backend structures Change this to a local variable now that the two functions that should be using it are combined. This commit intentionally introduces a bug that will be fixed in a follow up commit. Outputs may not be properly moved into place immediately after creation. In the future output creation and positioning will happen in the front end, and this will no longer be a problem. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 15 +------- libweston/backend-rdp/rdp.h | 1 - libweston/backend-rdp/rdpdisp.c | 67 ++++++++++++++++++++------------- 3 files changed, 41 insertions(+), 42 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 9f89cf7c0..35a04e4b0 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -564,20 +564,7 @@ rdp_output_enable(struct weston_output *base) if (b->rdp_peer && b->rdp_peer->context->settings->HiDefRemoteApp) HiDefRemoteApp = true; - if (HiDefRemoteApp) { - struct weston_head *eh; - wl_list_for_each(eh, &output->base.head_list, output_link) { - struct rdp_head *h = to_rdp_head(eh); - rdp_debug(b, "move head/output %s (%d,%d) -> (%d,%d)\n", - output->base.name, output->base.x, output->base.y, - h->monitorMode.rectWeston.x, - h->monitorMode.rectWeston.y); - weston_output_move(&output->base, - h->monitorMode.rectWeston.x, - h->monitorMode.rectWeston.y); - break; // must be only 1 head per output. - } - } else { + if (!HiDefRemoteApp) { output->shadow_surface = pixman_image_create_bits(PIXMAN_x8r8g8b8, output->base.current_mode->width, output->base.current_mode->height, diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index bf352ad5c..c49d954ef 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -179,7 +179,6 @@ struct rdp_monitor_mode { rdpMonitor monitorDef; // in client coordinate. int scale; // per monitor DPI scaling. float clientScale; - pixman_rectangle32_t rectWeston; // in weston coordinate. }; struct rdp_head { diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 7fd8ae7c7..c961072ad 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -356,7 +356,8 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) int upperLeftX = 0; int upperLeftY = 0; int i; - int count; + int count = wl_list_length(&ec->head_list); + pixman_rectangle32_t rectWeston[count]; wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(iter); @@ -466,38 +467,44 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) if (isScalingUsed && isScalingSupported) { uint32_t offsetFromOriginWeston = 0; + i = 0; wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(iter); - head->monitorMode.rectWeston.width = head->monitorMode.monitorDef.width / head->monitorMode.scale; - head->monitorMode.rectWeston.height = head->monitorMode.monitorDef.height / head->monitorMode.scale; + rectWeston[i].width = head->monitorMode.monitorDef.width / head->monitorMode.scale; + rectWeston[i].height = head->monitorMode.monitorDef.height / head->monitorMode.scale; if (isConnected_H) { assert(isConnected_V == false); - head->monitorMode.rectWeston.x = offsetFromOriginWeston; - head->monitorMode.rectWeston.y = abs((upperLeftY - head->monitorMode.monitorDef.y) / head->monitorMode.scale); - offsetFromOriginWeston += head->monitorMode.rectWeston.width; + rectWeston[i].x = offsetFromOriginWeston; + rectWeston[i].y = abs((upperLeftY - head->monitorMode.monitorDef.y) / head->monitorMode.scale); + offsetFromOriginWeston += rectWeston[i].width; } else { assert(isConnected_V == true); - head->monitorMode.rectWeston.x = abs((upperLeftX - head->monitorMode.monitorDef.x) / head->monitorMode.scale); - head->monitorMode.rectWeston.y = offsetFromOriginWeston; - offsetFromOriginWeston += head->monitorMode.rectWeston.height; + rectWeston[i].x = abs((upperLeftX - head->monitorMode.monitorDef.x) / head->monitorMode.scale); + rectWeston[i].y = offsetFromOriginWeston; + offsetFromOriginWeston += rectWeston[i].height; } - assert(head->monitorMode.rectWeston.x >= 0); - assert(head->monitorMode.rectWeston.y >= 0); + assert(rectWeston[i].x >= 0); + assert(rectWeston[i].y >= 0); + i++; } } else { + i = 0; + /* no scaling is used or monitor placement is too complex to scale in weston space, fallback to 1.0f */ wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(iter); - head->monitorMode.rectWeston.width = head->monitorMode.monitorDef.width; - head->monitorMode.rectWeston.height = head->monitorMode.monitorDef.height; - head->monitorMode.rectWeston.x = head->monitorMode.monitorDef.x + abs(upperLeftX); - head->monitorMode.rectWeston.y = head->monitorMode.monitorDef.y + abs(upperLeftY); - assert(head->monitorMode.rectWeston.x >= 0); - assert(head->monitorMode.rectWeston.y >= 0); + + rectWeston[i].width = head->monitorMode.monitorDef.width; + rectWeston[i].height = head->monitorMode.monitorDef.height; + rectWeston[i].x = head->monitorMode.monitorDef.x + abs(upperLeftX); + rectWeston[i].y = head->monitorMode.monitorDef.y + abs(upperLeftY); + assert(rectWeston[i].x >= 0); + assert(rectWeston[i].y >= 0); head->monitorMode.scale = 1; head->monitorMode.clientScale = 1.0f; + i++; } } @@ -511,8 +518,8 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) head->monitorMode.monitorDef.width, head->monitorMode.monitorDef.height, head->monitorMode.monitorDef.is_primary); weston_log(" rdpMonitor[%d]: weston x:%d, y:%d, width:%d, height:%d\n", - i, head->monitorMode.rectWeston.x, head->monitorMode.rectWeston.y, - head->monitorMode.rectWeston.width, head->monitorMode.rectWeston.height); + i, rectWeston[i].x, rectWeston[i].y, + rectWeston[i].width, rectWeston[i].height); weston_log(" rdpMonitor[%d]: physicalWidth:%d, physicalHeight:%d, orientation:%d\n", i, head->monitorMode.monitorDef.attributes.physicalWidth, head->monitorMode.monitorDef.attributes.physicalHeight, @@ -525,6 +532,7 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) i++; } + i = 0; wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *current = to_rdp_head(iter); struct weston_output *output = iter->output; @@ -553,15 +561,17 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) /* Notify clients for updated resolution/scale. */ weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); /* output size must match with monitor's rect in weston space */ - assert(output->width == (int32_t)current->monitorMode.rectWeston.width); - assert(output->height == (int32_t)current->monitorMode.rectWeston.height); + assert(output->width == (int32_t)rectWeston[i].width); + assert(output->height == (int32_t)rectWeston[i].height); } else { /* if head doesn't have output yet, mode is set at rdp_output_set_size */ weston_log("output doesn't exist for head %s\n", iter->name); } + i++; } /* move output to final location */ + i = 0; wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *current = to_rdp_head(iter); @@ -570,29 +580,31 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) current->base.name, current->base.output->x, current->base.output->y, - current->monitorMode.rectWeston.x, - current->monitorMode.rectWeston.y); + rectWeston[i].x, + rectWeston[i].y); /* Notify clients for updated output position. */ weston_output_move(current->base.output, - current->monitorMode.rectWeston.x, - current->monitorMode.rectWeston.y); + rectWeston[i].x, + rectWeston[i].y); } else { /* newly created head doesn't have output yet */ /* position will be set at rdp_output_enable */ } + i++; } /* make sure head list is not empty */ assert(!wl_list_empty(&ec->head_list)); BOOL is_primary_found = FALSE; + i = 0; wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *current = to_rdp_head(iter); if (current->monitorMode.monitorDef.is_primary) { weston_log("client origin (0,0) is (%d,%d) in Weston space\n", - current->monitorMode.rectWeston.x, - current->monitorMode.rectWeston.y); + rectWeston[i].x, + rectWeston[i].y); /* primary must be at (0,0) in client space */ assert(current->monitorMode.monitorDef.x == 0); assert(current->monitorMode.monitorDef.y == 0); @@ -600,6 +612,7 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) assert(is_primary_found == FALSE); is_primary_found = TRUE; } + i++; } } From 46acac9857c580551a17c2af54e6bda4ac4bd80b Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 20 Jul 2022 12:19:45 -0500 Subject: [PATCH 1578/1642] rdp: Remove ClientScale from monitor struct We can create this any time we need it, provided we appropriately change the desktopScale factor in validation. This lets us minimize the amount of state that needs to be passed between front and back ends. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 2 +- libweston/backend-rdp/rdp.h | 4 +++- libweston/backend-rdp/rdpdisp.c | 26 +++++++++++++++++--------- libweston/backend-rdp/rdprail.c | 7 ++++++- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 35a04e4b0..18c030fee 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -687,8 +687,8 @@ rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp monitorMode->monitorDef.x, monitorMode->monitorDef.y, monitorMode->monitorDef.width, monitorMode->monitorDef.height); } else { + head->monitorMode.monitorDef.attributes.desktopScaleFactor = 0.0; head->monitorMode.scale = 1.0f; - head->monitorMode.clientScale = 1; pixman_region32_init(&head->regionClient); } if (isPrimary) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index c49d954ef..9751cff89 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -178,7 +178,6 @@ struct rdp_peers_item { struct rdp_monitor_mode { rdpMonitor monitorDef; // in client coordinate. int scale; // per monitor DPI scaling. - float clientScale; }; struct rdp_head { @@ -404,6 +403,9 @@ to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32 void to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, int32_t *x, int32_t *y, uint32_t *width, uint32_t *height); +float +disp_get_client_scale_from_monitor(struct rdp_backend *b, const rdpMonitor *config); + // rdpclip.c int rdp_clipboard_init(freerdp_peer* client); void rdp_clipboard_destroy(RdpPeerContext *peerCtx); diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index c961072ad..4b1d8c708 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -71,7 +71,7 @@ compare_monitors_y(const void *p1, const void *p2) return l->monitorMode.monitorDef.y > r->monitorMode.monitorDef.y; } -static float +float disp_get_client_scale_from_monitor(struct rdp_backend *b, const rdpMonitor *config) { if (config->attributes.desktopScaleFactor == 0.0) @@ -291,6 +291,8 @@ disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mod /* dump client monitor topology */ rdp_debug(b, "%s:---INPUT---\n", __func__); for (i = 0; i < count; i++) { + float client_scale = disp_get_client_scale_from_monitor(b, &monitorMode[i].monitorDef); + rdp_debug(b, " rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", i, monitorMode[i].monitorDef.x, monitorMode[i].monitorDef.y, monitorMode[i].monitorDef.width, monitorMode[i].monitorDef.height, @@ -303,7 +305,7 @@ disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mod i, monitorMode[i].monitorDef.attributes.desktopScaleFactor, monitorMode[i].monitorDef.attributes.deviceScaleFactor); rdp_debug(b, " rdpMonitor[%d]: scale:%d, client scale :%3.2f\n", - i, monitorMode[i].scale, monitorMode[i].clientScale); + i, monitorMode[i].scale, client_scale); } for (i = 0; i < count; i++) { @@ -348,6 +350,7 @@ sort_head_list(struct weston_compositor *ec, int (*compar)(const void *, const v static void disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) { + struct rdp_backend *b = to_rdp_backend(ec); struct weston_head *iter; bool isConnected_H = false; bool isConnected_V = false; @@ -361,9 +364,10 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(iter); + float client_scale = disp_get_client_scale_from_monitor(b, &head->monitorMode.monitorDef); /* check if any monitor has scaling enabled */ - if (head->monitorMode.clientScale != 1.0f) + if (client_scale != 1.0f) isScalingUsed = true; /* find upper-left corner of combined monitors in client space */ @@ -500,10 +504,10 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) rectWeston[i].height = head->monitorMode.monitorDef.height; rectWeston[i].x = head->monitorMode.monitorDef.x + abs(upperLeftX); rectWeston[i].y = head->monitorMode.monitorDef.y + abs(upperLeftY); + head->monitorMode.monitorDef.attributes.desktopScaleFactor = 0.0; assert(rectWeston[i].x >= 0); assert(rectWeston[i].y >= 0); head->monitorMode.scale = 1; - head->monitorMode.clientScale = 1.0f; i++; } } @@ -512,6 +516,8 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) i = 0; wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(iter); + struct rdp_backend *b = to_rdp_backend(ec); + float client_scale = disp_get_client_scale_from_monitor(b, &head->monitorMode.monitorDef); weston_log(" rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", i, head->monitorMode.monitorDef.x, head->monitorMode.monitorDef.y, @@ -528,7 +534,7 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) i, head->monitorMode.monitorDef.attributes.desktopScaleFactor, head->monitorMode.monitorDef.attributes.deviceScaleFactor); weston_log(" rdpMonitor[%d]: scale:%d, clientScale:%3.2f\n", - i, head->monitorMode.scale, head->monitorMode.clientScale); + i, head->monitorMode.scale, client_scale); i++; } @@ -540,6 +546,8 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) if (output) { /* ask weston to adjust size */ struct weston_mode new_mode = {}; + struct rdp_backend *b = to_rdp_backend(ec); + float client_scale = disp_get_client_scale_from_monitor(b, ¤t->monitorMode.monitorDef); new_mode.width = current->monitorMode.monitorDef.width; new_mode.height = current->monitorMode.monitorDef.height; @@ -547,7 +555,7 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) output->name, current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height, current->monitorMode.scale, - current->monitorMode.clientScale); + client_scale); if (output->scale != current->monitorMode.scale) { weston_output_disable(output); output->scale = 0; /* reset scale first, otherwise assert */ @@ -631,7 +639,6 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor monitorMode[i].monitorDef = monitors[i]; monitorMode[i].monitorDef.orig_screen = 0; monitorMode[i].scale = disp_get_output_scale_from_monitor(b, &monitorMode[i].monitorDef); - monitorMode[i].clientScale = disp_get_client_scale_from_monitor(b, &monitorMode[i].monitorDef); } if (!disp_monitor_sanity_check_layout(peerCtx, monitorMode, monitor_count)) { @@ -672,7 +679,8 @@ to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32 if (pixman_region32_contains_point(&head->regionClient, sx, sy, NULL)) { struct weston_output *output = head->base.output; - float scale = 1.0f / head->monitorMode.clientScale; + float client_scale = disp_get_client_scale_from_monitor(b, &head->monitorMode.monitorDef); + float scale = 1.0f / client_scale; /* translate x/y to offset from this output on client space. */ sx -= head->monitorMode.monitorDef.x; @@ -715,7 +723,7 @@ to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, /* Pick first head from output. */ wl_list_for_each(head_iter, &output->head_list, output_link) { struct rdp_head *head = to_rdp_head(head_iter); - float scale = head->monitorMode.clientScale; + float scale = disp_get_client_scale_from_monitor(b, &head->monitorMode.monitorDef); /* translate x/y to offset from this output on weston space. */ sx -= output->x; diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 0b034e7bd..8719b6d1d 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -3685,6 +3685,11 @@ print_matrix(FILE *fp, const char *name, const struct weston_matrix *matrix) static void print_rdp_head(FILE *fp, const struct rdp_head *current) { + const struct weston_head *wh = ¤t->base; + struct weston_compositor *ec = wh->compositor; + struct rdp_backend *b = to_rdp_backend(ec); + float client_scale = disp_get_client_scale_from_monitor(b, ¤t->monitorMode.monitorDef); + fprintf(fp," rdp_head: %s: index:%d: is_primary:%d\n", current->base.name, current->index, current->monitorMode.monitorDef.is_primary); @@ -3702,7 +3707,7 @@ print_rdp_head(FILE *fp, const struct rdp_head *current) current->monitorMode.monitorDef.attributes.desktopScaleFactor, current->monitorMode.monitorDef.attributes.deviceScaleFactor); fprintf(fp," scale:%d, client scale :%3.2f\n", - current->monitorMode.scale, current->monitorMode.clientScale); + current->monitorMode.scale, client_scale); fprintf(fp," workarea: x:%d, y:%d, width:%d, height:%d\n", current->workarea.x, current->workarea.y, current->workarea.width, current->workarea.height); From 42c835050753cf22b5b0e78efb3b414d25a121f7 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 20 Jul 2022 12:49:38 -0500 Subject: [PATCH 1579/1642] rdp: Stop storing scale in monitor struct We can create this any time we need it. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 3 +-- libweston/backend-rdp/rdp.h | 4 +++- libweston/backend-rdp/rdpdisp.c | 30 ++++++++++++++++-------------- libweston/backend-rdp/rdprail.c | 3 ++- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 18c030fee..9ea14d5b7 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -479,7 +479,7 @@ rdp_output_get_config(struct weston_output *base, /* Return true client resolution (not adjusted by DPI) */ *width = h->monitorMode.monitorDef.width; *height = h->monitorMode.monitorDef.height; - *scale = h->monitorMode.scale; + *scale = disp_get_output_scale_from_monitor(rdpBackend, &h->monitorMode.monitorDef); } break; // only one head per output in HiDef. } @@ -688,7 +688,6 @@ rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp monitorMode->monitorDef.width, monitorMode->monitorDef.height); } else { head->monitorMode.monitorDef.attributes.desktopScaleFactor = 0.0; - head->monitorMode.scale = 1.0f; pixman_region32_init(&head->regionClient); } if (isPrimary) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 9751cff89..a24ddcce1 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -177,7 +177,6 @@ struct rdp_peers_item { struct rdp_monitor_mode { rdpMonitor monitorDef; // in client coordinate. - int scale; // per monitor DPI scaling. }; struct rdp_head { @@ -406,6 +405,9 @@ to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, float disp_get_client_scale_from_monitor(struct rdp_backend *b, const rdpMonitor *config); +int +disp_get_output_scale_from_monitor(struct rdp_backend *b, const rdpMonitor *config); + // rdpclip.c int rdp_clipboard_init(freerdp_peer* client); void rdp_clipboard_destroy(RdpPeerContext *peerCtx); diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 4b1d8c708..d59ede6d0 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -91,8 +91,8 @@ disp_get_client_scale_from_monitor(struct rdp_backend *b, const rdpMonitor *conf } } -static int -disp_get_output_scale_from_monitor(struct rdp_backend *b, rdpMonitor *config) +int +disp_get_output_scale_from_monitor(struct rdp_backend *b, const rdpMonitor *config) { return (int) disp_get_client_scale_from_monitor(b, config); } @@ -292,6 +292,7 @@ disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mod rdp_debug(b, "%s:---INPUT---\n", __func__); for (i = 0; i < count; i++) { float client_scale = disp_get_client_scale_from_monitor(b, &monitorMode[i].monitorDef); + int scale = disp_get_output_scale_from_monitor(b, &monitorMode[i].monitorDef); rdp_debug(b, " rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", i, monitorMode[i].monitorDef.x, monitorMode[i].monitorDef.y, @@ -305,7 +306,7 @@ disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mod i, monitorMode[i].monitorDef.attributes.desktopScaleFactor, monitorMode[i].monitorDef.attributes.deviceScaleFactor); rdp_debug(b, " rdpMonitor[%d]: scale:%d, client scale :%3.2f\n", - i, monitorMode[i].scale, client_scale); + i, scale, client_scale); } for (i = 0; i < count; i++) { @@ -475,17 +476,18 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(iter); + int scale = disp_get_output_scale_from_monitor(b, &head->monitorMode.monitorDef); - rectWeston[i].width = head->monitorMode.monitorDef.width / head->monitorMode.scale; - rectWeston[i].height = head->monitorMode.monitorDef.height / head->monitorMode.scale; + rectWeston[i].width = head->monitorMode.monitorDef.width / scale; + rectWeston[i].height = head->monitorMode.monitorDef.height / scale; if (isConnected_H) { assert(isConnected_V == false); rectWeston[i].x = offsetFromOriginWeston; - rectWeston[i].y = abs((upperLeftY - head->monitorMode.monitorDef.y) / head->monitorMode.scale); + rectWeston[i].y = abs((upperLeftY - head->monitorMode.monitorDef.y) / scale); offsetFromOriginWeston += rectWeston[i].width; } else { assert(isConnected_V == true); - rectWeston[i].x = abs((upperLeftX - head->monitorMode.monitorDef.x) / head->monitorMode.scale); + rectWeston[i].x = abs((upperLeftX - head->monitorMode.monitorDef.x) / scale); rectWeston[i].y = offsetFromOriginWeston; offsetFromOriginWeston += rectWeston[i].height; } @@ -507,7 +509,6 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) head->monitorMode.monitorDef.attributes.desktopScaleFactor = 0.0; assert(rectWeston[i].x >= 0); assert(rectWeston[i].y >= 0); - head->monitorMode.scale = 1; i++; } } @@ -518,6 +519,7 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) struct rdp_head *head = to_rdp_head(iter); struct rdp_backend *b = to_rdp_backend(ec); float client_scale = disp_get_client_scale_from_monitor(b, &head->monitorMode.monitorDef); + int scale = disp_get_output_scale_from_monitor(b, &head->monitorMode.monitorDef); weston_log(" rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", i, head->monitorMode.monitorDef.x, head->monitorMode.monitorDef.y, @@ -534,7 +536,7 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) i, head->monitorMode.monitorDef.attributes.desktopScaleFactor, head->monitorMode.monitorDef.attributes.deviceScaleFactor); weston_log(" rdpMonitor[%d]: scale:%d, clientScale:%3.2f\n", - i, head->monitorMode.scale, client_scale); + i, scale, client_scale); i++; } @@ -548,21 +550,22 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) struct weston_mode new_mode = {}; struct rdp_backend *b = to_rdp_backend(ec); float client_scale = disp_get_client_scale_from_monitor(b, ¤t->monitorMode.monitorDef); + int scale = disp_get_output_scale_from_monitor(b, ¤t->monitorMode.monitorDef); new_mode.width = current->monitorMode.monitorDef.width; new_mode.height = current->monitorMode.monitorDef.height; weston_log("Head mode change:%s NEW width:%d, height:%d, scale:%d, clientScale:%f\n", output->name, current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height, - current->monitorMode.scale, + scale, client_scale); - if (output->scale != current->monitorMode.scale) { + if (output->scale != scale) { weston_output_disable(output); output->scale = 0; /* reset scale first, otherwise assert */ - weston_output_set_scale(output, current->monitorMode.scale); + weston_output_set_scale(output, scale); weston_output_enable(output); } - weston_output_mode_set_native(iter->output, &new_mode, current->monitorMode.scale); + weston_output_mode_set_native(iter->output, &new_mode, scale); weston_head_set_physical_size(iter, current->monitorMode.monitorDef.attributes.physicalWidth, current->monitorMode.monitorDef.attributes.physicalHeight); @@ -638,7 +641,6 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor for (i = 0; i < monitor_count; i++) { monitorMode[i].monitorDef = monitors[i]; monitorMode[i].monitorDef.orig_screen = 0; - monitorMode[i].scale = disp_get_output_scale_from_monitor(b, &monitorMode[i].monitorDef); } if (!disp_monitor_sanity_check_layout(peerCtx, monitorMode, monitor_count)) { diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 8719b6d1d..2c57c8b3d 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -3689,6 +3689,7 @@ print_rdp_head(FILE *fp, const struct rdp_head *current) struct weston_compositor *ec = wh->compositor; struct rdp_backend *b = to_rdp_backend(ec); float client_scale = disp_get_client_scale_from_monitor(b, ¤t->monitorMode.monitorDef); + int scale = disp_get_output_scale_from_monitor(b, ¤t->monitorMode.monitorDef); fprintf(fp," rdp_head: %s: index:%d: is_primary:%d\n", current->base.name, current->index, @@ -3707,7 +3708,7 @@ print_rdp_head(FILE *fp, const struct rdp_head *current) current->monitorMode.monitorDef.attributes.desktopScaleFactor, current->monitorMode.monitorDef.attributes.deviceScaleFactor); fprintf(fp," scale:%d, client scale :%3.2f\n", - current->monitorMode.scale, client_scale); + scale, client_scale); fprintf(fp," workarea: x:%d, y:%d, width:%d, height:%d\n", current->workarea.x, current->workarea.y, current->workarea.width, current->workarea.height); From 7989025f986e159923bd0541d7c3e3c43ea204a7 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 20 Jul 2022 13:15:04 -0500 Subject: [PATCH 1580/1642] rdp: Remove struct rdp_monitor_mode We no longer need anything but the rdpMonitor, so let's reduce. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 38 +++--- libweston/backend-rdp/rdp.h | 8 +- libweston/backend-rdp/rdpdisp.c | 225 +++++++++++++++----------------- libweston/backend-rdp/rdprail.c | 22 ++-- 4 files changed, 137 insertions(+), 156 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 9ea14d5b7..7fbef1d38 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -470,16 +470,16 @@ rdp_output_get_config(struct weston_output *base, rdp_debug(rdpBackend, "get_config: attached head [%d]: make:%s, mode:%s, name:%s, (%p)\n", h->index, head->make, head->model, head->name, head); rdp_debug(rdpBackend, "get_config: attached head [%d]: x:%d, y:%d, width:%d, height:%d\n", - h->index, h->monitorMode.monitorDef.x, h->monitorMode.monitorDef.y, - h->monitorMode.monitorDef.width, h->monitorMode.monitorDef.height); + h->index, h->config.x, h->config.y, + h->config.width, h->config.height); /* In HiDef RAIL mode, get monitor resolution from RDP client if provided. */ if (client && client->context->settings->HiDefRemoteApp) { - if (h->monitorMode.monitorDef.width && h->monitorMode.monitorDef.height) { + if (h->config.width && h->config.height) { /* Return true client resolution (not adjusted by DPI) */ - *width = h->monitorMode.monitorDef.width; - *height = h->monitorMode.monitorDef.height; - *scale = disp_get_output_scale_from_monitor(rdpBackend, &h->monitorMode.monitorDef); + *width = h->config.width; + *height = h->config.height; + *scale = disp_get_output_scale_from_monitor(rdpBackend, &h->config); } break; // only one head per output in HiDef. } @@ -509,15 +509,15 @@ rdp_output_set_size(struct weston_output *base, rdp_debug(rdpBackend, "set_size: attached head [%d]: make:%s, mode:%s, name:%s, (%p)\n", h->index, head->make, head->model, head->name, head); rdp_debug(rdpBackend, "set_size: attached head [%d]: x:%d, y:%d, width:%d, height:%d\n", - h->index, h->monitorMode.monitorDef.x, h->monitorMode.monitorDef.y, - h->monitorMode.monitorDef.width, h->monitorMode.monitorDef.height); + h->index, h->config.x, h->config.y, + h->config.width, h->config.height); /* This is a virtual output, so report a zero physical size. * It's better to let frontends/clients use their defaults. */ /* If MonitorDef has it, use it from MonitorDef */ weston_head_set_physical_size(head, - h->monitorMode.monitorDef.attributes.physicalWidth, - h->monitorMode.monitorDef.attributes.physicalHeight); + h->config.attributes.physicalWidth, + h->config.attributes.physicalHeight); /* In HiDef RAIL mode, set this mode as preferred mode */ if (client && client->context->settings->HiDefRemoteApp) { @@ -627,7 +627,7 @@ rdp_output_attach_head(struct weston_output *output_base, struct rdp_output *o = to_rdp_output(output_base); struct rdp_head *h = to_rdp_head(head_base); rdp_debug(b, "Head attaching: %s, index:%d, is_primary: %d\n", - head_base->name, h->index, h->monitorMode.monitorDef.is_primary); + head_base->name, h->index, h->config.is_primary); if (!wl_list_empty(&output_base->head_list)) { rdp_debug_error(b, "attaching more than 1 head to single output (= clone) is not supported\n"); return -1; @@ -643,7 +643,7 @@ rdp_output_detach_head(struct weston_output *output_base, struct rdp_backend *b = to_rdp_backend(output_base->compositor); struct rdp_head *h = to_rdp_head(head_base); rdp_debug(b, "Head detaching: %s, index:%d, is_primary: %d\n", - head_base->name, h->index, h->monitorMode.monitorDef.is_primary); + head_base->name, h->index, h->config.is_primary); } static struct weston_output * @@ -672,7 +672,7 @@ rdp_output_create(struct weston_compositor *compositor, const char *name) } struct rdp_head * -rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp_monitor_mode *monitorMode) +rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, rdpMonitor *config) { struct rdp_backend *b = to_rdp_backend(compositor); struct rdp_head *head; @@ -681,19 +681,19 @@ rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp head = xzalloc(sizeof *head); head->index = b->head_index++; - if (monitorMode) { - head->monitorMode = *monitorMode; + if (config) { + head->config = *config; pixman_region32_init_rect(&head->regionClient, - monitorMode->monitorDef.x, monitorMode->monitorDef.y, - monitorMode->monitorDef.width, monitorMode->monitorDef.height); + config->x, config->y, + config->width, config->height); } else { - head->monitorMode.monitorDef.attributes.desktopScaleFactor = 0.0; + head->config.attributes.desktopScaleFactor = 0.0; pixman_region32_init(&head->regionClient); } if (isPrimary) rdp_debug(b, "Default head is being added\n"); - head->monitorMode.monitorDef.is_primary = isPrimary; + head->config.is_primary = isPrimary; sprintf(name, "rdp-%x", head->index); weston_head_init(&head->base, name); diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index a24ddcce1..9547af831 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -175,15 +175,11 @@ struct rdp_peers_item { struct wl_list link; // rdp_output::peers }; -struct rdp_monitor_mode { - rdpMonitor monitorDef; // in client coordinate. -}; - struct rdp_head { struct weston_head base; uint32_t index; - struct rdp_monitor_mode monitorMode; bool matched; + rdpMonitor config; /*TODO: these region/rectangles can be moved to rdp_output */ pixman_region32_t regionClient; // in client coordnate. pixman_rectangle32_t workareaClient; // in client coordinate. @@ -343,7 +339,7 @@ struct rdp_loop_task { // rdp.c void convert_rdp_keyboard_to_xkb_rule_names(UINT32 KeyboardType, UINT32 KeyboardSubType, UINT32 KeyboardLayout, struct xkb_rule_names *xkbRuleNames); -struct rdp_head * rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, struct rdp_monitor_mode *monitorMode); +struct rdp_head * rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, rdpMonitor *config); void rdp_head_destroy(struct weston_compositor *compositor, struct rdp_head *head); // rdputil.c diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index d59ede6d0..af0018d8d 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -57,7 +57,7 @@ compare_monitors_x(const void *p1, const void *p2) const struct rdp_head *l = to_rdp_head((void *)whl); const struct rdp_head *r = to_rdp_head((void *)whr); - return l->monitorMode.monitorDef.x > r->monitorMode.monitorDef.x; + return l->config.x > r->config.x; } static int @@ -68,7 +68,7 @@ compare_monitors_y(const void *p1, const void *p2) const struct rdp_head *l = to_rdp_head((void *)whl); const struct rdp_head *r = to_rdp_head((void *)whr); - return l->monitorMode.monitorDef.y > r->monitorMode.monitorDef.y; + return l->config.y > r->config.y; } float @@ -148,22 +148,22 @@ match_any(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) } static void -update_head(struct rdp_backend *rdp, struct rdp_head *head, struct rdp_monitor_mode *mm) +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 = disp_get_output_scale_from_monitor(rdp, &mm->monitorDef); + scale = disp_get_output_scale_from_monitor(rdp, config); - if (!match_position(rdp, &head->monitorMode.monitorDef, &mm->monitorDef)) + if (!match_position(rdp, &head->config, config)) changed = true; - if (!match_dimensions(rdp, &head->monitorMode.monitorDef, &mm->monitorDef)) { + if (!match_dimensions(rdp, &head->config, config)) { mode.flags = WL_OUTPUT_MODE_PREFERRED; - mode.width = mm->monitorDef.width; - mode.height = mm->monitorDef.height; + 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); @@ -173,18 +173,18 @@ update_head(struct rdp_backend *rdp, struct rdp_head *head, struct rdp_monitor_m if (changed) { weston_head_set_device_changed(&head->base); } - head->monitorMode = *mm; + head->config = *config; /* update monitor region in client */ pixman_region32_clear(&head->regionClient); pixman_region32_init_rect(&head->regionClient, - mm->monitorDef.x, - mm->monitorDef.y, - mm->monitorDef.width, - mm->monitorDef.height); + config->x, + config->y, + config->width, + config->height); } static void -match_heads(struct rdp_backend *rdp, struct rdp_monitor_mode *mm, uint32_t count, +match_heads(struct rdp_backend *rdp, rdpMonitor *config, uint32_t count, int *done, bool (*cmp)(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b)) { @@ -201,9 +201,9 @@ match_heads(struct rdp_backend *rdp, struct rdp_monitor_mode *mm, uint32_t count if (*done & (1 << i)) continue; - if (cmp(rdp, ¤t->monitorMode.monitorDef, &mm[i].monitorDef)) { + if (cmp(rdp, ¤t->config, &config[i])) { *done |= 1 << i; - update_head(rdp, current, &mm[i]); + update_head(rdp, current, &config[i]); break; } } @@ -211,7 +211,7 @@ match_heads(struct rdp_backend *rdp, struct rdp_monitor_mode *mm, uint32_t count } static void -disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode *monitorMode, UINT32 monitorCount) +disp_start_monitor_layout_change(freerdp_peer *client, rdpMonitor *config, UINT32 monitorCount) { RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; rdpSettings *settings = client->context->settings; @@ -238,16 +238,16 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * /* We want the primary head to remain primary - it * should always be rdp-0. */ - match_heads(b, monitorMode, monitorCount, &done, match_primary); + match_heads(b, config, monitorCount, &done, match_primary); /* Match first head with the same dimensions */ - match_heads(b, monitorMode, monitorCount, &done, match_dimensions); + match_heads(b, config, monitorCount, &done, match_dimensions); /* Match head with the same position */ - match_heads(b, monitorMode, monitorCount, &done, match_position); + match_heads(b, config, monitorCount, &done, match_position); /* Pick any available head */ - match_heads(b, monitorMode, monitorCount, &done, match_any); + 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) { @@ -259,20 +259,20 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * for (uint32_t i = 0; i < monitorCount; i++) { /* accumulate monitor layout */ - if (monitorMode[i].monitorDef.is_primary) { + if (config[i].is_primary) { /* it looks settings's desktopWidth/Height only represents primary */ - settings->DesktopWidth = monitorMode[i].monitorDef.width; - settings->DesktopHeight = monitorMode[i].monitorDef.height; + settings->DesktopWidth = config[i].width; + settings->DesktopHeight = config[i].height; } pixman_region32_union_rect(&desktop, &desktop, - monitorMode[i].monitorDef.x, - monitorMode[i].monitorDef.y, - monitorMode[i].monitorDef.width, - monitorMode[i].monitorDef.height); + 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, monitorMode[i].monitorDef.is_primary, &monitorMode[i]); + rdp_head_create(b->compositor, config[i].is_primary, &config[i]); } peerCtx->desktop_left = desktop.extents.x1; peerCtx->desktop_top = desktop.extents.y1; @@ -282,7 +282,7 @@ disp_start_monitor_layout_change(freerdp_peer *client, struct rdp_monitor_mode * } static bool -disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mode *monitorMode, uint32_t count) +disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, rdpMonitor *config, uint32_t count) { struct rdp_backend *b = peerCtx->rdpBackend; uint32_t primaryCount = 0; @@ -291,36 +291,36 @@ disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, struct rdp_monitor_mod /* dump client monitor topology */ rdp_debug(b, "%s:---INPUT---\n", __func__); for (i = 0; i < count; i++) { - float client_scale = disp_get_client_scale_from_monitor(b, &monitorMode[i].monitorDef); - int scale = disp_get_output_scale_from_monitor(b, &monitorMode[i].monitorDef); + float client_scale = disp_get_client_scale_from_monitor(b, &config[i]); + int scale = disp_get_output_scale_from_monitor(b, &config[i]); rdp_debug(b, " rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", - i, monitorMode[i].monitorDef.x, monitorMode[i].monitorDef.y, - monitorMode[i].monitorDef.width, monitorMode[i].monitorDef.height, - monitorMode[i].monitorDef.is_primary); + 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, monitorMode[i].monitorDef.attributes.physicalWidth, - monitorMode[i].monitorDef.attributes.physicalHeight, - monitorMode[i].monitorDef.attributes.orientation); + i, config[i].attributes.physicalWidth, + config[i].attributes.physicalHeight, + config[i].attributes.orientation); rdp_debug(b, " rdpMonitor[%d]: desktopScaleFactor:%d, deviceScaleFactor:%d\n", - i, monitorMode[i].monitorDef.attributes.desktopScaleFactor, - monitorMode[i].monitorDef.attributes.deviceScaleFactor); + i, config[i].attributes.desktopScaleFactor, + config[i].attributes.deviceScaleFactor); rdp_debug(b, " rdpMonitor[%d]: scale:%d, client scale :%3.2f\n", i, scale, client_scale); } for (i = 0; i < count; i++) { /* make sure there is only one primary and its position at client */ - if (monitorMode[i].monitorDef.is_primary) { + if (config[i].is_primary) { /* count number of primary */ if (++primaryCount > 1) { rdp_debug_error(b, "%s: RDP client reported unexpected primary count (%d)\n",__func__, primaryCount); return false; } /* primary must be at (0,0) in client space */ - if (monitorMode[i].monitorDef.x != 0 || monitorMode[i].monitorDef.y != 0) { + if (config[i].x != 0 || config[i].y != 0) { rdp_debug_error(b, "%s: RDP client reported primary is not at (0,0) but (%d,%d).\n", - __func__, monitorMode[i].monitorDef.x, monitorMode[i].monitorDef.y); + __func__, config[i].x, config[i].y); return false; } } @@ -365,17 +365,17 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(iter); - float client_scale = disp_get_client_scale_from_monitor(b, &head->monitorMode.monitorDef); + float client_scale = disp_get_client_scale_from_monitor(b, &head->config); /* check if any monitor has scaling enabled */ if (client_scale != 1.0f) isScalingUsed = true; /* find upper-left corner of combined monitors in client space */ - if (upperLeftX > head->monitorMode.monitorDef.x) - upperLeftX = head->monitorMode.monitorDef.x; - if (upperLeftY > head->monitorMode.monitorDef.y) - upperLeftY = head->monitorMode.monitorDef.y; + if (upperLeftX > head->config.x) + upperLeftX = head->config.x; + if (upperLeftY > head->config.y) + upperLeftY = head->config.y; } assert(upperLeftX <= 0); assert(upperLeftY <= 0); @@ -391,10 +391,10 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) sort_head_list(ec, compare_monitors_x); head = get_first_head(ec); last = head; - assert(upperLeftX == head->monitorMode.monitorDef.x); + assert(upperLeftX == head->config.x); /* check if monitors are horizontally connected each other */ - offsetFromOriginClient = head->monitorMode.monitorDef.x + head->monitorMode.monitorDef.width; + offsetFromOriginClient = head->config.x + head->config.width; i = 0; wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *cur = to_rdp_head(iter); @@ -403,16 +403,16 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) if (i == 1) continue; - if (offsetFromOriginClient != cur->monitorMode.monitorDef.x) { + if (offsetFromOriginClient != cur->config.x) { weston_log("\tRDP client reported monitors not horizontally connected each other at %d (x check)\n", i); break; } - offsetFromOriginClient += cur->monitorMode.monitorDef.width; + offsetFromOriginClient += cur->config.width; - if (!is_line_intersected(last->monitorMode.monitorDef.y, - last->monitorMode.monitorDef.y + last->monitorMode.monitorDef.height, - cur->monitorMode.monitorDef.y, - cur->monitorMode.monitorDef.y + cur->monitorMode.monitorDef.height)) { + if (!is_line_intersected(last->config.y, + last->config.y + last->config.height, + cur->config.y, + cur->config.y + cur->config.height)) { weston_log("\tRDP client reported monitors not horizontally connected each other at %d (y check)\n\n", i); break; } @@ -427,10 +427,10 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) sort_head_list(ec, compare_monitors_y); head = get_first_head(ec); last = head; - assert(upperLeftY == head->monitorMode.monitorDef.y); + assert(upperLeftY == head->config.y); /* make sure monitors are horizontally connected each other */ - offsetFromOriginClient = head->monitorMode.monitorDef.y + head->monitorMode.monitorDef.height; + offsetFromOriginClient = head->config.y + head->config.height; i = 0; wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *cur = to_rdp_head(iter); @@ -439,16 +439,16 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) if (i == 1) continue; - if (offsetFromOriginClient != cur->monitorMode.monitorDef.y) { + if (offsetFromOriginClient != cur->config.y) { weston_log("\tRDP client reported monitors not vertically connected each other at %d (y check)\n", i); break; } - offsetFromOriginClient += cur->monitorMode.monitorDef.height; + offsetFromOriginClient += cur->config.height; - if (!is_line_intersected(last->monitorMode.monitorDef.x, - last->monitorMode.monitorDef.x + last->monitorMode.monitorDef.width, - cur->monitorMode.monitorDef.x, - cur->monitorMode.monitorDef.x + cur->monitorMode.monitorDef.width)) { + if (!is_line_intersected(last->config.x, + last->config.x + last->config.width, + cur->config.x, + cur->config.x + cur->config.width)) { weston_log("\tRDP client reported monitors not horizontally connected each other at %d (x check)\n\n", i); break; } @@ -476,18 +476,18 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(iter); - int scale = disp_get_output_scale_from_monitor(b, &head->monitorMode.monitorDef); + int scale = disp_get_output_scale_from_monitor(b, &head->config); - rectWeston[i].width = head->monitorMode.monitorDef.width / scale; - rectWeston[i].height = head->monitorMode.monitorDef.height / scale; + rectWeston[i].width = head->config.width / scale; + rectWeston[i].height = head->config.height / scale; if (isConnected_H) { assert(isConnected_V == false); rectWeston[i].x = offsetFromOriginWeston; - rectWeston[i].y = abs((upperLeftY - head->monitorMode.monitorDef.y) / scale); + rectWeston[i].y = abs((upperLeftY - head->config.y) / scale); offsetFromOriginWeston += rectWeston[i].width; } else { assert(isConnected_V == true); - rectWeston[i].x = abs((upperLeftX - head->monitorMode.monitorDef.x) / scale); + rectWeston[i].x = abs((upperLeftX - head->config.x) / scale); rectWeston[i].y = offsetFromOriginWeston; offsetFromOriginWeston += rectWeston[i].height; } @@ -502,11 +502,11 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(iter); - rectWeston[i].width = head->monitorMode.monitorDef.width; - rectWeston[i].height = head->monitorMode.monitorDef.height; - rectWeston[i].x = head->monitorMode.monitorDef.x + abs(upperLeftX); - rectWeston[i].y = head->monitorMode.monitorDef.y + abs(upperLeftY); - head->monitorMode.monitorDef.attributes.desktopScaleFactor = 0.0; + rectWeston[i].width = head->config.width; + rectWeston[i].height = head->config.height; + rectWeston[i].x = head->config.x + abs(upperLeftX); + rectWeston[i].y = head->config.y + abs(upperLeftY); + head->config.attributes.desktopScaleFactor = 0.0; assert(rectWeston[i].x >= 0); assert(rectWeston[i].y >= 0); i++; @@ -518,23 +518,23 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(iter); struct rdp_backend *b = to_rdp_backend(ec); - float client_scale = disp_get_client_scale_from_monitor(b, &head->monitorMode.monitorDef); - int scale = disp_get_output_scale_from_monitor(b, &head->monitorMode.monitorDef); + float client_scale = disp_get_client_scale_from_monitor(b, &head->config); + int scale = disp_get_output_scale_from_monitor(b, &head->config); weston_log(" rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", - i, head->monitorMode.monitorDef.x, head->monitorMode.monitorDef.y, - head->monitorMode.monitorDef.width, head->monitorMode.monitorDef.height, - head->monitorMode.monitorDef.is_primary); + i, head->config.x, head->config.y, + head->config.width, head->config.height, + head->config.is_primary); weston_log(" rdpMonitor[%d]: weston x:%d, y:%d, width:%d, height:%d\n", i, rectWeston[i].x, rectWeston[i].y, rectWeston[i].width, rectWeston[i].height); weston_log(" rdpMonitor[%d]: physicalWidth:%d, physicalHeight:%d, orientation:%d\n", - i, head->monitorMode.monitorDef.attributes.physicalWidth, - head->monitorMode.monitorDef.attributes.physicalHeight, - head->monitorMode.monitorDef.attributes.orientation); + i, head->config.attributes.physicalWidth, + head->config.attributes.physicalHeight, + head->config.attributes.orientation); weston_log(" rdpMonitor[%d]: desktopScaleFactor:%d, deviceScaleFactor:%d\n", - i, head->monitorMode.monitorDef.attributes.desktopScaleFactor, - head->monitorMode.monitorDef.attributes.deviceScaleFactor); + i, head->config.attributes.desktopScaleFactor, + head->config.attributes.deviceScaleFactor); weston_log(" rdpMonitor[%d]: scale:%d, clientScale:%3.2f\n", i, scale, client_scale); i++; @@ -549,14 +549,14 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) /* ask weston to adjust size */ struct weston_mode new_mode = {}; struct rdp_backend *b = to_rdp_backend(ec); - float client_scale = disp_get_client_scale_from_monitor(b, ¤t->monitorMode.monitorDef); - int scale = disp_get_output_scale_from_monitor(b, ¤t->monitorMode.monitorDef); + float client_scale = disp_get_client_scale_from_monitor(b, ¤t->config); + int scale = disp_get_output_scale_from_monitor(b, ¤t->config); - new_mode.width = current->monitorMode.monitorDef.width; - new_mode.height = current->monitorMode.monitorDef.height; + new_mode.width = current->config.width; + new_mode.height = current->config.height; weston_log("Head mode change:%s NEW width:%d, height:%d, scale:%d, clientScale:%f\n", - output->name, current->monitorMode.monitorDef.width, - current->monitorMode.monitorDef.height, + output->name, current->config.width, + current->config.height, scale, client_scale); if (output->scale != scale) { @@ -567,8 +567,8 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) } weston_output_mode_set_native(iter->output, &new_mode, scale); weston_head_set_physical_size(iter, - current->monitorMode.monitorDef.attributes.physicalWidth, - current->monitorMode.monitorDef.attributes.physicalHeight); + current->config.attributes.physicalWidth, + current->config.attributes.physicalHeight); /* Notify clients for updated resolution/scale. */ weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); /* output size must match with monitor's rect in weston space */ @@ -612,13 +612,13 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) wl_list_for_each(iter, &ec->head_list, compositor_link) { struct rdp_head *current = to_rdp_head(iter); - if (current->monitorMode.monitorDef.is_primary) { + if (current->config.is_primary) { weston_log("client origin (0,0) is (%d,%d) in Weston space\n", rectWeston[i].x, rectWeston[i].y); /* primary must be at (0,0) in client space */ - assert(current->monitorMode.monitorDef.x == 0); - assert(current->monitorMode.monitorDef.y == 0); + assert(current->config.x == 0); + assert(current->config.y == 0); /* there must be only one primary */ assert(is_primary_found == FALSE); is_primary_found = TRUE; @@ -632,30 +632,15 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor { RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; - bool success = true; - struct rdp_monitor_mode *monitorMode = NULL; - int i; - - monitorMode = xmalloc(sizeof(struct rdp_monitor_mode) * monitor_count); - - for (i = 0; i < monitor_count; i++) { - monitorMode[i].monitorDef = monitors[i]; - monitorMode[i].monitorDef.orig_screen = 0; - } - if (!disp_monitor_sanity_check_layout(peerCtx, monitorMode, monitor_count)) { - success = true; - goto exit; - } + if (!disp_monitor_sanity_check_layout(peerCtx, monitors, monitor_count)) + return true; - disp_start_monitor_layout_change(client, monitorMode, monitor_count); + disp_start_monitor_layout_change(client, monitors, monitor_count); disp_monitor_validate_and_compute_layout(b->compositor); -exit: - free(monitorMode); - - return success; + return true; } static inline void @@ -681,12 +666,12 @@ to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32 if (pixman_region32_contains_point(&head->regionClient, sx, sy, NULL)) { struct weston_output *output = head->base.output; - float client_scale = disp_get_client_scale_from_monitor(b, &head->monitorMode.monitorDef); + float client_scale = disp_get_client_scale_from_monitor(b, &head->config); float scale = 1.0f / client_scale; /* translate x/y to offset from this output on client space. */ - sx -= head->monitorMode.monitorDef.x; - sy -= head->monitorMode.monitorDef.y; + sx -= head->config.x; + sy -= head->config.y; /* scale x/y to client output space. */ to_weston_scale_only(peerContext, output, scale, &sx, &sy); if (width && height) @@ -725,7 +710,7 @@ to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, /* Pick first head from output. */ wl_list_for_each(head_iter, &output->head_list, output_link) { struct rdp_head *head = to_rdp_head(head_iter); - float scale = disp_get_client_scale_from_monitor(b, &head->monitorMode.monitorDef); + float scale = disp_get_client_scale_from_monitor(b, &head->config); /* translate x/y to offset from this output on weston space. */ sx -= output->x; @@ -735,8 +720,8 @@ to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, if (width && height) to_client_scale_only(peerContext, output, scale, width, height); /* translate x/y to offset from this output on client space. */ - sx += head->monitorMode.monitorDef.x; - sy += head->monitorMode.monitorDef.y; + sx += head->config.x; + sy += head->config.y; rdp_debug_verbose(b, "%s: (x:%d, y:%d) -> (sx:%d, sy:%d) at head:%s\n", __func__, *x, *y, sx, sy, head_iter->name); *x = sx; diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 2c57c8b3d..2a774d30c 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -3688,25 +3688,25 @@ print_rdp_head(FILE *fp, const struct rdp_head *current) const struct weston_head *wh = ¤t->base; struct weston_compositor *ec = wh->compositor; struct rdp_backend *b = to_rdp_backend(ec); - float client_scale = disp_get_client_scale_from_monitor(b, ¤t->monitorMode.monitorDef); - int scale = disp_get_output_scale_from_monitor(b, ¤t->monitorMode.monitorDef); + float client_scale = disp_get_client_scale_from_monitor(b, ¤t->config); + int scale = disp_get_output_scale_from_monitor(b, ¤t->config); fprintf(fp," rdp_head: %s: index:%d: is_primary:%d\n", current->base.name, current->index, - current->monitorMode.monitorDef.is_primary); + current->config.is_primary); fprintf(fp," x:%d, y:%d, RDP client x:%d, y:%d\n", current->base.output->x, current->base.output->y, - current->monitorMode.monitorDef.x, current->monitorMode.monitorDef.y); + current->config.x, current->config.y); fprintf(fp," width:%d, height:%d, RDP client width:%d, height: %d\n", current->base.output->width, current->base.output->height, - current->monitorMode.monitorDef.width, current->monitorMode.monitorDef.height); + current->config.width, current->config.height); fprintf(fp," physicalWidth:%dmm, physicalHeight:%dmm, orientation:%d\n", - current->monitorMode.monitorDef.attributes.physicalWidth, - current->monitorMode.monitorDef.attributes.physicalHeight, - current->monitorMode.monitorDef.attributes.orientation); + current->config.attributes.physicalWidth, + current->config.attributes.physicalHeight, + current->config.attributes.orientation); fprintf(fp," desktopScaleFactor:%d, deviceScaleFactor:%d\n", - current->monitorMode.monitorDef.attributes.desktopScaleFactor, - current->monitorMode.monitorDef.attributes.deviceScaleFactor); + current->config.attributes.desktopScaleFactor, + current->config.attributes.deviceScaleFactor); fprintf(fp," scale:%d, client scale :%3.2f\n", scale, client_scale); fprintf(fp," workarea: x:%d, y:%d, width:%d, height:%d\n", @@ -4276,7 +4276,7 @@ rdp_rail_get_primary_output(void *rdp_backend) wl_list_for_each(current, &b->compositor->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(current); - if (head->monitorMode.monitorDef.is_primary) + if (head->config.is_primary) return current->output; } return NULL; From eef567a357a162899d9d2b1f4fb97f4eae8463ea Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 21 Jul 2022 11:15:01 -0500 Subject: [PATCH 1581/1642] rdp: Move some head setup to rdp_head_create We know these at creation time, so just set them immediately. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 7fbef1d38..443c76d08 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -504,21 +504,12 @@ rdp_output_set_size(struct weston_output *base, wl_list_for_each(head, &output->base.head_list, output_link) { struct rdp_head *h = to_rdp_head(head); - weston_head_set_monitor_strings(head, "weston", "rdp", NULL); - rdp_debug(rdpBackend, "set_size: attached head [%d]: make:%s, mode:%s, name:%s, (%p)\n", h->index, head->make, head->model, head->name, head); rdp_debug(rdpBackend, "set_size: attached head [%d]: x:%d, y:%d, width:%d, height:%d\n", h->index, h->config.x, h->config.y, h->config.width, h->config.height); - /* This is a virtual output, so report a zero physical size. - * It's better to let frontends/clients use their defaults. */ - /* If MonitorDef has it, use it from MonitorDef */ - weston_head_set_physical_size(head, - h->config.attributes.physicalWidth, - h->config.attributes.physicalHeight); - /* In HiDef RAIL mode, set this mode as preferred mode */ if (client && client->context->settings->HiDefRemoteApp) { break; // only one head per output in HiDef. @@ -697,6 +688,12 @@ rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, rdpMonitor sprintf(name, "rdp-%x", head->index); weston_head_init(&head->base, name); + weston_head_set_monitor_strings(&head->base, "weston", "rdp", NULL); + if (config) + weston_head_set_physical_size(&head->base, + config->attributes.physicalWidth, + config->attributes.physicalHeight); + weston_head_set_connection_status(&head->base, true); weston_compositor_add_head(compositor, &head->base); From 65fd398716134b33d69c374a968ed2df67bb0215 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 21 Jul 2022 10:35:20 -0500 Subject: [PATCH 1582/1642] rdp: Store dpi settings in the front end We'll need these for output setup Signed-off-by: Derek Foreman --- compositor/main.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/compositor/main.c b/compositor/main.c index 4c4d7964c..805cde3e4 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -115,6 +115,13 @@ struct wet_layoutput { struct wet_head_array add; /**< tmp: heads to add as clones */ }; +struct wet_rdp_params { + bool enable_hi_dpi_support; + bool enable_fractional_hi_dpi_support; + bool enable_fractional_hi_dpi_roundup; + int debug_desktop_scaling_factor; +}; + struct wet_compositor { struct weston_compositor *compositor; struct weston_config *config; @@ -124,6 +131,7 @@ struct wet_compositor { int (*simple_output_configure)(struct weston_output *output); bool init_failed; struct wl_list layoutput_list; /**< wet_layoutput::compositor_link */ + struct wet_rdp_params rdp_params; }; static FILE *weston_logfile = NULL; @@ -2776,6 +2784,7 @@ load_rdp_backend(struct weston_compositor *c, int ret = 0; struct wet_output_config *parsed_options = wet_init_parsed_options(c); bool audio_tmp; + struct wet_compositor *wet = to_wet_compositor(c); if (!parsed_options) return -1; @@ -2847,6 +2856,11 @@ load_rdp_backend(struct weston_compositor *c, config.rail_config.debug_desktop_scaling_factor = 0; } + wet->rdp_params.enable_hi_dpi_support = config.rail_config.enable_hi_dpi_support; + wet->rdp_params.enable_fractional_hi_dpi_support = config.rail_config.enable_fractional_hi_dpi_support; + wet->rdp_params.enable_fractional_hi_dpi_roundup = config.rail_config.enable_fractional_hi_dpi_roundup; + wet->rdp_params.debug_desktop_scaling_factor = config.rail_config.debug_desktop_scaling_factor; + config.rail_config.enable_window_zorder_sync = read_rdp_config_bool("WESTON_RDP_WINDOW_ZORDER_SYNC", true); config.rail_config.enable_window_snap_arrange = read_rdp_config_bool("WESTON_RDP_WINDOW_SNAP_ARRANGE", false); config.rail_config.enable_window_shadow_remoting = read_rdp_config_bool("WESTON_RDP_WINDOW_SHADOW_REMOTING", true); From d48260687a5456215c3d5979af2f575935b5e607 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 21 Jul 2022 10:37:28 -0500 Subject: [PATCH 1583/1642] rdp: Make backend-rdp require freerdp We'll need some freerdp headers for monitor configuration, so include freerdp headers from backend-rdp.h and fix up the build. Signed-off-by: Derek Foreman --- include/libweston/backend-rdp.h | 2 ++ rdprail-shell/meson.build | 1 + 2 files changed, 3 insertions(+) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index d1d02d60b..d07ecefbd 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -33,6 +33,8 @@ extern "C" { #include #include +#include + #define WESTON_RDP_MODE_FREQ 60 // Hz #define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v1" diff --git a/rdprail-shell/meson.build b/rdprail-shell/meson.build index 004ed361e..a44efdf75 100644 --- a/rdprail-shell/meson.build +++ b/rdprail-shell/meson.build @@ -35,6 +35,7 @@ if get_option('shell-rdprail') dep_librsvg, dep_winpr, dep_glib, + dep_frdp_server, ] plugin_shell_rdprail = shared_library( 'rdprail-shell', From 0f1f713b0aeee55ed0231115f5e70d7f1ce95ff1 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 21 Jul 2022 10:40:30 -0500 Subject: [PATCH 1584/1642] rdp: Make to_rdp_head's parameter const It'll never change it, and some future callers care about that. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 9547af831..e7cfbe0c5 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -409,7 +409,7 @@ int rdp_clipboard_init(freerdp_peer* client); void rdp_clipboard_destroy(RdpPeerContext *peerCtx); static inline struct rdp_head * -to_rdp_head(struct weston_head *base) +to_rdp_head(const struct weston_head *base) { return container_of(base, struct rdp_head, base); } From 7ea3d6309f75de56d6aef733c843a40060c4df34 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 21 Jul 2022 10:41:49 -0500 Subject: [PATCH 1585/1642] rdp: Divide head and output creation between back and front ends Makes all the validation code part of the front end, and leaves the basic creation in the backend. Signed-off-by: Derek Foreman --- compositor/main.c | 98 ++++---- compositor/meson.build | 1 + compositor/rdpdisp.c | 425 ++++++++++++++++++++++++++++++++ compositor/rdpdisp.h | 41 +++ compositor/weston.h | 3 + include/libweston/backend-rdp.h | 12 +- libweston/backend-rdp/rdp.c | 188 +++++--------- libweston/backend-rdp/rdpdisp.c | 343 -------------------------- 8 files changed, 571 insertions(+), 540 deletions(-) create mode 100644 compositor/rdpdisp.c create mode 100644 compositor/rdpdisp.h diff --git a/compositor/main.c b/compositor/main.c index 805cde3e4..a858261bf 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -67,6 +67,7 @@ #include "../pipewire/pipewire-plugin.h" #include +#include #define WINDOW_TITLE "Weston Compositor" /* flight recorder size (in bytes) */ @@ -115,13 +116,6 @@ struct wet_layoutput { struct wet_head_array add; /**< tmp: heads to add as clones */ }; -struct wet_rdp_params { - bool enable_hi_dpi_support; - bool enable_fractional_hi_dpi_support; - bool enable_fractional_hi_dpi_roundup; - int debug_desktop_scaling_factor; -}; - struct wet_compositor { struct weston_compositor *compositor; struct weston_config *config; @@ -2660,53 +2654,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); - struct weston_config_section *section = NULL; - int width = 640; - int height = 480; - int scale = 1; - - assert(parsed_options); - - if (!api) { - weston_log("Cannot use weston_rdp_output_api.\n"); - return -1; - } - - /* obtain output configuration from backend */ - if (api->output_get_config(output, &width, &height, &scale) < 0) { - weston_log("Cannot get output configuration from backend \"%s\" using weston_rdp_output_api.\n", - output->name); - return -1; - } - - if (parsed_options->width) - width = parsed_options->width; - - if (parsed_options->height) - height = parsed_options->height; - - wet_output_set_scale(output, section, scale, parsed_options->scale); - if (wet_output_set_transform(output, section, - WL_OUTPUT_TRANSFORM_NORMAL, - UINT32_MAX) < 0) { - return -1; - } - - 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) { @@ -2776,6 +2723,42 @@ read_rdp_config_int(char *config_name, int default_value) return default_value; } +struct wet_rdp_params * +wet_get_rdp_params(struct weston_compositor *ec) +{ + struct wet_compositor *wet = to_wet_compositor(ec); + + return &wet->rdp_params; +} + +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) { + struct weston_output *out; + + out = weston_compositor_create_output_with_head(head->compositor, + head); + wet_head_tracker_create(wet, head); + weston_output_attach_head(out, head); + } + } + + disp_monitor_validate_and_compute_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) @@ -2860,7 +2843,8 @@ load_rdp_backend(struct weston_compositor *c, wet->rdp_params.enable_fractional_hi_dpi_support = config.rail_config.enable_fractional_hi_dpi_support; wet->rdp_params.enable_fractional_hi_dpi_roundup = config.rail_config.enable_fractional_hi_dpi_roundup; wet->rdp_params.debug_desktop_scaling_factor = config.rail_config.debug_desktop_scaling_factor; - + wet->rdp_params.default_width = parsed_options->width; + wet->rdp_params.default_height = parsed_options->height; config.rail_config.enable_window_zorder_sync = read_rdp_config_bool("WESTON_RDP_WINDOW_ZORDER_SYNC", true); config.rail_config.enable_window_snap_arrange = read_rdp_config_bool("WESTON_RDP_WINDOW_SNAP_ARRANGE", false); config.rail_config.enable_window_shadow_remoting = read_rdp_config_bool("WESTON_RDP_WINDOW_SHADOW_REMOTING", true); @@ -2875,7 +2859,9 @@ load_rdp_backend(struct weston_compositor *c, config.rail_config.enable_copy_warning_title = read_rdp_config_bool("WESTON_RDP_COPY_WARNING_TITLE", true); #endif - wet_set_simple_head_configurator(c, rdp_backend_output_configure); + 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/compositor/meson.build b/compositor/meson.build index 70baffae2..21b6b57f7 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -3,6 +3,7 @@ srcs_weston = [ 'main.c', 'rdpaudio.c', 'rdpaudioin.c', + 'rdpdisp.c', 'testsuite-util.c', 'text-backend.c', 'weston-screenshooter.c', diff --git a/compositor/rdpdisp.c b/compositor/rdpdisp.c new file mode 100644 index 000000000..b60afe034 --- /dev/null +++ b/compositor/rdpdisp.c @@ -0,0 +1,425 @@ +/* + * 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 "weston.h" +#include "rdpdisp.h" +#include + + +static bool +is_line_intersected(int l1, int l2, int r1, int r2) +{ + int l = l1 > r1 ? l1 : r1; + int r = l2 < r2 ? l2 : r2; + return (l < r); +} + +static int +compare_monitors_x(const void *p1, const void *p2) +{ + const struct weston_rdp_output_api *api; + const struct weston_head *whl = *(const struct weston_head **)p1; + const struct weston_head *whr = *(const struct weston_head **)p2; + rdpMonitor *l, *r; + + api = weston_rdp_output_get_api(whr->compositor); + l = api->head_get_rdpmonitor(whl); + r = api->head_get_rdpmonitor(whr); + + return l->x > r->x; +} + +static int +compare_monitors_y(const void *p1, const void *p2) +{ + const struct weston_rdp_output_api *api; + const struct weston_head *whl = *(const struct weston_head **)p1; + const struct weston_head *whr = *(const struct weston_head **)p2; + rdpMonitor *l, *r; + + api = weston_rdp_output_get_api(whr->compositor); + l = api->head_get_rdpmonitor(whl); + r = api->head_get_rdpmonitor(whr); + + return l->y > r->y; +} + +static float +disp_get_client_scale_from_monitor(struct weston_compositor *ec, const rdpMonitor *config) +{ + struct wet_rdp_params *rdp_params = wet_get_rdp_params(ec); + + if (config->attributes.desktopScaleFactor == 0.0) + return 1.0f; + + if (rdp_params->enable_hi_dpi_support) { + if (rdp_params->debug_desktop_scaling_factor) + return (float)rdp_params->debug_desktop_scaling_factor / 100.f; + else if (rdp_params->enable_fractional_hi_dpi_support) + return (float)config->attributes.desktopScaleFactor / 100.0f; + else if (rdp_params->enable_fractional_hi_dpi_roundup) + return (float)(int)((config->attributes.desktopScaleFactor + 50) / 100); + else + return (float)(int)(config->attributes.desktopScaleFactor / 100); + } else { + return 1.0f; + } +} + +static int +disp_get_output_scale_from_monitor(struct weston_compositor *ec, const rdpMonitor *config) +{ + return (int) disp_get_client_scale_from_monitor(ec, config); +} + +static rdpMonitor * +get_first_head_config(struct weston_compositor *ec) +{ + const struct weston_rdp_output_api *api = weston_rdp_output_get_api(ec); + struct weston_head *head; + + wl_list_for_each(head, &ec->head_list, compositor_link) + return api->head_get_rdpmonitor(head); + + return NULL; +} + +static void +sort_head_list(struct weston_compositor *ec, int (*compar)(const void *, const void *)) +{ + int count = wl_list_length(&ec->head_list); + struct weston_head *head_array[count]; + struct weston_head *iter, *tmp; + int i = 0; + + wl_list_for_each_safe(iter, tmp, &ec->head_list, compositor_link) { + head_array[i++] = iter; + wl_list_remove(&iter->compositor_link); + } + + qsort(head_array, count, sizeof(struct weston_head *), compar); + + wl_list_init(&ec->head_list); + for (i = 0; i < count; i++) + wl_list_insert(ec->head_list.prev, &head_array[i]->compositor_link); +} + +void +disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) +{ + const struct weston_rdp_output_api *api = weston_rdp_output_get_api(ec); + struct weston_head *iter; + bool isConnected_H = false; + bool isConnected_V = false; + bool isScalingUsed = false; + bool isScalingSupported = true; + int upperLeftX = 0; + int upperLeftY = 0; + int i; + int count = wl_list_length(&ec->head_list); + pixman_rectangle32_t rectWeston[count]; + + wl_list_for_each(iter, &ec->head_list, compositor_link) { + rdpMonitor *head = api->head_get_rdpmonitor(iter); + float client_scale = disp_get_client_scale_from_monitor(ec, head); + + /* check if any monitor has scaling enabled */ + if (client_scale != 1.0f) + isScalingUsed = true; + + /* find upper-left corner of combined monitors in client space */ + if (upperLeftX > head->x) + upperLeftX = head->x; + if (upperLeftY > head->y) + upperLeftY = head->y; + } + assert(upperLeftX <= 0); + assert(upperLeftY <= 0); + weston_log("Client desktop upper left coordinate (%d,%d)\n", upperLeftX, upperLeftY); + + count = wl_list_length(&ec->head_list); + + if (count > 1) { + rdpMonitor *head, *last; + int32_t offsetFromOriginClient; + + /* first, sort monitors horizontally */ + sort_head_list(ec, compare_monitors_x); + head = get_first_head_config(ec); + last = head; + assert(upperLeftX == head->x); + + /* check if monitors are horizontally connected each other */ + offsetFromOriginClient = head->x + head->width; + i = 0; + wl_list_for_each(iter, &ec->head_list, compositor_link) { + rdpMonitor *cur = api->head_get_rdpmonitor(iter); + + i++; + if (i == 1) + continue; + + if (offsetFromOriginClient != cur->x) { + weston_log("\tRDP client reported monitors not horizontally connected each other at %d (x check)\n", i); + break; + } + offsetFromOriginClient += cur->width; + + if (!is_line_intersected(last->y, + last->y + last->height, + cur->y, + cur->y + cur->height)) { + weston_log("\tRDP client reported monitors not horizontally connected each other at %d (y check)\n\n", i); + break; + } + last = cur; + } + if (i == count) { + weston_log("\tAll monitors are horizontally placed\n"); + isConnected_H = true; + } else { + rdpMonitor *head, *last; + /* next, trying sort monitors vertically */ + sort_head_list(ec, compare_monitors_y); + head = get_first_head_config(ec); + last = head; + assert(upperLeftY == head->y); + + /* make sure monitors are horizontally connected each other */ + offsetFromOriginClient = head->y + head->height; + i = 0; + wl_list_for_each(iter, &ec->head_list, compositor_link) { + rdpMonitor *cur = api->head_get_rdpmonitor(iter); + + i++; + if (i == 1) + continue; + + if (offsetFromOriginClient != cur->y) { + weston_log("\tRDP client reported monitors not vertically connected each other at %d (y check)\n", i); + break; + } + offsetFromOriginClient += cur->height; + + if (!is_line_intersected(last->x, + last->x + last->width, + cur->x, + cur->x + cur->width)) { + weston_log("\tRDP client reported monitors not horizontally connected each other at %d (x check)\n\n", i); + break; + } + last = cur; + } + + if (i == count) { + weston_log("\tAll monitors are vertically placed\n"); + isConnected_V = true; + } + } + } else { + isConnected_H = true; + } + + if (isScalingUsed && (!isConnected_H && !isConnected_V)) { + /* scaling can't be supported in complex monitor placement */ + weston_log("\nWARNING\nWARNING\nWARNING: Scaling is used, but can't be supported in complex monitor placement\nWARNING\nWARNING\n"); + isScalingSupported = false; + } + + if (isScalingUsed && isScalingSupported) { + uint32_t offsetFromOriginWeston = 0; + i = 0; + + wl_list_for_each(iter, &ec->head_list, compositor_link) { + rdpMonitor *head = api->head_get_rdpmonitor(iter); + int scale = disp_get_output_scale_from_monitor(ec, head); + + rectWeston[i].width = head->width / scale; + rectWeston[i].height = head->height / scale; + if (isConnected_H) { + assert(isConnected_V == false); + rectWeston[i].x = offsetFromOriginWeston; + rectWeston[i].y = abs((upperLeftY - head->y) / scale); + offsetFromOriginWeston += rectWeston[i].width; + } else { + assert(isConnected_V == true); + rectWeston[i].x = abs((upperLeftX - head->x) / scale); + rectWeston[i].y = offsetFromOriginWeston; + offsetFromOriginWeston += rectWeston[i].height; + } + assert(rectWeston[i].x >= 0); + assert(rectWeston[i].y >= 0); + i++; + } + } else { + i = 0; + + /* no scaling is used or monitor placement is too complex to scale in weston space, fallback to 1.0f */ + wl_list_for_each(iter, &ec->head_list, compositor_link) { + rdpMonitor *head = api->head_get_rdpmonitor(iter); + + rectWeston[i].width = head->width; + rectWeston[i].height = head->height; + rectWeston[i].x = head->x + abs(upperLeftX); + rectWeston[i].y = head->y + abs(upperLeftY); + head->attributes.desktopScaleFactor = 0.0; + assert(rectWeston[i].x >= 0); + assert(rectWeston[i].y >= 0); + i++; + } + } + + weston_log("%s:---OUTPUT---\n", __func__); + i = 0; + wl_list_for_each(iter, &ec->head_list, compositor_link) { + rdpMonitor *head = api->head_get_rdpmonitor(iter); + float client_scale = disp_get_client_scale_from_monitor(ec, head); + int scale = disp_get_output_scale_from_monitor(ec, head); + + weston_log(" rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", + i, head->x, head->y, + head->width, head->height, + head->is_primary); + weston_log(" rdpMonitor[%d]: weston x:%d, y:%d, width:%d, height:%d\n", + i, rectWeston[i].x, rectWeston[i].y, + rectWeston[i].width, rectWeston[i].height); + weston_log(" rdpMonitor[%d]: physicalWidth:%d, physicalHeight:%d, orientation:%d\n", + i, head->attributes.physicalWidth, + head->attributes.physicalHeight, + head->attributes.orientation); + weston_log(" rdpMonitor[%d]: desktopScaleFactor:%d, deviceScaleFactor:%d\n", + i, head->attributes.desktopScaleFactor, + head->attributes.deviceScaleFactor); + weston_log(" rdpMonitor[%d]: scale:%d, clientScale:%3.2f\n", + i, scale, client_scale); + i++; + } + + i = 0; + wl_list_for_each(iter, &ec->head_list, compositor_link) { + rdpMonitor *current = api->head_get_rdpmonitor(iter); + struct weston_output *output = iter->output; + float client_scale = disp_get_client_scale_from_monitor(ec, current); + int scale = disp_get_output_scale_from_monitor(ec, current); + struct wet_rdp_params *rdp_params = wet_get_rdp_params(ec); + int force_width = rdp_params->default_width; + int force_height = rdp_params->default_height; + struct weston_mode new_mode = {}; + + assert(output); + + if (!output->enabled) { + int width = force_width; + int height = force_height; + + width = width ? width : current->width; + height = height ? height : current->height; + + /* At startup the backend creates a 0,0 request + * If this wasn't overridden by config, just + * set it to 640 x 480. + */ + width = width ? width : 640; + height = height ? height : 480; + + new_mode.width = width; + new_mode.height = height; + api->output_set_mode(iter->output, &new_mode); + } else if (force_width && force_height) { + /* If we had command line parameters, we want to forcibly + * set any outputs changed by the backend matching code + * back to the forced settings. + */ + new_mode.width = force_width; + new_mode.height = force_height; + api->output_set_mode(iter->output, &new_mode); + } + weston_log("Head mode change:%s NEW width:%d, height:%d, scale:%d, clientScale:%f\n", + output->name, current->width, + current->height, + scale, + client_scale); + if (output->scale != scale) { + bool was_enabled = false; + + if (output->enabled) { + weston_output_disable(output); + was_enabled = true; + } + output->scale = 0; /* reset scale first, otherwise assert */ + weston_output_set_scale(output, scale); + if (was_enabled) + weston_output_enable(output); + } + + /* Notify clients for updated resolution/scale. */ + weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); + i++; + } + + /* move output to final location */ + i = 0; + wl_list_for_each(iter, &ec->head_list, compositor_link) { + assert(iter->output); + + weston_log("move head/output %s (%d,%d) -> (%d,%d)\n", + iter->name, + iter->output->x, + iter->output->y, + rectWeston[i].x, + rectWeston[i].y); + /* Notify clients for updated output position. */ + weston_output_move(iter->output, + rectWeston[i].x, + rectWeston[i].y); + i++; + } + + /* make sure head list is not empty */ + assert(!wl_list_empty(&ec->head_list)); + + BOOL is_primary_found = FALSE; + i = 0; + wl_list_for_each(iter, &ec->head_list, compositor_link) { + rdpMonitor *current = api->head_get_rdpmonitor(iter); + + if (current->is_primary) { + weston_log("client origin (0,0) is (%d,%d) in Weston space\n", + rectWeston[i].x, + rectWeston[i].y); + /* primary must be at (0,0) in client space */ + assert(current->x == 0); + assert(current->y == 0); + /* there must be only one primary */ + assert(is_primary_found == FALSE); + is_primary_found = TRUE; + } + i++; + } +} diff --git a/compositor/rdpdisp.h b/compositor/rdpdisp.h new file mode 100644 index 000000000..6e93fc2fc --- /dev/null +++ b/compositor/rdpdisp.h @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#ifndef RDP_DISP_H +#define RDP_DISP_H + +struct wet_rdp_params { + bool enable_hi_dpi_support; + bool enable_fractional_hi_dpi_support; + bool enable_fractional_hi_dpi_roundup; + int debug_desktop_scaling_factor; + int default_width; + int default_height; +}; + +void +disp_monitor_validate_and_compute_layout(struct weston_compositor *ec); + +#endif diff --git a/compositor/weston.h b/compositor/weston.h index e09397f9b..b38c99465 100644 --- a/compositor/weston.h +++ b/compositor/weston.h @@ -61,6 +61,9 @@ weston_watch_process(struct weston_process *process); struct weston_config * wet_get_config(struct weston_compositor *compositor); +struct wet_rdp_params * +wet_get_rdp_params(struct weston_compositor *); + void * wet_load_module_entrypoint(const char *name, const char *entrypoint); diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index d07ecefbd..90960a9e5 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -40,16 +40,12 @@ extern "C" { #define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v1" struct weston_rdp_output_api { - /** Initialize a RDP output with specified width and height. - * - * Returns 0 on success, -1 on failure. - */ - int (*output_set_size)(struct weston_output *output, - int width, int height); /** Get config from RDP client when connected */ - int (*output_get_config)(struct weston_output *output, - int *width, int *height, int *scale); + rdpMonitor *(*head_get_rdpmonitor)(const struct weston_head *head); + + /** 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/rdp.c b/libweston/backend-rdp/rdp.c index 443c76d08..8f7c8c90a 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -382,163 +382,81 @@ ensure_matching_mode(struct weston_output *output, struct weston_mode *target) return rdp_insert_new_mode(output, target->width, target->height, b->rdp_monitor_refresh_rate); } -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_peers_item *rdpPeer; - rdpSettings *settings; pixman_image_t *new_shadow_buffer; - struct weston_mode *local_mode, *previous_mode; + 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; const struct pixman_renderer_output_options options = { .use_shadow = true, }; - bool HiDefRemoteApp = false; - - if (rdpBackend->rdp_peer && rdpBackend->rdp_peer->context->settings->HiDefRemoteApp) - HiDefRemoteApp = true; - - local_mode = ensure_matching_mode(output, target_mode); - if (!local_mode) { - rdp_debug_error(rdpBackend, "mode %dx%d not available\n", target_mode->width, target_mode->height); - return -ENOENT; - } - - if (local_mode == output->current_mode) - return 0; + struct rdp_peers_item *rdpPeer; + rdpSettings *settings; - if (HiDefRemoteApp) - previous_mode = output->current_mode; - else - output->current_mode->flags &= ~WL_OUTPUT_MODE_CURRENT; + mode->refresh = b->rdp_monitor_refresh_rate; + cur = ensure_matching_mode(base, mode); + cur->flags |= WL_OUTPUT_MODE_CURRENT; + cur->flags |= WL_OUTPUT_MODE_PREFERRED; - output->current_mode = local_mode; - output->current_mode->flags |= WL_OUTPUT_MODE_CURRENT; + if (base->current_mode) + base->current_mode->flags = 0; - if (HiDefRemoteApp) { - /* Mark current mode as preferred mode */ - output->current_mode->flags |= WL_OUTPUT_MODE_PREFERRED; + base->current_mode = cur; + base->native_mode = cur; - /* In HiDefRemoteApp mode, free previous current_mode, - since it only want to expose current mode to app */ - wl_list_remove(&previous_mode->link); - free(previous_mode); - } + if (b->rdp_peer && b->rdp_peer->context->settings->HiDefRemoteApp) + return; - if (!HiDefRemoteApp) { + if (base->enabled) { pixman_renderer_output_destroy(output); pixman_renderer_output_create(output, &options); - new_shadow_buffer = pixman_image_create_bits(PIXMAN_x8r8g8b8, target_mode->width, - target_mode->height, 0, target_mode->width * 4); + 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, target_mode->width, target_mode->height); + 0, 0, 0, 0, 0, 0, mode->width, mode->height); pixman_image_unref(rdpOutput->shadow_surface); rdpOutput->shadow_surface = new_shadow_buffer; - - wl_list_for_each(rdpPeer, &rdpBackend->peers, link) { - settings = rdpPeer->peer->context->settings; - if (settings->DesktopWidth == (UINT32)target_mode->width && - settings->DesktopHeight == (UINT32)target_mode->height) - continue; - - if (!settings->DesktopResize) { - /* too bad this peer does not support desktop resize */ - rdp_debug_error(rdpBackend, "%s: desktop resize is not allowed\n", __func__); - rdpPeer->peer->Close(rdpPeer->peer); - } else { - settings->DesktopWidth = target_mode->width; - settings->DesktopHeight = target_mode->height; - rdpPeer->peer->context->update->DesktopResize(rdpPeer->peer->context); - } - } } - return 0; -} -static int -rdp_output_get_config(struct weston_output *base, - int *width, int *height, int *scale) -{ - struct rdp_output *output = to_rdp_output(base); - struct rdp_backend *rdpBackend = to_rdp_backend(base->compositor); - freerdp_peer *client = rdpBackend->rdp_peer; - struct weston_head *head; - - wl_list_for_each(head, &output->base.head_list, output_link) { - struct rdp_head *h = to_rdp_head(head); - - rdp_debug(rdpBackend, "get_config: attached head [%d]: make:%s, mode:%s, name:%s, (%p)\n", - h->index, head->make, head->model, head->name, head); - rdp_debug(rdpBackend, "get_config: attached head [%d]: x:%d, y:%d, width:%d, height:%d\n", - h->index, h->config.x, h->config.y, - h->config.width, h->config.height); - - /* In HiDef RAIL mode, get monitor resolution from RDP client if provided. */ - if (client && client->context->settings->HiDefRemoteApp) { - if (h->config.width && h->config.height) { - /* Return true client resolution (not adjusted by DPI) */ - *width = h->config.width; - *height = h->config.height; - *scale = disp_get_output_scale_from_monitor(rdpBackend, &h->config); - } - break; // only one head per output in HiDef. + /* Apparently settings->DesktopWidth is supposed to be primary only, + * but we don't hit this path for RAIL, and we don't have more than + * one head for non-RAIL. + */ + wl_list_for_each(rdpPeer, &b->peers, link) { + settings = rdpPeer->peer->context->settings; + if (settings->DesktopWidth == (UINT32)mode->width && + settings->DesktopHeight == (UINT32)mode->height) + continue; + + if (!settings->DesktopResize) { + /* too bad this peer does not support desktop resize */ + rdp_debug_error(b, "%s: desktop resize is not allowed\n", __func__); + rdpPeer->peer->Close(rdpPeer->peer); + } else { + 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); - freerdp_peer *client = rdpBackend->rdp_peer; - struct weston_head *head; - struct weston_mode *currentMode; - struct weston_mode initMode; - - /* We can only be called once. */ - assert(!output->base.current_mode); - - wl_list_for_each(head, &output->base.head_list, output_link) { - struct rdp_head *h = to_rdp_head(head); - - rdp_debug(rdpBackend, "set_size: attached head [%d]: make:%s, mode:%s, name:%s, (%p)\n", - h->index, head->make, head->model, head->name, head); - rdp_debug(rdpBackend, "set_size: attached head [%d]: x:%d, y:%d, width:%d, height:%d\n", - h->index, h->config.x, h->config.y, - h->config.width, h->config.height); - - /* In HiDef RAIL mode, set this mode as preferred mode */ - if (client && client->context->settings->HiDefRemoteApp) { - break; // only one head per output in HiDef. - } - } + rdp_output_set_mode(base, mode); - initMode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; - initMode.width = width; - initMode.height = height; - initMode.refresh = rdpBackend->rdp_monitor_refresh_rate; - currentMode = ensure_matching_mode(&output->base, &initMode); - if (!currentMode) - return -1; - - currentMode->flags |= WL_OUTPUT_MODE_CURRENT; - currentMode->flags |= WL_OUTPUT_MODE_PREFERRED; + return 0; +} - output->base.current_mode = currentMode; - output->base.native_mode = currentMode; - output->base.native_scale = base->scale; - output->base.start_repaint_loop = rdp_output_start_repaint_loop; - output->base.repaint = rdp_output_repaint; - output->base.assign_planes = NULL; - output->base.set_backlight = NULL; - output->base.set_dpms = NULL; - output->base.switch_mode = rdp_switch_mode; +static rdpMonitor * +rdp_head_get_rdpmonitor(const struct weston_head *base) +{ + struct rdp_head *h = to_rdp_head(base); - return 0; + return &h->config; } static int @@ -657,6 +575,10 @@ rdp_output_create(struct weston_compositor *compositor, const char *name) output->base.attach_head = rdp_output_attach_head; output->base.detach_head = rdp_output_detach_head; + output->base.start_repaint_loop = rdp_output_start_repaint_loop; + output->base.repaint = rdp_output_repaint; + output->base.switch_mode = rdp_output_switch_mode; + weston_compositor_add_pending_output(&output->base, compositor); return &output->base; @@ -2052,8 +1974,8 @@ rdp_generate_session_tls(struct rdp_backend *b) #endif static const struct weston_rdp_output_api api = { - rdp_output_set_size, - rdp_output_get_config, + rdp_head_get_rdpmonitor, + rdp_output_set_mode, }; static int create_vsock_fd(int port) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index af0018d8d..5c510a003 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -41,36 +41,6 @@ #include "shared/xalloc.h" -static BOOL -is_line_intersected(int l1, int l2, int r1, int r2) -{ - int l = l1 > r1 ? l1 : r1; - int r = l2 < r2 ? l2 : r2; - return (l < r); -} - -static int -compare_monitors_x(const void *p1, const void *p2) -{ - const struct weston_head *whl = *(const struct weston_head **)p1; - const struct weston_head *whr = *(const struct weston_head **)p2; - const struct rdp_head *l = to_rdp_head((void *)whl); - const struct rdp_head *r = to_rdp_head((void *)whr); - - return l->config.x > r->config.x; -} - -static int -compare_monitors_y(const void *p1, const void *p2) -{ - const struct weston_head *whl = *(const struct weston_head **)p1; - const struct weston_head *whr = *(const struct weston_head **)p2; - const struct rdp_head *l = to_rdp_head((void *)whl); - const struct rdp_head *r = to_rdp_head((void *)whr); - - return l->config.y > r->config.y; -} - float disp_get_client_scale_from_monitor(struct rdp_backend *b, const rdpMonitor *config) { @@ -97,17 +67,6 @@ disp_get_output_scale_from_monitor(struct rdp_backend *b, const rdpMonitor *conf return (int) disp_get_client_scale_from_monitor(b, config); } -static struct rdp_head * -get_first_head(struct weston_compositor *ec) -{ - struct weston_head *head; - - wl_list_for_each(head, &ec->head_list, compositor_link) - return to_rdp_head(head); - - return NULL; -} - static bool match_primary(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) { @@ -328,318 +287,16 @@ disp_monitor_sanity_check_layout(RdpPeerContext *peerCtx, rdpMonitor *config, ui return true; } -static void -sort_head_list(struct weston_compositor *ec, int (*compar)(const void *, const void *)) -{ - int count = wl_list_length(&ec->head_list); - struct weston_head *head_array[count]; - struct weston_head *iter, *tmp; - int i = 0; - - wl_list_for_each_safe(iter, tmp, &ec->head_list, compositor_link) { - head_array[i++] = iter; - wl_list_remove(&iter->compositor_link); - } - - qsort(head_array, count, sizeof(struct weston_head *), compar); - - wl_list_init(&ec->head_list); - for (i = 0; i < count; i++) - wl_list_insert(ec->head_list.prev, &head_array[i]->compositor_link); -} - -static void -disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) -{ - struct rdp_backend *b = to_rdp_backend(ec); - struct weston_head *iter; - bool isConnected_H = false; - bool isConnected_V = false; - bool isScalingUsed = false; - bool isScalingSupported = true; - int upperLeftX = 0; - int upperLeftY = 0; - int i; - int count = wl_list_length(&ec->head_list); - pixman_rectangle32_t rectWeston[count]; - - wl_list_for_each(iter, &ec->head_list, compositor_link) { - struct rdp_head *head = to_rdp_head(iter); - float client_scale = disp_get_client_scale_from_monitor(b, &head->config); - - /* check if any monitor has scaling enabled */ - if (client_scale != 1.0f) - isScalingUsed = true; - - /* find upper-left corner of combined monitors in client space */ - if (upperLeftX > head->config.x) - upperLeftX = head->config.x; - if (upperLeftY > head->config.y) - upperLeftY = head->config.y; - } - assert(upperLeftX <= 0); - assert(upperLeftY <= 0); - weston_log("Client desktop upper left coordinate (%d,%d)\n", upperLeftX, upperLeftY); - - count = wl_list_length(&ec->head_list); - - if (count > 1) { - struct rdp_head *head, *last; - int32_t offsetFromOriginClient; - - /* first, sort monitors horizontally */ - sort_head_list(ec, compare_monitors_x); - head = get_first_head(ec); - last = head; - assert(upperLeftX == head->config.x); - - /* check if monitors are horizontally connected each other */ - offsetFromOriginClient = head->config.x + head->config.width; - i = 0; - wl_list_for_each(iter, &ec->head_list, compositor_link) { - struct rdp_head *cur = to_rdp_head(iter); - - i++; - if (i == 1) - continue; - - if (offsetFromOriginClient != cur->config.x) { - weston_log("\tRDP client reported monitors not horizontally connected each other at %d (x check)\n", i); - break; - } - offsetFromOriginClient += cur->config.width; - - if (!is_line_intersected(last->config.y, - last->config.y + last->config.height, - cur->config.y, - cur->config.y + cur->config.height)) { - weston_log("\tRDP client reported monitors not horizontally connected each other at %d (y check)\n\n", i); - break; - } - last = cur; - } - if (i == count) { - weston_log("\tAll monitors are horizontally placed\n"); - isConnected_H = true; - } else { - struct rdp_head *head, *last; - /* next, trying sort monitors vertically */ - sort_head_list(ec, compare_monitors_y); - head = get_first_head(ec); - last = head; - assert(upperLeftY == head->config.y); - - /* make sure monitors are horizontally connected each other */ - offsetFromOriginClient = head->config.y + head->config.height; - i = 0; - wl_list_for_each(iter, &ec->head_list, compositor_link) { - struct rdp_head *cur = to_rdp_head(iter); - - i++; - if (i == 1) - continue; - - if (offsetFromOriginClient != cur->config.y) { - weston_log("\tRDP client reported monitors not vertically connected each other at %d (y check)\n", i); - break; - } - offsetFromOriginClient += cur->config.height; - - if (!is_line_intersected(last->config.x, - last->config.x + last->config.width, - cur->config.x, - cur->config.x + cur->config.width)) { - weston_log("\tRDP client reported monitors not horizontally connected each other at %d (x check)\n\n", i); - break; - } - last = cur; - } - - if (i == count) { - weston_log("\tAll monitors are vertically placed\n"); - isConnected_V = true; - } - } - } else { - isConnected_H = true; - } - - if (isScalingUsed && (!isConnected_H && !isConnected_V)) { - /* scaling can't be supported in complex monitor placement */ - weston_log("\nWARNING\nWARNING\nWARNING: Scaling is used, but can't be supported in complex monitor placement\nWARNING\nWARNING\n"); - isScalingSupported = false; - } - - if (isScalingUsed && isScalingSupported) { - uint32_t offsetFromOriginWeston = 0; - i = 0; - - wl_list_for_each(iter, &ec->head_list, compositor_link) { - struct rdp_head *head = to_rdp_head(iter); - int scale = disp_get_output_scale_from_monitor(b, &head->config); - - rectWeston[i].width = head->config.width / scale; - rectWeston[i].height = head->config.height / scale; - if (isConnected_H) { - assert(isConnected_V == false); - rectWeston[i].x = offsetFromOriginWeston; - rectWeston[i].y = abs((upperLeftY - head->config.y) / scale); - offsetFromOriginWeston += rectWeston[i].width; - } else { - assert(isConnected_V == true); - rectWeston[i].x = abs((upperLeftX - head->config.x) / scale); - rectWeston[i].y = offsetFromOriginWeston; - offsetFromOriginWeston += rectWeston[i].height; - } - assert(rectWeston[i].x >= 0); - assert(rectWeston[i].y >= 0); - i++; - } - } else { - i = 0; - - /* no scaling is used or monitor placement is too complex to scale in weston space, fallback to 1.0f */ - wl_list_for_each(iter, &ec->head_list, compositor_link) { - struct rdp_head *head = to_rdp_head(iter); - - rectWeston[i].width = head->config.width; - rectWeston[i].height = head->config.height; - rectWeston[i].x = head->config.x + abs(upperLeftX); - rectWeston[i].y = head->config.y + abs(upperLeftY); - head->config.attributes.desktopScaleFactor = 0.0; - assert(rectWeston[i].x >= 0); - assert(rectWeston[i].y >= 0); - i++; - } - } - - weston_log("%s:---OUTPUT---\n", __func__); - i = 0; - wl_list_for_each(iter, &ec->head_list, compositor_link) { - struct rdp_head *head = to_rdp_head(iter); - struct rdp_backend *b = to_rdp_backend(ec); - float client_scale = disp_get_client_scale_from_monitor(b, &head->config); - int scale = disp_get_output_scale_from_monitor(b, &head->config); - - weston_log(" rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", - i, head->config.x, head->config.y, - head->config.width, head->config.height, - head->config.is_primary); - weston_log(" rdpMonitor[%d]: weston x:%d, y:%d, width:%d, height:%d\n", - i, rectWeston[i].x, rectWeston[i].y, - rectWeston[i].width, rectWeston[i].height); - weston_log(" rdpMonitor[%d]: physicalWidth:%d, physicalHeight:%d, orientation:%d\n", - i, head->config.attributes.physicalWidth, - head->config.attributes.physicalHeight, - head->config.attributes.orientation); - weston_log(" rdpMonitor[%d]: desktopScaleFactor:%d, deviceScaleFactor:%d\n", - i, head->config.attributes.desktopScaleFactor, - head->config.attributes.deviceScaleFactor); - weston_log(" rdpMonitor[%d]: scale:%d, clientScale:%3.2f\n", - i, scale, client_scale); - i++; - } - - i = 0; - wl_list_for_each(iter, &ec->head_list, compositor_link) { - struct rdp_head *current = to_rdp_head(iter); - struct weston_output *output = iter->output; - - if (output) { - /* ask weston to adjust size */ - struct weston_mode new_mode = {}; - struct rdp_backend *b = to_rdp_backend(ec); - float client_scale = disp_get_client_scale_from_monitor(b, ¤t->config); - int scale = disp_get_output_scale_from_monitor(b, ¤t->config); - - new_mode.width = current->config.width; - new_mode.height = current->config.height; - weston_log("Head mode change:%s NEW width:%d, height:%d, scale:%d, clientScale:%f\n", - output->name, current->config.width, - current->config.height, - scale, - client_scale); - if (output->scale != scale) { - weston_output_disable(output); - output->scale = 0; /* reset scale first, otherwise assert */ - weston_output_set_scale(output, scale); - weston_output_enable(output); - } - weston_output_mode_set_native(iter->output, &new_mode, scale); - weston_head_set_physical_size(iter, - current->config.attributes.physicalWidth, - current->config.attributes.physicalHeight); - /* Notify clients for updated resolution/scale. */ - weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); - /* output size must match with monitor's rect in weston space */ - assert(output->width == (int32_t)rectWeston[i].width); - assert(output->height == (int32_t)rectWeston[i].height); - } else { - /* if head doesn't have output yet, mode is set at rdp_output_set_size */ - weston_log("output doesn't exist for head %s\n", iter->name); - } - i++; - } - - /* move output to final location */ - i = 0; - wl_list_for_each(iter, &ec->head_list, compositor_link) { - struct rdp_head *current = to_rdp_head(iter); - - if (current->base.output) { - weston_log("move head/output %s (%d,%d) -> (%d,%d)\n", - current->base.name, - current->base.output->x, - current->base.output->y, - rectWeston[i].x, - rectWeston[i].y); - /* Notify clients for updated output position. */ - weston_output_move(current->base.output, - rectWeston[i].x, - rectWeston[i].y); - } else { - /* newly created head doesn't have output yet */ - /* position will be set at rdp_output_enable */ - } - i++; - } - - /* make sure head list is not empty */ - assert(!wl_list_empty(&ec->head_list)); - - BOOL is_primary_found = FALSE; - i = 0; - wl_list_for_each(iter, &ec->head_list, compositor_link) { - struct rdp_head *current = to_rdp_head(iter); - - if (current->config.is_primary) { - weston_log("client origin (0,0) is (%d,%d) in Weston space\n", - rectWeston[i].x, - rectWeston[i].y); - /* primary must be at (0,0) in client space */ - assert(current->config.x == 0); - assert(current->config.y == 0); - /* there must be only one primary */ - assert(is_primary_found == FALSE); - is_primary_found = TRUE; - } - i++; - } -} - bool handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor *monitors) { RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; if (!disp_monitor_sanity_check_layout(peerCtx, monitors, monitor_count)) return true; disp_start_monitor_layout_change(client, monitors, monitor_count); - disp_monitor_validate_and_compute_layout(b->compositor); - return true; } From eebc955dccb71fbd454e14b7a7c42255e5985d9d Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 21 Jul 2022 12:49:38 -0500 Subject: [PATCH 1586/1642] rdp: Remove regionClient from head structure We don't need a pixman region for these simple rectangles. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 11 +++-------- libweston/backend-rdp/rdp.h | 1 - libweston/backend-rdp/rdpdisp.c | 24 ++++++++++++++++-------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 8f7c8c90a..15231437e 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -594,15 +594,11 @@ rdp_head_create(struct weston_compositor *compositor, BOOL isPrimary, rdpMonitor head = xzalloc(sizeof *head); head->index = b->head_index++; - if (config) { + if (config) head->config = *config; - pixman_region32_init_rect(&head->regionClient, - config->x, config->y, - config->width, config->height); - } else { + else head->config.attributes.desktopScaleFactor = 0.0; - pixman_region32_init(&head->regionClient); - } + if (isPrimary) rdp_debug(b, "Default head is being added\n"); @@ -626,7 +622,6 @@ void rdp_head_destroy(struct weston_compositor *compositor, struct rdp_head *head) { weston_head_release(&head->base); - pixman_region32_fini(&head->regionClient); free(head); } diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index e7cfbe0c5..4507e9205 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -181,7 +181,6 @@ struct rdp_head { bool matched; rdpMonitor config; /*TODO: these region/rectangles can be moved to rdp_output */ - pixman_region32_t regionClient; // in client coordnate. pixman_rectangle32_t workareaClient; // in client coordinate. pixman_rectangle32_t workarea; // in weston coordinate. }; diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 5c510a003..8ff3f4079 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -133,13 +133,6 @@ update_head(struct rdp_backend *rdp, struct rdp_head *head, rdpMonitor *config) weston_head_set_device_changed(&head->base); } head->config = *config; - /* update monitor region in client */ - pixman_region32_clear(&head->regionClient); - pixman_region32_init_rect(&head->regionClient, - config->x, - config->y, - config->width, - config->height); } static void @@ -309,6 +302,21 @@ to_weston_scale_only(RdpPeerContext *peer, struct weston_output *output, float s *y = (float)(*y) * scale; } +static bool +rdp_monitor_contains(rdpMonitor *monitor, int32_t x, int32_t y) +{ + if (x < monitor->x) + return FALSE; + if (y < monitor->y) + return FALSE; + if (x >= monitor->x + monitor->width) + return FALSE; + if (y >= monitor->y + monitor->height) + return FALSE; + + return TRUE; +} + /* 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, uint32_t *width, uint32_t *height) @@ -321,7 +329,7 @@ to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32 wl_list_for_each(head_iter, &b->compositor->head_list, compositor_link) { struct rdp_head *head = to_rdp_head(head_iter); - if (pixman_region32_contains_point(&head->regionClient, sx, sy, NULL)) { + if (rdp_monitor_contains(&head->config, sx, sy)) { struct weston_output *output = head->base.output; float client_scale = disp_get_client_scale_from_monitor(b, &head->config); float scale = 1.0f / client_scale; From 15041fbf6b9c4f81f3e170a2371b47ae6b91efbb Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 21 Jul 2022 13:32:55 -0500 Subject: [PATCH 1587/1642] rdp: Remove scale only functions They're currently both the same and perform a simple operation. For now just use multiplication. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdpdisp.c | 37 ++++++++++++--------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 8ff3f4079..4de0a2357 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -293,15 +293,6 @@ handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor return true; } -static inline void -to_weston_scale_only(RdpPeerContext *peer, struct weston_output *output, float scale, int *x, int *y) -{ - //rdp_matrix_transform_scale(&output->inverse_matrix, x, y); - /* TODO: built-in to matrix */ - *x = (float)(*x) * scale; - *y = (float)(*y) * scale; -} - static bool rdp_monitor_contains(rdpMonitor *monitor, int32_t x, int32_t y) { @@ -338,9 +329,12 @@ to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32 sx -= head->config.x; sy -= head->config.y; /* scale x/y to client output space. */ - to_weston_scale_only(peerContext, output, scale, &sx, &sy); - if (width && height) - to_weston_scale_only(peerContext, output, scale, width, height); + sx *= scale; + sy *= scale; + if (width && height) { + *width *= scale; + *height *= scale; + } /* translate x/y to offset from this output on weston space. */ sx += output->x; sy += output->y; @@ -355,15 +349,6 @@ to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y, uint32 return NULL; } -static inline void -to_client_scale_only(RdpPeerContext *peer, struct weston_output *output, float scale, int *x, int *y) -{ - //rdp_matrix_transform_scale(&output->matrix, x, y); - /* TODO: built-in to matrix */ - *x = (float)(*x) * scale; - *y = (float)(*y) * scale; -} - /* Input x/y in weston space, output x/y in client space */ void to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, int32_t *x, int32_t *y, uint32_t *width, uint32_t *height) @@ -381,9 +366,13 @@ to_client_coordinate(RdpPeerContext *peerContext, struct weston_output *output, sx -= output->x; sy -= output->y; /* scale x/y to client output space. */ - to_client_scale_only(peerContext, output, scale, &sx, &sy); - if (width && height) - to_client_scale_only(peerContext, output, scale, width, height); + sx *= scale; + sy *= scale; + + if (width && height) { + *width *= scale; + *height *= scale; + } /* translate x/y to offset from this output on client space. */ sx += head->config.x; sy += head->config.y; From 55d1f351affa3410a8ee99412c357f69a22c5d8f Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 22 Jul 2022 12:22:39 -0500 Subject: [PATCH 1588/1642] rdp: Move outputs in the same loop as scale and modeset We can do this all in a single loop. Signed-off-by: Derek Foreman --- compositor/rdpdisp.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/compositor/rdpdisp.c b/compositor/rdpdisp.c index b60afe034..ce5d7a963 100644 --- a/compositor/rdpdisp.c +++ b/compositor/rdpdisp.c @@ -380,14 +380,8 @@ disp_monitor_validate_and_compute_layout(struct weston_compositor *ec) /* Notify clients for updated resolution/scale. */ weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); - i++; - } - - /* move output to final location */ - i = 0; - wl_list_for_each(iter, &ec->head_list, compositor_link) { - assert(iter->output); + /* move output to final location */ weston_log("move head/output %s (%d,%d) -> (%d,%d)\n", iter->name, iter->output->x, From ed48643674e425c30226dd518dc46d4aece110c5 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 26 Jul 2022 16:17:17 -0500 Subject: [PATCH 1589/1642] libweston: Don't move outputs during enable This is pretty counter-intuitive, and should probably happen outside of the core in the front end while configuring the outputs. Signed-off-by: Derek Foreman --- compositor/main.c | 36 ++++++++++++++++++++++++++++++++++++ libweston/compositor.c | 17 +---------------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index a858261bf..c6d84c12e 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1410,6 +1410,38 @@ wet_head_tracker_create(struct wet_compositor *compositor, weston_head_add_destroy_listener(head, &track->head_destroy_listener); } +/* Place output exactly to the right of the most recently enabled output. + * + * Historically, we haven't given much thought to output placement, + * simply adding outputs in a horizontal line as they're enabled. This + * function simply sets an output's x coordinate to the right of the + * most recently enabled output, and its y to zero. + * + * If you're adding new calls to this function, you're also not giving + * much thought to output placement, so please consider carefully if + * it's really doing what you want. + * + * You especially don't want to use this for any code that won't + * immediately enable the passed output. + */ +static void +weston_output_lazy_align(struct weston_output *output) +{ + struct weston_compositor *c; + struct weston_output *peer; + int next_x = 0; + + /* Put this output to the right of the most recently enabled output */ + c = output->compositor; + if (!wl_list_empty(&c->output_list)) { + peer = container_of(c->output_list.prev, + struct weston_output, link); + next_x = peer->x + peer->width; + } + output->x = next_x; + output->y = 0; +} + static void simple_head_enable(struct wet_compositor *wet, struct weston_head *head) { @@ -1426,6 +1458,8 @@ simple_head_enable(struct wet_compositor *wet, struct weston_head *head) return; } + weston_output_lazy_align(output); + if (wet->simple_output_configure) ret = wet->simple_output_configure(output); if (ret < 0) { @@ -2046,6 +2080,8 @@ drm_try_enable(struct weston_output *output, { /* Try to enable, and detach heads one by one until it succeeds. */ while (!output->enabled) { + weston_output_lazy_align(output); + if (weston_output_enable(output) == 0) return 0; diff --git a/libweston/compositor.c b/libweston/compositor.c index 6b0c23865..901cbbf2d 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6405,11 +6405,8 @@ weston_output_create_heads_string(struct weston_output *output) WL_EXPORT int weston_output_enable(struct weston_output *output) { - struct weston_compositor *c = output->compositor; - struct weston_output *iterator; struct weston_head *head; char *head_names; - int x = 0, y = 0; if (output->enabled) { weston_log("Error: attempt to enable an enabled output '%s'\n", @@ -6434,24 +6431,12 @@ weston_output_enable(struct weston_output *output) assert(head->model); } - iterator = container_of(c->output_list.prev, - struct weston_output, link); - - /* TODO: no need for auto arrange position for HiRAIL with RDP-backend, */ - /* it's better here does query position from backend and init */ - /* geomerty with it, so no need to re-arrage and re-init with */ - /* the position specified by backend */ - if (!wl_list_empty(&c->output_list)) - x = iterator->x + iterator->width; - /* Make sure the scale is set up */ assert(output->scale); /* Make sure we have a transform set */ assert(output->transform != UINT32_MAX); - output->x = x; - output->y = y; output->dirty = 1; output->original_scale = output->scale; @@ -6461,7 +6446,7 @@ weston_output_enable(struct weston_output *output) weston_output_transform_scale_init(output, output->transform, output->scale); weston_output_init_zoom(output); - weston_output_init_geometry(output, x, y); + weston_output_init_geometry(output, output->x, output->y); weston_output_damage(output); wl_list_init(&output->animation_list); From c410d830ae553e3a92897b909553512bcacf192b Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 27 Jul 2022 10:34:37 -0500 Subject: [PATCH 1590/1642] libweston: Check output placement Make sure we don't enable an output that overlaps with other enabled outputs. We should probably do something similar when moving outputs, but we can't realistically do that right now, so at least leave a comment explaining why we're ignoring that case. Signed-off-by: Derek Foreman --- libweston/compositor.c | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index 901cbbf2d..a1390a5d4 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -6026,6 +6026,18 @@ weston_output_set_position(struct weston_output *output, int x, int y) WL_EXPORT void weston_output_move(struct weston_output *output, int x, int y) { + /* XXX: we should probably perform some sanity checking here + * as we do for weston_output_enable, and allow moves to fail. + * + * However, while a front-end is rearranging outputs it may + * pass through indeterminate states where outputs overlap + * or are discontinuous, and this may be ok as long as no + * input processing or rendering occurs at that time. + * + * Ultimately, we probably need a way to pass complete output + * config atomically to libweston. + */ + output->compositor->output_flow_dirty = true; weston_output_set_position(output, x, y); } @@ -6367,6 +6379,45 @@ weston_output_create_heads_string(struct weston_output *output) return str; } +static bool +weston_outputs_overlap(struct weston_output *a, struct weston_output *b) +{ + bool overlap; + pixman_region32_t intersection; + + pixman_region32_init(&intersection); + pixman_region32_intersect(&intersection, &a->region, &b->region); + overlap = pixman_region32_not_empty(&intersection); + pixman_region32_fini(&intersection); + + return overlap; +} + +/* This only works if the output region is current! + * + * That means we shouldn't expect it to return usable results unless + * the output is at least undergoing enabling. + */ +static bool +weston_output_placement_ok(struct weston_output *output) +{ + struct weston_compositor *c = output->compositor; + struct weston_output *iter; + + wl_list_for_each(iter, &c->output_list, link) { + if (!iter->enabled) + continue; + + if (weston_outputs_overlap(iter, output)) { + weston_log("Error: output '%s' overlaps enabled output '%s'.\n", + output->name, iter->name); + return false; + } + } + + return true; +} + /** Constructs a weston_output object that can be used by the compositor. * * \param output The weston_output object that needs to be enabled. Must not @@ -6447,6 +6498,11 @@ weston_output_enable(struct weston_output *output) weston_output_init_zoom(output); weston_output_init_geometry(output, output->x, output->y); + + /* At this point we have a valid region so we can check placement. */ + if (!weston_output_placement_ok(output)) + return -1; + weston_output_damage(output); wl_list_init(&output->animation_list); From a09e93095205ab425612887e8ca45a2684126cb5 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 27 Jul 2022 11:05:32 -0500 Subject: [PATCH 1591/1642] libweston: Don't send output_changed signal when moving disabled outputs weston_output_set_position() currently assumes the output is enabled, but we could be using weston_output_move() to configure an output that hasn't yet been enabled. If that's the case, we don't want to send signals or perform setup that will eventually happen when the output is enabled anyway. Signed-off-by: Derek Foreman --- libweston/compositor.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libweston/compositor.c b/libweston/compositor.c index a1390a5d4..c3e4bd78c 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -5980,6 +5980,12 @@ weston_output_set_position(struct weston_output *output, int x, int y) struct wl_resource *resource; int ver; + if (!output->enabled) { + output->x = x; + output->y = y; + return; + } + output->move_x = x - output->x; output->move_y = y - output->y; From 165c3a519ff266a5d6c85ae757336a5750e716dd Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 2 Aug 2022 12:54:41 -0500 Subject: [PATCH 1592/1642] rdp: Add a perfect match for the head re-use test It may be better in some cases to look for a perfect match, and the search is fairly cheap, so let's throw it in the mix. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdpdisp.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c index 4de0a2357..693e2f0ea 100644 --- a/libweston/backend-rdp/rdpdisp.c +++ b/libweston/backend-rdp/rdpdisp.c @@ -100,6 +100,16 @@ match_position(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) 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) { @@ -192,6 +202,9 @@ disp_start_monitor_layout_change(freerdp_peer *client, rdpMonitor *config, UINT3 */ 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); From 75254aea0bd3e66385e47bcd0438d70664c6a894 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 3 Aug 2022 13:10:14 -0500 Subject: [PATCH 1593/1642] rdp: Only keep one mode on the mode list RDP outputs only really have a single mode - either the mode set in configuration, or the mode set at connection time. Make sure we only have the one mode in the mode list, and update it when appropriate. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 43 +++++++++++++++---------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 15231437e..82b8ff706 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -355,31 +355,27 @@ finish_frame_handler(void *data) } static struct weston_mode * -rdp_insert_new_mode(struct weston_output *output, int width, int height, int rate) -{ - struct weston_mode *ret; - ret = zalloc(sizeof *ret); - if (!ret) - return NULL; - ret->width = width; - ret->height = height; - ret->refresh = rate; - wl_list_insert(&output->mode_list, &ret->link); - return ret; -} - -static struct weston_mode * -ensure_matching_mode(struct weston_output *output, struct weston_mode *target) +ensure_mode(struct weston_output *output, struct weston_mode *target) { struct rdp_backend *b = to_rdp_backend(output->compositor); - struct weston_mode *local; + struct weston_mode *iter, *mode = NULL; - wl_list_for_each(local, &output->mode_list, link) { - if ((local->width == target->width) && (local->height == target->height)) - return local; + wl_list_for_each(iter, &output->mode_list, link) { + mode = iter; + break; + } + + if (!mode) { + mode = xzalloc(sizeof *mode); + wl_list_insert(&output->mode_list, &mode->link); } - return rdp_insert_new_mode(output, target->width, target->height, b->rdp_monitor_refresh_rate); + mode->width = target->width; + mode->height = target->height; + mode->refresh = target->refresh; + mode->flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + + return mode; } static void @@ -395,12 +391,7 @@ rdp_output_set_mode(struct weston_output *base, struct weston_mode *mode) rdpSettings *settings; mode->refresh = b->rdp_monitor_refresh_rate; - cur = ensure_matching_mode(base, mode); - cur->flags |= WL_OUTPUT_MODE_CURRENT; - cur->flags |= WL_OUTPUT_MODE_PREFERRED; - - if (base->current_mode) - base->current_mode->flags = 0; + cur = ensure_mode(base, mode); base->current_mode = cur; base->native_mode = cur; From a5a4b38b279a799d10c33a41bbb399949e460212 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 2 Aug 2022 12:47:19 -0500 Subject: [PATCH 1594/1642] rdp: upstream coding style changes Try to bring RAIL code closer to upstream style. Signed-off-by: Derek Foreman --- libweston/backend-rdp/rdp.c | 1 - libweston/backend-rdp/rdp.h | 48 +- libweston/backend-rdp/rdprail.c | 3623 +++++++++++++++++-------------- 3 files changed, 2030 insertions(+), 1642 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 82b8ff706..765db51f9 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -357,7 +357,6 @@ finish_frame_handler(void *data) static struct weston_mode * ensure_mode(struct weston_output *output, struct weston_mode *target) { - struct rdp_backend *b = to_rdp_backend(output->compositor); struct weston_mode *iter, *mode = NULL; wl_list_for_each(iter, &output->mode_list, link) { diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index 4507e9205..fb3ee90b7 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -143,7 +143,7 @@ struct rdp_backend { #ifdef HAVE_FREERDP_RDPAPPLIST_H /* import from libfreerdp-server2.so */ - RdpAppListServerContext* (*rdpapplist_server_context_new)(HANDLE vcm); + RdpAppListServerContext *(*rdpapplist_server_context_new)(HANDLE vcm); void (*rdpapplist_server_context_free)(RdpAppListServerContext* context); void *libRDPApplistServer; @@ -152,7 +152,7 @@ struct rdp_backend { #ifdef HAVE_FREERDP_GFXREDIR_H /* import from libfreerdp-server2.so */ - GfxRedirServerContext* (*gfxredir_server_context_new)(HANDLE vcm); + GfxRedirServerContext *(*gfxredir_server_context_new)(HANDLE vcm); void (*gfxredir_server_context_free)(GfxRedirServerContext* context); void *libFreeRDPServer; @@ -221,30 +221,30 @@ struct rdp_peer_context { // RAIL support HANDLE vcm; - RailServerContext* rail_server_context; - DrdynvcServerContext* drdynvc_server_context; - DispServerContext* disp_server_context; - RdpgfxServerContext* rail_grfx_server_context; + RailServerContext *rail_server_context; + DrdynvcServerContext *drdynvc_server_context; + DispServerContext *disp_server_context; + RdpgfxServerContext *rail_grfx_server_context; #ifdef HAVE_FREERDP_GFXREDIR_H - GfxRedirServerContext* gfxredir_server_context; + GfxRedirServerContext *gfxredir_server_context; #endif // HAVE_FREERDP_GFXREDIR_H #ifdef HAVE_FREERDP_RDPAPPLIST_H - RdpAppListServerContext* applist_server_context; + RdpAppListServerContext *applist_server_context; #endif // HAVE_FREERDP_RDPAPPLIST_H - BOOL handshakeCompleted; - BOOL activationRailCompleted; - BOOL activationGraphicsCompleted; - BOOL activationGraphicsRedirectionCompleted; - UINT32 clientStatusFlags; + bool handshakeCompleted; + bool activationRailCompleted; + bool activationGraphicsCompleted; + bool activationGraphicsRedirectionCompleted; + uint32_t clientStatusFlags; struct rdp_id_manager windowId; struct rdp_id_manager surfaceId; #ifdef HAVE_FREERDP_GFXREDIR_H struct rdp_id_manager poolId; struct rdp_id_manager bufferId; #endif // HAVE_FREERDP_GFXREDIR_H - UINT32 currentFrameId; - UINT32 acknowledgedFrameId; - BOOL isAcknowledgedSuspended; + uint32_t currentFrameId; + uint32_t acknowledgedFrameId; + bool isAcknowledgedSuspended; struct wl_client *clientExec; struct wl_listener clientExec_destroy_listener; struct weston_surface *cursorSurface; @@ -377,15 +377,15 @@ void rdp_destroy_dispatch_task_event_source(RdpPeerContext *peerCtx); // rdprail.c int rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config *config); void rdp_rail_destroy(struct rdp_backend *b); -BOOL rdp_rail_peer_activate(freerdp_peer* client); -void rdp_rail_sync_window_status(freerdp_peer* client); -BOOL rdp_rail_peer_init(freerdp_peer *client, RdpPeerContext *peerCtx); -void rdp_rail_peer_context_free(freerdp_peer* client, RdpPeerContext* context); +bool rdp_rail_peer_activate(freerdp_peer *client); +void rdp_rail_sync_window_status(freerdp_peer *client); +bool rdp_rail_peer_init(freerdp_peer *client, RdpPeerContext *peerCtx); +void rdp_rail_peer_context_free(freerdp_peer *client, RdpPeerContext *context); void rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage); -BOOL rdp_drdynvc_init(freerdp_peer *client); -void rdp_drdynvc_destroy(RdpPeerContext* context); -void rdp_rail_start_window_move(struct weston_surface* surface, int pointerGrabX, int pointerGrabY, struct weston_size minSize, struct weston_size maxSize); -void rdp_rail_end_window_move(struct weston_surface* surface); +bool rdp_drdynvc_init(freerdp_peer *client); +void rdp_drdynvc_destroy(RdpPeerContext *context); +void rdp_rail_start_window_move(struct weston_surface *surface, int pointerGrabX, int pointerGrabY, struct weston_size minSize, struct weston_size maxSize); +void rdp_rail_end_window_move(struct weston_surface *surface); // rdpdisp.c bool diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 2a774d30c..e09d8de81 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -59,163 +59,174 @@ static void rdp_rail_destroy_window(struct wl_listener *listener, void *data); static void rdp_rail_schedule_update_window(struct wl_listener *listener, void *data); static void rdp_rail_dump_window_label(struct weston_surface *surface, char *label, uint32_t label_size); +struct lang_GUID { + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + BYTE Data4_0; + BYTE Data4_1; + BYTE Data4_2; + BYTE Data4_3; + BYTE Data4_4; + BYTE Data4_5; + BYTE Data4_6; + BYTE Data4_7; +}; + struct rdp_rail_dispatch_data { struct rdp_loop_task task_base; freerdp_peer *client; union { - RAIL_SYSPARAM_ORDER u_sysParam; - RAIL_SYSCOMMAND_ORDER u_sysCommand; - RAIL_ACTIVATE_ORDER u_activate; - RAIL_EXEC_ORDER u_exec; - RAIL_WINDOW_MOVE_ORDER u_windowMove; - RAIL_SNAP_ARRANGE u_snapArrange; - RAIL_GET_APPID_REQ_ORDER u_getAppidReq; - RAIL_LANGUAGEIME_INFO_ORDER u_languageImeInfo; + RAIL_SYSPARAM_ORDER sys_param; + RAIL_SYSCOMMAND_ORDER sys_command; + RAIL_ACTIVATE_ORDER activate; + RAIL_EXEC_ORDER exec; + RAIL_WINDOW_MOVE_ORDER window_move; + RAIL_SNAP_ARRANGE snap_arrange; + RAIL_GET_APPID_REQ_ORDER get_appid_req; + RAIL_LANGUAGEIME_INFO_ORDER language_ime_info; #ifdef HAVE_FREERDP_RDPAPPLIST_H - RDPAPPLIST_CLIENT_CAPS_PDU u_appListCaps; -#endif // HAVE_FREERDP_RDPAPPLIST_H + RDPAPPLIST_CLIENT_CAPS_PDU app_list_caps; +#endif /* HAVE_FREERDP_RDPAPPLIST_H */ }; }; #define RDP_DISPATCH_TO_DISPLAY_LOOP(context, arg_type, arg, callback) \ { \ - freerdp_peer *client = (freerdp_peer*)(context)->custom; \ - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; \ - struct rdp_backend *b = peerCtx->rdpBackend; \ + freerdp_peer *client = (context)->custom; \ + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; \ + struct rdp_backend *b = peer_ctx->rdpBackend; \ struct rdp_rail_dispatch_data *dispatch_data; \ - dispatch_data = (struct rdp_rail_dispatch_data *)malloc(sizeof(*dispatch_data)); \ - if (dispatch_data) { \ - assert_not_compositor_thread(b); \ - dispatch_data->client = client; \ - dispatch_data->u_##arg_type = *(arg); \ - rdp_dispatch_task_to_display_loop(peerCtx, callback, &dispatch_data->task_base); \ - } else { \ - rdp_debug_error(b, "%s: malloc failed\n", __func__); \ - } \ + dispatch_data = xmalloc(sizeof(*dispatch_data)); \ + assert_not_compositor_thread(b); \ + dispatch_data->client = client; \ + dispatch_data->arg_type = *(arg); \ + rdp_dispatch_task_to_display_loop(peer_ctx, callback, &dispatch_data->task_base); \ } #ifdef HAVE_FREERDP_RDPAPPLIST_H static void applist_client_Caps_callback(bool freeOnly, void *arg) { - struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); - const RDPAPPLIST_CLIENT_CAPS_PDU* caps = &data->u_appListCaps; + struct rdp_rail_dispatch_data *data = wl_container_of(arg, data, task_base); + const RDPAPPLIST_CLIENT_CAPS_PDU *caps = &data->app_list_caps; freerdp_peer *client = data->client; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - char clientLanguageId[RDPAPPLIST_LANG_SIZE + 1] = {}; // +1 to ensure null-terminate. + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; + char client_language_id[RDPAPPLIST_LANG_SIZE + 1] = {}; /* +1 to ensure null-terminate. */ rdp_debug(b, "Client AppList caps version:%d\n", caps->version); assert_compositor_thread(b); - if (!freeOnly && - b->rdprail_shell_api && - b->rdprail_shell_api->start_app_list_update) { + if (freeOnly) + goto free; - strncpy(clientLanguageId, caps->clientLanguageId, RDPAPPLIST_LANG_SIZE); - rdp_debug(b, "Client AppList client language id: %s\n", clientLanguageId); + if (!api || !api->start_app_list_update) + goto free; - peerCtx->isAppListEnabled = - b->rdprail_shell_api->start_app_list_update( - b->rdprail_shell_context, - clientLanguageId); - } + strncpy(client_language_id, caps->clientLanguageId, RDPAPPLIST_LANG_SIZE); + rdp_debug(b, "Client AppList client language id: %s\n", client_language_id); + + peer_ctx->isAppListEnabled = api->start_app_list_update(b->rdprail_shell_context, + client_language_id); +free: free(data); } static UINT -applist_client_Caps(RdpAppListServerContext *context, const RDPAPPLIST_CLIENT_CAPS_PDU* arg) +applist_client_Caps(RdpAppListServerContext *context, const RDPAPPLIST_CLIENT_CAPS_PDU *arg) { - RDP_DISPATCH_TO_DISPLAY_LOOP(context, appListCaps, arg, applist_client_Caps_callback); + RDP_DISPATCH_TO_DISPLAY_LOOP(context, app_list_caps, arg, applist_client_Caps_callback); return CHANNEL_RC_OK; } -#endif // HAVE_FREERDP_RDPAPPLIST_H +#endif /* HAVE_FREERDP_RDPAPPLIST_H */ static UINT -rail_client_Handshake(RailServerContext* context, const RAIL_HANDSHAKE_ORDER* handshake) +rail_client_Handshake(RailServerContext *context, const RAIL_HANDSHAKE_ORDER *handshake) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = context->custom; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; rdp_debug(b, "Client HandShake buildNumber:%d\n", handshake->buildNumber); - peerCtx->handshakeCompleted = TRUE; + peer_ctx->handshakeCompleted = TRUE; return CHANNEL_RC_OK; } static void rail_ClientExec_destroy(struct wl_listener *listener, void *data) { - RdpPeerContext *peerCtx = container_of(listener, RdpPeerContext, - clientExec_destroy_listener); - struct rdp_backend *b = peerCtx->rdpBackend; + RdpPeerContext *peer_ctx = container_of(listener, RdpPeerContext, + clientExec_destroy_listener); + struct rdp_backend *b = peer_ctx->rdpBackend; rdp_debug(b, "Client ExecOrder program terminated\n"); - wl_list_remove(&peerCtx->clientExec_destroy_listener.link); - peerCtx->clientExec_destroy_listener.notify = NULL; - peerCtx->clientExec = NULL; + wl_list_remove(&peer_ctx->clientExec_destroy_listener.link); + peer_ctx->clientExec_destroy_listener.notify = NULL; + peer_ctx->clientExec = NULL; } static void rail_client_Exec_callback(bool freeOnly, void *arg) { - struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); - const RAIL_EXEC_ORDER* exec = &data->u_exec; + struct rdp_rail_dispatch_data *data = wl_container_of(arg, data, task_base); + const RAIL_EXEC_ORDER *exec = &data->exec; freerdp_peer *client = data->client; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - UINT result = RAIL_EXEC_E_FAIL; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; + UINT result = RAIL_EXEC_E_FAIL; RAIL_EXEC_RESULT_ORDER orderResult = {}; char *remoteProgramAndArgs = exec->RemoteApplicationProgram; rdp_debug(b, "Client ExecOrder:0x%08X, Program:%s, WorkingDir:%s, RemoteApplicationArguments:%s\n", - (UINT)exec->flags, - exec->RemoteApplicationProgram, - exec->RemoteApplicationWorkingDir, - exec->RemoteApplicationArguments); + (UINT)exec->flags, + exec->RemoteApplicationProgram, + exec->RemoteApplicationWorkingDir, + exec->RemoteApplicationArguments); - assert_compositor_thread(peerCtx->rdpBackend); + assert_compositor_thread(peer_ctx->rdpBackend); if (!freeOnly && - exec->RemoteApplicationProgram) { + exec->RemoteApplicationProgram) { if (!utf8_string_to_rail_string(exec->RemoteApplicationProgram, &orderResult.exeOrFile)) goto send_result; if (exec->RemoteApplicationArguments) { /* construct remote program path and arguments */ remoteProgramAndArgs = malloc(strlen(exec->RemoteApplicationProgram) + - strlen(exec->RemoteApplicationArguments) + - 2); // space between program and args + null terminate. + strlen(exec->RemoteApplicationArguments) + + 2); /* space between program and args + null terminate. */ if (!remoteProgramAndArgs) goto send_result; sprintf(remoteProgramAndArgs, "%s %s", exec->RemoteApplicationProgram, exec->RemoteApplicationArguments); } /* TODO: server state machine, wait until activation complated */ - while (!peerCtx->activationRailCompleted) - USleep(10000); + while (!peer_ctx->activationRailCompleted) + usleep(10000); /* launch the process specified by RDP client. */ rdp_debug(b, "Client ExecOrder launching %s\n", remoteProgramAndArgs); - if (b->rdprail_shell_api && - b->rdprail_shell_api->request_launch_shell_process) { - peerCtx->clientExec = - b->rdprail_shell_api->request_launch_shell_process( - b->rdprail_shell_context, remoteProgramAndArgs); + if (api && api->request_launch_shell_process) { + peer_ctx->clientExec = + api->request_launch_shell_process(b->rdprail_shell_context, + remoteProgramAndArgs); } - if (peerCtx->clientExec) { - assert(NULL == peerCtx->clientExec_destroy_listener.notify); - peerCtx->clientExec_destroy_listener.notify = rail_ClientExec_destroy; - wl_client_add_destroy_listener(peerCtx->clientExec, - &peerCtx->clientExec_destroy_listener); + if (peer_ctx->clientExec) { + assert(!peer_ctx->clientExec_destroy_listener.notify); + peer_ctx->clientExec_destroy_listener.notify = rail_ClientExec_destroy; + wl_client_add_destroy_listener(peer_ctx->clientExec, + &peer_ctx->clientExec_destroy_listener); result = RAIL_EXEC_S_OK; } else { rdp_debug_error(b, "%s: fail to launch shell process %s\n", - __func__, remoteProgramAndArgs); + __func__, remoteProgramAndArgs); } } @@ -225,155 +236,152 @@ rail_client_Exec_callback(bool freeOnly, void *arg) orderResult.flags = exec->flags; orderResult.execResult = result; orderResult.rawResult = 0; - peerCtx->rail_server_context->ServerExecResult(peerCtx->rail_server_context, &orderResult); + peer_ctx->rail_server_context->ServerExecResult(peer_ctx->rail_server_context, + &orderResult); } - if (orderResult.exeOrFile.string) - free(orderResult.exeOrFile.string); - if (remoteProgramAndArgs && remoteProgramAndArgs != exec->RemoteApplicationProgram) + free(orderResult.exeOrFile.string); + if (remoteProgramAndArgs != exec->RemoteApplicationProgram) free(remoteProgramAndArgs); - if (exec->RemoteApplicationProgram) - free(exec->RemoteApplicationProgram); - if (exec->RemoteApplicationWorkingDir) - free(exec->RemoteApplicationWorkingDir); - if (exec->RemoteApplicationArguments) - free(exec->RemoteApplicationArguments); + free(exec->RemoteApplicationProgram); + free(exec->RemoteApplicationWorkingDir); + free(exec->RemoteApplicationArguments); free(data); } static UINT -rail_client_Exec(RailServerContext* context, const RAIL_EXEC_ORDER* arg) +rail_client_Exec(RailServerContext *context, const RAIL_EXEC_ORDER *arg) { - RAIL_EXEC_ORDER execOrder = {}; - execOrder.flags = arg->flags; + RAIL_EXEC_ORDER exec_order = {}; + + exec_order.flags = arg->flags; if (arg->RemoteApplicationProgram) { - execOrder.RemoteApplicationProgram = malloc(strlen(arg->RemoteApplicationProgram)+1); - if (!execOrder.RemoteApplicationProgram) - goto Exit_Error; - strcpy(execOrder.RemoteApplicationProgram, arg->RemoteApplicationProgram); + exec_order.RemoteApplicationProgram = xmalloc(strlen(arg->RemoteApplicationProgram) + 1); + strcpy(exec_order.RemoteApplicationProgram, + arg->RemoteApplicationProgram); } if (arg->RemoteApplicationWorkingDir) { - execOrder.RemoteApplicationWorkingDir = malloc(strlen(arg->RemoteApplicationWorkingDir)+1); - if (!execOrder.RemoteApplicationWorkingDir) - goto Exit_Error; - strcpy(execOrder.RemoteApplicationWorkingDir, arg->RemoteApplicationWorkingDir); + exec_order.RemoteApplicationWorkingDir = xmalloc(strlen(arg->RemoteApplicationWorkingDir) + 1); + strcpy(exec_order.RemoteApplicationWorkingDir, + arg->RemoteApplicationWorkingDir); } if (arg->RemoteApplicationArguments) { - execOrder.RemoteApplicationArguments = malloc(strlen(arg->RemoteApplicationArguments)+1); - if (!execOrder.RemoteApplicationArguments) - goto Exit_Error; - strcpy(execOrder.RemoteApplicationArguments, arg->RemoteApplicationArguments); + exec_order.RemoteApplicationArguments = xmalloc(strlen(arg->RemoteApplicationArguments) + 1); + strcpy(exec_order.RemoteApplicationArguments, + arg->RemoteApplicationArguments); } - RDP_DISPATCH_TO_DISPLAY_LOOP(context, exec, &execOrder, rail_client_Exec_callback); + RDP_DISPATCH_TO_DISPLAY_LOOP(context, exec, &exec_order, + rail_client_Exec_callback); return CHANNEL_RC_OK; - -Exit_Error: - if (execOrder.RemoteApplicationProgram) - free(execOrder.RemoteApplicationProgram); - if (execOrder.RemoteApplicationWorkingDir) - free(execOrder.RemoteApplicationWorkingDir); - if (execOrder.RemoteApplicationArguments) - free(execOrder.RemoteApplicationArguments); - return CHANNEL_RC_NO_BUFFER; } static void rail_client_Activate_callback(bool freeOnly, void *arg) { - struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); - const RAIL_ACTIVATE_ORDER* activate = &data->u_activate; + struct rdp_rail_dispatch_data *data = wl_container_of(arg, data, task_base); + const RAIL_ACTIVATE_ORDER *activate = &data->activate; freerdp_peer *client = data->client; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; struct weston_surface *surface = NULL; - rdp_debug_verbose(b, "Client: ClientActivate: WindowId:0x%x, enabled:%d\n", activate->windowId, activate->enabled); + rdp_debug_verbose(b, "Client: ClientActivate: WindowId:0x%x, enabled:%d\n", + activate->windowId, activate->enabled); assert_compositor_thread(b); if (!freeOnly && - b->rdprail_shell_api && - b->rdprail_shell_api->request_window_activate && - b->rdprail_shell_context) { + api && api->request_window_activate && + b->rdprail_shell_context) { if (activate->windowId && activate->enabled) { - surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, activate->windowId); + surface = rdp_id_manager_lookup(&peer_ctx->windowId, + activate->windowId); if (!surface) - rdp_debug_error(b, "Client: ClientActivate: WindowId:0x%x is not found.\n", activate->windowId); + rdp_debug_error(b, "Client: ClientActivate: WindowId:0x%x is not found.\n", + activate->windowId); } - b->rdprail_shell_api->request_window_activate(b->rdprail_shell_context, peerCtx->item.seat, surface); + api->request_window_activate(b->rdprail_shell_context, + peer_ctx->item.seat, + surface); } free(data); } static UINT -rail_client_Activate(RailServerContext* context, const RAIL_ACTIVATE_ORDER* arg) +rail_client_Activate(RailServerContext *context, const RAIL_ACTIVATE_ORDER *arg) { - RDP_DISPATCH_TO_DISPLAY_LOOP(context, activate, arg, rail_client_Activate_callback); + RDP_DISPATCH_TO_DISPLAY_LOOP(context, activate, + arg, rail_client_Activate_callback); return CHANNEL_RC_OK; } static void rail_client_SnapArrange_callback(bool freeOnly, void *arg) { - struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); - const RAIL_SNAP_ARRANGE* snap = &data->u_snapArrange; + struct rdp_rail_dispatch_data *data = wl_container_of(arg, data, task_base); + const RAIL_SNAP_ARRANGE *snap = &data->snap_arrange; freerdp_peer *client = data->client; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; struct weston_surface *surface; struct weston_surface_rail_state *rail_state; - pixman_rectangle32_t snapArrangeRect; - struct weston_geometry windowGeometry; + pixman_rectangle32_t snap_rect; + struct weston_geometry geometry; - rdp_debug(b, "Client: SnapArrange: WindowId:0x%x at (%d, %d) %dx%d\n", - snap->windowId, - snap->left, - snap->top, - snap->right - snap->left, - snap->bottom - snap->top); + rdp_debug(b, "Client: SnapArrange: WindowId:0x%x at (%d, %d) %dx%d\n", + snap->windowId, + snap->left, + snap->top, + snap->right - snap->left, + snap->bottom - snap->top); assert_compositor_thread(b); surface = NULL; if (!freeOnly) - surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, snap->windowId); + surface = rdp_id_manager_lookup(&peer_ctx->windowId, + snap->windowId); if (surface) { rail_state = surface->backend_state; - if (b->rdprail_shell_api && - b->rdprail_shell_api->request_window_snap) { - snapArrangeRect.x = snap->left; - snapArrangeRect.y = snap->top; - snapArrangeRect.width = snap->right - snap->left; - snapArrangeRect.height = snap->bottom - snap->top; + if (api && api->request_window_snap) { + snap_rect.x = snap->left; + snap_rect.y = snap->top; + snap_rect.width = snap->right - snap->left; + snap_rect.height = snap->bottom - snap->top; /* SnapArrange PDU include window resize margin */ /* [MS-RDPERP] - v20200304 - 3.2.5.1.6 Processing Window Information Orders However, the Client Window Move PDU (section 2.2.2.7.4) and Client Window Snap PDU (section 2.2.2.7.5) do include resize margins in the window boundaries. */ - snapArrangeRect.x += rail_state->window_margin_left; - snapArrangeRect.y += rail_state->window_margin_top; - snapArrangeRect.width -= rail_state->window_margin_left + - rail_state->window_margin_right; - snapArrangeRect.height -= rail_state->window_margin_top + - rail_state->window_margin_bottom; - to_weston_coordinate(peerCtx, - &snapArrangeRect.x, &snapArrangeRect.y, - &snapArrangeRect.width, &snapArrangeRect.height); - if (is_window_shadow_remoting_disabled(peerCtx)) { + snap_rect.x += rail_state->window_margin_left; + snap_rect.y += rail_state->window_margin_top; + snap_rect.width -= rail_state->window_margin_left + + rail_state->window_margin_right; + snap_rect.height -= rail_state->window_margin_top + + rail_state->window_margin_bottom; + to_weston_coordinate(peer_ctx, + &snap_rect.x, + &snap_rect.y, + &snap_rect.width, + &snap_rect.height); + if (is_window_shadow_remoting_disabled(peer_ctx)) { /* offset window shadow area */ /* window_geometry here is last commited geometry */ - b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); - snapArrangeRect.x -= windowGeometry.x; - snapArrangeRect.y -= windowGeometry.y; - snapArrangeRect.width += (surface->width - windowGeometry.width); - snapArrangeRect.height += (surface->height - windowGeometry.height); + api->get_window_geometry(surface, + &geometry); + snap_rect.x -= geometry.x; + snap_rect.y -= geometry.y; + snap_rect.width += (surface->width - geometry.width); + snap_rect.height += (surface->height - geometry.height); } - b->rdprail_shell_api->request_window_snap(surface, - snapArrangeRect.x, - snapArrangeRect.y, - snapArrangeRect.width, - snapArrangeRect.height); + api->request_window_snap(surface, + snap_rect.x, + snap_rect.y, + snap_rect.width, + snap_rect.height); rail_state->forceUpdateWindowState = true; rdp_rail_schedule_update_window(NULL, surface); } @@ -383,41 +391,43 @@ rail_client_SnapArrange_callback(bool freeOnly, void *arg) } static UINT -rail_client_SnapArrange(RailServerContext* context, const RAIL_SNAP_ARRANGE* arg) +rail_client_SnapArrange(RailServerContext *context, const RAIL_SNAP_ARRANGE *arg) { - RDP_DISPATCH_TO_DISPLAY_LOOP(context, snapArrange, arg, rail_client_SnapArrange_callback); + RDP_DISPATCH_TO_DISPLAY_LOOP(context, snap_arrange, arg, + rail_client_SnapArrange_callback); return CHANNEL_RC_OK; } static void rail_client_WindowMove_callback(bool freeOnly, void *arg) { - struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); - const RAIL_WINDOW_MOVE_ORDER* windowMove = &data->u_windowMove; + struct rdp_rail_dispatch_data *data = wl_container_of(arg, data, task_base); + const RAIL_WINDOW_MOVE_ORDER *windowMove = &data->window_move; freerdp_peer *client = data->client; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; struct weston_surface *surface; struct weston_surface_rail_state *rail_state; pixman_rectangle32_t windowMoveRect; - struct weston_geometry windowGeometry; + struct weston_geometry geometry; + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; - rdp_debug(b, "Client: WindowMove: WindowId:0x%x at (%d,%d) %dx%d\n", - windowMove->windowId, - windowMove->left, - windowMove->top, - windowMove->right - windowMove->left, - windowMove->bottom - windowMove->top); + rdp_debug(b, "Client: WindowMove: WindowId:0x%x at (%d,%d) %dx%d\n", + windowMove->windowId, + windowMove->left, + windowMove->top, + windowMove->right - windowMove->left, + windowMove->bottom - windowMove->top); assert_compositor_thread(b); surface = NULL; if (!freeOnly) - surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, windowMove->windowId); + surface = rdp_id_manager_lookup(&peer_ctx->windowId, + windowMove->windowId); if (surface) { rail_state = surface->backend_state; - if (b->rdprail_shell_api && - b->rdprail_shell_api->request_window_move) { + if (api && api->request_window_move) { windowMoveRect.x = windowMove->left; windowMoveRect.y = windowMove->top; windowMoveRect.width = windowMove->right - windowMove->left; @@ -432,23 +442,26 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) rail_state->window_margin_right; windowMoveRect.height -= rail_state->window_margin_top + rail_state->window_margin_bottom; - to_weston_coordinate(peerCtx, - &windowMoveRect.x, &windowMoveRect.y, - &windowMoveRect.width, &windowMoveRect.height); - if (is_window_shadow_remoting_disabled(peerCtx)) { + to_weston_coordinate(peer_ctx, + &windowMoveRect.x, + &windowMoveRect.y, + &windowMoveRect.width, + &windowMoveRect.height); + if (is_window_shadow_remoting_disabled(peer_ctx)) { /* offset window shadow area */ /* window_geometry here is last commited geometry */ - b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); - windowMoveRect.x -= windowGeometry.x; - windowMoveRect.y -= windowGeometry.y; - windowMoveRect.width += (surface->width - windowGeometry.width); - windowMoveRect.height += (surface->height - windowGeometry.height); + api->get_window_geometry(surface, + &geometry); + windowMoveRect.x -= geometry.x; + windowMoveRect.y -= geometry.y; + windowMoveRect.width += (surface->width - geometry.width); + windowMoveRect.height += (surface->height - geometry.height); } - b->rdprail_shell_api->request_window_move(surface, - windowMoveRect.x, - windowMoveRect.y, - windowMoveRect.width, - windowMoveRect.height); + api->request_window_move(surface, + windowMoveRect.x, + windowMoveRect.y, + windowMoveRect.width, + windowMoveRect.height); rail_state->forceUpdateWindowState = true; rdp_rail_schedule_update_window(NULL, surface); } @@ -457,30 +470,35 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) free(data); } -static UINT -rail_client_WindowMove(RailServerContext* context, const RAIL_WINDOW_MOVE_ORDER* arg) +static UINT +rail_client_WindowMove(RailServerContext *context, const RAIL_WINDOW_MOVE_ORDER *arg) { - RDP_DISPATCH_TO_DISPLAY_LOOP(context, windowMove, arg, rail_client_WindowMove_callback); + RDP_DISPATCH_TO_DISPLAY_LOOP(context, window_move, arg, + rail_client_WindowMove_callback); return CHANNEL_RC_OK; } static void rail_client_Syscommand_callback(bool freeOnly, void *arg) { - struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); - const RAIL_SYSCOMMAND_ORDER* syscommand = &data->u_sysCommand; + struct rdp_rail_dispatch_data *data = wl_container_of(arg, data, task_base); + const RAIL_SYSCOMMAND_ORDER *syscommand = &data->sys_command; freerdp_peer *client = data->client; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - struct weston_surface* surface; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + struct weston_surface *surface; + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; assert_compositor_thread(b); surface = NULL; if (!freeOnly) - surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, syscommand->windowId); + surface = rdp_id_manager_lookup(&peer_ctx->windowId, + syscommand->windowId); if (!surface) { - rdp_debug_error(b, "Client: ClientSyscommand: WindowId:0x%x is not found.\n", syscommand->windowId); + rdp_debug_error(b, + "Client: ClientSyscommand: WindowId:0x%x is not found.\n", + syscommand->windowId); goto Exit; } @@ -492,32 +510,28 @@ rail_client_Syscommand_callback(bool freeOnly, void *arg) case SC_MOVE: commandString = "SC_MOVE"; break; - case SC_MINIMIZE: + case SC_MINIMIZE: commandString = "SC_MINIMIZE"; - if (b->rdprail_shell_api && - b->rdprail_shell_api->request_window_minimize) - b->rdprail_shell_api->request_window_minimize(surface); + if (api && api->request_window_minimize) + api->request_window_minimize(surface); break; case SC_MAXIMIZE: commandString = "SC_MAXIMIZE"; - if (b->rdprail_shell_api && - b->rdprail_shell_api->request_window_maximize) - b->rdprail_shell_api->request_window_maximize(surface); + if (api && api->request_window_maximize) + api->request_window_maximize(surface); break; case SC_CLOSE: commandString = "SC_CLOSE"; - if (b->rdprail_shell_api && - b->rdprail_shell_api->request_window_close) - b->rdprail_shell_api->request_window_close(surface); + if (api && api->request_window_close) + api->request_window_close(surface); break; case SC_KEYMENU: commandString = "SC_KEYMENU"; break; case SC_RESTORE: commandString = "SC_RESTORE"; - if (b->rdprail_shell_api && - b->rdprail_shell_api->request_window_restore) - b->rdprail_shell_api->request_window_restore(surface); + if (api && api->request_window_restore) + api->request_window_restore(surface); break; case SC_DEFAULT: commandString = "SC_DEFAULT"; @@ -527,74 +541,83 @@ rail_client_Syscommand_callback(bool freeOnly, void *arg) break; } - rdp_debug(b, "Client: ClientSyscommand: WindowId:0x%x, surface:0x%p, command:%s (0x%x)\n", - syscommand->windowId, surface, commandString, syscommand->command); + rdp_debug(b, + "Client: ClientSyscommand: WindowId:0x%x, surface:0x%p, command:%s (0x%x)\n", + syscommand->windowId, surface, commandString, + syscommand->command); Exit: free(data); } static UINT -rail_client_Syscommand(RailServerContext* context, const RAIL_SYSCOMMAND_ORDER* arg) +rail_client_Syscommand(RailServerContext *context, const RAIL_SYSCOMMAND_ORDER *arg) { - RDP_DISPATCH_TO_DISPLAY_LOOP(context, sysCommand, arg, rail_client_Syscommand_callback); + RDP_DISPATCH_TO_DISPLAY_LOOP(context, sys_command, arg, + rail_client_Syscommand_callback); return CHANNEL_RC_OK; } static void rail_client_ClientSysparam_callback(bool freeOnly, void *arg) { - struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); - const RAIL_SYSPARAM_ORDER* sysparam = &data->u_sysParam; + struct rdp_rail_dispatch_data *data = wl_container_of(arg, data, + task_base); + const RAIL_SYSPARAM_ORDER *sysparam = &data->sys_param; freerdp_peer *client = data->client; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; pixman_rectangle32_t workareaRect; pixman_rectangle32_t workareaRectClient; struct weston_output *base_output; struct weston_head *base_head_iter; + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; assert_compositor_thread(b); if (sysparam->params & SPI_MASK_SET_DRAG_FULL_WINDOWS) { - rdp_debug(b, "Client: ClientSysparam: dragFullWindows:%d\n", sysparam->dragFullWindows); + rdp_debug(b, "Client: ClientSysparam: dragFullWindows:%d\n", + sysparam->dragFullWindows); } if (sysparam->params & SPI_MASK_SET_KEYBOARD_CUES) { - rdp_debug(b, "Client: ClientSysparam: keyboardCues:%d\n", sysparam->keyboardCues); + rdp_debug(b, "Client: ClientSysparam: keyboardCues:%d\n", + sysparam->keyboardCues); } if (sysparam->params & SPI_MASK_SET_KEYBOARD_PREF) { - rdp_debug(b, "Client: ClientSysparam: keyboardPref:%d\n", sysparam->keyboardPref); + rdp_debug(b, "Client: ClientSysparam: keyboardPref:%d\n", + sysparam->keyboardPref); } if (sysparam->params & SPI_MASK_SET_MOUSE_BUTTON_SWAP) { rdp_debug(b, "Client: ClientSysparam: mouseButtonSwap:%d\n", sysparam->mouseButtonSwap); - peerCtx->mouseButtonSwap = sysparam->mouseButtonSwap; + peer_ctx->mouseButtonSwap = sysparam->mouseButtonSwap; } if (sysparam->params & SPI_MASK_SET_WORK_AREA) { - rdp_debug(b, "Client: ClientSysparam: workArea:(left:%d, top:%d, right:%d, bottom:%d)\n", - (INT32)(INT16)sysparam->workArea.left, - (INT32)(INT16)sysparam->workArea.top, - (INT32)(INT16)sysparam->workArea.right, - (INT32)(INT16)sysparam->workArea.bottom); + rdp_debug(b, + "Client: ClientSysparam: workArea:(left:%u, top:%u, right:%u, bottom:%u)\n", + sysparam->workArea.left, + sysparam->workArea.top, + sysparam->workArea.right, + sysparam->workArea.bottom); } if (sysparam->params & SPI_MASK_DISPLAY_CHANGE) { - rdp_debug(b, "Client: ClientSysparam: displayChange:(left:%d, top:%d, right:%d, bottom:%d)\n", - (INT32)(INT16)sysparam->displayChange.left, - (INT32)(INT16)sysparam->displayChange.top, - (INT32)(INT16)sysparam->displayChange.right, - (INT32)(INT16)sysparam->displayChange.bottom); + rdp_debug(b, "Client: ClientSysparam: displayChange:(left:%u, top:%u, right:%u, bottom:%u)\n", + sysparam->displayChange.left, + sysparam->displayChange.top, + sysparam->displayChange.right, + sysparam->displayChange.bottom); } if (sysparam->params & SPI_MASK_TASKBAR_POS) { - rdp_debug(b, "Client: ClientSysparam: taskbarPos:(left:%d, top:%d, right:%d, bottom:%d)\n", - (INT32)(INT16)sysparam->taskbarPos.left, - (INT32)(INT16)sysparam->taskbarPos.top, - (INT32)(INT16)sysparam->taskbarPos.right, - (INT32)(INT16)sysparam->taskbarPos.bottom); + rdp_debug(b, "Client: ClientSysparam: taskbarPos:(left:%u, top:%u, right:%u, bottom:%u)\n", + sysparam->taskbarPos.left, + sysparam->taskbarPos.top, + sysparam->taskbarPos.right, + sysparam->taskbarPos.bottom); } if (sysparam->params & SPI_MASK_SET_HIGH_CONTRAST) { @@ -602,15 +625,18 @@ rail_client_ClientSysparam_callback(bool freeOnly, void *arg) } if (sysparam->params & SPI_MASK_SET_CARET_WIDTH) { - rdp_debug(b, "Client: ClientSysparam: caretWidth:%d\n", sysparam->caretWidth); + rdp_debug(b, "Client: ClientSysparam: caretWidth:%d\n", + sysparam->caretWidth); } if (sysparam->params & SPI_MASK_SET_STICKY_KEYS) { - rdp_debug(b, "Client: ClientSysparam: stickyKeys:%d\n", sysparam->stickyKeys); + rdp_debug(b, "Client: ClientSysparam: stickyKeys:%d\n", + sysparam->stickyKeys); } if (sysparam->params & SPI_MASK_SET_TOGGLE_KEYS) { - rdp_debug(b, "Client: ClientSysparam: toggleKeys:%d\n", sysparam->toggleKeys); + rdp_debug(b, "Client: ClientSysparam: toggleKeys:%d\n", + sysparam->toggleKeys); } if (sysparam->params & SPI_MASK_SET_FILTER_KEYS) { @@ -618,38 +644,48 @@ rail_client_ClientSysparam_callback(bool freeOnly, void *arg) } if (sysparam->params & SPI_MASK_SET_SCREEN_SAVE_ACTIVE) { - rdp_debug(b, "Client: ClientSysparam: setScreenSaveActive:%d\n", sysparam->setScreenSaveActive); + rdp_debug(b, "Client: ClientSysparam: setScreenSaveActive:%d\n", + sysparam->setScreenSaveActive); } if (sysparam->params & SPI_MASK_SET_SET_SCREEN_SAVE_SECURE) { - rdp_debug(b, "Client: ClientSysparam: setScreenSaveSecure:%d\n", sysparam->setScreenSaveSecure); + rdp_debug(b, "Client: ClientSysparam: setScreenSaveSecure:%d\n", + sysparam->setScreenSaveSecure); } if (!freeOnly) { if (sysparam->params & SPI_MASK_SET_WORK_AREA && - b->rdprail_shell_api && - b->rdprail_shell_api->set_desktop_workarea) { - workareaRectClient.x = (INT32)(INT16)sysparam->workArea.left; - workareaRectClient.y = (INT32)(INT16)sysparam->workArea.top; - workareaRectClient.width = (INT32)(INT16)sysparam->workArea.right - workareaRectClient.x; - workareaRectClient.height = (INT32)(INT16)sysparam->workArea.bottom - workareaRectClient.y; + api && api->set_desktop_workarea) { + workareaRectClient.x = (int32_t)(int16_t)sysparam->workArea.left; + workareaRectClient.y = (int32_t)(int16_t)sysparam->workArea.top; + workareaRectClient.width = (int32_t)(int16_t)sysparam->workArea.right - workareaRectClient.x; + workareaRectClient.height = (int32_t)(int16_t)sysparam->workArea.bottom - workareaRectClient.y; /* Workarea is reported in client coordinate where primary monitor' upper-left is (0,0). */ /* traslate to weston coordinate where entire desktop's upper-left is (0,0). */ workareaRect = workareaRectClient; - base_output = to_weston_coordinate(peerCtx, - &workareaRect.x, &workareaRect.y, - &workareaRect.width, &workareaRect.height); + base_output = to_weston_coordinate(peer_ctx, + &workareaRect.x, + &workareaRect.y, + &workareaRect.width, + &workareaRect.height); if (base_output) { - rdp_debug(b, "Translated workarea:(%d,%d)-(%d,%d) at %s:(%d,%d)-(%d,%d)\n", - workareaRect.x, workareaRect.y, - workareaRect.x + workareaRect.width, workareaRect.y + workareaRect.height, - base_output->name, - base_output->x, base_output->y, - base_output->x + base_output->width, base_output->y + base_output->height); - b->rdprail_shell_api->set_desktop_workarea(base_output, b->rdprail_shell_context, &workareaRect); + rdp_debug(b, + "Translated workarea:(%d,%d)-(%d,%d) at %s:(%d,%d)-(%d,%d)\n", + workareaRect.x, workareaRect.y, + workareaRect.x + workareaRect.width, + workareaRect.y + workareaRect.height, + base_output->name, + base_output->x, base_output->y, + base_output->x + base_output->width, + base_output->y + base_output->height); + api->set_desktop_workarea(base_output, + b->rdprail_shell_context, + &workareaRect); wl_list_for_each(base_head_iter, &base_output->head_list, output_link) { - to_rdp_head(base_head_iter)->workarea = workareaRect; - to_rdp_head(base_head_iter)->workareaClient = workareaRectClient; + struct rdp_head *head = to_rdp_head(base_head_iter); + + head->workarea = workareaRect; + head->workareaClient = workareaRectClient; } } else { rdp_debug_error(b, "Client: ClientSysparam: workArea isn't belonging to an output\n"); @@ -661,9 +697,10 @@ rail_client_ClientSysparam_callback(bool freeOnly, void *arg) } static UINT -rail_client_ClientSysparam(RailServerContext* context, const RAIL_SYSPARAM_ORDER* arg) +rail_client_ClientSysparam(RailServerContext *context, const RAIL_SYSPARAM_ORDER *arg) { - RDP_DISPATCH_TO_DISPLAY_LOOP(context, sysParam, arg, rail_client_ClientSysparam_callback); + RDP_DISPATCH_TO_DISPLAY_LOOP(context, sys_param, arg, + rail_client_ClientSysparam_callback); return CHANNEL_RC_OK; } @@ -671,10 +708,11 @@ static void rail_client_ClientGetAppidReq_callback(bool freeOnly, void *arg) { struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); - const RAIL_GET_APPID_REQ_ORDER* getAppidReq = &data->u_getAppidReq; + const RAIL_GET_APPID_REQ_ORDER* getAppidReq = &data->get_appid_req; freerdp_peer *client = data->client; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; char appId[520] = {}; char imageName[520] = {}; pid_t pid; @@ -682,47 +720,58 @@ rail_client_ClientGetAppidReq_callback(bool freeOnly, void *arg) unsigned short *p; struct weston_surface *surface; - rdp_debug_verbose(b, "Client: ClientGetAppidReq: WindowId:0x%x\n", getAppidReq->windowId); + rdp_debug_verbose(b, "Client: ClientGetAppidReq: WindowId:0x%x\n", + getAppidReq->windowId); assert_compositor_thread(b); if (!freeOnly && - b->rdprail_shell_api && - b->rdprail_shell_api->get_window_app_id) { + api && api->get_window_app_id) { - surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, getAppidReq->windowId); + surface = rdp_id_manager_lookup(&peer_ctx->windowId, getAppidReq->windowId); if (!surface) { - rdp_debug_error(b, "Client: ClientGetAppidReq: WindowId:0x%x is not found.\n", getAppidReq->windowId); + rdp_debug_error(b, "Client: ClientGetAppidReq: WindowId:0x%x is not found.\n", + getAppidReq->windowId); goto Exit; } - pid = b->rdprail_shell_api->get_window_app_id(b->rdprail_shell_context, - surface, &appId[0], sizeof(appId), &imageName[0], sizeof(imageName)); + pid = api->get_window_app_id(b->rdprail_shell_context, + surface, &appId[0], + sizeof(appId), &imageName[0], + sizeof(imageName)); if (appId[0] == '\0') { - rdp_debug_error(b, "Client: ClientGetAppidReq: WindowId:0x%x does not have appId, or not top level window.\n", getAppidReq->windowId); + rdp_debug_error(b, "Client: ClientGetAppidReq: WindowId:0x%x does not have appId, or not top level window.\n", + getAppidReq->windowId); goto Exit; } - rdp_debug(b, "Client: ClientGetAppidReq: pid:%d appId:%s\n", (UINT32)pid, appId); - rdp_debug_verbose(b, "Client: ClientGetAppidReq: pid:%d imageName:%s\n", (UINT32)pid, imageName); + rdp_debug(b, "Client: ClientGetAppidReq: pid:%d appId:%s\n", + (uint32_t)pid, appId); + rdp_debug_verbose(b, + "Client: ClientGetAppidReq: pid:%d imageName:%s\n", + (uint32_t)pid, imageName); /* Reply with RAIL_GET_APPID_RESP_EX when pid/imageName is valid and client supports it */ if ((pid >= 0) && (imageName[0] != '\0') && - peerCtx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_GET_APPID_RESPONSE_EX_SUPPORTED) { + peer_ctx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_GET_APPID_RESPONSE_EX_SUPPORTED) { RAIL_GET_APPID_RESP_EX getAppIdRespEx = {}; + getAppIdRespEx.windowID = getAppidReq->windowId; for (i = 0, p = &getAppIdRespEx.applicationID[0]; i < strlen(appId); i++, p++) *p = (unsigned short)appId[i]; - getAppIdRespEx.processId = (UINT32) pid; + getAppIdRespEx.processId = (uint32_t) pid; for (i = 0, p = &getAppIdRespEx.processImageName[0]; i < strlen(imageName); i++, p++) *p = (unsigned short)imageName[i]; - peerCtx->rail_server_context->ServerGetAppidRespEx(peerCtx->rail_server_context, &getAppIdRespEx); + peer_ctx->rail_server_context->ServerGetAppidRespEx(peer_ctx->rail_server_context, + &getAppIdRespEx); } else { RAIL_GET_APPID_RESP_ORDER getAppIdResp = {}; + getAppIdResp.windowId = getAppidReq->windowId; for (i = 0, p = &getAppIdResp.applicationId[0]; i < strlen(appId); i++, p++) *p = (unsigned short)appId[i]; - peerCtx->rail_server_context->ServerGetAppidResp(peerCtx->rail_server_context, &getAppIdResp); + peer_ctx->rail_server_context->ServerGetAppidResp(peer_ctx->rail_server_context, + &getAppIdResp); } } @@ -731,20 +780,21 @@ rail_client_ClientGetAppidReq_callback(bool freeOnly, void *arg) } static UINT -rail_client_ClientGetAppidReq(RailServerContext* context, - const RAIL_GET_APPID_REQ_ORDER* arg) +rail_client_ClientGetAppidReq(RailServerContext *context, + const RAIL_GET_APPID_REQ_ORDER *arg) { - RDP_DISPATCH_TO_DISPLAY_LOOP(context, getAppidReq, arg, rail_client_ClientGetAppidReq_callback); + RDP_DISPATCH_TO_DISPLAY_LOOP(context, get_appid_req, arg, + rail_client_ClientGetAppidReq_callback); return CHANNEL_RC_OK; } static UINT -rail_client_ClientStatus(RailServerContext* context, - const RAIL_CLIENT_STATUS_ORDER* clientStatus) +rail_client_ClientStatus(RailServerContext *context, + const RAIL_CLIENT_STATUS_ORDER *clientStatus) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = context->custom; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; rdp_debug(b, "Client: ClientStatus:0x%x\n", clientStatus->flags); if (clientStatus->flags & TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE) @@ -766,19 +816,20 @@ rail_client_ClientStatus(RailServerContext* context, if (clientStatus->flags & TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED) rdp_debug(b, " - TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED\n"); - peerCtx->clientStatusFlags = clientStatus->flags; + peer_ctx->clientStatusFlags = clientStatus->flags; return CHANNEL_RC_OK; } static UINT -rail_client_LangbarInfo(RailServerContext* context, - const RAIL_LANGBAR_INFO_ORDER* langbarInfo) +rail_client_LangbarInfo(RailServerContext *context, + const RAIL_LANGBAR_INFO_ORDER *langbarInfo) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = context->custom; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; - rdp_debug(b, "Client: LangbarInfo: LanguageBarStatus:%d\n", langbarInfo->languageBarStatus); + rdp_debug(b, "Client: LangbarInfo: LanguageBarStatus:%d\n", + langbarInfo->languageBarStatus); return CHANNEL_RC_OK; } @@ -786,34 +837,20 @@ rail_client_LangbarInfo(RailServerContext* context, static char * languageGuid_to_string(const GUID *guid) { - typedef struct _lang_GUID - { - UINT32 Data1; - UINT16 Data2; - UINT16 Data3; - BYTE Data4_0; - BYTE Data4_1; - BYTE Data4_2; - BYTE Data4_3; - BYTE Data4_4; - BYTE Data4_5; - BYTE Data4_6; - BYTE Data4_7; - } lang_GUID; - - static const lang_GUID c_GUID_NULL = GUID_NULL; - static const lang_GUID c_GUID_JPNIME = GUID_MSIME_JPN; - static const lang_GUID c_GUID_KORIME = GUID_MSIME_KOR; - static const lang_GUID c_GUID_CHSIME = GUID_CHSIME; - static const lang_GUID c_GUID_CHTIME = GUID_CHTIME; - static const lang_GUID c_GUID_PROFILE_NEWPHONETIC = GUID_PROFILE_NEWPHONETIC; - static const lang_GUID c_GUID_PROFILE_CHANGJIE = GUID_PROFILE_CHANGJIE; - static const lang_GUID c_GUID_PROFILE_QUICK = GUID_PROFILE_QUICK; - static const lang_GUID c_GUID_PROFILE_CANTONESE = GUID_PROFILE_CANTONESE; - static const lang_GUID c_GUID_PROFILE_PINYIN = GUID_PROFILE_PINYIN; - static const lang_GUID c_GUID_PROFILE_SIMPLEFAST = GUID_PROFILE_SIMPLEFAST; - static const lang_GUID c_GUID_PROFILE_MSIME_JPN = GUID_GUID_PROFILE_MSIME_JPN; - static const lang_GUID c_GUID_PROFILE_MSIME_KOR = GUID_PROFILE_MSIME_KOR; + static_assert(sizeof(struct lang_GUID) == sizeof(GUID)); + static const struct lang_GUID c_GUID_NULL = GUID_NULL; + static const struct lang_GUID c_GUID_JPNIME = GUID_MSIME_JPN; + static const struct lang_GUID c_GUID_KORIME = GUID_MSIME_KOR; + static const struct lang_GUID c_GUID_CHSIME = GUID_CHSIME; + static const struct lang_GUID c_GUID_CHTIME = GUID_CHTIME; + static const struct lang_GUID c_GUID_PROFILE_NEWPHONETIC = GUID_PROFILE_NEWPHONETIC; + static const struct lang_GUID c_GUID_PROFILE_CHANGJIE = GUID_PROFILE_CHANGJIE; + static const struct lang_GUID c_GUID_PROFILE_QUICK = GUID_PROFILE_QUICK; + static const struct lang_GUID c_GUID_PROFILE_CANTONESE = GUID_PROFILE_CANTONESE; + static const struct lang_GUID c_GUID_PROFILE_PINYIN = GUID_PROFILE_PINYIN; + static const struct lang_GUID c_GUID_PROFILE_SIMPLEFAST = GUID_PROFILE_SIMPLEFAST; + static const struct lang_GUID c_GUID_PROFILE_MSIME_JPN = GUID_GUID_PROFILE_MSIME_JPN; + static const struct lang_GUID c_GUID_PROFILE_MSIME_KOR = GUID_PROFILE_MSIME_KOR; RPC_STATUS rpc_status; if (UuidEqual(guid, (GUID *)&c_GUID_NULL, &rpc_status)) @@ -849,21 +886,21 @@ languageGuid_to_string(const GUID *guid) static void rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) { - struct rdp_rail_dispatch_data* data = wl_container_of(arg, data, task_base); - const RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo = &data->u_languageImeInfo; + struct rdp_rail_dispatch_data *data = wl_container_of(arg, data, + task_base); + const RAIL_LANGUAGEIME_INFO_ORDER *languageImeInfo = &data->language_ime_info; freerdp_peer *client = data->client; rdpSettings *settings = client->context->settings; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - UINT32 new_keyboard_layout = 0; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + uint32_t new_keyboard_layout = 0; struct xkb_keymap *keymap = NULL; struct xkb_rule_names xkbRuleNames; char *s; assert_compositor_thread(b); - switch (languageImeInfo->ProfileType) - { + switch (languageImeInfo->ProfileType) { case TF_PROFILETYPE_INPUTPROCESSOR: s = "TF_PROFILETYPE_INPUTPROCESSOR"; break; @@ -874,56 +911,47 @@ rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) s = "Unknown profile type"; break; } - rdp_debug(b, "Client: LanguageImeInfo: ProfileType: %d (%s)\n", languageImeInfo->ProfileType, s); - rdp_debug(b, "Client: LanguageImeInfo: LanguageID: 0x%x\n", languageImeInfo->LanguageID); + rdp_debug(b, "Client: LanguageImeInfo: ProfileType: %d (%s)\n", + languageImeInfo->ProfileType, s); + rdp_debug(b, "Client: LanguageImeInfo: LanguageID: 0x%x\n", + languageImeInfo->LanguageID); rdp_debug(b, "Client: LanguageImeInfo: LanguageProfileCLSID: %s\n", - languageGuid_to_string(&languageImeInfo->LanguageProfileCLSID)); + languageGuid_to_string(&languageImeInfo->LanguageProfileCLSID)); rdp_debug(b, "Client: LanguageImeInfo: ProfileGUID: %s\n", - languageGuid_to_string(&languageImeInfo->ProfileGUID)); - rdp_debug(b, "Client: LanguageImeInfo: KeyboardLayout: 0x%x\n", languageImeInfo->KeyboardLayout); + languageGuid_to_string(&languageImeInfo->ProfileGUID)); + rdp_debug(b, "Client: LanguageImeInfo: KeyboardLayout: 0x%x\n", + languageImeInfo->KeyboardLayout); if (!freeOnly) { if (languageImeInfo->ProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT) { new_keyboard_layout = languageImeInfo->KeyboardLayout; } else if (languageImeInfo->ProfileType == TF_PROFILETYPE_INPUTPROCESSOR) { - typedef struct _lang_GUID - { - UINT32 Data1; - UINT16 Data2; - UINT16 Data3; - BYTE Data4_0; - BYTE Data4_1; - BYTE Data4_2; - BYTE Data4_3; - BYTE Data4_4; - BYTE Data4_5; - BYTE Data4_6; - BYTE Data4_7; - } lang_GUID; - - static const lang_GUID c_GUID_JPNIME = GUID_MSIME_JPN; - static const lang_GUID c_GUID_KORIME = GUID_MSIME_KOR; - static const lang_GUID c_GUID_CHSIME = GUID_CHSIME; - static const lang_GUID c_GUID_CHTIME = GUID_CHTIME; + static_assert(sizeof(struct lang_GUID) == sizeof(GUID)); + + static const struct lang_GUID c_GUID_JPNIME = GUID_MSIME_JPN; + static const struct lang_GUID c_GUID_KORIME = GUID_MSIME_KOR; + static const struct lang_GUID c_GUID_CHSIME = GUID_CHSIME; + static const struct lang_GUID c_GUID_CHTIME = GUID_CHTIME; RPC_STATUS rpc_status; if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, - (GUID *)&c_GUID_JPNIME, &rpc_status)) + (GUID *)&c_GUID_JPNIME, &rpc_status)) new_keyboard_layout = KBD_JAPANESE; else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, - (GUID *)&c_GUID_KORIME, &rpc_status)) + (GUID *)&c_GUID_KORIME, &rpc_status)) new_keyboard_layout = KBD_KOREAN; else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, - (GUID *)&c_GUID_CHSIME, &rpc_status)) + (GUID *)&c_GUID_CHSIME, &rpc_status)) new_keyboard_layout = KBD_CHINESE_SIMPLIFIED_US; else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, - (GUID *)&c_GUID_CHTIME, &rpc_status)) + (GUID *)&c_GUID_CHTIME, &rpc_status)) new_keyboard_layout = KBD_CHINESE_TRADITIONAL_US; - else + else new_keyboard_layout = KBD_US; } - if (new_keyboard_layout && (new_keyboard_layout != settings->KeyboardLayout)) { + if (new_keyboard_layout && + (new_keyboard_layout != settings->KeyboardLayout)) { convert_rdp_keyboard_to_xkb_rule_names(settings->KeyboardType, settings->KeyboardSubType, new_keyboard_layout, @@ -932,14 +960,16 @@ rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) keymap = xkb_keymap_new_from_names(b->compositor->xkb_context, &xkbRuleNames, 0); if (keymap) { - weston_seat_update_keymap(peerCtx->item.seat, keymap); + weston_seat_update_keymap(peer_ctx->item.seat, keymap); xkb_keymap_unref(keymap); settings->KeyboardLayout = new_keyboard_layout; } } if (!keymap) { rdp_debug_error(b, "%s: Failed to switch to kbd_layout:0x%x kbd_type:0x%x kbd_subType:0x%x\n", - __func__, new_keyboard_layout, settings->KeyboardType, settings->KeyboardSubType); + __func__, new_keyboard_layout, + settings->KeyboardType, + settings->KeyboardSubType); } } } @@ -948,42 +978,50 @@ rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) } static UINT -rail_client_LanguageImeInfo(RailServerContext* context, - const RAIL_LANGUAGEIME_INFO_ORDER* arg) +rail_client_LanguageImeInfo(RailServerContext *context, + const RAIL_LANGUAGEIME_INFO_ORDER *arg) { - RDP_DISPATCH_TO_DISPLAY_LOOP(context, languageImeInfo, arg, rail_client_LanguageImeInfo_callback); + RDP_DISPATCH_TO_DISPLAY_LOOP(context, language_ime_info, arg, + rail_client_LanguageImeInfo_callback); return CHANNEL_RC_OK; } static UINT -rail_client_CompartmentInfo(RailServerContext* context, - const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +rail_client_CompartmentInfo(RailServerContext *context, + const RAIL_COMPARTMENT_INFO_ORDER *compartmentInfo) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - - rdp_debug(b, "Client: CompartmentInfo: ImeStatus: %s\n", compartmentInfo->ImeState ? "OPEN" : "CLOSED"); - rdp_debug(b, "Client: CompartmentInfo: ImeConvMode: 0x%x\n", compartmentInfo->ImeConvMode); - rdp_debug(b, "Client: CompartmentInfo: ImeSentenceMode: 0x%x\n", compartmentInfo->ImeSentenceMode); - rdp_debug(b, "Client: CompartmentInfo: KanaMode: %s\n", compartmentInfo->KanaMode ? "ON" : "OFF"); + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + + rdp_debug(b, "Client: CompartmentInfo: ImeStatus: %s\n", + compartmentInfo->ImeState ? "OPEN" : "CLOSED"); + rdp_debug(b, "Client: CompartmentInfo: ImeConvMode: 0x%x\n", + compartmentInfo->ImeConvMode); + rdp_debug(b, "Client: CompartmentInfo: ImeSentenceMode: 0x%x\n", + compartmentInfo->ImeSentenceMode); + rdp_debug(b, "Client: CompartmentInfo: KanaMode: %s\n", + compartmentInfo->KanaMode ? "ON" : "OFF"); return CHANNEL_RC_OK; } static UINT -rail_grfx_client_caps_advertise(RdpgfxServerContext* context, const RDPGFX_CAPS_ADVERTISE_PDU* capsAdvertise) +rail_grfx_client_caps_advertise(RdpgfxServerContext *context, const RDPGFX_CAPS_ADVERTISE_PDU *capsAdvertise) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; - rdp_debug(b, "Client: GrfxCaps count:0x%x\n", capsAdvertise->capsSetCount); + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + RdpgfxServerContext *gfx_ctx = peer_ctx->rail_grfx_server_context; + + rdp_debug(b, "Client: GrfxCaps count:0x%x\n", + capsAdvertise->capsSetCount); for (int i = 0; i < capsAdvertise->capsSetCount; i++) { - RDPGFX_CAPSET* capsSet = &(capsAdvertise->capsSets[i]); + RDPGFX_CAPSET *capsSet = &(capsAdvertise->capsSets[i]); + rdp_debug(b, "Client: GrfxCaps[%d] version:0x%x length:%d flags:0x%x\n", - i, capsSet->version, capsSet->length, capsSet->flags); - switch(capsSet->version) - { + i, capsSet->version, capsSet->length, capsSet->flags); + switch(capsSet->version) { case RDPGFX_CAPVERSION_8: rdp_debug(b, " Version : RDPGFX_CAPVERSION_8\n"); break; @@ -1023,17 +1061,16 @@ rail_grfx_client_caps_advertise(RdpgfxServerContext* context, const RDPGFX_CAPS_ rdp_debug(b, " - RDPGFX_CAPS_FLAG_AVC_DISABLED\n"); if (capsSet->flags & RDPGFX_CAPS_FLAG_AVC_THINCLIENT) rdp_debug(b, " - RDPGFX_CAPS_FLAG_AVC_THINCLIENT\n"); - - switch(capsSet->version) - { + + switch(capsSet->version) { case RDPGFX_CAPVERSION_8: { - //RDPGFX_CAPSET_VERSION8 *caps8 = (RDPGFX_CAPSET_VERSION8 *)capsSet; + /*RDPGFX_CAPSET_VERSION8 *caps8 = (RDPGFX_CAPSET_VERSION8 *)capsSet;*/ break; } case RDPGFX_CAPVERSION_81: - { - //RDPGFX_CAPSET_VERSION81 *caps81 = (RDPGFX_CAPSET_VERSION81 *)capsSet; + { + /*RDPGFX_CAPSET_VERSION81 *caps81 = (RDPGFX_CAPSET_VERSION81 *)capsSet;*/ break; } case RDPGFX_CAPVERSION_10: @@ -1044,60 +1081,68 @@ rail_grfx_client_caps_advertise(RdpgfxServerContext* context, const RDPGFX_CAPS_ case RDPGFX_CAPVERSION_105: case RDPGFX_CAPVERSION_106: { - //RDPGFX_CAPSET_VERSION10 *caps10 = (RDPGFX_CAPSET_VERSION10 *)capsSet; + /*RDPGFX_CAPSET_VERSION10 *caps10 = (RDPGFX_CAPSET_VERSION10 *)capsSet;*/ break; } default: - rdp_debug_error(b, " Version : UNKNOWN(%d)\n", capsSet->version); + rdp_debug_error(b, " Version : UNKNOWN(%d)\n", + capsSet->version); } } /* send caps confirm */ RDPGFX_CAPS_CONFIRM_PDU capsConfirm = {}; - capsConfirm.capsSet = capsAdvertise->capsSets; // TODO: choose right one. - peerCtx->rail_grfx_server_context->CapsConfirm(peerCtx->rail_grfx_server_context, &capsConfirm); + + capsConfirm.capsSet = capsAdvertise->capsSets; /* TODO: choose right one.*/ + gfx_ctx->CapsConfirm(gfx_ctx, &capsConfirm); /* ready to use graphics channel */ - peerCtx->activationGraphicsCompleted = TRUE; + peer_ctx->activationGraphicsCompleted = TRUE; return CHANNEL_RC_OK; } static UINT -rail_grfx_client_cache_import_offer(RdpgfxServerContext* context, const RDPGFX_CACHE_IMPORT_OFFER_PDU* cacheImportOffer) +rail_grfx_client_cache_import_offer(RdpgfxServerContext *context, + const RDPGFX_CACHE_IMPORT_OFFER_PDU *cacheImportOffer) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = context->custom; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + rdp_debug_verbose(b, "Client: GrfxCacheImportOffer\n"); return CHANNEL_RC_OK; } static UINT -rail_grfx_client_frame_acknowledge(RdpgfxServerContext* context, const RDPGFX_FRAME_ACKNOWLEDGE_PDU* frameAcknowledge) +rail_grfx_client_frame_acknowledge(RdpgfxServerContext *context, + const RDPGFX_FRAME_ACKNOWLEDGE_PDU *frameAcknowledge) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = context->custom; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; rdp_debug_verbose(b, "Client: GrfxFrameAcknowledge(queueDepth = 0x%x, frameId = 0x%x, decodedFrame = %d)\n", frameAcknowledge->queueDepth, frameAcknowledge->frameId, frameAcknowledge->totalFramesDecoded); - peerCtx->acknowledgedFrameId = frameAcknowledge->frameId; - peerCtx->isAcknowledgedSuspended = (frameAcknowledge->queueDepth == 0xffffffff); + peer_ctx->acknowledgedFrameId = frameAcknowledge->frameId; + peer_ctx->isAcknowledgedSuspended = (frameAcknowledge->queueDepth == 0xffffffff); return CHANNEL_RC_OK; } #ifdef HAVE_FREERDP_GFXREDIR_H static UINT -gfxredir_client_graphics_redirection_legacy_caps(GfxRedirServerContext* context, const GFXREDIR_LEGACY_CAPS_PDU* redirectionCaps) +gfxredir_client_graphics_redirection_legacy_caps(GfxRedirServerContext *context, + const GFXREDIR_LEGACY_CAPS_PDU *redirectionCaps) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = context->custom; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; - rdp_debug(b, "Client: gfxredir_caps: version:%d\n", redirectionCaps->version); + rdp_debug(b, "Client: gfxredir_caps: version:%d\n", + redirectionCaps->version); /* This is legacy caps callback, version must be 1 */ if (redirectionCaps->version != GFXREDIR_CHANNEL_VERSION_LEGACY) { - rdp_debug_error(b, "Client: gfxredir_caps: invalid version:%d\n", redirectionCaps->version); - return ERROR_INTERNAL_ERROR; + rdp_debug_error(b, "Client: gfxredir_caps: invalid version:%d\n", + redirectionCaps->version); + return ERROR_INTERNAL_ERROR; } /* Legacy version 1 client is not supported, so don't set 'activationGraphicsRedirectionCompleted'. */ @@ -1107,26 +1152,33 @@ gfxredir_client_graphics_redirection_legacy_caps(GfxRedirServerContext* context, } static UINT -gfxredir_client_graphics_redirection_caps_advertise(GfxRedirServerContext* context, const GFXREDIR_CAPS_ADVERTISE_PDU* redirectionCaps) +gfxredir_client_graphics_redirection_caps_advertise(GfxRedirServerContext *context, + const GFXREDIR_CAPS_ADVERTISE_PDU *redirectionCaps) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = context->custom; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; const GFXREDIR_CAPS_HEADER *current = (const GFXREDIR_CAPS_HEADER *)redirectionCaps->caps; const GFXREDIR_CAPS_V2_0_PDU *capsV2 = NULL; /* dump client caps */ uint32_t i = 0; uint32_t length = redirectionCaps->length; - rdp_debug(b, "Client: gfxredir_caps: length:%d\n", redirectionCaps->length); - while (length <= redirectionCaps->length && + + rdp_debug(b, "Client: gfxredir_caps: length:%d\n", + redirectionCaps->length); + while (length <= redirectionCaps->length && length >= sizeof(GFXREDIR_CAPS_HEADER)) { - rdp_debug(b, "Client: gfxredir_caps[%d]: signature:0x%x\n", i, current->signature); - rdp_debug(b, "Client: gfxredir_caps[%d]: version:0x%x\n", i, current->version); - rdp_debug(b, "Client: gfxredir_caps[%d]: length:%d\n", i, current->length); + rdp_debug(b, "Client: gfxredir_caps[%d]: signature:0x%x\n", + i, current->signature); + rdp_debug(b, "Client: gfxredir_caps[%d]: version:0x%x\n", + i, current->version); + rdp_debug(b, "Client: gfxredir_caps[%d]: length:%d\n", + i, current->length); if (current->version == GFXREDIR_CAPS_VERSION2_0) { capsV2 = (GFXREDIR_CAPS_V2_0_PDU *)current; - rdp_debug(b, "Client: gfxredir_caps[%d]: supportedFeatures:0x%x\n", i, capsV2->supportedFeatures); + rdp_debug(b, "Client: gfxredir_caps[%d]: supportedFeatures:0x%x\n", + i, capsV2->supportedFeatures); } i++; length -= current->length; @@ -1136,9 +1188,10 @@ gfxredir_client_graphics_redirection_caps_advertise(GfxRedirServerContext* conte /* select client caps */ const GFXREDIR_CAPS_HEADER *selected = NULL; uint32_t selectedVersion = 0; + current = (const GFXREDIR_CAPS_HEADER *)redirectionCaps->caps; length = redirectionCaps->length; - while (length <= redirectionCaps->length && + while (length <= redirectionCaps->length && length >= sizeof(GFXREDIR_CAPS_HEADER)) { if (current->signature != GFXREDIR_CAPS_SIGNATURE) return ERROR_INVALID_DATA; @@ -1155,67 +1208,80 @@ gfxredir_client_graphics_redirection_caps_advertise(GfxRedirServerContext* conte if (selected) { GFXREDIR_CAPS_CONFIRM_PDU confirmPdu = {}; - rdp_debug(b, "Client: gfxredir selected caps: version:0x%x\n", selected->version); + rdp_debug(b, "Client: gfxredir selected caps: version:0x%x\n", + selected->version); confirmPdu.version = selected->version; /* return the version of selected caps */ confirmPdu.length = selected->length; /* must return same length as selected caps from advertised */ - confirmPdu.capsData = (const BYTE*)(selected+1); /* return caps data in selected caps */ + confirmPdu.capsData = (const BYTE *)(selected + 1); /* return caps data in selected caps */ - peerCtx->gfxredir_server_context->GraphicsRedirectionCapsConfirm(context, &confirmPdu); + context->GraphicsRedirectionCapsConfirm(context, + &confirmPdu); } /* ready to use graphics redirection channel */ - peerCtx->activationGraphicsRedirectionCompleted = TRUE; + peer_ctx->activationGraphicsRedirectionCompleted = true; return CHANNEL_RC_OK; } static UINT -gfxredir_client_present_buffer_ack(GfxRedirServerContext* context, const GFXREDIR_PRESENT_BUFFER_ACK_PDU* presentAck) +gfxredir_client_present_buffer_ack(GfxRedirServerContext *context, + const GFXREDIR_PRESENT_BUFFER_ACK_PDU *presentAck) { - freerdp_peer *client = (freerdp_peer*)context->custom; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + freerdp_peer *client = context->custom; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; struct weston_surface *surface; struct weston_surface_rail_state *rail_state; - rdp_debug_verbose(b, "Client: gfxredir_present_buffer_ack: windowId:0x%lx\n", presentAck->windowId); - rdp_debug_verbose(b, "Client: gfxredir_present_buffer_ack: presentId:0x%lx\n", presentAck->presentId); + rdp_debug_verbose(b, + "Client: gfxredir_present_buffer_ack: windowId:0x%lx\n", + presentAck->windowId); + rdp_debug_verbose(b, + "Client: gfxredir_present_buffer_ack: presentId:0x%lx\n", + presentAck->presentId); - peerCtx->acknowledgedFrameId = (UINT32)presentAck->presentId; + peer_ctx->acknowledgedFrameId = (uint32_t)presentAck->presentId; /* when accessing ID outside of wayland display loop thread, aquire lock */ - rdp_id_manager_lock(&peerCtx->windowId); - surface = (struct weston_surface *)rdp_id_manager_lookup(&peerCtx->windowId, presentAck->windowId); + rdp_id_manager_lock(&peer_ctx->windowId); + surface = rdp_id_manager_lookup(&peer_ctx->windowId, + presentAck->windowId); if (surface) { - rail_state = (struct weston_surface_rail_state *)surface->backend_state; + rail_state = surface->backend_state; rail_state->isUpdatePending = FALSE; } else { - rdp_debug_error(b, "Client: PresentBufferAck: WindowId:0x%lx is not found.\n", presentAck->windowId); + rdp_debug_error(b, + "Client: PresentBufferAck: WindowId:0x%lx is not found.\n", + presentAck->windowId); } - rdp_id_manager_unlock(&peerCtx->windowId); + rdp_id_manager_unlock(&peer_ctx->windowId); return CHANNEL_RC_OK; } -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ static int rdp_rail_create_cursor(struct weston_surface *surface) { struct weston_compositor *compositor = surface->compositor; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; - RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct rdp_backend *b = to_rdp_backend(compositor); + RdpPeerContext *peer_ctx = (RdpPeerContext *)b->rdp_peer->context; + struct weston_surface_rail_state *rail_state = surface->backend_state; assert_compositor_thread(b); rail_state->clientPos.width = 0; /* triggers force update on next update. */ rail_state->clientPos.height = 0; - pixman_region32_init_rect(&rail_state->damage, - 0, 0, surface->width_from_buffer, surface->height_from_buffer); + pixman_region32_init_rect(&rail_state->damage, 0, 0, + surface->width_from_buffer, + surface->height_from_buffer); - if (peerCtx->cursorSurface) - rdp_debug_error(b, "cursor surface already exists old %p vs new %p\n", peerCtx->cursorSurface, surface); - peerCtx->cursorSurface = surface; + if (peer_ctx->cursorSurface) + rdp_debug_error(b, + "cursor surface already exists old %p vs new %p\n", + peer_ctx->cursorSurface, surface); + peer_ctx->cursorSurface = surface; return 0; } @@ -1225,34 +1291,38 @@ rdp_rail_update_cursor(struct weston_surface *surface) { struct weston_pointer *pointer = surface->committed_private; struct weston_compositor *compositor = surface->compositor; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; - RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; + struct weston_surface_rail_state *rail_state = surface->backend_state; + struct rdp_backend *b = to_rdp_backend(compositor); + RdpPeerContext *peer_ctx = (RdpPeerContext *)b->rdp_peer->context; + rdpUpdate *update = b->rdp_peer->context->update; BOOL isCursorResized = FALSE; BOOL isCursorHidden = FALSE; BOOL isCursorDamanged = FALSE; struct weston_rdp_rail_window_pos newPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; struct weston_rdp_rail_window_pos newClientPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; - int contentBufferWidth; - int contentBufferHeight; + int content_buffer_width; + int content_buffer_height; assert_compositor_thread(b); assert(rail_state); - weston_surface_get_content_size(surface, &contentBufferWidth, &contentBufferHeight); - newClientPos.width = contentBufferWidth; - newClientPos.height = contentBufferHeight; + weston_surface_get_content_size(surface, + &content_buffer_width, + &content_buffer_height); + newClientPos.width = content_buffer_width; + newClientPos.height = content_buffer_height; if (surface->output) - to_client_coordinate(peerCtx, surface->output, - &newClientPos.x, &newClientPos.y, /* these are zero since position at client side doesn't matter */ - &newClientPos.width, &newClientPos.height); + to_client_coordinate(peer_ctx, surface->output, + &newClientPos.x, &newClientPos.y, /* these are zero since position at client side doesn't matter */ + &newClientPos.width, + &newClientPos.height); if (newPos.x < 0 || newPos.y < 0 || /* check if negative in weston space */ - newClientPos.width <= 0 || newClientPos.height <= 0) { + newClientPos.width <= 0 || newClientPos.height <= 0) { isCursorHidden = TRUE; rdp_debug_verbose(b, "CursorUpdate: hidden\n"); } else if (rail_state->clientPos.width != newClientPos.width || /* check if size changed in client side */ - rail_state->clientPos.height != newClientPos.height) { + rail_state->clientPos.height != newClientPos.height) { isCursorResized = TRUE; rdp_debug_verbose(b, "CursorUpdate: resized\n"); } else if (pixman_region32_not_empty(&rail_state->damage)) { @@ -1266,32 +1336,35 @@ rdp_rail_update_cursor(struct weston_surface *surface) if (isCursorHidden) { /* hide pointer */ POINTER_SYSTEM_UPDATE pointerSystem = {}; + pointerSystem.type = SYSPTR_NULL; - b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); - b->rdp_peer->context->update->pointer->PointerSystem(b->rdp_peer->context->update->context, &pointerSystem); - b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); + update->BeginPaint(update->context); + update->pointer->PointerSystem(update->context, + &pointerSystem); + update->EndPaint(update->context); } else if (isCursorResized || isCursorDamanged) { POINTER_LARGE_UPDATE pointerUpdate = {}; - int cursorBpp = 4; // Bytes Per Pixel. - int pointerBitsSize = newClientPos.width*cursorBpp*newClientPos.height; - BYTE *pointerBits = malloc(pointerBitsSize); - if (!pointerBits) { - rdp_debug_error(b, "malloc failed for cursor shape\n"); - return -1; - } + int cursorBpp = 4; /* Bytes Per Pixel. */ + int pointerBitsSize = newClientPos.width * cursorBpp*newClientPos.height; + BYTE *pointerBits = xmalloc(pointerBitsSize); /* client expects y-flip image for cursor */ if (weston_surface_copy_content(surface, - pointerBits, pointerBitsSize, 0, - newClientPos.width, newClientPos.height, - 0, 0, contentBufferWidth, contentBufferHeight, - true /* y-flip */, true /* is_argb */) < 0) { + pointerBits, + pointerBitsSize, 0, + newClientPos.width, + newClientPos.height, + 0, 0, + content_buffer_width, + content_buffer_height, + true /* y-flip */, + true /* is_argb */) < 0) { rdp_debug_error(b, "weston_surface_copy_content failed for cursor shape\n"); free(pointerBits); return -1; } - pointerUpdate.xorBpp = cursorBpp*8; // Bits Per Pixel. + pointerUpdate.xorBpp = cursorBpp * 8; /* Bits Per Pixel. */ pointerUpdate.cacheIndex = 0; pointerUpdate.hotSpotX = pointer ? pointer->hotspot_x : 0; pointerUpdate.hotSpotY = pointer ? pointer->hotspot_y : 0; @@ -1303,9 +1376,9 @@ rdp_rail_update_cursor(struct weston_surface *surface) pointerUpdate.andMaskData = NULL; rdp_debug_verbose(b, "CursorUpdate(width %d, height %d)\n", newPos.width, newPos.height); - b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); - b->rdp_peer->context->update->pointer->PointerLarge(b->rdp_peer->context->update->context, &pointerUpdate); - b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); + update->BeginPaint(update->context); + update->pointer->PointerLarge(update->context, &pointerUpdate); + update->EndPaint(update->context); free(pointerBits); } @@ -1316,22 +1389,34 @@ rdp_rail_update_cursor(struct weston_surface *surface) static void rdp_rail_create_window(struct wl_listener *listener, void *data) { - struct weston_surface *surface = (struct weston_surface *)data; + struct weston_surface *surface = data; struct weston_compositor *compositor = surface->compositor; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + struct weston_surface_rail_state *rail_state = surface->backend_state; + struct rdp_backend *b = to_rdp_backend(compositor); + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; + rdpUpdate *update; WINDOW_ORDER_INFO window_order_info = {}; WINDOW_STATE_ORDER window_state_order = {}; - struct weston_rdp_rail_window_pos pos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; - struct weston_rdp_rail_window_pos clientPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; - struct weston_geometry windowGeometry = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; + struct weston_rdp_rail_window_pos pos = { + .x = 0, .y = 0, + .width = surface->width, .height = surface->height, + }; + struct weston_rdp_rail_window_pos clientPos = { + .x = 0, .y = 0, + .width = surface->width, .height = surface->height, + }; + struct weston_geometry geometry = { + .x = 0, .y = 0, + .width = surface->width, .height = surface->height, + }; RECTANGLE_16 window_rect = { 0, 0, surface->width, surface->height }; RECTANGLE_16 window_vis = { 0, 0, surface->width, surface->height }; - uint32_t window_margin_top = 0, window_margin_left = 0, window_margin_right = 0, window_margin_bottom = 0; + uint32_t window_margin_top = 0, window_margin_left = 0; + uint32_t window_margin_right = 0, window_margin_bottom = 0; int numViews; struct weston_view *view; - UINT32 window_id; - RdpPeerContext *peerCtx; + uint32_t window_id; + RdpPeerContext *peer_ctx; /* negative width/height is not allowed, allow window to be created with zeros */ if (surface->width < 0 || surface->height < 0) { @@ -1352,26 +1437,24 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) return; } - peerCtx = (RdpPeerContext *)b->rdp_peer->context; + peer_ctx = (RdpPeerContext *)b->rdp_peer->context; assert_compositor_thread(b); - if (!peerCtx->activationRailCompleted) { + if (!peer_ctx->activationRailCompleted) { rdp_debug_verbose(b, "CreateWindow(): rdp_peer rail is not activated.\n"); return; } /* HiDef requires graphics channel to be ready */ - if (!peerCtx->activationGraphicsCompleted) { + if (!peer_ctx->activationGraphicsCompleted) { rdp_debug_verbose(b, "CreateWindow(): graphics channel is not activated.\n"); return; } if (!rail_state) { - rail_state = zalloc(sizeof *rail_state); - if (!rail_state) - return; - surface->backend_state = (void *)rail_state; + rail_state = xzalloc(sizeof *rail_state); + surface->backend_state = rail_state; } else { /* If ever encouter error for this window, no more attempt to create window */ if (rail_state->error) @@ -1379,23 +1462,23 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) } /* windowId can be assigned only after activation completed */ - if (!rdp_id_manager_allocate_id(&peerCtx->windowId, (void*)surface, &window_id)) { + if (!rdp_id_manager_allocate_id(&peer_ctx->windowId, surface, &window_id)) { rail_state->error = true; rdp_debug_error(b, "CreateWindow(): fail to insert windowId (windowId:0x%x surface:%p).\n", - window_id, surface); + window_id, surface); return; } rail_state->window_id = window_id; /* Once this surface is inserted to hash table, we want to be notified for destroy */ - assert(NULL == rail_state->destroy_listener.notify); + assert(!rail_state->destroy_listener.notify); rail_state->destroy_listener.notify = rdp_rail_destroy_window; wl_signal_add(&surface->destroy_signal, &rail_state->destroy_listener); if (surface->role_name != NULL) { - if (strncmp(surface->role_name, "wl_subsurface", sizeof("wl_subsurface")) == 0) { + if (strcmp(surface->role_name, "wl_subsurface") == 0) { rail_state->parent_surface = weston_surface_get_main_surface(surface); assert(surface != rail_state->parent_surface); - } else if (strncmp(surface->role_name, "wl_pointer-cursor", sizeof("wl_pointer-cursor")) == 0) { + } else if (strcmp(surface->role_name, "wl_pointer-cursor") == 0) { rail_state->isCursor = true; } } @@ -1409,55 +1492,62 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) numViews = 0; wl_list_for_each(view, &surface->views, surface_link) { float sx, sy; + weston_view_to_global_float(view, 0, 0, &sx, &sy); clientPos.x = pos.x = (int)sx; clientPos.y = pos.y = (int)sy; numViews++; - break; // just handle the first view for this hack + break; /* just handle the first view for this hack */ } if (numViews == 0) { view = NULL; - rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); + rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", + __func__, rail_state->window_id); } - if (is_window_shadow_remoting_disabled(peerCtx)) { + if (is_window_shadow_remoting_disabled(peer_ctx)) { /* drop window shadow area */ - b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + api->get_window_geometry(surface, &geometry); /* calculate window margin from input extents */ - if (windowGeometry.x > max(0, surface->input.extents.x1)) - window_margin_left = windowGeometry.x - max(0, surface->input.extents.x1); + if (geometry.x > max(0, surface->input.extents.x1)) + window_margin_left = geometry.x - max(0, surface->input.extents.x1); window_margin_left = max(window_margin_left, RDP_RAIL_WINDOW_RESIZE_MARGIN); - if (windowGeometry.y > max(0, surface->input.extents.y1)) - window_margin_top = windowGeometry.y - max(0, surface->input.extents.y1); + if (geometry.y > max(0, surface->input.extents.y1)) + window_margin_top = geometry.y - max(0, surface->input.extents.y1); window_margin_top = max(window_margin_top, RDP_RAIL_WINDOW_RESIZE_MARGIN); - if (min(surface->input.extents.x2, surface->width) > (windowGeometry.x + windowGeometry.width)) - window_margin_right = min(surface->input.extents.x2, surface->width) - (windowGeometry.x + windowGeometry.width); + if (min(surface->input.extents.x2, surface->width) > (geometry.x + geometry.width)) + window_margin_right = min(surface->input.extents.x2, surface->width) - (geometry.x + geometry.width); window_margin_right = max(window_margin_right, RDP_RAIL_WINDOW_RESIZE_MARGIN); - if (min(surface->input.extents.y2, surface->height) > (windowGeometry.y + windowGeometry.height)) - window_margin_bottom = min(surface->input.extents.y2, surface->height) - (windowGeometry.y + windowGeometry.height); + if (min(surface->input.extents.y2, surface->height) > (geometry.y + geometry.height)) + window_margin_bottom = min(surface->input.extents.y2, surface->height) - (geometry.y + geometry.height); window_margin_bottom = max(window_margin_bottom, RDP_RAIL_WINDOW_RESIZE_MARGIN); /* offset window origin by window geometry */ - clientPos.x += windowGeometry.x; - clientPos.y += windowGeometry.y; - clientPos.width = windowGeometry.width; - clientPos.height = windowGeometry.height; + clientPos.x += geometry.x; + clientPos.y += geometry.y; + clientPos.width = geometry.width; + clientPos.height = geometry.height; } /* apply global to output transform, and translate to client coordinate */ if (surface->output) { - to_client_coordinate(peerCtx, surface->output, - &clientPos.x, &clientPos.y, &clientPos.width, &clientPos.height); - - if (is_window_shadow_remoting_disabled(peerCtx)) { - to_client_coordinate(peerCtx, surface->output, - &window_margin_left, &window_margin_top, NULL, NULL); - to_client_coordinate(peerCtx, surface->output, - &window_margin_right, &window_margin_bottom, NULL, NULL); + to_client_coordinate(peer_ctx, surface->output, + &clientPos.x, &clientPos.y, + &clientPos.width, &clientPos.height); + + if (is_window_shadow_remoting_disabled(peer_ctx)) { + to_client_coordinate(peer_ctx, surface->output, + &window_margin_left, + &window_margin_top, + NULL, NULL); + to_client_coordinate(peer_ctx, surface->output, + &window_margin_right, + &window_margin_bottom, + NULL, NULL); } } @@ -1466,8 +1556,8 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) window_rect.right = window_vis.right = clientPos.x + clientPos.width; window_rect.bottom = window_vis.bottom = clientPos.y + clientPos.height; - window_order_info.fieldFlags = - (WINDOW_ORDER_TYPE_WINDOW | WINDOW_ORDER_STATE_NEW); + window_order_info.fieldFlags = WINDOW_ORDER_TYPE_WINDOW | + WINDOW_ORDER_STATE_NEW; window_order_info.windowId = window_id; window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_STYLE; @@ -1478,7 +1568,8 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) if (rail_state->parent_surface && rail_state->parent_surface->backend_state) { struct weston_surface_rail_state *parent_rail_state = - (struct weston_surface_rail_state *)rail_state->parent_surface->backend_state; + rail_state->parent_surface->backend_state; + window_state_order.ownerWindowId = parent_rail_state->window_id; } else { window_state_order.ownerWindowId = RDP_RAIL_DESKTOP_WINDOW_ID; @@ -1486,8 +1577,8 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) /* window is created with hidden and no taskbar icon always, and it become visbile when window has some contents to show. */ - window_order_info.fieldFlags |= - (WINDOW_ORDER_FIELD_SHOW | WINDOW_ORDER_FIELD_TASKBAR_BUTTON); + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_SHOW | + WINDOW_ORDER_FIELD_TASKBAR_BUTTON; window_state_order.showState = WINDOW_HIDE; window_state_order.TaskbarButton = 1; @@ -1523,10 +1614,10 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) window_state_order.numVisibilityRects = 1; window_state_order.visibilityRects = &window_vis; - if (is_window_shadow_remoting_disabled(peerCtx)) { + if (is_window_shadow_remoting_disabled(peer_ctx)) { /* add resize margin area */ - window_order_info.fieldFlags |= - WINDOW_ORDER_FIELD_RESIZE_MARGIN_X | WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y; + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_RESIZE_MARGIN_X | + WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y; window_state_order.resizeMarginLeft = window_margin_left; window_state_order.resizeMarginTop = window_margin_top; window_state_order.resizeMarginRight = window_margin_right; @@ -1537,10 +1628,15 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) /*window_state_order.OverlayDescription = 0;*/ rdp_debug_verbose(b, "WindowCreate(0x%x - (%d, %d, %d, %d)\n", - window_id, clientPos.x, clientPos.y, clientPos.width, clientPos.height); - b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); - b->rdp_peer->context->update->window->WindowCreate(b->rdp_peer->context->update->context, &window_order_info, &window_state_order); - b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); + window_id, clientPos.x, clientPos.y, + clientPos.width, clientPos.height); + + update = b->rdp_peer->context->update; + update->BeginPaint(update->context); + update->window->WindowCreate(update->context, + &window_order_info, + &window_state_order); + update->EndPaint(update->context); rail_state->parent_window_id = window_state_order.ownerWindowId; rail_state->pos = pos; @@ -1550,23 +1646,25 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) rail_state->window_margin_right = window_margin_right; rail_state->window_margin_bottom = window_margin_bottom; rail_state->isWindowCreated = TRUE; - rail_state->get_label = (void *)-1; // label to be re-checked at update. + rail_state->get_label = (void *)-1; /* label to be re-checked at update. */ rail_state->taskbarButton = window_state_order.TaskbarButton; - pixman_region32_init_rect(&rail_state->damage, - 0, 0, surface->width_from_buffer, surface->height_from_buffer); + pixman_region32_init_rect(&rail_state->damage, 0, 0, + surface->width_from_buffer, + surface->height_from_buffer); /* as new window created, mark z order dirty */ /* TODO: ideally this better be triggered from shell, but shell isn't notified creation/destruction of certain type of window, such as dropdown menu (popup in Wayland, override_redirect in X), thus do it here. */ - peerCtx->is_window_zorder_dirty = true; + peer_ctx->is_window_zorder_dirty = true; Exit: /* once window is successfully created, start listening repaint update */ if (!rail_state->error) { - assert(NULL == rail_state->repaint_listener.notify); + assert(!rail_state->repaint_listener.notify); rail_state->repaint_listener.notify = rdp_rail_schedule_update_window; - wl_signal_add(&surface->repaint_signal, &rail_state->repaint_listener); + wl_signal_add(&surface->repaint_signal, + &rail_state->repaint_listener); } return; @@ -1577,9 +1675,10 @@ static void rdp_destroy_shared_buffer(struct weston_surface *surface) { struct weston_compositor *compositor = surface->compositor; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; - RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; + struct weston_surface_rail_state *rail_state = surface->backend_state; + struct rdp_backend *b = to_rdp_backend(compositor); + RdpPeerContext *peer_ctx = (RdpPeerContext *)b->rdp_peer->context; + GfxRedirServerContext *redir_ctx = peer_ctx->gfxredir_server_context; assert(b->use_gfxredir); @@ -1587,9 +1686,10 @@ rdp_destroy_shared_buffer(struct weston_surface *surface) GFXREDIR_DESTROY_BUFFER_PDU destroyBuffer = {}; destroyBuffer.bufferId = rail_state->buffer_id; - peerCtx->gfxredir_server_context->DestroyBuffer(peerCtx->gfxredir_server_context, &destroyBuffer); + redir_ctx->DestroyBuffer(redir_ctx, &destroyBuffer); - rdp_id_manager_free_id(&peerCtx->bufferId, rail_state->buffer_id); + rdp_id_manager_free_id(&peer_ctx->bufferId, + rail_state->buffer_id); rail_state->buffer_id = 0; } @@ -1597,9 +1697,9 @@ rdp_destroy_shared_buffer(struct weston_surface *surface) GFXREDIR_CLOSE_POOL_PDU closePool = {}; closePool.poolId = rail_state->pool_id; - peerCtx->gfxredir_server_context->ClosePool(peerCtx->gfxredir_server_context, &closePool); + redir_ctx->ClosePool(redir_ctx, &closePool); - rdp_id_manager_free_id(&peerCtx->poolId, rail_state->pool_id); + rdp_id_manager_free_id(&peer_ctx->poolId, rail_state->pool_id); rail_state->pool_id = 0; } @@ -1607,19 +1707,21 @@ rdp_destroy_shared_buffer(struct weston_surface *surface) rail_state->surfaceBuffer = NULL; } -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ static void rdp_rail_destroy_window(struct wl_listener *listener, void *data) { - struct weston_surface *surface = (struct weston_surface *)data; + struct weston_surface *surface = data; struct weston_compositor *compositor = surface->compositor; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + struct weston_surface_rail_state *rail_state = surface->backend_state; + struct rdp_backend *b = to_rdp_backend(compositor); + RdpgfxServerContext *gfx_ctx; + rdpUpdate *update; WINDOW_ORDER_INFO window_order_info = {}; POINTER_SYSTEM_UPDATE pointerSystem = {}; - UINT32 window_id; - RdpPeerContext *peerCtx; + uint32_t window_id; + RdpPeerContext *peer_ctx; if (!rail_state) return; @@ -1632,60 +1734,66 @@ rdp_rail_destroy_window(struct wl_listener *listener, void *data) assert_compositor_thread(b); - peerCtx = (RdpPeerContext *)b->rdp_peer->context; + peer_ctx = (RdpPeerContext *)b->rdp_peer->context; + gfx_ctx = peer_ctx->rail_grfx_server_context; + update = b->rdp_peer->context->update; if (rail_state->isCursor) { pointerSystem.type = SYSPTR_NULL; - b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); - b->rdp_peer->context->update->pointer->PointerSystem(b->rdp_peer->context->update->context, &pointerSystem); - b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); - if (peerCtx->cursorSurface == surface) - peerCtx->cursorSurface = NULL; + update->BeginPaint(update->context); + update->pointer->PointerSystem(update->context, + &pointerSystem); + update->EndPaint(update->context); + if (peer_ctx->cursorSurface == surface) + peer_ctx->cursorSurface = NULL; rail_state->isCursor = false; } else { if (rail_state->isWindowCreated) { - if (rail_state->surface_id || rail_state->buffer_id) { /* When update is pending, need to wait reply from client */ /* TODO: Defer destroy to FreeRDP callback ? */ - freerdp_peer *client = (freerdp_peer*)peerCtx->rail_grfx_server_context->custom; + freerdp_peer *client = peer_ctx->rail_grfx_server_context->custom; int waitRetry = 0; while (rail_state->isUpdatePending || - (peerCtx->currentFrameId != peerCtx->acknowledgedFrameId && - !peerCtx->isAcknowledgedSuspended)) { - if (++waitRetry > 1000) { // timeout after 10 sec. + (peer_ctx->currentFrameId != peer_ctx->acknowledgedFrameId && + !peer_ctx->isAcknowledgedSuspended)) { + if (++waitRetry > 1000) { /* timeout after 10 sec. */ rdp_debug_error(b, "%s: update is still pending in client side (windowId:0x%x)\n", __func__, window_id); break; } - USleep(10000); // wait 0.01 sec. + usleep(10000); /* wait 0.01 sec. */ client->CheckFileDescriptor(client); - WTSVirtualChannelManagerCheckFileDescriptor(peerCtx->vcm); + WTSVirtualChannelManagerCheckFileDescriptor(peer_ctx->vcm); } } #ifdef HAVE_FREERDP_GFXREDIR_H if (b->use_gfxredir) rdp_destroy_shared_buffer(surface); -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ window_order_info.windowId = window_id; - window_order_info.fieldFlags = WINDOW_ORDER_TYPE_WINDOW | WINDOW_ORDER_STATE_DELETED; + window_order_info.fieldFlags = WINDOW_ORDER_TYPE_WINDOW | + WINDOW_ORDER_STATE_DELETED; - rdp_debug_verbose(b, "WindowDestroy(0x%x)\n", window_id); - b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); - b->rdp_peer->context->update->window->WindowDelete(b->rdp_peer->context->update->context, &window_order_info); - b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); + rdp_debug_verbose(b, "WindowDestroy(0x%x)\n", + window_id); + update->BeginPaint(update->context); + update->window->WindowDelete(update->context, + &window_order_info); + update->EndPaint(update->context); if (rail_state->surface_id) { - RDPGFX_DELETE_SURFACE_PDU deleteSurface = {}; - rdp_debug_verbose(b, "DeleteSurface(surfaceId:0x%x for windowsId:0x%x)\n", rail_state->surface_id, window_id); + rdp_debug_verbose(b, "DeleteSurface(surfaceId:0x%x for windowsId:0x%x)\n", + rail_state->surface_id, window_id); - deleteSurface.surfaceId = (UINT16)rail_state->surface_id; - peerCtx->rail_grfx_server_context->DeleteSurface(peerCtx->rail_grfx_server_context, &deleteSurface); + deleteSurface.surfaceId = (uint16_t)rail_state->surface_id; + gfx_ctx->DeleteSurface(gfx_ctx, &deleteSurface); - rdp_id_manager_free_id(&peerCtx->surfaceId, rail_state->surface_id); + rdp_id_manager_free_id(&peer_ctx->surfaceId, + rail_state->surface_id); rail_state->surface_id = 0; } rail_state->isWindowCreated = FALSE; @@ -1693,14 +1801,14 @@ rdp_rail_destroy_window(struct wl_listener *listener, void *data) } pixman_region32_fini(&rail_state->damage); - rdp_id_manager_free_id(&peerCtx->windowId, window_id); + rdp_id_manager_free_id(&peer_ctx->windowId, window_id); rail_state->window_id = 0; /* as window destroyed, mark z order dirty and if this is active window, clear it */ /* TODO: ideally this better be triggered from shell, but shell isn't notified creation/destruction of certain type of window, such as dropdown menu (popup in Wayland, override_redirect in X), thus do it here. */ - peerCtx->is_window_zorder_dirty = true; + peer_ctx->is_window_zorder_dirty = true; if (rail_state->repaint_listener.notify) { wl_list_remove(&rail_state->repaint_listener.link); @@ -1722,11 +1830,11 @@ rdp_rail_destroy_window(struct wl_listener *listener, void *data) static void rdp_rail_schedule_update_window(struct wl_listener *listener, void *data) { - struct weston_surface *surface = (struct weston_surface *)data; + struct weston_surface *surface = data; struct weston_compositor *compositor = surface->compositor; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - UINT32 window_id; + struct rdp_backend *b = to_rdp_backend(compositor); + struct weston_surface_rail_state *rail_state = surface->backend_state; + uint32_t window_id; if (!rail_state || rail_state->error) return; @@ -1746,15 +1854,18 @@ rdp_rail_schedule_update_window(struct wl_listener *listener, void *data) /* TODO: what width or hight 0 means? should window be hidden? */ if (surface->width == 0 || surface->height == 0) { rdp_debug_verbose(b, "surface width and height are zero WindowId:0x%x (%dx%d)\n", - rail_state->window_id, surface->width, surface->height); + rail_state->window_id, surface->width, surface->height); return; } - if (!pixman_region32_union(&rail_state->damage, &rail_state->damage, &surface->damage)) { + if (!pixman_region32_union(&rail_state->damage, + &rail_state->damage, + &surface->damage)) { /* if union failed, make entire size of bufer based on current buffer */ pixman_region32_clear(&rail_state->damage); - pixman_region32_init_rect(&rail_state->damage, - 0, 0, surface->width_from_buffer, surface->height_from_buffer); + pixman_region32_init_rect(&rail_state->damage, 0, 0, + surface->width_from_buffer, + surface->height_from_buffer); } return; @@ -1762,32 +1873,45 @@ rdp_rail_schedule_update_window(struct wl_listener *listener, void *data) struct update_window_iter_data { uint32_t output_id; - UINT32 startedFrameId; + uint32_t startedFrameId; BOOL needEndFrame; BOOL isUpdatePending; }; static int -rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter_data *iter_data) +rdp_rail_update_window(struct weston_surface *surface, + struct update_window_iter_data *iter_data) { struct weston_compositor *compositor = surface->compositor; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; + struct weston_surface_rail_state *rail_state = surface->backend_state; + struct rdp_backend *b = to_rdp_backend(compositor); + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; + rdpUpdate *update; WINDOW_ORDER_INFO window_order_info = {}; WINDOW_STATE_ORDER window_state_order = {}; - struct weston_rdp_rail_window_pos newPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; - struct weston_rdp_rail_window_pos newClientPos = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; - struct weston_geometry windowGeometry = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; - struct weston_geometry contentBufferWindowGeometry; + struct weston_rdp_rail_window_pos newPos = { + .x = 0, .y = 0, + .width = surface->width, .height = surface->height, + }; + struct weston_rdp_rail_window_pos newClientPos = { + .x = 0, .y = 0, + .width = surface->width, .height = surface->height, + }; + struct weston_geometry geometry = { + .x = 0, .y = 0, + .width = surface->width, .height = surface->height, + }; + struct weston_geometry content_buffer_window_geometry; RECTANGLE_16 window_rect; RECTANGLE_16 window_vis; - uint32_t window_margin_top = 0, window_margin_left = 0, window_margin_right = 0, window_margin_bottom = 0; + uint32_t window_margin_top = 0, window_margin_left = 0; + uint32_t window_margin_right = 0, window_margin_bottom = 0; int numViews; struct weston_view *view; - UINT32 window_id; - RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; - UINT32 new_surface_id = 0; - UINT32 old_surface_id = 0; + uint32_t window_id; + RdpPeerContext *peer_ctx = (RdpPeerContext *)b->rdp_peer->context; + uint32_t new_surface_id = 0; + uint32_t old_surface_id = 0; RAIL_UNICODE_STRING rail_window_title_string = { 0, NULL }; char window_title[256]; char window_title_mod[256]; @@ -1804,21 +1928,21 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter if (surface->role_name != NULL) { if (!rail_state->parent_surface) { - if (strncmp(surface->role_name, "wl_subsurface", sizeof("wl_subsurface")) == 0) { + if (strcmp(surface->role_name, "wl_subsurface") == 0) { rail_state->parent_surface = weston_surface_get_main_surface(surface); assert(surface != rail_state->parent_surface); } } if (!rail_state->isCursor) { - if (strncmp(surface->role_name, "wl_pointer-cursor", sizeof("wl_pointer-cursor")) == 0) { + if (strcmp(surface->role_name, "wl_pointer-cursor") == 0) { rdp_debug_error(b, "!!!cursor role is added after creation - WindowId:0x%x\n", window_id); /* convert to RDP cursor */ - rdp_rail_destroy_window(NULL, (void *)surface); + rdp_rail_destroy_window(NULL, surface); assert(!surface->backend_state); - rdp_rail_create_window(NULL, (void *)surface); - rail_state = (struct weston_surface_rail_state *)surface->backend_state; + rdp_rail_create_window(NULL, surface); + rail_state = surface->backend_state; if (!rail_state || rail_state->window_id == 0) { rdp_debug_error(b, "Fail to convert to RDP cursor - surface:0x%p\n", surface); return 0; @@ -1846,9 +1970,11 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter .type = 0, }; -// if (memcmp(&surface->buffer_to_surface_matrix.d, &identity.d, sizeof(identity.d)) != 0) -// rdp_debug(b, "buffer to surface matrix is not identity matrix type:0x%x (windowId:0x%x)\n", -// surface->buffer_to_surface_matrix.type, window_id); +#if 0 + if (memcmp(&surface->buffer_to_surface_matrix.d, &identity.d, sizeof(identity.d)) != 0) + rdp_debug(b, "buffer to surface matrix is not identity matrix type:0x%x (windowId:0x%x)\n", + surface->buffer_to_surface_matrix.type, window_id); +#endif if (!surface->is_opaque && pixman_region32_not_empty(&surface->opaque)) { int numRects = 0; @@ -1879,7 +2005,7 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rdp_debug(b, "suface has more than 1 views. numViews = %d (windowId:0x%x)\n", numViews, window_id); } - //TODO: when surface is not associated to any output, it looks must not be visible. Need to verify. + /*TODO: when surface is not associated to any output, it looks must not be visible. Need to verify. */ if (!surface->output) rdp_debug_verbose(b, "surface has no output assigned. (windowId:0x%x)\n", window_id); @@ -1893,56 +2019,72 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter numViews = 0; wl_list_for_each(view, &surface->views, surface_link) { float sx, sy; + weston_view_to_global_float(view, 0, 0, &sx, &sy); newClientPos.x = newPos.x = (int)sx; newClientPos.y = newPos.y = (int)sy; numViews++; - break; // just handle the first view for this hack + break; /* just handle the first view for this hack */ } if (numViews == 0) { view = NULL; - rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); + rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", + __func__, rail_state->window_id); } - if (is_window_shadow_remoting_disabled(peerCtx)) { + if (is_window_shadow_remoting_disabled(peer_ctx)) { /* drop window shadow area */ - b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + api->get_window_geometry(surface, &geometry); /* calculate window margin from input extents */ - if (windowGeometry.x > max(0, surface->input.extents.x1)) - window_margin_left = windowGeometry.x - max(0, surface->input.extents.x1); - window_margin_left = max(window_margin_left, RDP_RAIL_WINDOW_RESIZE_MARGIN); - - if (windowGeometry.y > max(0, surface->input.extents.y1)) - window_margin_top = windowGeometry.y - max(0, surface->input.extents.y1); - window_margin_top = max(window_margin_top, RDP_RAIL_WINDOW_RESIZE_MARGIN); - - if (min(surface->input.extents.x2, surface->width) > (windowGeometry.x + windowGeometry.width)) - window_margin_right = min(surface->input.extents.x2, surface->width) - (windowGeometry.x + windowGeometry.width); - window_margin_right = max(window_margin_right, RDP_RAIL_WINDOW_RESIZE_MARGIN); - - if (min(surface->input.extents.y2, surface->height) > (windowGeometry.y + windowGeometry.height)) - window_margin_bottom = min(surface->input.extents.y2, surface->height) - (windowGeometry.y + windowGeometry.height); - window_margin_bottom = max(window_margin_bottom, RDP_RAIL_WINDOW_RESIZE_MARGIN); + if (geometry.x > max(0, surface->input.extents.x1)) + window_margin_left = geometry.x - + max(0, surface->input.extents.x1); + window_margin_left = max(window_margin_left, + RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (geometry.y > max(0, surface->input.extents.y1)) + window_margin_top = geometry.y - + max(0, surface->input.extents.y1); + window_margin_top = max(window_margin_top, + RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (min(surface->input.extents.x2, surface->width) > (geometry.x + geometry.width)) + window_margin_right = min(surface->input.extents.x2, surface->width) - + (geometry.x + geometry.width); + window_margin_right = max(window_margin_right, + RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (min(surface->input.extents.y2, surface->height) > (geometry.y + geometry.height)) + window_margin_bottom = min(surface->input.extents.y2, surface->height) - + (geometry.y + geometry.height); + window_margin_bottom = max(window_margin_bottom, + RDP_RAIL_WINDOW_RESIZE_MARGIN); /* offset window origin by window geometry */ - newClientPos.x += windowGeometry.x; - newClientPos.y += windowGeometry.y; - newClientPos.width = windowGeometry.width; - newClientPos.height = windowGeometry.height; + newClientPos.x += geometry.x; + newClientPos.y += geometry.y; + newClientPos.width = geometry.width; + newClientPos.height = geometry.height; } - contentBufferWindowGeometry = windowGeometry; + content_buffer_window_geometry = geometry; /* apply global to output transform, and translate to client coordinate */ if (surface->output) { - to_client_coordinate(peerCtx, surface->output, - &newClientPos.x, &newClientPos.y, &newClientPos.width, &newClientPos.height); - - if (is_window_shadow_remoting_disabled(peerCtx)) { - to_client_coordinate(peerCtx, surface->output, - &window_margin_left, &window_margin_top, NULL, NULL); - to_client_coordinate(peerCtx, surface->output, - &window_margin_right, &window_margin_bottom, NULL, NULL); + to_client_coordinate(peer_ctx, surface->output, + &newClientPos.x, &newClientPos.y, + &newClientPos.width, + &newClientPos.height); + + if (is_window_shadow_remoting_disabled(peer_ctx)) { + to_client_coordinate(peer_ctx, surface->output, + &window_margin_left, + &window_margin_top, + NULL, NULL); + to_client_coordinate(peer_ctx, surface->output, + &window_margin_right, + &window_margin_bottom, + NULL, NULL); } } @@ -1955,21 +2097,20 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter /* Adjust the Windows size and position on the screen */ if (rail_state->clientPos.x != newClientPos.x || - rail_state->clientPos.y != newClientPos.y || - rail_state->clientPos.width != newClientPos.width || - rail_state->clientPos.height != newClientPos.height || - rail_state->is_minimized != rail_state->is_minimized_requested || - rail_state->get_label != surface->get_label || - rail_state->forceUpdateWindowState) { - + rail_state->clientPos.y != newClientPos.y || + rail_state->clientPos.width != newClientPos.width || + rail_state->clientPos.height != newClientPos.height || + rail_state->is_minimized != rail_state->is_minimized_requested || + rail_state->get_label != surface->get_label || + rail_state->forceUpdateWindowState) { window_order_info.windowId = window_id; - window_order_info.fieldFlags = + window_order_info.fieldFlags = WINDOW_ORDER_TYPE_WINDOW; if (rail_state->parent_surface && rail_state->parent_surface->backend_state) { struct weston_surface_rail_state *parent_rail_state = - (struct weston_surface_rail_state *)rail_state->parent_surface->backend_state; + rail_state->parent_surface->backend_state; if (rail_state->parent_window_id != parent_rail_state->window_id) { window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_OWNER; @@ -1978,7 +2119,8 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rail_state->parent_window_id = parent_rail_state->window_id; rdp_debug_verbose(b, "WindowUpdate(0x%x - parent window id:%x)\n", - window_id, rail_state->parent_window_id); + window_id, + rail_state->parent_window_id); } } @@ -1991,18 +2133,21 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rail_state->is_minimized = rail_state->is_minimized_requested; rdp_debug_verbose(b, "WindowUpdate(0x%x - is_minimized:%d)\n", - window_id, rail_state->is_minimized_requested); + window_id, + rail_state->is_minimized_requested); } if (rail_state->is_maximized != rail_state->is_maximized_requested) { rdp_debug_verbose(b, "WindowUpdate(0x%x - is_maximized:%d)\n", - window_id, rail_state->is_maximized_requested); + window_id, + rail_state->is_maximized_requested); rail_state->is_maximized = rail_state->is_maximized_requested; } if (rail_state->is_fullscreen != rail_state->is_fullscreen_requested) { rdp_debug_verbose(b, "WindowUpdate(0x%x - is_fullscreen:%d)\n", - window_id, rail_state->is_fullscreen_requested); + window_id, + rail_state->is_fullscreen_requested); rail_state->is_fullscreen = rail_state->is_fullscreen_requested; window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_STYLE; @@ -2016,15 +2161,17 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter } if (rail_state->forceUpdateWindowState || - rail_state->get_label != surface->get_label) { + rail_state->get_label != surface->get_label) { window_order_info.fieldFlags |= - WINDOW_ORDER_FIELD_TASKBAR_BUTTON; - if (rail_state->parent_surface || (surface->get_label == NULL)) + WINDOW_ORDER_FIELD_TASKBAR_BUTTON; + if (rail_state->parent_surface || + (surface->get_label == NULL)) window_state_order.TaskbarButton = 1; else window_state_order.TaskbarButton = 0; - if (surface->get_label && surface->get_label(surface, window_title, sizeof(window_title))) { + if (surface->get_label && + surface->get_label(surface, window_title, sizeof(window_title))) { /* see rdprail-shell for naming convension for label */ /* TODO: For X11 app, ideally it should check "override" property, but somehow Andriod Studio's (at least 4.1.1) dropdown menu is not "override" window, @@ -2033,9 +2180,9 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter in taskbar. */ if (strncmp(window_title, "child window", sizeof("child window") - 1) == 0) window_state_order.TaskbarButton = 1; - title = strchr(window_title, 39); + title = strchr(window_title, '\''); if (title) { - char *end = strrchr(window_title, 39); + char *end = strrchr(window_title, '\''); if (end != title) { *title++ = '\0'; *end = '\0'; @@ -2047,28 +2194,27 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter /* this is for debugging only */ if (!b->use_gfxredir && b->enable_copy_warning_title) { if (snprintf(window_title_mod, - sizeof window_title_mod, - "[WARN:COPY MODE] %s (%s)", - title, b->rdprail_shell_name ? b->rdprail_shell_name : "Linux") > 0) + sizeof window_title_mod, + "[WARN:COPY MODE] %s (%s)", + title, b->rdprail_shell_name ? b->rdprail_shell_name : "Linux") > 0) title = window_title_mod; } else -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ if (b->enable_distro_name_title) { if (snprintf(window_title_mod, - sizeof window_title_mod, - "%s (%s)", - title, b->rdprail_shell_name ? b->rdprail_shell_name : "Linux") > 0) + sizeof window_title_mod, + "%s (%s)", + title, b->rdprail_shell_name ? b->rdprail_shell_name : "Linux") > 0) title = window_title_mod; } else { if (snprintf(window_title_mod, - sizeof window_title_mod, - "%s", - title) > 0) + sizeof window_title_mod, + "%s", + title) > 0) title = window_title_mod; } if (utf8_string_to_rail_string(title, &rail_window_title_string)) { - window_order_info.fieldFlags |= - WINDOW_ORDER_FIELD_TITLE; + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_TITLE; window_state_order.titleInfo = rail_window_title_string; } } @@ -2076,25 +2222,26 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rail_state->get_label = surface->get_label; rail_state->taskbarButton = window_state_order.TaskbarButton; - rdp_debug_verbose(b, "WindowUpdate(0x%x - title \"%s\") TaskbarButton:%d\n", - window_id, title, window_state_order.TaskbarButton); + rdp_debug_verbose(b, + "WindowUpdate(0x%x - title \"%s\") TaskbarButton:%d\n", + window_id, title, + window_state_order.TaskbarButton); } else { /* There seems a bug in mstsc client that previous taskbar button state is not preserved, thus sending taskbar field always. */ - window_order_info.fieldFlags |= - WINDOW_ORDER_FIELD_TASKBAR_BUTTON; - window_state_order.TaskbarButton = (BYTE) rail_state->taskbarButton; + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_TASKBAR_BUTTON; + window_state_order.TaskbarButton = (BYTE)rail_state->taskbarButton; } - if (is_window_shadow_remoting_disabled(peerCtx)) { + if (is_window_shadow_remoting_disabled(peer_ctx)) { if (rail_state->forceUpdateWindowState || - rail_state->window_margin_left != window_margin_left || - rail_state->window_margin_top != window_margin_top || - rail_state->window_margin_right != window_margin_right || - rail_state->window_margin_bottom != window_margin_bottom) { + rail_state->window_margin_left != window_margin_left || + rail_state->window_margin_top != window_margin_top || + rail_state->window_margin_right != window_margin_right || + rail_state->window_margin_bottom != window_margin_bottom) { /* add resize margin area */ - window_order_info.fieldFlags |= - WINDOW_ORDER_FIELD_RESIZE_MARGIN_X | WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y; + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_RESIZE_MARGIN_X | + WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y; window_state_order.resizeMarginLeft = window_margin_left; window_state_order.resizeMarginTop = window_margin_top; window_state_order.resizeMarginRight = window_margin_right; @@ -2105,25 +2252,31 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rail_state->window_margin_right = window_margin_right; rail_state->window_margin_bottom = window_margin_bottom; - rdp_debug_verbose(b, "WindowUpdate(0x%x - window margin left:%d, top:%d, right:%d, bottom:%d\n", - window_id, window_margin_left, window_margin_top, window_margin_right, window_margin_bottom); + rdp_debug_verbose(b, + "WindowUpdate(0x%x - window margin left:%d, top:%d, right:%d, bottom:%d\n", + window_id, + window_margin_left, + window_margin_top, + window_margin_right, + window_margin_bottom); } } if (rail_state->forceUpdateWindowState || - rail_state->clientPos.width != newClientPos.width || - rail_state->clientPos.height != newClientPos.height || - rail_state->output != surface->output) { - window_order_info.fieldFlags |= - (WINDOW_ORDER_FIELD_WND_SIZE | - WINDOW_ORDER_FIELD_WND_RECTS | - WINDOW_ORDER_FIELD_VISIBILITY | - WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE); + rail_state->clientPos.width != newClientPos.width || + rail_state->clientPos.height != newClientPos.height || + rail_state->output != surface->output) { + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_WND_SIZE | + WINDOW_ORDER_FIELD_WND_RECTS | + WINDOW_ORDER_FIELD_VISIBILITY | + WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE; window_rect.top = window_vis.top = newClientPos.y; window_rect.left = window_vis.left = newClientPos.x; - window_rect.right = window_vis.right = newClientPos.x + newClientPos.width; - window_rect.bottom = window_vis.bottom = newClientPos.y + newClientPos.height; + window_rect.right = window_vis.right = newClientPos.x + + newClientPos.width; + window_rect.bottom = window_vis.bottom = newClientPos.y + + newClientPos.height; window_state_order.windowWidth = newClientPos.width; window_state_order.windowHeight = newClientPos.height; @@ -2147,8 +2300,8 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter /* if previous window size is 0 and new window is not, show and place in taskbar (if not set yet) */ if (rail_state->output == NULL || - ((rail_state->clientPos.width == 0 || rail_state->clientPos.height == 0) && - newClientPos.width && newClientPos.height)) { + ((rail_state->clientPos.width == 0 || rail_state->clientPos.height == 0) && + newClientPos.width && newClientPos.height)) { if ((window_order_info.fieldFlags & WINDOW_ORDER_FIELD_TASKBAR_BUTTON) == 0) { window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_TASKBAR_BUTTON; @@ -2160,27 +2313,29 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter window_state_order.showState = WINDOW_SHOW; } - rdp_debug_verbose(b, "WindowUpdate(0x%x - taskbar:%d showState:%d))\n", - window_id, - window_state_order.TaskbarButton, - window_state_order.showState); + rdp_debug_verbose(b, + "WindowUpdate(0x%x - taskbar:%d showState:%d))\n", + window_id, + window_state_order.TaskbarButton, + window_state_order.showState); } /* if new window size is 0, and previous is not, or no output assigned, do not show window and do not place in taskbar */ if (surface->output == NULL || - ((newClientPos.width == 0 || newClientPos.height == 0) && - rail_state->clientPos.width && rail_state->clientPos.height)) { + ((newClientPos.width == 0 || newClientPos.height == 0) && + rail_state->clientPos.width && rail_state->clientPos.height)) { window_order_info.fieldFlags |= - (WINDOW_ORDER_FIELD_SHOW | - WINDOW_ORDER_FIELD_TASKBAR_BUTTON); + WINDOW_ORDER_FIELD_SHOW | + WINDOW_ORDER_FIELD_TASKBAR_BUTTON; window_state_order.TaskbarButton = 1; window_state_order.showState = WINDOW_HIDE; - rdp_debug_verbose(b, "WindowUpdate(0x%x - taskbar:%d showState:%d))\n", - window_id, - window_state_order.TaskbarButton, - window_state_order.showState); + rdp_debug_verbose(b, + "WindowUpdate(0x%x - taskbar:%d showState:%d))\n", + window_id, + window_state_order.TaskbarButton, + window_state_order.showState); } rail_state->pos.width = newPos.width; @@ -2189,13 +2344,16 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rail_state->clientPos.height = newClientPos.height; rail_state->output = surface->output; - rdp_debug_verbose(b, "WindowUpdate(0x%x - size (%d, %d) in RDP client size (%d, %d)\n", - window_id, newPos.width, newPos.height, newClientPos.width, newClientPos.height); + rdp_debug_verbose(b, + "WindowUpdate(0x%x - size (%d, %d) in RDP client size (%d, %d)\n", + window_id, newPos.width, + newPos.height, newClientPos.width, + newClientPos.height); } if (rail_state->forceUpdateWindowState || - rail_state->clientPos.x != newClientPos.x || - rail_state->clientPos.y != newClientPos.y) { + rail_state->clientPos.x != newClientPos.x || + rail_state->clientPos.y != newClientPos.y) { window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_WND_OFFSET; @@ -2207,77 +2365,88 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter rail_state->clientPos.x = newClientPos.x; rail_state->clientPos.y = newClientPos.y; - rdp_debug_verbose(b, "WindowUpdate(0x%x - pos (%d, %d) - RDP client pos (%d, %d)\n", - window_id, newPos.x, newPos.y, newClientPos.x, newClientPos.y); + rdp_debug_verbose(b, + "WindowUpdate(0x%x - pos (%d, %d) - RDP client pos (%d, %d)\n", + window_id, newPos.x, newPos.y, + newClientPos.x, newClientPos.y); } - b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); - b->rdp_peer->context->update->window->WindowUpdate(b->rdp_peer->context->update->context, &window_order_info, &window_state_order); - b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); + update = b->rdp_peer->context->update; + update->BeginPaint(update->context); + update->window->WindowUpdate(update->context, &window_order_info, &window_state_order); + update->EndPaint(update->context); - if (rail_window_title_string.string) - free(rail_window_title_string.string); + free(rail_window_title_string.string); rail_state->forceUpdateWindowState = false; } /* update window buffer contents */ { +#ifdef HAVE_FREERDP_GFXREDIR_H + GfxRedirServerContext *redir_ctx = peer_ctx->gfxredir_server_context; +#endif /* HAVE_FREERDP_GFXREDIR_H */ BOOL isBufferSizeChanged = FALSE; float scaleFactorWidth = 1.0f, scaleFactorHeight = 1.0f; - int damageWidth, damageHeight; - int copyBufferStride, copyBufferSize; - int copyBufferWidth, copyBufferHeight; - int clientBufferWidth, clientBufferHeight; - int contentBufferWidth, contentBufferHeight; - int bufferBpp = 4; // Bytes Per Pixel. + int damage_width, damage_height; + int copy_buffer_stride, copy_buffer_size; + int copy_buffer_width, copy_buffer_height; + int client_buffer_width, client_buffer_height; + int content_buffer_width, content_buffer_height; + int bufferBpp = 4; /* Bytes Per Pixel. */ bool hasAlpha = view ? !weston_view_is_opaque(view, &view->transform.boundingbox) : false; - pixman_box32_t damageBox = *pixman_region32_extents(&rail_state->damage); + pixman_box32_t damage_box = *pixman_region32_extents(&rail_state->damage); long page_size = sysconf(_SC_PAGESIZE); /* clientBuffer represents Windows size on client desktop */ /* this size is adjusted on whether including or excluding window shadow */ - clientBufferWidth = newClientPos.width; - clientBufferHeight = newClientPos.height; + client_buffer_width = newClientPos.width; + client_buffer_height = newClientPos.height; /* contentBuffer represents buffer from Linux application. */ /* this size could be larger than it's window size for native HI-DPI rendering */ - weston_surface_get_content_size(surface, &contentBufferWidth, &contentBufferHeight); + weston_surface_get_content_size(surface, + &content_buffer_width, + &content_buffer_height); /* scale window geometry to content buffer base */ rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, - &contentBufferWindowGeometry.x, &contentBufferWindowGeometry.y); + &content_buffer_window_geometry.x, + &content_buffer_window_geometry.y); rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, - &contentBufferWindowGeometry.width, &contentBufferWindowGeometry.height); + &content_buffer_window_geometry.width, + &content_buffer_window_geometry.height); /* copy buffer represents the buffer allocated to share with RDP client. */ /* this can be shared memory buffer or grfx channel surface */ - copyBufferWidth = contentBufferWindowGeometry.width; - copyBufferHeight = contentBufferWindowGeometry.height; - copyBufferStride = copyBufferWidth * bufferBpp; - copyBufferSize = ((copyBufferStride * copyBufferHeight) + page_size - 1) & ~(page_size - 1); + copy_buffer_width = content_buffer_window_geometry.width; + copy_buffer_height = content_buffer_window_geometry.height; + copy_buffer_stride = copy_buffer_width * bufferBpp; + copy_buffer_size = ((copy_buffer_stride * copy_buffer_height) + page_size - 1) & ~(page_size - 1); - if (contentBufferWidth && contentBufferHeight && - copyBufferWidth && copyBufferHeight) { + if (content_buffer_width && content_buffer_height && + copy_buffer_width && copy_buffer_height) { #ifdef HAVE_FREERDP_GFXREDIR_H if (b->use_gfxredir) { - scaleFactorWidth = 1.0f; // scaling is done by client. - scaleFactorHeight = 1.0f; // scaling is done by client. - + /* scaling is done by client. */ + scaleFactorWidth = 1.0f; + scaleFactorHeight = 1.0f; } else { #else { -#endif // HAVE_FREERDP_GFXREDIR_H - scaleFactorWidth = (float)surface->width / contentBufferWidth; - scaleFactorHeight = (float)surface->height / contentBufferHeight; +#endif /* HAVE_FREERDP_GFXREDIR_H */ + scaleFactorWidth = (float)surface->width / + content_buffer_width; + scaleFactorHeight = (float)surface->height / + content_buffer_height; } - if (rail_state->bufferWidth != copyBufferWidth || - rail_state->bufferHeight != copyBufferHeight) + if (rail_state->bufferWidth != copy_buffer_width || + rail_state->bufferHeight != copy_buffer_height) isBufferSizeChanged = TRUE; if (isBufferSizeChanged || rail_state->forceRecreateSurface || - (rail_state->surfaceBuffer == NULL && rail_state->surface_id == 0)) { + (rail_state->surfaceBuffer == NULL && rail_state->surface_id == 0)) { #ifdef HAVE_FREERDP_GFXREDIR_H if (b->use_gfxredir) { @@ -2290,42 +2459,45 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter } assert(rail_state->surfaceBuffer == NULL); assert(rail_state->shared_memory.addr == NULL); - rail_state->shared_memory.size = copyBufferSize; + rail_state->shared_memory.size = copy_buffer_size; if (rdp_allocate_shared_memory(b, &rail_state->shared_memory)) { - UINT32 new_pool_id = 0; - if (rdp_id_manager_allocate_id(&peerCtx->poolId, (void*)surface, &new_pool_id)) { - // +1 for NULL terminate. - unsigned short sectionName[RDP_SHARED_MEMORY_NAME_SIZE + 1]; - // In Linux wchar_t is 4 types, but Windows wants 2 bytes wchar... - // convert to 2 bytes wchar_t. + uint32_t new_pool_id = 0; + + if (rdp_id_manager_allocate_id(&peer_ctx->poolId, surface, &new_pool_id)) { + /* +1 for NULL terminate. */ + unsigned short section_name[RDP_SHARED_MEMORY_NAME_SIZE + 1]; + /* In Linux wchar_t is 4 types, but Windows wants 2 bytes wchar... + * convert to 2 bytes wchar_t. + */ for (uint32_t i = 0; i < RDP_SHARED_MEMORY_NAME_SIZE; i++) - sectionName[i] = rail_state->shared_memory.name[i]; - sectionName[RDP_SHARED_MEMORY_NAME_SIZE] = 0; - - GFXREDIR_OPEN_POOL_PDU openPool = {}; - openPool.poolId = new_pool_id; - openPool.poolSize = copyBufferSize; - openPool.sectionNameLength = RDP_SHARED_MEMORY_NAME_SIZE + 1; - openPool.sectionName = sectionName; - if (peerCtx->gfxredir_server_context->OpenPool( - peerCtx->gfxredir_server_context, &openPool) == 0) { - UINT32 new_buffer_id = 0; - if (rdp_id_manager_allocate_id(&peerCtx->bufferId, (void*)surface, &new_buffer_id)) { - GFXREDIR_CREATE_BUFFER_PDU createBuffer = {}; - createBuffer.poolId = openPool.poolId; - createBuffer.bufferId = new_buffer_id; - createBuffer.offset = 0; - createBuffer.stride = copyBufferStride; - createBuffer.width = copyBufferWidth; - createBuffer.height = copyBufferHeight; - createBuffer.format = GFXREDIR_BUFFER_PIXEL_FORMAT_ARGB_8888; - if (peerCtx->gfxredir_server_context->CreateBuffer( - peerCtx->gfxredir_server_context, &createBuffer) == 0) { + section_name[i] = rail_state->shared_memory.name[i]; + section_name[RDP_SHARED_MEMORY_NAME_SIZE] = 0; + + GFXREDIR_OPEN_POOL_PDU open_pool = {}; + open_pool.poolId = new_pool_id; + open_pool.poolSize = copy_buffer_size; + open_pool.sectionNameLength = RDP_SHARED_MEMORY_NAME_SIZE + 1; + open_pool.sectionName = section_name; + + if (redir_ctx->OpenPool(redir_ctx, &open_pool) == 0) { + uint32_t new_buffer_id = 0; + + if (rdp_id_manager_allocate_id(&peer_ctx->bufferId, (void *)surface, &new_buffer_id)) { + GFXREDIR_CREATE_BUFFER_PDU create_buffer = {}; + + create_buffer.poolId = open_pool.poolId; + create_buffer.bufferId = new_buffer_id; + create_buffer.offset = 0; + create_buffer.stride = copy_buffer_stride; + create_buffer.width = copy_buffer_width; + create_buffer.height = copy_buffer_height; + create_buffer.format = GFXREDIR_BUFFER_PIXEL_FORMAT_ARGB_8888; + if (redir_ctx->CreateBuffer(redir_ctx, &create_buffer) == 0) { rail_state->surfaceBuffer = rail_state->shared_memory.addr; - rail_state->buffer_id = createBuffer.bufferId; - rail_state->pool_id = openPool.poolId; - rail_state->bufferWidth = copyBufferWidth; - rail_state->bufferHeight = copyBufferHeight; + rail_state->buffer_id = create_buffer.bufferId; + rail_state->pool_id = open_pool.poolId; + rail_state->bufferWidth = copy_buffer_width; + rail_state->bufferHeight = copy_buffer_height; } } } @@ -2337,182 +2509,217 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter } else { #else { -#endif // HAVE_FREERDP_GFXREDIR_H - if (rdp_id_manager_allocate_id(&peerCtx->surfaceId, (void*)surface, &new_surface_id)) { +#endif /* HAVE_FREERDP_GFXREDIR_H */ + if (rdp_id_manager_allocate_id(&peer_ctx->surfaceId, surface, &new_surface_id)) { + RdpgfxServerContext *gfx_ctx; + + gfx_ctx = peer_ctx->rail_grfx_server_context; RDPGFX_CREATE_SURFACE_PDU createSurface = {}; /* create surface */ - rdp_debug_verbose(b, "CreateSurface(surfaceId:0x%x - (%d, %d) size:%d for windowsId:0x%x)\n", - new_surface_id, copyBufferWidth, copyBufferHeight, copyBufferSize, window_id); - createSurface.surfaceId = (UINT16)new_surface_id; - createSurface.width = copyBufferWidth; - createSurface.height = copyBufferHeight; + rdp_debug_verbose(b, + "CreateSurface(surfaceId:0x%x - (%d, %d) size:%d for windowsId:0x%x)\n", + new_surface_id, + copy_buffer_width, + copy_buffer_height, + copy_buffer_size, + window_id); + createSurface.surfaceId = (uint16_t)new_surface_id; + createSurface.width = copy_buffer_width; + createSurface.height = copy_buffer_height; /* regardless buffer as alpha or not, always use alpha to avoid mstsc bug */ createSurface.pixelFormat = GFX_PIXEL_FORMAT_ARGB_8888; - if (peerCtx->rail_grfx_server_context->CreateSurface(peerCtx->rail_grfx_server_context, &createSurface) == 0) { + if (gfx_ctx->CreateSurface(gfx_ctx, &createSurface) == 0) { /* store new surface id */ old_surface_id = rail_state->surface_id; rail_state->surface_id = new_surface_id; - rail_state->bufferWidth = copyBufferWidth; - rail_state->bufferHeight = copyBufferHeight; + rail_state->bufferWidth = copy_buffer_width; + rail_state->bufferHeight = copy_buffer_height; } } } rail_state->forceRecreateSurface = false; /* make entire content buffer damaged */ - damageBox.x1 = 0; - damageBox.y1 = 0; - damageBox.x2 = contentBufferWidth; - damageBox.y2 = contentBufferHeight; - } else if (damageBox.x2 > 0 && damageBox.y2 > 0) { + damage_box.x1 = 0; + damage_box.y1 = 0; + damage_box.x2 = content_buffer_width; + damage_box.y2 = content_buffer_height; + } else if (damage_box.x2 > 0 && damage_box.y2 > 0) { /* scale damage using surface to buffer matrix */ - rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, &damageBox.x1, &damageBox.y1); - rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, &damageBox.x2, &damageBox.y2); + rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, + &damage_box.x1, + &damage_box.y1); + rdp_matrix_transform_position(&surface->surface_to_buffer_matrix, + &damage_box.x2, &damage_box.y2); } - /* damageBox represents damaged area in contentBuffer */ - /* if it's not remoting window shadow, exclude the area from damageBox */ - if (is_window_shadow_remoting_disabled(peerCtx)) { - if (damageBox.x1 < contentBufferWindowGeometry.x) - damageBox.x1 = contentBufferWindowGeometry.x; - if (damageBox.x2 > contentBufferWindowGeometry.x + contentBufferWindowGeometry.width) - damageBox.x2 = contentBufferWindowGeometry.x + contentBufferWindowGeometry.width; - if (damageBox.y1 < contentBufferWindowGeometry.y) - damageBox.y1 = contentBufferWindowGeometry.y; - if (damageBox.y2 > contentBufferWindowGeometry.y + contentBufferWindowGeometry.height) - damageBox.y2 = contentBufferWindowGeometry.y + contentBufferWindowGeometry.height; - damageWidth = damageBox.x2 - damageBox.x1; - damageHeight = damageBox.y2 - damageBox.y1; + /* damage_box represents damaged area in contentBuffer */ + /* if it's not remoting window shadow, exclude the area from damage_box */ + if (is_window_shadow_remoting_disabled(peer_ctx)) { + if (damage_box.x1 < content_buffer_window_geometry.x) + damage_box.x1 = content_buffer_window_geometry.x; + if (damage_box.x2 > content_buffer_window_geometry.x + content_buffer_window_geometry.width) + damage_box.x2 = content_buffer_window_geometry.x + content_buffer_window_geometry.width; + if (damage_box.y1 < content_buffer_window_geometry.y) + damage_box.y1 = content_buffer_window_geometry.y; + if (damage_box.y2 > content_buffer_window_geometry.y + content_buffer_window_geometry.height) + damage_box.y2 = content_buffer_window_geometry.y + content_buffer_window_geometry.height; + damage_width = damage_box.x2 - damage_box.x1; + damage_height = damage_box.y2 - damage_box.y1; } else { - damageWidth = damageBox.x2 - damageBox.x1; - if (damageWidth > contentBufferWidth) { - rdp_debug(b, "damageWidth (%d) is larger than content width(%d), clamp to avoid protocol error.\n", - damageWidth, contentBufferWidth); - damageBox.x1 = 0; - damageBox.x2 = contentBufferWidth; - damageWidth = contentBufferWidth; + damage_width = damage_box.x2 - damage_box.x1; + if (damage_width > content_buffer_width) { + rdp_debug(b, + "damage_width (%d) is larger than content width(%d), clamp to avoid protocol error.\n", + damage_width, + content_buffer_width); + damage_box.x1 = 0; + damage_box.x2 = content_buffer_width; + damage_width = content_buffer_width; } - damageHeight = damageBox.y2 - damageBox.y1; - if (damageHeight > contentBufferHeight) { - rdp_debug(b, "damageHeight (%d) is larger than content height(%d), clamp to avoid protocol error.\n", - damageHeight, contentBufferHeight); - damageBox.y1 = 0; - damageBox.y2 = contentBufferHeight; - damageHeight = contentBufferHeight; + damage_height = damage_box.y2 - damage_box.y1; + if (damage_height > content_buffer_height) { + rdp_debug(b, + "damage_height (%d) is larger than content height(%d), clamp to avoid protocol error.\n", + damage_height, + content_buffer_height); + damage_box.y1 = 0; + damage_box.y2 = content_buffer_height; + damage_height = content_buffer_height; } } } else { /* no content buffer bound, thus no damage */ - damageWidth = 0; - damageHeight = 0; + damage_width = 0; + damage_height = 0; } /* Check to see if we have any content update to send to the new surface */ - if (damageWidth > 0 && damageHeight > 0) { + if (damage_width > 0 && damage_height > 0) { #ifdef HAVE_FREERDP_GFXREDIR_H - if (b->use_gfxredir && - rail_state->surfaceBuffer) { - - int copyDamageX1 = (float)(damageBox.x1 - contentBufferWindowGeometry.x) * scaleFactorWidth; - int copyDamageY1 = (float)(damageBox.y1 - contentBufferWindowGeometry.y) * scaleFactorHeight; - int copyDamageWidth = (float)damageWidth * scaleFactorWidth; - int copyDamageHeight = (float)damageHeight * scaleFactorHeight; - int copyStartOffset = copyDamageX1*bufferBpp + copyDamageY1*copyBufferStride; - BYTE *copyBufferBits = (BYTE*)(rail_state->surfaceBuffer) + copyStartOffset; - - rdp_debug_verbose(b, "copy source: x:%d, y:%d, width:%d, height:%d\n", - damageBox.x1, damageBox.y1, damageWidth, damageHeight); - rdp_debug_verbose(b, "copy target: x:%d, y:%d, width:%d, height:%d, stride:%d\n", - copyDamageX1, copyDamageY1, copyDamageWidth, copyDamageHeight, copyBufferStride); - rdp_debug_verbose(b, "copy scale: scaleFactorWidth:%5.3f, scaleFactorHeight:%5.3f\n", - scaleFactorWidth, scaleFactorHeight); + if (b->use_gfxredir && rail_state->surfaceBuffer) { + int copy_damage_x1 = (float)(damage_box.x1 - content_buffer_window_geometry.x) * scaleFactorWidth; + int copy_damage_y1 = (float)(damage_box.y1 - content_buffer_window_geometry.y) * scaleFactorHeight; + int copy_damage_width = (float)damage_width * scaleFactorWidth; + int copy_damage_height = (float)damage_height * scaleFactorHeight; + int copyStartOffset = copy_damage_x1 * bufferBpp + copy_damage_y1 * copy_buffer_stride; + BYTE *copy_buffer_bits = (BYTE *)(rail_state->surfaceBuffer) + copyStartOffset; + GfxRedirServerContext *redir_ctx; + redir_ctx = peer_ctx->gfxredir_server_context; + + rdp_debug_verbose(b, + "copy source: x:%d, y:%d, width:%d, height:%d\n", + damage_box.x1, damage_box.y1, + damage_width, damage_height); + rdp_debug_verbose(b, + "copy target: x:%d, y:%d, width:%d, height:%d, stride:%d\n", + copy_damage_x1, copy_damage_y1, + copy_damage_width, + copy_damage_height, + copy_buffer_stride); + rdp_debug_verbose(b, + "copy scale: scaleFactorWidth:%5.3f, scaleFactorHeight:%5.3f\n", + scaleFactorWidth, + scaleFactorHeight); if (weston_surface_copy_content(surface, - copyBufferBits, copyBufferSize, copyBufferStride, - copyDamageWidth, copyDamageHeight, - damageBox.x1, damageBox.y1, damageWidth, damageHeight, - false /* y-flip */, true /* is_argb */) < 0) { + copy_buffer_bits, + copy_buffer_size, + copy_buffer_stride, + copy_damage_width, + copy_damage_height, + damage_box.x1, + damage_box.y1, + damage_width, + damage_height, + false /* y-flip */, + true /* is_argb */) < 0) { rdp_debug_error(b, - "weston_surface_copy_content failed for windowId:0x%x, copyBuffer:%dx%d %d, damage:(%d,%d) %dx%d, content:%dx%d\n", - window_id, copyDamageWidth, copyDamageHeight, copyBufferSize, - damageBox.x1, damageBox.y1, damageWidth, damageHeight, - contentBufferWidth, contentBufferHeight); + "weston_surface_copy_content failed for windowId:0x%x, copyBuffer:%dx%d %d, damage:(%d,%d) %dx%d, content:%dx%d\n", + window_id, + copy_damage_width, + copy_damage_height, + copy_buffer_size,damage_box.x1, + damage_box.y1, + damage_width, + damage_height, + content_buffer_width, + content_buffer_height); return -1; } - GFXREDIR_PRESENT_BUFFER_PDU presentBuffer = {}; - RECTANGLE_32 opaqueRect; + GFXREDIR_PRESENT_BUFFER_PDU present_buffer = {}; + RECTANGLE_32 opaque_rect; /* specify opaque area */ if (!hasAlpha) { - opaqueRect.left = copyDamageX1; - opaqueRect.top = copyDamageY1; - opaqueRect.width = copyDamageWidth; - opaqueRect.height = copyDamageHeight; + opaque_rect.left = copy_damage_x1; + opaque_rect.top = copy_damage_y1; + opaque_rect.width = copy_damage_width; + opaque_rect.height = copy_damage_height; } - presentBuffer.timestamp = 0; /* set 0 to disable A/V sync at client side */ - presentBuffer.presentId = ++peerCtx->currentFrameId; - presentBuffer.windowId = window_id; - presentBuffer.bufferId = rail_state->buffer_id; - presentBuffer.orientation = 0; // 0, 90, 180 or 270. - presentBuffer.targetWidth = newClientPos.width; - presentBuffer.targetHeight = newClientPos.height; - presentBuffer.dirtyRect.left = copyDamageX1; - presentBuffer.dirtyRect.top = copyDamageY1; - presentBuffer.dirtyRect.width = copyDamageWidth; - presentBuffer.dirtyRect.height = copyDamageHeight; + present_buffer.timestamp = 0; /* set 0 to disable A/V sync at client side */ + present_buffer.presentId = ++peer_ctx->currentFrameId; + present_buffer.windowId = window_id; + present_buffer.bufferId = rail_state->buffer_id; + present_buffer.orientation = 0; /* 0, 90, 180 or 270 */ + present_buffer.targetWidth = newClientPos.width; + present_buffer.targetHeight = newClientPos.height; + present_buffer.dirtyRect.left = copy_damage_x1; + present_buffer.dirtyRect.top = copy_damage_y1; + present_buffer.dirtyRect.width = copy_damage_width; + present_buffer.dirtyRect.height = copy_damage_height; if (!hasAlpha) { - presentBuffer.numOpaqueRects = 1; - presentBuffer.opaqueRects = &opaqueRect; + present_buffer.numOpaqueRects = 1; + present_buffer.opaqueRects = &opaque_rect; } else { - presentBuffer.numOpaqueRects = 0; - presentBuffer.opaqueRects = NULL; + present_buffer.numOpaqueRects = 0; + present_buffer.opaqueRects = NULL; } - if (peerCtx->gfxredir_server_context->PresentBuffer(peerCtx->gfxredir_server_context, &presentBuffer) == 0) { + if (redir_ctx->PresentBuffer(redir_ctx, &present_buffer) == 0) { rail_state->isUpdatePending = TRUE; iter_data->isUpdatePending = TRUE; } else { - rdp_debug_error(b, "PresentBuffer failed for windowId:0x%x\n",window_id); + rdp_debug_error(b, + "PresentBuffer failed for windowId:0x%x\n", + window_id); } } else -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ if (rail_state->surface_id) { - RDPGFX_SURFACE_COMMAND surfaceCommand = {}; - int damageStride = damageWidth*bufferBpp; - int damageSize = damageStride*damageHeight; + int damageStride = damage_width * bufferBpp; + int damageSize = damageStride * damage_height; BYTE *data = NULL; int alphaCodecHeaderSize = 4; BYTE *alpha = NULL; int alphaSize; - - data = malloc(damageSize); - if (!data) { - // need better handling to avoid leaking surface on host. - rdp_debug_error(b, "Couldn't allocate memory for bitmap update.\n"); - return -1; - } + RdpgfxServerContext *gfx_ctx = peer_ctx->rail_grfx_server_context; + data = xmalloc(damageSize); if (hasAlpha) - alphaSize = alphaCodecHeaderSize+damageWidth*damageHeight; + alphaSize = alphaCodecHeaderSize + + damage_width * + damage_height; else { - alphaSize = alphaCodecHeaderSize+8; // 8 = max of ALPHA_RLE_SEGMENT for single alpha value. - } - alpha = malloc(alphaSize); - if (!alpha) { - free(data); - // need better handling to avoid leaking surface on host. - rdp_debug_error(b, "Couldn't allocate memory for alpha update.\n"); - return -1; + /* 8 = max of ALPHA_RLE_SEGMENT for single alpha value. */ + alphaSize = alphaCodecHeaderSize + 8; } + alpha = xmalloc(alphaSize); if (weston_surface_copy_content(surface, - data, damageSize, 0, 0, 0, - damageBox.x1, damageBox.y1, damageWidth, damageHeight, - false /* y-flip */, true /* is_argb */) < 0) { + data, damageSize, 0, 0, 0, + damage_box.x1, damage_box.y1, damage_width, damage_height, + false /* y-flip */, true /* is_argb */) < 0) { rdp_debug_error(b, - "weston_surface_copy_content failed for windowId:0x%x, damageSize:%d, damage:(%d,%d) %dx%d, content:%dx%d\n", - window_id, damageSize, damageBox.x1, damageBox.y1, damageWidth, damageHeight, contentBufferWidth, contentBufferHeight); + "weston_surface_copy_content failed for windowId:0x%x, damageSize:%d, damage:(%d,%d) %dx%d, content:%dx%d\n", + window_id, damageSize, + damage_box.x1, + damage_box.y1, + damage_width, + damage_height, + content_buffer_width, + content_buffer_height); free(data); free(alpha); return -1; @@ -2520,46 +2727,52 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter /* generate alpha only bitmap */ /* set up alpha codec header */ - alpha[0] = 'L'; // signature - alpha[1] = 'A'; // signature - alpha[2] = hasAlpha ? 0 : 1; // compression: RDP spec inticate this is non-zero value for compressed, but it must be 1. - alpha[3] = 0; // compression + alpha[0] = 'L'; /* signature */ + alpha[1] = 'A'; /* signature */ + alpha[2] = hasAlpha ? 0 : 1; /* compression: RDP spec indicate this is non-zero value for compressed, but it must be 1.*/ + alpha[3] = 0; /* compression */ if (hasAlpha) { BYTE *alphaBits = &data[0]; - for (int i = 0; i < damageHeight; i++, alphaBits+=damageStride) { - BYTE *srcAlphaPixel = alphaBits + 3; // 3 = xxxA. - BYTE *dstAlphaPixel = &alpha[alphaCodecHeaderSize+(i*damageWidth)]; - for (int j = 0; j < damageWidth; j++, srcAlphaPixel+=bufferBpp, dstAlphaPixel++) { + + for (int i = 0; i < damage_height; i++, alphaBits+=damageStride) { + BYTE *srcAlphaPixel = alphaBits + 3; /* 3 = xxxA. */ + BYTE *dstAlphaPixel = &alpha[alphaCodecHeaderSize + (i * damage_width)]; + + for (int j = 0; j < damage_width; j++, srcAlphaPixel += bufferBpp, dstAlphaPixel++) { *dstAlphaPixel = *srcAlphaPixel; } } } else { - /* regardless buffer as alpha or not, always use alpha to avoid mstsc bug */ + /* whether buffer has alpha or not, always use alpha to avoid mstsc bug */ /* CLEARCODEC_ALPHA_RLE_SEGMENT */ - int bitmapSize = damageWidth*damageHeight; - alpha[alphaCodecHeaderSize] = 0xFF; // alpha value (opaque) + int bitmapSize = damage_width * damage_height; + + alpha[alphaCodecHeaderSize] = 0xFF; /* alpha value (opaque) */ if (bitmapSize < 0xFF) { - alpha[alphaCodecHeaderSize+1] = (BYTE)bitmapSize; - alphaSize = alphaCodecHeaderSize+2; // alpha value + size in byte. - } else if (bitmapSize < 0xFFFF) { + alpha[alphaCodecHeaderSize + 1] = (BYTE)bitmapSize; + alphaSize = alphaCodecHeaderSize + 2; /* alpha value + size in byte. */ + } else if (bitmapSize < 0xFFFF) { alpha[alphaCodecHeaderSize+1] = 0xFF; *(short*)&(alpha[alphaCodecHeaderSize+2]) = (short)bitmapSize; - alphaSize = alphaCodecHeaderSize+4; // alpha value + 1 + size in short. + alphaSize = alphaCodecHeaderSize+4; /* alpha value + 1 + size in short. */ } else { alpha[alphaCodecHeaderSize+1] = 0xFF; *(short*)&(alpha[alphaCodecHeaderSize+2]) = 0xFFFF; *(int*)&(alpha[alphaCodecHeaderSize+4]) = bitmapSize; - alphaSize = alphaCodecHeaderSize+8; // alpha value + 1 + 2 + size in int. + alphaSize = alphaCodecHeaderSize+8; /* alpha value + 1 + 2 + size in int. */ } } if (iter_data->needEndFrame == FALSE) { /* if frame is not started yet, send StartFrame first before sendng surface command. */ RDPGFX_START_FRAME_PDU startFrame = {}; - startFrame.frameId = ++peerCtx->currentFrameId; - rdp_debug_verbose(b, "StartFrame(frameId:0x%x, windowId:0x%x)\n", startFrame.frameId, window_id); - peerCtx->rail_grfx_server_context->StartFrame(peerCtx->rail_grfx_server_context, &startFrame); + startFrame.frameId = ++peer_ctx->currentFrameId; + rdp_debug_verbose(b, "StartFrame(frameId:0x%x, windowId:0x%x)\n", + startFrame.frameId, + window_id); + gfx_ctx->StartFrame(gfx_ctx, + &startFrame); iter_data->startedFrameId = startFrame.frameId; iter_data->needEndFrame = TRUE; iter_data->isUpdatePending = TRUE; @@ -2568,12 +2781,12 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter surfaceCommand.surfaceId = rail_state->surface_id; surfaceCommand.contextId = 0; surfaceCommand.format = PIXEL_FORMAT_BGRA32; - surfaceCommand.left = damageBox.x1 - contentBufferWindowGeometry.x; - surfaceCommand.top = damageBox.y1 - contentBufferWindowGeometry.y; - surfaceCommand.right = damageBox.x2 - contentBufferWindowGeometry.x; - surfaceCommand.bottom = damageBox.y2 - contentBufferWindowGeometry.y; - surfaceCommand.width = damageWidth; - surfaceCommand.height = damageHeight; + surfaceCommand.left = damage_box.x1 - content_buffer_window_geometry.x; + surfaceCommand.top = damage_box.y1 - content_buffer_window_geometry.y; + surfaceCommand.right = damage_box.x2 - content_buffer_window_geometry.x; + surfaceCommand.bottom = damage_box.y2 - content_buffer_window_geometry.y; + surfaceCommand.width = damage_width; + surfaceCommand.height = damage_height; surfaceCommand.extra = NULL; /* send alpha channel */ @@ -2581,16 +2794,19 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter surfaceCommand.length = alphaSize; surfaceCommand.data = &alpha[0]; rdp_debug_verbose(b, "SurfaceCommand(frameId:0x%x, windowId:0x%x) for alpha\n", - iter_data->startedFrameId, window_id); - peerCtx->rail_grfx_server_context->SurfaceCommand(peerCtx->rail_grfx_server_context, &surfaceCommand); + iter_data->startedFrameId, + window_id); + gfx_ctx->SurfaceCommand(gfx_ctx, + &surfaceCommand); /* send bitmap data */ surfaceCommand.codecId = RDPGFX_CODECID_UNCOMPRESSED; surfaceCommand.length = damageSize; surfaceCommand.data = &data[0]; rdp_debug_verbose(b, "SurfaceCommand(frameId:0x%x, windowId:0x%x) for bitmap\n", - iter_data->startedFrameId, window_id); - peerCtx->rail_grfx_server_context->SurfaceCommand(peerCtx->rail_grfx_server_context, &surfaceCommand); + iter_data->startedFrameId, + window_id); + gfx_ctx->SurfaceCommand(gfx_ctx, &surfaceCommand); free(data); free(alpha); @@ -2598,13 +2814,13 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter pixman_region32_clear(&rail_state->damage); - /* TODO: this is temporary workaround, some window is not visible to shell + /* TODO: this is a temporary workaround, some windows are not visible to shell (such as subsurfaces, override_redirect), so z order update is not done by activate callback, thus trigger it at first update. solution would make those surface visible to shell or hook signal on when view_list is changed on libweston/compositor.c */ if (!rail_state->isFirstUpdateDone) { - peerCtx->is_window_zorder_dirty = true; + peer_ctx->is_window_zorder_dirty = true; rail_state->isFirstUpdateDone = true; } } @@ -2613,28 +2829,37 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter if (!b->use_gfxredir) { #else { -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ + RdpgfxServerContext *gfx_ctx = peer_ctx->rail_grfx_server_context; + if (new_surface_id || - rail_state->bufferScaleFactorWidth != scaleFactorWidth || - rail_state->bufferScaleFactorHeight != scaleFactorHeight) { + rail_state->bufferScaleFactorWidth != scaleFactorWidth || + rail_state->bufferScaleFactorHeight != scaleFactorHeight) { /* map surface to window */ assert(new_surface_id == 0 || (new_surface_id == rail_state->surface_id)); rdp_debug_verbose(b, "MapSurfaceToWindow(surfaceId:0x%x - windowsId:%x)\n", - rail_state->surface_id, window_id); + rail_state->surface_id, + window_id); rdp_debug_verbose(b, " targetWidth:0x%d - targetWidth:%d)\n", - newClientPos.width, newClientPos.height); + newClientPos.width, + newClientPos.height); rdp_debug_verbose(b, " mappedWidth:0x%d - mappedHeight:%d)\n", - contentBufferWidth, contentBufferHeight); - // Always use scaled version to avoid bug in mstsc.exe, mstsc.exe - // seems can't handle mixed of scale and non-scaled version of procotols. - RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU mapSurfaceToScaledWindow = {}; - mapSurfaceToScaledWindow.surfaceId = (UINT16)rail_state->surface_id; - mapSurfaceToScaledWindow.windowId = window_id; - mapSurfaceToScaledWindow.mappedWidth = copyBufferWidth; - mapSurfaceToScaledWindow.mappedHeight = copyBufferHeight; - mapSurfaceToScaledWindow.targetWidth = clientBufferWidth; - mapSurfaceToScaledWindow.targetHeight = clientBufferHeight; - peerCtx->rail_grfx_server_context->MapSurfaceToScaledWindow(peerCtx->rail_grfx_server_context, &mapSurfaceToScaledWindow); + content_buffer_width, + content_buffer_height); + /* Always use scaled version to avoid bug in mstsc.exe, mstsc.exe + * seems can't handle mixed of scale and non-scaled version of procotols. + */ + RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU mapSurfaceToScaledWindow = { + .surfaceId = (uint16_t)rail_state->surface_id, + .windowId = window_id, + .mappedWidth = copy_buffer_width, + .mappedHeight = copy_buffer_height, + .targetWidth = client_buffer_width, + .targetHeight = client_buffer_height, + }; + + gfx_ctx->MapSurfaceToScaledWindow(gfx_ctx, + &mapSurfaceToScaledWindow); rail_state->bufferScaleFactorWidth = scaleFactorWidth; rail_state->bufferScaleFactorHeight = scaleFactorHeight; } @@ -2642,9 +2867,11 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter /* destroy old surface */ if (old_surface_id) { RDPGFX_DELETE_SURFACE_PDU deleteSurface = {}; - rdp_debug_verbose(b, "DeleteSurface(surfaceId:0x%x for windowId:0x%x)\n", old_surface_id, window_id); - deleteSurface.surfaceId = (UINT16)old_surface_id; - peerCtx->rail_grfx_server_context->DeleteSurface(peerCtx->rail_grfx_server_context, &deleteSurface); + + rdp_debug_verbose(b, "DeleteSurface(surfaceId:0x%x for windowId:0x%x)\n", + old_surface_id, window_id); + deleteSurface.surfaceId = (uint16_t)old_surface_id; + gfx_ctx->DeleteSurface(gfx_ctx, &deleteSurface); } } } @@ -2655,41 +2882,54 @@ rdp_rail_update_window(struct weston_surface *surface, struct update_window_iter static void rdp_rail_update_window_iter(void *element, void *data) { - struct weston_surface *surface = (struct weston_surface *)element; + struct weston_surface *surface = element; struct weston_compositor *compositor = surface->compositor; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; - struct update_window_iter_data *iter_data = (struct update_window_iter_data *)data; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - assert(rail_state); // this iter is looping from window hash table, thus it must have rail_state initialized. - if (surface->output_mask & (1u << iter_data->output_id)) { - if (rail_state->isCursor) - rdp_rail_update_cursor(surface); - else if (rail_state->isUpdatePending == FALSE) - rdp_rail_update_window(surface, iter_data); - else - rdp_debug_verbose(b, "window update is skipped for windowId:0x%x, isUpdatePending = %d\n", - rail_state->window_id, rail_state->isUpdatePending); - } + struct rdp_backend *b = to_rdp_backend(compositor); + struct update_window_iter_data *iter_data = data; + struct weston_surface_rail_state *rail_state = surface->backend_state; + + /* this iter is looping from window hash table, thus it must have + * rail_state initialized. + **/ + assert(rail_state); + + if (!(surface->output_mask & (1u << iter_data->output_id))) + return; + + if (rail_state->isCursor) + rdp_rail_update_cursor(surface); + else if (rail_state->isUpdatePending == FALSE) + rdp_rail_update_window(surface, iter_data); + else + rdp_debug_verbose(b, "window update is skipped for windowId:0x%x, isUpdatePending = %d\n", + rail_state->window_id, + rail_state->isUpdatePending); } -static UINT32 -rdp_insert_window_zorder_array(struct weston_view *view, UINT32 *windowIdArray, UINT32 WindowIdArraySize, UINT32 iCurrent) +static uint32_t +rdp_insert_window_zorder_array(struct weston_view *view, + uint32_t *windowIdArray, + uint32_t WindowIdArraySize, + uint32_t iCurrent) { struct weston_surface *surface = view->surface; struct weston_compositor *compositor = surface->compositor; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; - struct weston_surface_rail_state *rail_state = - (struct weston_surface_rail_state *)surface->backend_state; + struct rdp_backend *b = to_rdp_backend(compositor); + struct weston_surface_rail_state *rail_state = surface->backend_state; + struct weston_subsurface *sub; /* insert subsurface first to zorder list */ - struct weston_subsurface *sub; wl_list_for_each(sub, &surface->subsurface_list, parent_link) { struct weston_view *sub_view; + wl_list_for_each(sub_view, &sub->surface->views, surface_link) { if (sub_view->parent_view != view) continue; - iCurrent = rdp_insert_window_zorder_array(sub_view, windowIdArray, WindowIdArraySize, iCurrent); + iCurrent = rdp_insert_window_zorder_array(sub_view, + windowIdArray, + WindowIdArraySize, + iCurrent); if (iCurrent == UINT_MAX) return iCurrent; } @@ -2711,8 +2951,13 @@ rdp_insert_window_zorder_array(struct weston_view *view, UINT32 *windowIdArray, } if (b->debugLevel >= RDP_DEBUG_LEVEL_VERBOSE) { char label[256]; - rdp_rail_dump_window_label(surface, label, sizeof(label)); - rdp_debug_verbose(b, " window[%d]: %x: %s\n", iCurrent, rail_state->window_id, label); + + rdp_rail_dump_window_label(surface, + label, + sizeof(label)); + rdp_debug_verbose(b, " window[%d]: %x: %s\n", + iCurrent, + rail_state->window_id, label); } windowIdArray[iCurrent++] = rail_state->window_id; } @@ -2725,36 +2970,41 @@ rdp_rail_sync_window_zorder(struct weston_compositor *compositor) { struct rdp_backend *b = to_rdp_backend(compositor); freerdp_peer* client = b->rdp_peer; - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - UINT32 numWindowId = 0; - UINT32 *windowIdArray = NULL; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + uint32_t numWindowId = 0; + uint32_t *windowIdArray = NULL; WINDOW_ORDER_INFO window_order_info = {}; MONITORED_DESKTOP_ORDER monitored_desktop_order = {}; - UINT32 iCurrent = 0; + uint32_t iCurrent = 0; assert_compositor_thread(b); if (!b->enable_window_zorder_sync) return; - numWindowId = peerCtx->windowId.id_used + 1; // +1 for marker window (aka proxy_surface) - windowIdArray = zalloc(numWindowId * sizeof(UINT32)); - if (!windowIdArray) { - rdp_debug_error(b, "%s: zalloc(%ld bytes) failed\n", __func__, numWindowId * sizeof(UINT32)); - return; - } + /* +1 for marker window (aka proxy_surface) */ + numWindowId = peer_ctx->windowId.id_used + 1; + windowIdArray = xzalloc(numWindowId * sizeof(uint32_t)); rdp_debug_verbose(b, "Dump Window Z order\n"); /* walk windows in z-order */ struct weston_layer *layer; + wl_list_for_each(layer, &compositor->layer_list, link) { struct weston_view *view; + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { if (view->surface == b->proxy_surface) { - rdp_debug_verbose(b, " window[%d]: %x: %s\n", iCurrent, RDP_RAIL_MARKER_WINDOW_ID, "marker window"); + rdp_debug_verbose(b, " window[%d]: %x: %s\n", + iCurrent, + RDP_RAIL_MARKER_WINDOW_ID, + "marker window"); windowIdArray[iCurrent++] = RDP_RAIL_MARKER_WINDOW_ID; } else { - iCurrent = rdp_insert_window_zorder_array(view, windowIdArray, numWindowId, iCurrent); + iCurrent = rdp_insert_window_zorder_array(view, + windowIdArray, + numWindowId, + iCurrent); if (iCurrent == UINT_MAX) goto Exit; } @@ -2762,52 +3012,66 @@ rdp_rail_sync_window_zorder(struct weston_compositor *compositor) } assert(iCurrent <= numWindowId); assert(iCurrent > 0); - rdp_debug_verbose(b, " send Window Z order: numWindowIds:%d\n", iCurrent); + rdp_debug_verbose(b, " send Window Z order: numWindowIds:%d\n", + iCurrent); window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | - WINDOW_ORDER_FIELD_DESKTOP_ZORDER | - WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND; + WINDOW_ORDER_FIELD_DESKTOP_ZORDER | + WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND; monitored_desktop_order.activeWindowId = windowIdArray[0]; monitored_desktop_order.numWindowIds = iCurrent; monitored_desktop_order.windowIds = windowIdArray; - client->context->update->window->MonitoredDesktop(client->context, &window_order_info, &monitored_desktop_order); + client->context->update->window->MonitoredDesktop(client->context, + &window_order_info, + &monitored_desktop_order); client->DrainOutputBuffer(client); Exit: - if (windowIdArray) - free(windowIdArray); + free(windowIdArray); return; } -void -rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage) +void +rdp_rail_output_repaint(struct weston_output *output, + pixman_region32_t *damage) { struct weston_compositor *ec = output->compositor; struct rdp_backend *b = to_rdp_backend(ec); - RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; + RdpPeerContext *peer_ctx = (RdpPeerContext *)b->rdp_peer->context; + + if (peer_ctx->isAcknowledgedSuspended || + ((peer_ctx->currentFrameId - peer_ctx->acknowledgedFrameId) < 2)) { + struct update_window_iter_data iter_data = {}; - if (peerCtx->isAcknowledgedSuspended || ((peerCtx->currentFrameId - peerCtx->acknowledgedFrameId) < 2)) { /* notify window z order to client first, mstsc/msrdc needs this to be sent before window update. */ - if (peerCtx->is_window_zorder_dirty) { + if (peer_ctx->is_window_zorder_dirty) { rdp_rail_sync_window_zorder(b->compositor); - peerCtx->is_window_zorder_dirty = false; + peer_ctx->is_window_zorder_dirty = false; } rdp_debug_verbose(b, "currentFrameId:0x%x, acknowledgedFrameId:0x%x, isAcknowledgedSuspended:%d\n", - peerCtx->currentFrameId, peerCtx->acknowledgedFrameId, peerCtx->isAcknowledgedSuspended); - struct update_window_iter_data iter_data = {}; + peer_ctx->currentFrameId, + peer_ctx->acknowledgedFrameId, + peer_ctx->isAcknowledgedSuspended); + iter_data.output_id = output->id; - rdp_id_manager_for_each(&peerCtx->windowId, rdp_rail_update_window_iter, (void*) &iter_data); + rdp_id_manager_for_each(&peer_ctx->windowId, + rdp_rail_update_window_iter, + &iter_data); if (iter_data.needEndFrame) { /* if frame is started at above iteration, send EndFrame here. */ RDPGFX_END_FRAME_PDU endFrame = {}; + RdpgfxServerContext *gfx_ctx; + gfx_ctx = peer_ctx->rail_grfx_server_context; + endFrame.frameId = iter_data.startedFrameId; rdp_debug_verbose(b, "EndFrame(frameId:0x%x)\n", endFrame.frameId); - peerCtx->rail_grfx_server_context->EndFrame(peerCtx->rail_grfx_server_context, &endFrame); + gfx_ctx->EndFrame(gfx_ctx, &endFrame); } - if (iter_data.isUpdatePending && b->enable_display_power_by_screenupdate) { + if (iter_data.isUpdatePending && + b->enable_display_power_by_screenupdate) { /* By default, compositor won't update idle timer by screen activity, thus, here manually call wake function to postpone idle timer when RDP backend sends frame to client. */ @@ -2815,7 +3079,9 @@ rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage) } } else { rdp_debug_verbose(b, "frame update is skipped. currentFrameId:%d, acknowledgedFrameId:%d, isAcknowledgedSuspended:%d\n", - peerCtx->currentFrameId, peerCtx->acknowledgedFrameId, peerCtx->isAcknowledgedSuspended); + peer_ctx->currentFrameId, + peer_ctx->acknowledgedFrameId, + peer_ctx->isAcknowledgedSuspended); } return; } @@ -2925,23 +3191,26 @@ disp_client_monitor_layout_change(DispServerContext *context, const DISPLAY_CONT return CHANNEL_RC_OK; } -BOOL +bool rdp_rail_peer_activate(freerdp_peer* client) { - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; rdpSettings *settings = client->context->settings; - BOOL rail_server_started = FALSE; - BOOL disp_server_opened = FALSE; - BOOL rail_grfx_server_opened = FALSE; + RailServerContext *rail_ctx; + RdpgfxServerContext *gfx_ctx; + DispServerContext *disp_ctx; + bool rail_server_started = false; + bool disp_server_opened = false; + bool rail_grfx_server_opened = false; #ifdef HAVE_FREERDP_GFXREDIR_H - BOOL gfxredir_server_opened = FALSE; -#endif // HAVE_FREERDP_GFXREDIR_H + bool gfxredir_server_opened = false; +#endif /* HAVE_FREERDP_GFXREDIR_H */ #ifdef HAVE_FREERDP_RDPAPPLIST_H - BOOL applist_server_opened = FALSE; - RDPAPPLIST_SERVER_CAPS_PDU appListCaps = {}; -#endif // HAVE_FREERDP_RDPAPPLIST_H - UINT waitRetry; + bool applist_server_opened = false; + RDPAPPLIST_SERVER_CAPS_PDU app_list_caps = {}; +#endif /* HAVE_FREERDP_RDPAPPLIST_H */ + uint waitRetry; assert_compositor_thread(b); @@ -2959,136 +3228,145 @@ rdp_rail_peer_activate(freerdp_peer* client) } /* Start RAIL server */ - peerCtx->rail_server_context = rail_server_context_new(peerCtx->vcm); - if (!peerCtx->rail_server_context) + rail_ctx = rail_server_context_new(peer_ctx->vcm); + if (!rail_ctx) goto error_exit; - peerCtx->rail_server_context->custom = (void*)client; - peerCtx->rail_server_context->ClientHandshake = rail_client_Handshake; - peerCtx->rail_server_context->ClientClientStatus = rail_client_ClientStatus; - peerCtx->rail_server_context->ClientExec = rail_client_Exec; - peerCtx->rail_server_context->ClientActivate = rail_client_Activate; - peerCtx->rail_server_context->ClientSyscommand = rail_client_Syscommand; - peerCtx->rail_server_context->ClientSysparam = rail_client_ClientSysparam; - peerCtx->rail_server_context->ClientGetAppidReq = rail_client_ClientGetAppidReq; - peerCtx->rail_server_context->ClientWindowMove = rail_client_WindowMove; - peerCtx->rail_server_context->ClientSnapArrange = rail_client_SnapArrange; - peerCtx->rail_server_context->ClientLangbarInfo = rail_client_LangbarInfo; - peerCtx->rail_server_context->ClientLanguageImeInfo = rail_client_LanguageImeInfo; - peerCtx->rail_server_context->ClientCompartmentInfo = rail_client_CompartmentInfo; - if (peerCtx->rail_server_context->Start(peerCtx->rail_server_context) != CHANNEL_RC_OK) + peer_ctx->rail_server_context = rail_ctx; + rail_ctx->custom = client; + rail_ctx->ClientHandshake = rail_client_Handshake; + rail_ctx->ClientClientStatus = rail_client_ClientStatus; + rail_ctx->ClientExec = rail_client_Exec; + rail_ctx->ClientActivate = rail_client_Activate; + rail_ctx->ClientSyscommand = rail_client_Syscommand; + rail_ctx->ClientSysparam = rail_client_ClientSysparam; + rail_ctx->ClientGetAppidReq = rail_client_ClientGetAppidReq; + rail_ctx->ClientWindowMove = rail_client_WindowMove; + rail_ctx->ClientSnapArrange = rail_client_SnapArrange; + rail_ctx->ClientLangbarInfo = rail_client_LangbarInfo; + rail_ctx->ClientLanguageImeInfo = rail_client_LanguageImeInfo; + rail_ctx->ClientCompartmentInfo = rail_client_CompartmentInfo; + if (rail_ctx->Start(rail_ctx) != CHANNEL_RC_OK) goto error_exit; - rail_server_started = TRUE; + rail_server_started = true; /* send handshake to client */ if (settings->RemoteApplicationSupportLevel & RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED) { RAIL_HANDSHAKE_EX_ORDER handshakeEx = {}; - UINT32 railHandshakeFlags = - (TS_RAIL_ORDER_HANDSHAKEEX_FLAGS_HIDEF - | TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED); + uint32_t railHandshakeFlags = TS_RAIL_ORDER_HANDSHAKEEX_FLAGS_HIDEF | + TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED; + if (b->enable_window_snap_arrange) railHandshakeFlags |= TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED; handshakeEx.buildNumber = 0; handshakeEx.railHandshakeFlags = railHandshakeFlags; - if (peerCtx->rail_server_context->ServerHandshakeEx(peerCtx->rail_server_context, &handshakeEx) != CHANNEL_RC_OK) + if (rail_ctx->ServerHandshakeEx(rail_ctx, &handshakeEx) != CHANNEL_RC_OK) goto error_exit; - client->DrainOutputBuffer(client); + client->DrainOutputBuffer(client); } else { RAIL_HANDSHAKE_ORDER handshake = {}; + handshake.buildNumber = 0; - if (peerCtx->rail_server_context->ServerHandshake(peerCtx->rail_server_context, &handshake) != CHANNEL_RC_OK) + if (rail_ctx->ServerHandshake(rail_ctx, &handshake) != CHANNEL_RC_OK) goto error_exit; - client->DrainOutputBuffer(client); + client->DrainOutputBuffer(client); } /* wait handshake reponse from client */ waitRetry = 0; - while (!peerCtx->handshakeCompleted) { - if (++waitRetry > 10000) // timeout after 100 sec. + while (!peer_ctx->handshakeCompleted) { + if (++waitRetry > 10000) /* timeout after 100 sec. */ goto error_exit; - USleep(10000); // wait 0.01 sec. + usleep(10000); /* wait 0.01 sec. */ client->CheckFileDescriptor(client); - WTSVirtualChannelManagerCheckFileDescriptor(peerCtx->vcm); + WTSVirtualChannelManagerCheckFileDescriptor(peer_ctx->vcm); } /* open Disp channel */ - peerCtx->disp_server_context = disp_server_context_new(peerCtx->vcm); - if (!peerCtx->disp_server_context) + disp_ctx = disp_server_context_new(peer_ctx->vcm); + if (!disp_ctx) goto error_exit; - peerCtx->disp_server_context->custom = (void*)client; - peerCtx->disp_server_context->MaxNumMonitors = RDP_MAX_MONITOR; - peerCtx->disp_server_context->MaxMonitorAreaFactorA = DISPLAY_CONTROL_MAX_MONITOR_WIDTH; - peerCtx->disp_server_context->MaxMonitorAreaFactorB = DISPLAY_CONTROL_MAX_MONITOR_HEIGHT; - peerCtx->disp_server_context->DispMonitorLayout = disp_client_monitor_layout_change; - if (peerCtx->disp_server_context->Open(peerCtx->disp_server_context) != CHANNEL_RC_OK) + peer_ctx->disp_server_context = disp_ctx; + disp_ctx->custom = client; + disp_ctx->MaxNumMonitors = RDP_MAX_MONITOR; + disp_ctx->MaxMonitorAreaFactorA = DISPLAY_CONTROL_MAX_MONITOR_WIDTH; + disp_ctx->MaxMonitorAreaFactorB = DISPLAY_CONTROL_MAX_MONITOR_HEIGHT; + disp_ctx->DispMonitorLayout = disp_client_monitor_layout_change; + if (disp_ctx->Open(disp_ctx) != CHANNEL_RC_OK) goto error_exit; disp_server_opened = TRUE; - if (peerCtx->disp_server_context->DisplayControlCaps(peerCtx->disp_server_context) != CHANNEL_RC_OK) + if (disp_ctx->DisplayControlCaps(disp_ctx) != CHANNEL_RC_OK) goto error_exit; /* open HiDef (aka rdpgfx) channel. */ - peerCtx->rail_grfx_server_context = rdpgfx_server_context_new(peerCtx->vcm); - if (!peerCtx->rail_grfx_server_context) + gfx_ctx = rdpgfx_server_context_new(peer_ctx->vcm); + if (!gfx_ctx) goto error_exit; - peerCtx->rail_grfx_server_context->custom = (void*)client; - peerCtx->rail_grfx_server_context->CapsAdvertise = rail_grfx_client_caps_advertise; - peerCtx->rail_grfx_server_context->CacheImportOffer = rail_grfx_client_cache_import_offer; - peerCtx->rail_grfx_server_context->FrameAcknowledge = rail_grfx_client_frame_acknowledge; - if (!peerCtx->rail_grfx_server_context->Open(peerCtx->rail_grfx_server_context)) + peer_ctx->rail_grfx_server_context = gfx_ctx; + gfx_ctx->custom = client; + gfx_ctx->CapsAdvertise = rail_grfx_client_caps_advertise; + gfx_ctx->CacheImportOffer = rail_grfx_client_cache_import_offer; + gfx_ctx->FrameAcknowledge = rail_grfx_client_frame_acknowledge; + if (!gfx_ctx->Open(gfx_ctx)) goto error_exit; rail_grfx_server_opened = TRUE; #ifdef HAVE_FREERDP_GFXREDIR_H + GfxRedirServerContext *redir_ctx; /* open Graphics Redirection channel. */ if (b->use_gfxredir) { - peerCtx->gfxredir_server_context = b->gfxredir_server_context_new(peerCtx->vcm); - if (!peerCtx->gfxredir_server_context) + + redir_ctx = b->gfxredir_server_context_new(peer_ctx->vcm); + if (!redir_ctx) goto error_exit; - peerCtx->gfxredir_server_context->custom = (void*)client; - peerCtx->gfxredir_server_context->GraphicsRedirectionLegacyCaps = gfxredir_client_graphics_redirection_legacy_caps; - peerCtx->gfxredir_server_context->GraphicsRedirectionCapsAdvertise = gfxredir_client_graphics_redirection_caps_advertise; - peerCtx->gfxredir_server_context->PresentBufferAck = gfxredir_client_present_buffer_ack; - if (peerCtx->gfxredir_server_context->Open(peerCtx->gfxredir_server_context) != CHANNEL_RC_OK) + peer_ctx->gfxredir_server_context = redir_ctx; + redir_ctx->custom = client; + redir_ctx->GraphicsRedirectionLegacyCaps = gfxredir_client_graphics_redirection_legacy_caps; + redir_ctx->GraphicsRedirectionCapsAdvertise = gfxredir_client_graphics_redirection_caps_advertise; + redir_ctx->PresentBufferAck = gfxredir_client_present_buffer_ack; + if (redir_ctx->Open(redir_ctx) != CHANNEL_RC_OK) goto error_exit; gfxredir_server_opened = TRUE; } -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ #ifdef HAVE_FREERDP_RDPAPPLIST_H + RdpAppListServerContext *applist_ctx; /* open Application List channel. */ - if (b->rdprail_shell_api && - b->rdprail_shell_name && - b->use_rdpapplist) { - peerCtx->applist_server_context = b->rdpapplist_server_context_new(peerCtx->vcm); - if (!peerCtx->applist_server_context) + if (api && b->rdprail_shell_name && b->use_rdpapplist) { + + applist_ctx = b->rdpapplist_server_context_new(peer_ctx->vcm); + if (!applist_ctx) goto error_exit; - peerCtx->applist_server_context->custom = (void *)client; - peerCtx->applist_server_context->ApplicationListClientCaps = applist_client_Caps; - if (peerCtx->applist_server_context->Open(peerCtx->applist_server_context) != CHANNEL_RC_OK) + peer_ctx->applist_server_context = applist_ctx; + applist_ctx->custom = client; + applist_ctx->ApplicationListClientCaps = applist_client_Caps; + if (applist_ctx->Open(applist_ctx) != CHANNEL_RC_OK) goto error_exit; applist_server_opened = TRUE; rdp_debug(b, "Server AppList caps version:%d\n", RDPAPPLIST_CHANNEL_VERSION); - appListCaps.version = RDPAPPLIST_CHANNEL_VERSION; - if (!utf8_string_to_rail_string(b->rdprail_shell_name, &appListCaps.appListProviderName)) + app_list_caps.version = RDPAPPLIST_CHANNEL_VERSION; + if (!utf8_string_to_rail_string(b->rdprail_shell_name, + &app_list_caps.appListProviderName)) goto error_exit; - if (peerCtx->applist_server_context->ApplicationListCaps(peerCtx->applist_server_context, &appListCaps) != CHANNEL_RC_OK) + if (applist_ctx->ApplicationListCaps(applist_ctx, &app_list_caps) != CHANNEL_RC_OK) goto error_exit; - free(appListCaps.appListProviderName.string); + free(app_list_caps.appListProviderName.string); } -#endif // HAVE_FREERDP_RDPAPPLIST_H +#endif /* HAVE_FREERDP_RDPAPPLIST_H */ /* wait graphics channel (and optionally graphics redir channel) reponse from client */ waitRetry = 0; - while (!peerCtx->activationGraphicsCompleted + while (!peer_ctx->activationGraphicsCompleted #ifdef HAVE_FREERDP_GFXREDIR_H - || (gfxredir_server_opened && !peerCtx->activationGraphicsRedirectionCompleted) -#endif // HAVE_FREERDP_GFXREDIR_H + || (gfxredir_server_opened && !peer_ctx->activationGraphicsRedirectionCompleted) +#endif /* HAVE_FREERDP_GFXREDIR_H */ ) { - if (++waitRetry > 10000) // timeout after 100 sec. + if (++waitRetry > 10000) /* timeout after 100 sec. */ goto error_exit; - USleep(10000); // wait 0.01 sec. + usleep(10000); /* wait 0.01 sec. */ client->CheckFileDescriptor(client); - WTSVirtualChannelManagerCheckFileDescriptor(peerCtx->vcm); + WTSVirtualChannelManagerCheckFileDescriptor(peer_ctx->vcm); } return TRUE; @@ -3097,48 +3375,47 @@ rdp_rail_peer_activate(freerdp_peer* client) #ifdef HAVE_FREERDP_RDPAPPLIST_H if (applist_server_opened) { - peerCtx->applist_server_context->Close(peerCtx->applist_server_context); - if (appListCaps.appListProviderName.string) - free(appListCaps.appListProviderName.string); + applist_ctx->Close(applist_ctx); + free(app_list_caps.appListProviderName.string); } - if (peerCtx->applist_server_context) { + if (applist_ctx) { assert(b->rdpapplist_server_context_free); - b->rdpapplist_server_context_free(peerCtx->applist_server_context); - peerCtx->applist_server_context = NULL; + b->rdpapplist_server_context_free(applist_ctx); + peer_ctx->applist_server_context = NULL; } -#endif // HAVE_FREERDP_RDPAPPLIST_H +#endif /* HAVE_FREERDP_RDPAPPLIST_H */ #ifdef HAVE_FREERDP_GFXREDIR_H if (gfxredir_server_opened) - peerCtx->gfxredir_server_context->Close(peerCtx->gfxredir_server_context); - if (peerCtx->gfxredir_server_context) { + redir_ctx->Close(redir_ctx); + if (redir_ctx) { assert(b->gfxredir_server_context_free); - b->gfxredir_server_context_free(peerCtx->gfxredir_server_context); - peerCtx->gfxredir_server_context = NULL; - peerCtx->activationGraphicsRedirectionCompleted = FALSE; + b->gfxredir_server_context_free(redir_ctx); + peer_ctx->gfxredir_server_context = NULL; + peer_ctx->activationGraphicsRedirectionCompleted = FALSE; } -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ if (rail_grfx_server_opened) - peerCtx->rail_grfx_server_context->Close(peerCtx->rail_grfx_server_context); - if (peerCtx->rail_grfx_server_context) { - rdpgfx_server_context_free(peerCtx->rail_grfx_server_context); - peerCtx->rail_grfx_server_context = NULL; - peerCtx->activationGraphicsCompleted = FALSE; + gfx_ctx->Close(gfx_ctx); + if (gfx_ctx) { + rdpgfx_server_context_free(gfx_ctx); + peer_ctx->rail_grfx_server_context = NULL; + peer_ctx->activationGraphicsCompleted = FALSE; } if (disp_server_opened) - peerCtx->disp_server_context->Close(peerCtx->disp_server_context); - if (peerCtx->disp_server_context) { - disp_server_context_free(peerCtx->disp_server_context); - peerCtx->disp_server_context = NULL; + disp_ctx->Close(disp_ctx); + if (disp_ctx) { + disp_server_context_free(disp_ctx); + peer_ctx->disp_server_context = NULL; } if (rail_server_started) - peerCtx->rail_server_context->Stop(peerCtx->rail_server_context); - if (peerCtx->rail_server_context) { - rail_server_context_free(peerCtx->rail_server_context); - peerCtx->rail_server_context = NULL; + rail_ctx->Stop(rail_ctx); + if (rail_ctx) { + rail_server_context_free(rail_ctx); + peer_ctx->rail_server_context = NULL; } return FALSE; @@ -3148,34 +3425,34 @@ static void rdp_rail_idle_handler(struct wl_listener *listener, void *data) { RAIL_POWER_DISPLAY_REQUEST displayRequest; - RdpPeerContext *peerCtx = - container_of(listener, RdpPeerContext, idle_listener); - struct rdp_backend *b = peerCtx->rdpBackend; + RdpPeerContext *peer_ctx = container_of(listener, RdpPeerContext, + idle_listener); + struct rdp_backend *b = peer_ctx->rdpBackend; + RailServerContext *rail_ctx = peer_ctx->rail_server_context; assert_compositor_thread(b); - rdp_debug(b, "%s is called on peerCtx:%p\n", __func__, peerCtx); + rdp_debug(b, "%s is called on peer_ctx:%p\n", __func__, peer_ctx); displayRequest.active = FALSE; - peerCtx->rail_server_context->ServerPowerDisplayRequest( - peerCtx->rail_server_context, &displayRequest); + rail_ctx->ServerPowerDisplayRequest(rail_ctx, &displayRequest); } static void rdp_rail_wake_handler(struct wl_listener *listener, void *data) { RAIL_POWER_DISPLAY_REQUEST displayRequest; - RdpPeerContext *peerCtx = - container_of(listener, RdpPeerContext, wake_listener); - struct rdp_backend *b = peerCtx->rdpBackend; + RdpPeerContext *peer_ctx = container_of(listener, RdpPeerContext, + wake_listener); + struct rdp_backend *b = peer_ctx->rdpBackend; + RailServerContext *rail_ctx = peer_ctx->rail_server_context; assert_compositor_thread(b); - rdp_debug(b, "%s is called on peerCtx:%p\n", __func__, peerCtx); + rdp_debug(b, "%s is called on peer_ctx:%p\n", __func__, peer_ctx); displayRequest.active = TRUE; - peerCtx->rail_server_context->ServerPowerDisplayRequest( - peerCtx->rail_server_context, &displayRequest); + rail_ctx->ServerPowerDisplayRequest(rail_ctx, &displayRequest); } static void @@ -3192,126 +3469,141 @@ static void rdp_rail_notify_window_zorder_change(struct weston_compositor *compositor) { struct rdp_backend *b = to_rdp_backend(compositor); - RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context; + RdpPeerContext *peer_ctx = (RdpPeerContext *)b->rdp_peer->context; assert_compositor_thread(b); /* z order will be sent to client at next repaint */ - peerCtx->is_window_zorder_dirty = true; + peer_ctx->is_window_zorder_dirty = true; } void -rdp_rail_sync_window_status(freerdp_peer* client) +rdp_rail_sync_window_status(freerdp_peer *client) { - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; - struct rdp_backend *b = peerCtx->rdpBackend; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; + RailServerContext *rail_ctx = peer_ctx->rail_server_context; + rdpUpdate *update = b->rdp_peer->context->update; struct weston_view *view; assert_compositor_thread(b); { - RAIL_SYSPARAM_ORDER sysParamOrder = {}; - sysParamOrder.param = SPI_SETSCREENSAVESECURE; - sysParamOrder.setScreenSaveSecure = 0; - peerCtx->rail_server_context->ServerSysparam(peerCtx->rail_server_context, &sysParamOrder); - client->DrainOutputBuffer(client); + RAIL_SYSPARAM_ORDER sysParamOrder = { + .param = SPI_SETSCREENSAVESECURE, + .setScreenSaveSecure = 0, + }; + rail_ctx->ServerSysparam(rail_ctx, &sysParamOrder); + client->DrainOutputBuffer(client); } { - RAIL_SYSPARAM_ORDER sysParamOrder = {}; - sysParamOrder.param = SPI_SETSCREENSAVEACTIVE; - sysParamOrder.setScreenSaveActive = 0; - peerCtx->rail_server_context->ServerSysparam(peerCtx->rail_server_context, &sysParamOrder); - client->DrainOutputBuffer(client); + RAIL_SYSPARAM_ORDER sysParamOrder = { + .param = SPI_SETSCREENSAVEACTIVE, + .setScreenSaveActive = 0, + }; + rail_ctx->ServerSysparam(rail_ctx, &sysParamOrder); + client->DrainOutputBuffer(client); } { - RAIL_ZORDER_SYNC zOrderSync = {}; - zOrderSync.windowIdMarker = RDP_RAIL_MARKER_WINDOW_ID; - peerCtx->rail_server_context->ServerZOrderSync(peerCtx->rail_server_context, &zOrderSync); - client->DrainOutputBuffer(client); + RAIL_ZORDER_SYNC zOrderSync = { + .windowIdMarker = RDP_RAIL_MARKER_WINDOW_ID, + }; + rail_ctx->ServerZOrderSync(rail_ctx, &zOrderSync); + client->DrainOutputBuffer(client); } { - WINDOW_ORDER_INFO window_order_info = {}; + WINDOW_ORDER_INFO window_order_info = { + .windowId = RDP_RAIL_MARKER_WINDOW_ID, + .fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | + WINDOW_ORDER_FIELD_DESKTOP_HOOKED | + WINDOW_ORDER_FIELD_DESKTOP_ARC_BEGAN, + }; MONITORED_DESKTOP_ORDER monitored_desktop_order = {}; - window_order_info.windowId = RDP_RAIL_MARKER_WINDOW_ID; - window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_HOOKED | WINDOW_ORDER_FIELD_DESKTOP_ARC_BEGAN; - - client->context->update->window->MonitoredDesktop(client->context->update->context, &window_order_info, &monitored_desktop_order); + update->window->MonitoredDesktop(update->context, + &window_order_info, + &monitored_desktop_order); client->DrainOutputBuffer(client); } { - UINT32 windowsIdArray[1] = {}; - WINDOW_ORDER_INFO window_order_info = {}; - MONITORED_DESKTOP_ORDER monitored_desktop_order = {}; - - window_order_info.windowId = RDP_RAIL_MARKER_WINDOW_ID; - window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_ZORDER | WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND; - - monitored_desktop_order.activeWindowId = RDP_RAIL_DESKTOP_WINDOW_ID; - monitored_desktop_order.numWindowIds = 1; + uint32_t windowsIdArray[1] = {}; + WINDOW_ORDER_INFO window_order_info = { + .windowId = RDP_RAIL_MARKER_WINDOW_ID, + .fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | + WINDOW_ORDER_FIELD_DESKTOP_ZORDER | + WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND, + }; windowsIdArray[0] = RDP_RAIL_MARKER_WINDOW_ID; - monitored_desktop_order.windowIds = (UINT*)&windowsIdArray; + MONITORED_DESKTOP_ORDER monitored_desktop_order = { + .activeWindowId = RDP_RAIL_DESKTOP_WINDOW_ID, + .numWindowIds = 1, + .windowIds = (UINT *)&windowsIdArray, + }; - client->context->update->window->MonitoredDesktop(client->context->update->context, &window_order_info, &monitored_desktop_order); + update->window->MonitoredDesktop(update->context, + &window_order_info, + &monitored_desktop_order); client->DrainOutputBuffer(client); } { - WINDOW_ORDER_INFO window_order_info = {}; + WINDOW_ORDER_INFO window_order_info = { + .windowId = RDP_RAIL_MARKER_WINDOW_ID, + .fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | + WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED, + }; MONITORED_DESKTOP_ORDER monitored_desktop_order = {}; - window_order_info.windowId = RDP_RAIL_MARKER_WINDOW_ID; - window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED; - - client->context->update->window->MonitoredDesktop(client->context->update->context, &window_order_info, &monitored_desktop_order); + update->window->MonitoredDesktop(update->context, + &window_order_info, + &monitored_desktop_order); client->DrainOutputBuffer(client); } - peerCtx->activationRailCompleted = true; + peer_ctx->activationRailCompleted = true; - { - wl_list_for_each(view, &b->compositor->view_list, link) { - struct weston_surface *surface = view->surface; - struct weston_subsurface *sub; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - if (!rail_state || rail_state->window_id == 0) { - rdp_rail_create_window(NULL, (void *)surface); - rail_state = (struct weston_surface_rail_state *)surface->backend_state; - if (rail_state && rail_state->window_id) { - if (b->rdprail_shell_api && - b->rdprail_shell_api->request_window_icon) - b->rdprail_shell_api->request_window_icon(surface); - } - wl_list_for_each(sub, &surface->subsurface_list, parent_link) { - struct weston_surface_rail_state *sub_rail_state = (struct weston_surface_rail_state *)sub->surface->backend_state; - if (sub->surface == surface) - continue; - if (!sub_rail_state || sub_rail_state->window_id == 0) - rdp_rail_create_window(NULL, (void *)sub->surface); - } + wl_list_for_each(view, &b->compositor->view_list, link) { + struct weston_surface *surface = view->surface; + struct weston_subsurface *sub; + struct weston_surface_rail_state *rail_state = surface->backend_state; + + if (!rail_state || rail_state->window_id == 0) { + rdp_rail_create_window(NULL, surface); + rail_state = surface->backend_state; + if (rail_state && rail_state->window_id) { + if (api && api->request_window_icon) + api->request_window_icon(surface); } - } + wl_list_for_each(sub, &surface->subsurface_list, parent_link) { + struct weston_surface_rail_state *sub_rail_state = sub->surface->backend_state; - /* this assume repaint to be scheduled on idle loop, not directly from here */ - weston_compositor_damage_all(b->compositor); + if (sub->surface == surface) + continue; + if (!sub_rail_state || sub_rail_state->window_id == 0) + rdp_rail_create_window(NULL, sub->surface); + } + } } - if (peerCtx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED) { + /* this assume repaint to be scheduled on idle loop, not directly from here */ + weston_compositor_damage_all(b->compositor); + + if (peer_ctx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED) { RAIL_POWER_DISPLAY_REQUEST displayRequest; /* subscribe idle/wake signal from compositor */ - peerCtx->idle_listener.notify = rdp_rail_idle_handler; - wl_signal_add(&b->compositor->idle_signal, &peerCtx->idle_listener); - peerCtx->wake_listener.notify = rdp_rail_wake_handler; - wl_signal_add(&b->compositor->wake_signal, &peerCtx->wake_listener); + peer_ctx->idle_listener.notify = rdp_rail_idle_handler; + wl_signal_add(&b->compositor->idle_signal, &peer_ctx->idle_listener); + peer_ctx->wake_listener.notify = rdp_rail_wake_handler; + wl_signal_add(&b->compositor->wake_signal, &peer_ctx->wake_listener); displayRequest.active = TRUE; - peerCtx->rail_server_context->ServerPowerDisplayRequest( - peerCtx->rail_server_context, &displayRequest); + rail_ctx->ServerPowerDisplayRequest(rail_ctx, &displayRequest); /* Upon client connection, make sure compositor is in wake state */ weston_compositor_wake(b->compositor); @@ -3320,30 +3612,38 @@ rdp_rail_sync_window_status(freerdp_peer* client) void rdp_rail_start_window_move( - struct weston_surface* surface, - int pointerGrabX, + struct weston_surface* surface, + int pointerGrabX, int pointerGrabY, struct weston_size minSize, struct weston_size maxSize) { struct weston_compositor *compositor = surface->compositor; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - struct weston_geometry windowGeometry = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; - RdpPeerContext *peerCtx = (RdpPeerContext *)b->rdp_peer->context;; + struct weston_surface_rail_state *rail_state = surface->backend_state; + struct weston_geometry geometry = { + .x = 0, + .y = 0, + .width = surface->width, + .height = surface->height, + }; + struct rdp_backend *b = to_rdp_backend(compositor); + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; + RdpPeerContext *peer_ctx; RAIL_MINMAXINFO_ORDER minmax_order; RAIL_LOCALMOVESIZE_ORDER move_order; + int posX = 0, posY = 0; + int numViews = 0; + struct weston_view* view; + RailServerContext *rail_ctx; - if (!b->rdp_peer || !b->rdp_peer->context->settings->HiDefRemoteApp) { - return; - } + if (!b->rdp_peer || !b->rdp_peer->context->settings->HiDefRemoteApp) + return; + + peer_ctx = (RdpPeerContext *)b->rdp_peer->context; assert_compositor_thread(b); assert(rail_state); - int posX=0, posY=0; - int numViews = 0; - struct weston_view* view; wl_list_for_each(view, &surface->views, surface_link) { numViews++; posX = view->geometry.x; @@ -3352,25 +3652,29 @@ rdp_rail_start_window_move( } if (numViews == 0) { view = NULL; - rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); + rdp_debug_verbose(b, + "%s: surface has no view (windowId:0x%x)\n", + __func__, rail_state->window_id); } - if (is_window_shadow_remoting_disabled(peerCtx)) { + if (is_window_shadow_remoting_disabled(peer_ctx)) { /* offset window shadow area */ - b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); - posX += windowGeometry.x; - posY += windowGeometry.y; + api->get_window_geometry(surface, &geometry); + posX += geometry.x; + posY += geometry.y; } /* apply global to output transform, and translate to client coordinate */ if (surface->output) { - to_client_coordinate(peerCtx, surface->output, - &posX, &posY, &minSize.width, &minSize.height); - to_client_coordinate(peerCtx, surface->output, - &pointerGrabX, &pointerGrabY, &maxSize.width, &maxSize.height); + to_client_coordinate(peer_ctx, surface->output, + &posX, &posY, + &minSize.width, &minSize.height); + to_client_coordinate(peer_ctx, surface->output, + &pointerGrabX, &pointerGrabY, + &maxSize.width, &maxSize.height); } - rdp_debug(b, "====================== StartWindowMove =============================\n"); + rdp_debug(b, "============== StartWindowMove ==============\n"); rdp_debug(b, "WindowsPosition: Pre-move (%d,%d) at client.\n", posX, posY); rdp_debug(b, "pointerGrab: (%d,%d)\n", pointerGrabX, pointerGrabY); rdp_debug(b, "minSize: (%dx%d)\n", minSize.width, minSize.height); @@ -3378,9 +3682,9 @@ rdp_rail_start_window_move( /* Inform the RDP client about the minimum/maximum width and height allowed * on this window. - */ + */ minmax_order.windowId = rail_state->window_id; - minmax_order.maxPosX = 0; + minmax_order.maxPosX = 0; minmax_order.maxPosY = 0; minmax_order.maxWidth = 0; minmax_order.maxHeight = 0; @@ -3389,50 +3693,62 @@ rdp_rail_start_window_move( minmax_order.maxTrackWidth = maxSize.width; minmax_order.maxTrackHeight = maxSize.height; - rdp_debug(b, "Minmax order: maxPosX:%d, maxPosY:%d, maxWidth:%d, maxHeight:%d\n", - minmax_order.maxPosX, - minmax_order.maxPosY, - minmax_order.maxWidth, - minmax_order.maxHeight); - rdp_debug(b, "Minmax order: minTrackWidth:%d, minTrackHeight:%d, maxTrackWidth:%d, maxTrackHeight:%d\n", - minmax_order.minTrackWidth, - minmax_order.minTrackHeight, - minmax_order.maxTrackWidth, - minmax_order.maxTrackHeight); - - peerCtx->rail_server_context->ServerMinMaxInfo( - peerCtx->rail_server_context, &minmax_order); + rdp_debug(b, + "Minmax order: maxPosX:%d, maxPosY:%d, maxWidth:%d, maxHeight:%d\n", + minmax_order.maxPosX, + minmax_order.maxPosY, + minmax_order.maxWidth, + minmax_order.maxHeight); + rdp_debug(b, + "Minmax order: minTrackWidth:%d, minTrackHeight:%d, maxTrackWidth:%d, maxTrackHeight:%d\n", + minmax_order.minTrackWidth, + minmax_order.minTrackHeight, + minmax_order.maxTrackWidth, + minmax_order.maxTrackHeight); + + rail_ctx = peer_ctx->rail_server_context; + rail_ctx->ServerMinMaxInfo(rail_ctx, &minmax_order); /* Start the local Window move. - */ + */ move_order.windowId = rail_state->window_id; move_order.isMoveSizeStart = true; move_order.moveSizeType = RAIL_WMSZ_MOVE; move_order.posX = pointerGrabX - posX; move_order.posY = pointerGrabY - posY; - rdp_debug(b, "Move order: windowId:0x%x, isMoveSizeStart:%d, moveType:%d, pos:(%d,%d)\n", - move_order.windowId, - move_order.isMoveSizeStart, - move_order.moveSizeType, - move_order.posX, - move_order.posY); + rdp_debug(b, + "Move order: windowId:0x%x, isMoveSizeStart:%d, moveType:%d, pos:(%d,%d)\n", + move_order.windowId, + move_order.isMoveSizeStart, + move_order.moveSizeType, + move_order.posX, + move_order.posY); - peerCtx->rail_server_context->ServerLocalMoveSize( - peerCtx->rail_server_context, &move_order); + rail_ctx->ServerLocalMoveSize(rail_ctx, &move_order); - rdp_debug(b, "====================== StartWindowMove =============================\n"); + rdp_debug(b, "=============== StartWindowMove ===============\n"); } void -rdp_rail_end_window_move(struct weston_surface* surface) +rdp_rail_end_window_move(struct weston_surface *surface) { struct weston_compositor *compositor = surface->compositor; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - struct weston_geometry windowGeometry = {.x = 0, .y = 0, .width = surface->width, .height = surface->height}; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; - RdpPeerContext *peerCtx = NULL; + struct weston_surface_rail_state *rail_state = surface->backend_state; + struct weston_geometry geometry = { + .x = 0, + .y = 0, + .width = surface->width, + .height = surface->height, + }; + struct rdp_backend *b = to_rdp_backend(compositor); + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; + RdpPeerContext *peer_ctx = NULL; + RailServerContext *rail_ctx; RAIL_LOCALMOVESIZE_ORDER move_order; + int posX = 0, posY = 0; + int numViews = 0; + struct weston_view *view; if (!b->rdp_peer || !b->rdp_peer->context->settings->HiDefRemoteApp) { return; @@ -3441,11 +3757,8 @@ rdp_rail_end_window_move(struct weston_surface* surface) assert_compositor_thread(b); assert(rail_state); - peerCtx = (RdpPeerContext *)b->rdp_peer->context; + peer_ctx = (RdpPeerContext *)b->rdp_peer->context; - int posX=0, posY=0; - int numViews = 0; - struct weston_view *view; wl_list_for_each(view, &surface->views, surface_link) { numViews++; posX = view->geometry.x; @@ -3454,22 +3767,24 @@ rdp_rail_end_window_move(struct weston_surface* surface) } if (numViews == 0) { view = NULL; - rdp_debug_verbose(b, "%s: surface has no view (windowId:0x%x)\n", __func__, rail_state->window_id); + rdp_debug_verbose(b, + "%s: surface has no view (windowId:0x%x)\n", + __func__, rail_state->window_id); } - if (is_window_shadow_remoting_disabled(peerCtx)) { + if (is_window_shadow_remoting_disabled(peer_ctx)) { /* offset window shadow area */ - b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); - posX += windowGeometry.x; - posY += windowGeometry.y; + api->get_window_geometry(surface, &geometry); + posX += geometry.x; + posY += geometry.y; } /* apply global to output transform, and translate to client coordinate */ if (surface->output) - to_client_coordinate(peerCtx, surface->output, - &posX, &posY, NULL, NULL); + to_client_coordinate(peer_ctx, surface->output, + &posX, &posY, NULL, NULL); - rdp_debug(b, "====================== EndWindowMove =============================\n"); + rdp_debug(b, "=============== EndWindowMove ===============\n"); rdp_debug(b, "WindowsPosition: Post-move (%d,%d) at client.\n", posX, posY); move_order.windowId = rail_state->window_id; @@ -3478,64 +3793,79 @@ rdp_rail_end_window_move(struct weston_surface* surface) move_order.posX = posX; move_order.posY = posY; - rdp_debug(b, "Move order: windowId:0x%x, isMoveSizeStart:%d, moveType:%d, pos:(%d,%d)\n", - move_order.windowId, - move_order.isMoveSizeStart, - move_order.moveSizeType, - move_order.posX, - move_order.posY); + rdp_debug(b, "Move order: windowId:0x%x, isMoveSizeStart:%d, moveType:%d, pos:(%d,%d)\n", + move_order.windowId, + move_order.isMoveSizeStart, + move_order.moveSizeType, + move_order.posX, + move_order.posY); - peerCtx->rail_server_context->ServerLocalMoveSize( - peerCtx->rail_server_context, &move_order); + rail_ctx = peer_ctx->rail_server_context; + rail_ctx->ServerLocalMoveSize(rail_ctx, &move_order); - rdp_debug(b, "====================== EndWindowMove =============================\n"); + rdp_debug(b, "=============== EndWindowMove ===============\n"); } static void rdp_rail_destroy_window_iter(void *element, void *data) { - struct weston_surface *surface = (struct weston_surface *)element; - rdp_rail_destroy_window(NULL, (void *)surface); + struct weston_surface *surface = element; + + rdp_rail_destroy_window(NULL, surface); } void -rdp_rail_peer_context_free(freerdp_peer* client, RdpPeerContext* context) +rdp_rail_peer_context_free(freerdp_peer *client, RdpPeerContext *context) { - rdp_id_manager_for_each(&context->windowId, rdp_rail_destroy_window_iter, NULL); + RailServerContext *rail_ctx; + RdpgfxServerContext *gfx_ctx; + DispServerContext *disp_ctx; + + rail_ctx = context->rail_server_context; + gfx_ctx = context->rail_grfx_server_context; + disp_ctx = context->disp_server_context; + + rdp_id_manager_for_each(&context->windowId, + rdp_rail_destroy_window_iter, + NULL); #ifdef HAVE_FREERDP_RDPAPPLIST_H if (context->applist_server_context) { struct rdp_backend *b = context->rdpBackend; + if (context->isAppListEnabled) context->rdpBackend->rdprail_shell_api->stop_app_list_update(context->rdpBackend->rdprail_shell_context); context->applist_server_context->Close(context->applist_server_context); assert(b->rdpapplist_server_context_free); b->rdpapplist_server_context_free(context->applist_server_context); } -#endif // HAVE_FREERDP_RDPAPPLIST_H +#endif /* HAVE_FREERDP_RDPAPPLIST_H */ #ifdef HAVE_FREERDP_GFXREDIR_H if (context->gfxredir_server_context) { struct rdp_backend *b = context->rdpBackend; - context->gfxredir_server_context->Close(context->gfxredir_server_context); + GfxRedirServerContext *redir_ctx; + redir_ctx = context->gfxredir_server_context; + + redir_ctx->Close(redir_ctx); assert(b->gfxredir_server_context_free); - b->gfxredir_server_context_free(context->gfxredir_server_context); + b->gfxredir_server_context_free(redir_ctx); } -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ - if (context->rail_grfx_server_context) { - context->rail_grfx_server_context->Close(context->rail_grfx_server_context); - rdpgfx_server_context_free(context->rail_grfx_server_context); + if (gfx_ctx) { + gfx_ctx->Close(gfx_ctx); + rdpgfx_server_context_free(gfx_ctx); } - if (context->disp_server_context) { - context->disp_server_context->Close(context->disp_server_context); - disp_server_context_free(context->disp_server_context); + if (disp_ctx) { + disp_ctx->Close(disp_ctx); + disp_server_context_free(disp_ctx); } - if (context->rail_server_context) { - context->rail_server_context->Stop(context->rail_server_context); - rail_server_context_free(context->rail_server_context); + if (rail_ctx) { + rail_ctx->Stop(rail_ctx); + rail_server_context_free(rail_ctx); } if (context->clientExec_destroy_listener.notify) { @@ -3556,97 +3886,103 @@ rdp_rail_peer_context_free(freerdp_peer* client, RdpPeerContext* context) #ifdef HAVE_FREERDP_GFXREDIR_H rdp_id_manager_free(&context->bufferId); rdp_id_manager_free(&context->poolId); -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ rdp_id_manager_free(&context->surfaceId); rdp_id_manager_free(&context->windowId); } -BOOL +bool rdp_drdynvc_init(freerdp_peer *client) { - RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + DrdynvcServerContext *vc_ctx; - assert_compositor_thread(peerCtx->rdpBackend); + assert_compositor_thread(peer_ctx->rdpBackend); /* Open Dynamic virtual channel */ - peerCtx->drdynvc_server_context = drdynvc_server_context_new(peerCtx->vcm); - if (!peerCtx->drdynvc_server_context) - return FALSE; - if (peerCtx->drdynvc_server_context->Start(peerCtx->drdynvc_server_context) != CHANNEL_RC_OK) { - drdynvc_server_context_free(peerCtx->drdynvc_server_context); - peerCtx->drdynvc_server_context = NULL; - return FALSE; + vc_ctx = drdynvc_server_context_new(peer_ctx->vcm); + + peer_ctx->drdynvc_server_context = drdynvc_server_context_new(peer_ctx->vcm); + if (!vc_ctx) + return false; + if (vc_ctx->Start(vc_ctx) != CHANNEL_RC_OK) { + drdynvc_server_context_free(vc_ctx); + return false; } + peer_ctx->drdynvc_server_context = vc_ctx; /* Force Dynamic virtual channel to exchange caps */ - if (WTSVirtualChannelManagerGetDrdynvcState(peerCtx->vcm) == DRDYNVC_STATE_NONE) { + if (WTSVirtualChannelManagerGetDrdynvcState(peer_ctx->vcm) == DRDYNVC_STATE_NONE) { + int waitRetry = 0; + client->activated = TRUE; /* Wait reply to arrive from client */ - UINT waitRetry = 0; - while (WTSVirtualChannelManagerGetDrdynvcState(peerCtx->vcm) != DRDYNVC_STATE_READY) { - if (++waitRetry > 10000) { // timeout after 100 sec. - rdp_drdynvc_destroy(peerCtx); + while (WTSVirtualChannelManagerGetDrdynvcState(peer_ctx->vcm) != DRDYNVC_STATE_READY) { + if (++waitRetry > 10000) { /* timeout after 100 sec. */ + rdp_drdynvc_destroy(peer_ctx); return FALSE; } - USleep(10000); // wait 0.01 sec. + usleep(10000); /* wait 0.01 sec. */ client->CheckFileDescriptor(client); - WTSVirtualChannelManagerCheckFileDescriptor(peerCtx->vcm); + WTSVirtualChannelManagerCheckFileDescriptor(peer_ctx->vcm); } } - return TRUE; + return true; } void -rdp_drdynvc_destroy(RdpPeerContext* context) +rdp_drdynvc_destroy(RdpPeerContext *context) { - if (context->drdynvc_server_context) { - context->drdynvc_server_context->Stop(context->drdynvc_server_context); - drdynvc_server_context_free(context->drdynvc_server_context); + DrdynvcServerContext *vc_ctx = context->drdynvc_server_context; + + if (vc_ctx) { + vc_ctx->Stop(vc_ctx); + drdynvc_server_context_free(vc_ctx); } } -BOOL -rdp_rail_peer_init(freerdp_peer *client, RdpPeerContext *peerCtx) +bool +rdp_rail_peer_init(freerdp_peer *client, RdpPeerContext *peer_ctx) { - struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_backend *b = peer_ctx->rdpBackend; /* RDP window ID must be within 31 bits range. MSB is reserved and exclude 0. */ - if (!rdp_id_manager_init(b, &peerCtx->windowId, 0x1, 0x7FFFFFFF)) { + if (!rdp_id_manager_init(b, &peer_ctx->windowId, 0x1, 0x7FFFFFFF)) { rdp_debug_error(b, "unable to create windowId.\n"); goto error_return; } /* RDP surface ID must be within 16 bits range, exclude 0. */ - if (!rdp_id_manager_init(b, &peerCtx->surfaceId, 0x1, 0xFFFF)) { + if (!rdp_id_manager_init(b, &peer_ctx->surfaceId, 0x1, 0xFFFF)) { rdp_debug_error(b, "unable to create windowId.\n"); goto error_return; } #ifdef HAVE_FREERDP_GFXREDIR_H /* RDP pool ID must be within 32 bits range, exclude 0. */ - if (!rdp_id_manager_init(b, &peerCtx->poolId, 0x1, 0xFFFFFFFF)) { + if (!rdp_id_manager_init(b, &peer_ctx->poolId, 0x1, 0xFFFFFFFF)) { rdp_debug_error(b, "unable to create windowId.\n"); goto error_return; } /* RDP buffer ID must be within 32 bits range, exclude 0. */ - if (!rdp_id_manager_init(b, &peerCtx->bufferId, 0x1, 0xFFFFFFFF)) { + if (!rdp_id_manager_init(b, &peer_ctx->bufferId, 0x1, 0xFFFFFFFF)) { rdp_debug_error(b, "unable to create windowId.\n"); goto error_return; } -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ - peerCtx->currentFrameId = 0; - peerCtx->acknowledgedFrameId = 0; + peer_ctx->currentFrameId = 0; + peer_ctx->acknowledgedFrameId = 0; return TRUE; error_return: #ifdef HAVE_FREERDP_GFXREDIR_H - rdp_id_manager_free(&peerCtx->bufferId); - rdp_id_manager_free(&peerCtx->poolId); -#endif // HAVE_FREERDP_GFXREDIR_H - rdp_id_manager_free(&peerCtx->surfaceId); - rdp_id_manager_free(&peerCtx->windowId); + rdp_id_manager_free(&peer_ctx->bufferId); + rdp_id_manager_free(&peer_ctx->poolId); +#endif /* HAVE_FREERDP_GFXREDIR_H */ + rdp_id_manager_free(&peer_ctx->surfaceId); + rdp_id_manager_free(&peer_ctx->windowId); return FALSE; } @@ -3674,12 +4010,15 @@ static void print_matrix(FILE *fp, const char *name, const struct weston_matrix *matrix) { int i; + if (name) - fprintf(fp," %s\n", name); + fprintf(fp, " %s\n", name); print_matrix_type(fp, matrix->type); for (i = 0; i < 4; i++) - fprintf(fp," %8.2f, %8.2f, %8.2f, %8.2f\n", - matrix->d[4*i+0], matrix->d[4*i+1], matrix->d[4*1+2], matrix->d[4*i+3]); + fprintf(fp, + " %8.2f, %8.2f, %8.2f, %8.2f\n", + matrix->d[4*i+0], matrix->d[4*i+1], + matrix->d[4*1+2], matrix->d[4*i+3]); } static void @@ -3712,35 +4051,43 @@ print_rdp_head(FILE *fp, const struct rdp_head *current) fprintf(fp," workarea: x:%d, y:%d, width:%d, height:%d\n", current->workarea.x, current->workarea.y, current->workarea.width, current->workarea.height); - fprintf(fp," RDP client workarea: x:%d, y:%d, width:%d, height%d\n", + fprintf(fp, " RDP client workarea: x:%d, y:%d, width:%d, height%d\n", current->workareaClient.x, current->workareaClient.y, current->workareaClient.width, current->workareaClient.height); - fprintf(fp," connected:%d, non_desktop:%d\n", + fprintf(fp, " connected:%d, non_desktop:%d\n", current->base.connected, current->base.non_desktop); - fprintf(fp," assigned output: %s\n", + fprintf(fp, " assigned output: %s\n", current->base.output ? current->base.output->name : "(no output)"); if (current->base.output) { - fprintf(fp," output extents box: x1:%d, y1:%d, x2:%d, y2:%d\n", - current->base.output->region.extents.x1, current->base.output->region.extents.y1, - current->base.output->region.extents.x2, current->base.output->region.extents.y2); - fprintf(fp," output scale:%d, output native_scale:%d\n", - current->base.output->scale, current->base.output->native_scale); - print_matrix(fp, "global to output matrix:", ¤t->base.output->matrix); - print_matrix(fp, "output to global matrix:", ¤t->base.output->inverse_matrix); + fprintf(fp, " output extents box: x1:%d, y1:%d, x2:%d, y2:%d\n", + current->base.output->region.extents.x1, + current->base.output->region.extents.y1, + current->base.output->region.extents.x2, + current->base.output->region.extents.y2); + fprintf(fp, " output scale:%d, output native_scale:%d\n", + current->base.output->scale, + current->base.output->native_scale); + print_matrix(fp, "global to output matrix:", + ¤t->base.output->matrix); + print_matrix(fp, "output to global matrix:", + ¤t->base.output->inverse_matrix); } } static void rdp_rail_dump_monitor_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) + const struct timespec *time, + uint32_t key, void *data) { - struct rdp_backend *b = (struct rdp_backend *)data; + struct rdp_backend *b = data; + if (b) { struct weston_head *current; int err; char *str; size_t len; FILE *fp = open_memstream(&str, &len); + assert(fp); fprintf(fp,"\nrdp debug binding 'M' - dump all monitor.\n"); wl_list_for_each(current, &b->compositor->head_list, compositor_link) { @@ -3758,17 +4105,18 @@ rdp_rail_dump_monitor_binding(struct weston_keyboard *keyboard, struct rdp_rail_dump_window_context { FILE *fp; - RdpPeerContext *peerCtx; + RdpPeerContext *peer_ctx; }; static void rdp_rail_dump_window_label(struct weston_surface *surface, char *label, uint32_t label_size) { if (surface->get_label) { - strcpy(label, "Label: "); // 7 chars + strcpy(label, "Label: "); /* 7 chars */ surface->get_label(surface, label + 7, label_size - 7); } else if (surface->role_name) { - snprintf(label, label_size, "RoleName: %s", surface->role_name); + snprintf(label, label_size, + "RoleName: %s", surface->role_name); } else { strcpy(label, "(No Label, No Role name)"); } @@ -3777,75 +4125,83 @@ rdp_rail_dump_window_label(struct weston_surface *surface, char *label, uint32_t static void rdp_rail_dump_window_iter(void *element, void *data) { - struct weston_surface *surface = (struct weston_surface *)element; - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - struct rdp_rail_dump_window_context *context = (struct rdp_rail_dump_window_context *)data; - struct rdp_backend *b = context->peerCtx->rdpBackend; - assert(rail_state); // this iter is looping from window hash table, thus it must have rail_state initialized. + struct weston_surface *surface = element; + struct weston_surface_rail_state *rail_state = surface->backend_state; + struct rdp_rail_dump_window_context *context = data; + struct rdp_backend *b = context->peer_ctx->rdpBackend; + const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; FILE *fp = context->fp; char label[256] = {}; - struct weston_geometry windowGeometry = {}; + struct weston_geometry geometry = {}; struct weston_view *view; - int contentBufferWidth, contentBufferHeight; - weston_surface_get_content_size(surface, &contentBufferWidth, &contentBufferHeight); + int content_buffer_width, content_buffer_height; + + /* this iter is looping from window hash table, + * thus it must have rail_state initialized. + */ + assert(rail_state); + + weston_surface_get_content_size(surface, + &content_buffer_width, + &content_buffer_height); - if (b->rdprail_shell_api && - b->rdprail_shell_api->get_window_geometry) - b->rdprail_shell_api->get_window_geometry(surface, &windowGeometry); + if (api && api->get_window_geometry) + api->get_window_geometry(surface, &geometry); rdp_rail_dump_window_label(surface, label, sizeof(label)); - fprintf(fp," %s\n", label); - fprintf(fp," WindowId:0x%x, SurfaceId:0x%x\n", + fprintf(fp, " %s\n", label); + fprintf(fp, " WindowId:0x%x, SurfaceId:0x%x\n", rail_state->window_id, rail_state->surface_id); - fprintf(fp," PoolId:0x%x, BufferId:0x%x\n", + fprintf(fp, " PoolId:0x%x, BufferId:0x%x\n", rail_state->pool_id, rail_state->buffer_id); - fprintf(fp," Position x:%d, y:%d width:%d height:%d\n", + fprintf(fp, " Position x:%d, y:%d width:%d height:%d\n", rail_state->pos.x, rail_state->pos.y, rail_state->pos.width, rail_state->pos.height); - fprintf(fp," RDP client position x:%d, y:%d width:%d height:%d\n", + fprintf(fp, " RDP client position x:%d, y:%d width:%d height:%d\n", rail_state->clientPos.x, rail_state->clientPos.y, rail_state->clientPos.width, rail_state->clientPos.height); - fprintf(fp," Window margin left:%d, top:%d, right:%d bottom:%d\n", + fprintf(fp, " Window margin left:%d, top:%d, right:%d bottom:%d\n", rail_state->window_margin_left, rail_state->window_margin_top, rail_state->window_margin_right, rail_state->window_margin_bottom); - fprintf(fp," Window geometry x:%d, y:%d, width:%d height:%d\n", - windowGeometry.x, windowGeometry.y, - windowGeometry.width, windowGeometry.height); - fprintf(fp," input extents: x1:%d, y1:%d, x2:%d, y2:%d\n", + fprintf(fp, " Window geometry x:%d, y:%d, width:%d height:%d\n", + geometry.x, geometry.y, + geometry.width, geometry.height); + fprintf(fp, " input extents: x1:%d, y1:%d, x2:%d, y2:%d\n", surface->input.extents.x1, surface->input.extents.y1, surface->input.extents.x2, surface->input.extents.y2); - fprintf(fp," bufferWidth:%d, bufferHeight:%d\n", + fprintf(fp, " bufferWidth:%d, bufferHeight:%d\n", rail_state->bufferWidth, rail_state->bufferHeight); - fprintf(fp," bufferScaleFactorWidth:%.2f, bufferScaleFactorHeight:%.2f\n", + fprintf(fp, " bufferScaleFactorWidth:%.2f, bufferScaleFactorHeight:%.2f\n", rail_state->bufferScaleFactorWidth, rail_state->bufferScaleFactorHeight); - fprintf(fp," contentBufferWidth:%d, contentBufferHeight:%d\n", - contentBufferWidth, contentBufferHeight); - fprintf(fp," is_opaque:%d\n", surface->is_opaque); + fprintf(fp, " content_buffer_width:%d, content_buffer_height:%d\n", + content_buffer_width, content_buffer_height); + fprintf(fp, " is_opaque:%d\n", surface->is_opaque); if (!surface->is_opaque && pixman_region32_not_empty(&surface->opaque)) { int numRects = 0; pixman_box32_t *rects = pixman_region32_rectangles(&surface->opaque, &numRects); + fprintf(fp, " opaque region: numRects:%d\n", numRects); for (int n = 0; n < numRects; n++) fprintf(fp, " [%d]: (%d, %d) - (%d, %d)\n", n, rects[n].x1, rects[n].y1, rects[n].x2, rects[n].y2); } - fprintf(fp," parent_surface:%p, isCursor:%d, isWindowCreated:%d\n", + fprintf(fp, " parent_surface:%p, isCursor:%d, isWindowCreated:%d\n", rail_state->parent_surface, rail_state->isCursor, rail_state->isWindowCreated); - fprintf(fp," isWindowMinimized:%d, isWindowMinimizedRequested:%d\n", + fprintf(fp, " isWindowMinimized:%d, isWindowMinimizedRequested:%d\n", rail_state->is_minimized, rail_state->is_minimized_requested); - fprintf(fp," isWindowMaximized:%d, isWindowMaximizedRequested:%d\n", + fprintf(fp, " isWindowMaximized:%d, isWindowMaximizedRequested:%d\n", rail_state->is_maximized, rail_state->is_maximized_requested); - fprintf(fp," isWindowFullscreen:%d, isWindowFullscreenRequested:%d\n", + fprintf(fp, " isWindowFullscreen:%d, isWindowFullscreenRequested:%d\n", rail_state->is_fullscreen, rail_state->is_fullscreen_requested); - fprintf(fp," forceRecreateSurface:%d, error:%d\n", + fprintf(fp, " forceRecreateSurface:%d, error:%d\n", rail_state->forceRecreateSurface, rail_state->error); - fprintf(fp," isUdatePending:%d, isFirstUpdateDone:%d\n", + fprintf(fp, " isUdatePending:%d, isFirstUpdateDone:%d\n", rail_state->isUpdatePending, rail_state->isFirstUpdateDone); - fprintf(fp," surface:0x%p\n", surface); + fprintf(fp, " surface:0x%p\n", surface); wl_list_for_each(view, &surface->views, surface_link) { - fprintf(fp," view: %p\n", view); - fprintf(fp," view's alpha: %3.2f\n", view->alpha); - fprintf(fp," view's opaque region: x1:%d, y1:%d, x2:%d, y2:%d\n", + fprintf(fp, " view: %p\n", view); + fprintf(fp, " view's alpha: %3.2f\n", view->alpha); + fprintf(fp, " view's opaque region: x1:%d, y1:%d, x2:%d, y2:%d\n", view->transform.opaque.extents.x1, view->transform.opaque.extents.y1, view->transform.opaque.extents.x2, @@ -3853,32 +4209,36 @@ rdp_rail_dump_window_iter(void *element, void *data) if (pixman_region32_not_empty(&view->transform.opaque)) { int numRects = 0; pixman_box32_t *rects = pixman_region32_rectangles(&view->transform.opaque, &numRects); - fprintf(fp," view's opaque region: numRects:%d\n", numRects); + + fprintf(fp, " view's opaque region: numRects:%d\n", numRects); for (int n = 0; n < numRects; n++) fprintf(fp, " [%d]: (%d, %d) - (%d, %d)\n", n, rects[n].x1, rects[n].y1, rects[n].x2, rects[n].y2); } - fprintf(fp," view's boundingbox: x1:%d, y1:%d, x2:%d, y2:%d\n", + fprintf(fp, " view's boundingbox: x1:%d, y1:%d, x2:%d, y2:%d\n", view->transform.boundingbox.extents.x1, view->transform.boundingbox.extents.y1, view->transform.boundingbox.extents.x2, view->transform.boundingbox.extents.y2); - fprintf(fp," view's scissor: x1:%d, y1:%d, x2:%d, y2:%d\n", + fprintf(fp, " view's scissor: x1:%d, y1:%d, x2:%d, y2:%d\n", view->geometry.scissor.extents.x1, view->geometry.scissor.extents.y1, view->geometry.scissor.extents.x2, view->geometry.scissor.extents.y2); - fprintf(fp," view's transform: enabled:%d\n", + fprintf(fp, " view's transform: enabled:%d\n", view->transform.enabled); if (view->transform.enabled) print_matrix(fp, NULL, &view->transform.matrix); } - print_matrix(fp, "buffer to surface matrix:", &surface->buffer_to_surface_matrix); - print_matrix(fp, "surface to buffer matrix:", &surface->surface_to_buffer_matrix); - fprintf(fp," output:0x%p (%s)\n", surface->output, + print_matrix(fp, "buffer to surface matrix:", + &surface->buffer_to_surface_matrix); + print_matrix(fp, "surface to buffer matrix:", + &surface->surface_to_buffer_matrix); + fprintf(fp, " output:0x%p (%s)\n", surface->output, surface->output ? surface->output->name : "(no output assigned)"); if (surface->output) { struct weston_head *base_head; + wl_list_for_each(base_head, &surface->output->head_list, output_link) print_rdp_head(fp, to_rdp_head(base_head)); } @@ -3887,10 +4247,12 @@ rdp_rail_dump_window_iter(void *element, void *data) static void rdp_rail_dump_window_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) + const struct timespec *time, + uint32_t key, void *data) { - struct rdp_backend *b = (struct rdp_backend *)data; - RdpPeerContext *peerCtx; + struct rdp_backend *b = data; + RdpPeerContext *peer_ctx; + if (b && b->rdp_peer && b->rdp_peer->context) { /* print window from window hash table */ struct rdp_rail_dump_window_context context; @@ -3898,18 +4260,19 @@ rdp_rail_dump_window_binding(struct weston_keyboard *keyboard, char *str; size_t len; FILE *fp = open_memstream(&str, &len); + assert(fp); - fprintf(fp,"\nrdp debug binding 'W' - dump all window.\n"); - peerCtx = (RdpPeerContext *)b->rdp_peer->context; - dump_id_manager_state(fp, &peerCtx->windowId, "windowId"); - dump_id_manager_state(fp, &peerCtx->surfaceId, "surfaceId"); + fprintf(fp, "\nrdp debug binding 'W' - dump all window.\n"); + peer_ctx = (RdpPeerContext *)b->rdp_peer->context; + dump_id_manager_state(fp, &peer_ctx->windowId, "windowId"); + dump_id_manager_state(fp, &peer_ctx->surfaceId, "surfaceId"); #ifdef HAVE_FREERDP_GFXREDIR_H - dump_id_manager_state(fp, &peerCtx->poolId, "poolId"); - dump_id_manager_state(fp, &peerCtx->bufferId, "bufferId"); -#endif // HAVE_FREERDP_GFXREDIR_H - context.peerCtx = peerCtx; + dump_id_manager_state(fp, &peer_ctx->poolId, "poolId"); + dump_id_manager_state(fp, &peer_ctx->bufferId, "bufferId"); +#endif /* HAVE_FREERDP_GFXREDIR_H */ + context.peer_ctx = peer_ctx; context.fp = fp; - rdp_id_manager_for_each(&peerCtx->windowId, rdp_rail_dump_window_iter, (void*)&context); + rdp_id_manager_for_each(&peer_ctx->windowId, rdp_rail_dump_window_iter, (void*)&context); err = fclose(fp); assert(err == 0); rdp_debug_error(b, "%s", str); @@ -3923,16 +4286,19 @@ rdp_rail_dump_window_binding(struct weston_keyboard *keyboard, } static void * -rdp_rail_shell_initialize_notify(struct weston_compositor *compositor, const struct weston_rdprail_shell_api *rdprail_shell_api, void *context, char *name) +rdp_rail_shell_initialize_notify(struct weston_compositor *compositor, + const struct weston_rdprail_shell_api *rdprail_shell_api, + void *context, char *name) { struct rdp_backend *b = to_rdp_backend(compositor); + b->rdprail_shell_api = rdprail_shell_api; b->rdprail_shell_context = context; - if (b->rdprail_shell_name) - free(b->rdprail_shell_name); + free(b->rdprail_shell_name); b->rdprail_shell_name = name ? strdup(name) : NULL; - rdp_debug(b, "%s: shell: distro name: %s\n",__func__, b->rdprail_shell_name); - return (void *) b; + rdp_debug(b, "%s: shell: distro name: %s\n", + __func__, b->rdprail_shell_name); + return (void *)b; } #define WINDOW_ORDER_ICON_ROWLENGTH( W, BPP ) ((((W) * (BPP) + 31) / 32) * 4) @@ -3940,35 +4306,37 @@ rdp_rail_shell_initialize_notify(struct weston_compositor *compositor, const str static void rdp_rail_set_window_icon(struct weston_surface *surface, pixman_image_t *icon) { - struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct weston_surface_rail_state *rail_state = surface->backend_state; struct weston_compositor *compositor = surface->compositor; - struct rdp_backend *b = (struct rdp_backend*)compositor->backend; - RdpPeerContext *peerCtx; - WINDOW_ORDER_INFO orderInfo = {}; - WINDOW_ICON_ORDER iconOrder = {}; - ICON_INFO iconInfo = {}; - pixman_image_t *scaledIcon = NULL; - bool bitsColorAllocated = false; - void *bitsColor = NULL; - void *bitsMask = NULL; + struct rdp_backend *b = to_rdp_backend(compositor); + rdpUpdate *update; + RdpPeerContext *peer_ctx; + WINDOW_ORDER_INFO order_info = {}; + WINDOW_ICON_ORDER icon_order = {}; + ICON_INFO icon_info = {}; + pixman_image_t *scaled_icon = NULL; + bool bits_color_allocated = false; + void *bits_color = NULL; + void *bits_mask = NULL; int width; int height; int stride; - double xScale; - double yScale; + double x_scale; + double y_scale; struct pixman_transform transform; pixman_format_code_t format; - int maxIconWidth; - int maxIconHeight; - int targetIconWidth; - int targetIconHeight; + int max_icon_width; + int max_icon_height; + int target_icon_width; + int target_icon_height; if (!b || !b->rdp_peer) { rdp_debug_error(b, "set_window_icon(): rdp_peer is not initalized\n"); return; } - peerCtx = (RdpPeerContext *)b->rdp_peer->context; + peer_ctx = (RdpPeerContext *)b->rdp_peer->context; + update = b->rdp_peer->context->update; if (!b->rdp_peer->context->settings->HiDefRemoteApp) return; @@ -3977,7 +4345,7 @@ rdp_rail_set_window_icon(struct weston_surface *surface, pixman_image_t *icon) if (!rail_state || rail_state->window_id == 0) { rdp_rail_create_window(NULL, (void *)surface); - rail_state = (struct weston_surface_rail_state *)surface->backend_state; + rail_state = surface->backend_state; if (!rail_state || rail_state->window_id == 0) return; } @@ -3991,164 +4359,171 @@ rdp_rail_set_window_icon(struct weston_surface *surface, pixman_image_t *icon) return; rdp_debug_verbose(b, "rdp_rail_set_window_icon: original icon width:%d height:%d format:%d\n", - width, height, format); + width, height, format); /* TS_RAIL_CLIENTSTATUS_HIGH_DPI_ICONS_SUPPORTED Indicates that the client supports icons up to 96 pixels in size in the Window Icon PDU. If this flag is not present, icon dimensions are limited to 32 pixels. */ - if (peerCtx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_HIGH_DPI_ICONS_SUPPORTED) { - maxIconWidth = 96; - maxIconHeight = 96; + if (peer_ctx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_HIGH_DPI_ICONS_SUPPORTED) { + max_icon_width = 96; + max_icon_height = 96; } else { - maxIconWidth = 32; - maxIconHeight = 32; + max_icon_width = 32; + max_icon_height = 32; } - if (width > maxIconWidth) - targetIconWidth = maxIconWidth; + if (width > max_icon_width) + target_icon_width = max_icon_width; else - targetIconWidth = width; + target_icon_width = width; - if (height > maxIconHeight) - targetIconHeight = maxIconHeight; + if (height > max_icon_height) + target_icon_height = max_icon_height; else - targetIconHeight = height; + target_icon_height = height; /* create icon bitmap with flip in Y-axis, and client always expects a8r8g8b8 format. */ - scaledIcon = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, - targetIconWidth, targetIconHeight, NULL, 0); - if (!scaledIcon) + scaled_icon = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, + target_icon_width, + target_icon_height, + NULL, 0); + if (!scaled_icon) return; - xScale = (double)width / targetIconWidth; - yScale = (double)height / targetIconHeight; + x_scale = (double)width / target_icon_width; + y_scale = (double)height / target_icon_height; pixman_transform_init_scale(&transform, - pixman_double_to_fixed(xScale), - pixman_double_to_fixed(yScale * -1)); // flip Y. + pixman_double_to_fixed(x_scale), + pixman_double_to_fixed(y_scale * -1)); /* flip Y. */ pixman_transform_translate(&transform, NULL, - 0, pixman_int_to_fixed(height)); + 0, pixman_int_to_fixed(height)); pixman_image_set_transform(icon, &transform); pixman_image_set_filter(icon, PIXMAN_FILTER_BILINEAR, NULL, 0); pixman_image_composite32(PIXMAN_OP_SRC, - icon, /* src */ - NULL, /* mask */ - scaledIcon, /* dest */ - 0, 0, /* src_x, src_y */ - 0, 0, /* mask_x, mask_y */ - 0, 0, /* dest_x, dest_y */ - targetIconWidth, /* width */ - targetIconHeight /* height */); + icon, /* src */ + NULL, /* mask */ + scaled_icon, /* dest */ + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + target_icon_width, /* width */ + target_icon_height /* height */); pixman_image_set_filter(icon, PIXMAN_FILTER_NEAREST, NULL, 0); pixman_image_set_transform(icon, NULL); - icon = scaledIcon; + icon = scaled_icon; width = pixman_image_get_width(icon); height = pixman_image_get_height(icon); format = pixman_image_get_format(icon); stride = pixman_image_get_stride(icon); - assert(width == targetIconWidth); - assert(height == targetIconHeight); + assert(width == target_icon_width); + assert(height == target_icon_height); assert(format == PIXMAN_a8r8g8b8); rdp_debug_verbose(b, "rdp_rail_set_window_icon: converted icon width:%d height:%d format:%d\n", - width, height, format); + width, height, format); /* color bitmap is 32 bits */ - int strideColor = WINDOW_ORDER_ICON_ROWLENGTH(width, 32); - int sizeColor = strideColor * height; - if (strideColor != stride) { + int stride_color = WINDOW_ORDER_ICON_ROWLENGTH(width, 32); + int size_color = stride_color * height; + + if (stride_color != stride) { /* when pixman's stride is differnt from client's expetation, need to adjust. */ - sizeColor = strideColor * height; - bitsColor = malloc(sizeColor); - if (!bitsColor) - goto exit; - bitsColorAllocated = true; + size_color = stride_color * height; + bits_color = xmalloc(size_color); + bits_color_allocated = true; } else { - bitsColor = (char *)pixman_image_get_data(icon); + bits_color = pixman_image_get_data(icon); } /* Mask is 1 bit */ - int strideMask = WINDOW_ORDER_ICON_ROWLENGTH(width, 1); - int sizeMask = strideMask * height; - bitsMask = zalloc(sizeMask); - if (!bitsMask) - goto exit; + int stride_mask = WINDOW_ORDER_ICON_ROWLENGTH(width, 1); + int size_mask = stride_mask * height; + + bits_mask = xzalloc(size_mask); /* generate mask and copy color bits, match to the stride RDP wants when different. */ - char *srcColor = (char *)pixman_image_get_data(icon); - char *dstColor = (char *)bitsColor; - char *dstMask = (char *)bitsMask; + char *src_color = (char *)pixman_image_get_data(icon); + char *dst_color = bits_color; + char *dst_mask = bits_mask; + for (int i = 0; i < height; i++) { - uint32_t *src = (uint32_t *)srcColor; - uint32_t *dst = (uint32_t *)dstColor; - char *mask = dstMask; + uint32_t *src = (uint32_t *)src_color; + uint32_t *dst = (uint32_t *)dst_color; + char *mask = dst_mask; + for (int j = 0; j < width; j++) { if (dst != src) *dst = *src; if (*dst & 0xFF000000) - mask[j / 8] |= (0x80 >> (j % 8)); - dst++; src++; + mask[j / 8] |= 0x80 >> (j % 8); + dst++; + src++; } - srcColor += stride; - dstColor += strideColor; - dstMask += strideMask; - } - - orderInfo.windowId = rail_state->window_id; - orderInfo.fieldFlags = WINDOW_ORDER_TYPE_WINDOW | WINDOW_ORDER_ICON; - iconInfo.cacheEntry = 0xFFFF; // no cache - iconInfo.cacheId = 0xFF; // no cache - iconInfo.bpp = 32; - iconInfo.width = (UINT32)width; - iconInfo.height = (UINT32)height; - iconInfo.cbColorTable = 0; - iconInfo.cbBitsMask = sizeMask; - iconInfo.cbBitsColor = sizeColor; - iconInfo.bitsMask = bitsMask; - iconInfo.colorTable = NULL; - iconInfo.bitsColor = bitsColor; - iconOrder.iconInfo = &iconInfo; - - b->rdp_peer->context->update->BeginPaint(b->rdp_peer->context->update->context); - b->rdp_peer->context->update->window->WindowIcon(b->rdp_peer->context->update->context, &orderInfo, &iconOrder); - b->rdp_peer->context->update->EndPaint(b->rdp_peer->context->update->context); - -exit: - if (bitsMask) - free(bitsMask); - if (bitsColorAllocated) - free(bitsColor); - if (scaledIcon) - pixman_image_unref(scaledIcon); + src_color += stride; + dst_color += stride_color; + dst_mask += stride_mask; + } + + order_info.windowId = rail_state->window_id; + order_info.fieldFlags = WINDOW_ORDER_TYPE_WINDOW | WINDOW_ORDER_ICON; + icon_info.cacheEntry = 0xFFFF; /* no cache */ + icon_info.cacheId = 0xFF; /* no cache */ + icon_info.bpp = 32; + icon_info.width = (uint32_t)width; + icon_info.height = (uint32_t)height; + icon_info.cbColorTable = 0; + icon_info.cbBitsMask = size_mask; + icon_info.cbBitsColor = size_color; + icon_info.bitsMask = bits_mask; + icon_info.colorTable = NULL; + icon_info.bitsColor = bits_color; + icon_order.iconInfo = &icon_info; + + update->BeginPaint(update->context); + update->window->WindowIcon(update->context, &order_info, &icon_order); + update->EndPaint(update->context); + + free(bits_mask); + if (bits_color_allocated) + free(bits_color); + if (scaled_icon) + pixman_image_unref(scaled_icon); return; } #ifdef HAVE_FREERDP_RDPAPPLIST_H static bool -rdp_rail_notify_app_list(void *rdp_backend, struct weston_rdprail_app_list_data *app_list_data) +rdp_rail_notify_app_list(void *rdp_backend, + struct weston_rdprail_app_list_data *app_list_data) { - struct rdp_backend *b = (struct rdp_backend*)rdp_backend; - RdpPeerContext *peerCtx; + struct rdp_backend *b = rdp_backend; + RdpAppListServerContext *applist_ctx; + RdpPeerContext *peer_ctx; if (!b || !b->rdp_peer) { rdp_debug_error(b, "rdp_rail_notify_app_list(): rdp_peer is not initalized\n"); - return false; // return false only when peer is not ready for possible re-send. + /* return false only when peer is not ready for + * possible re-send. + */ + return false; } if (!b->rdp_peer->context->settings->HiDefRemoteApp) return true; - peerCtx = (RdpPeerContext *)b->rdp_peer->context; + peer_ctx = (RdpPeerContext *)b->rdp_peer->context; - if (!peerCtx->applist_server_context) + applist_ctx = peer_ctx->applist_server_context; + if (applist_ctx) return false; - rdp_debug(b, "rdp_rail_notify_app_list(): rdp_peer %p\n", peerCtx); + rdp_debug(b, "rdp_rail_notify_app_list(): rdp_peer %p\n", peer_ctx); rdp_debug(b, " inSync: %d\n", app_list_data->inSync); rdp_debug(b, " syncStart: %d\n", app_list_data->syncStart); rdp_debug(b, " syncEnd: %d\n", app_list_data->syncEnd); @@ -4164,68 +4539,68 @@ rdp_rail_notify_app_list(void *rdp_backend, struct weston_rdprail_app_list_data rdp_debug(b, " appProvider: %s\n", app_list_data->appProvider); if (app_list_data->deleteAppId) { - RDPAPPLIST_DELETE_APPLIST_PDU deleteAppList = {}; - assert(app_list_data->appProvider == NULL); // provider must be NULL. - deleteAppList.flags = RDPAPPLIST_FIELD_ID; - if (app_list_data->appId == NULL || // appId is required. - !utf8_string_to_rail_string(app_list_data->appId, &deleteAppList.appId)) + RDPAPPLIST_DELETE_APPLIST_PDU delete_app_list = {}; + + assert(app_list_data->appProvider == NULL); + delete_app_list.flags = RDPAPPLIST_FIELD_ID; + if (app_list_data->appId == NULL || + !utf8_string_to_rail_string(app_list_data->appId, &delete_app_list.appId)) goto Exit_deletePath; - if (app_list_data->appGroup && // appGroup is optional. - utf8_string_to_rail_string(app_list_data->appGroup, &deleteAppList.appGroup)) { + if (app_list_data->appGroup && + utf8_string_to_rail_string(app_list_data->appGroup, &delete_app_list.appGroup)) { deleteAppList.flags |= RDPAPPLIST_FIELD_GROUP; } - peerCtx->applist_server_context->DeleteApplicationList(peerCtx->applist_server_context, &deleteAppList); + applist_ctx->DeleteApplicationList(applist_ctx, &delete_app_list); Exit_deletePath: - if (deleteAppList.appId.string) - free(deleteAppList.appId.string); - if (deleteAppList.appGroup.string) - free(deleteAppList.appGroup.string); + free(delete_app_list.appId.string); + free(delete_app_list.appGroup.string); } else if (app_list_data->deleteAppProvider) { - RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU deleteAppListProvider = {}; - deleteAppListProvider.flags = RDPAPPLIST_FIELD_PROVIDER; - if (app_list_data->appProvider && // appProvider is required. - utf8_string_to_rail_string(app_list_data->appProvider, &deleteAppListProvider.appListProviderName)) - peerCtx->applist_server_context->DeleteApplicationListProvider(peerCtx->applist_server_context, &deleteAppListProvider); - if (deleteAppListProvider.appListProviderName.string) - free(deleteAppListProvider.appListProviderName.string); + RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU del_provider = {}; + + del_provider.flags = RDPAPPLIST_FIELD_PROVIDER; + if (app_list_data->appProvider && + utf8_string_to_rail_string(app_list_data->appProvider, &del_provider.appListProviderName)) + applist_ctx->DeleteApplicationListProvider(applistctx, &del_provider); + free(del_provider.appListProviderName.string); } else { - RDPAPPLIST_UPDATE_APPLIST_PDU updateAppList = {}; + RDPAPPLIST_UPDATE_APPLIST_PDU update_app_list = {}; RDPAPPLIST_ICON_DATA iconData = {}; - assert(app_list_data->appProvider == NULL); // group must be NULL. - updateAppList.flags = app_list_data->newAppId ? RDPAPPLIST_HINT_NEWID : 0; + + assert(app_list_data->appProvider == NULL); + update_app_list.flags = app_list_data->newAppId ? RDPAPPLIST_HINT_NEWID : 0; if (app_list_data->inSync) - updateAppList.flags |= RDPAPPLIST_HINT_SYNC; + update_app_list.flags |= RDPAPPLIST_HINT_SYNC; if (app_list_data->syncStart) { assert(app_list_data->inSync); - updateAppList.flags |= RDPAPPLIST_HINT_SYNC_START; + update_app_list.flags |= RDPAPPLIST_HINT_SYNC_START; } if (app_list_data->syncEnd) { assert(app_list_data->inSync); - updateAppList.flags |= RDPAPPLIST_HINT_SYNC_END; + update_app_list.flags |= RDPAPPLIST_HINT_SYNC_END; } - updateAppList.flags |= (RDPAPPLIST_FIELD_ID | - RDPAPPLIST_FIELD_EXECPATH | - RDPAPPLIST_FIELD_DESC); - if (app_list_data->appId == NULL || // id is required. - !utf8_string_to_rail_string(app_list_data->appId, &updateAppList.appId)) + update_app_list.flags |= RDPAPPLIST_FIELD_ID | + RDPAPPLIST_FIELD_EXECPATH | + RDPAPPLIST_FIELD_DESC; + if (app_list_data->appId == NULL || /* id is required. */ + !utf8_string_to_rail_string(app_list_data->appId, &update_app_list.appId)) goto Exit_updatePath; - if (app_list_data->appExecPath == NULL || // exePath is required. - !utf8_string_to_rail_string(app_list_data->appExecPath, &updateAppList.appExecPath)) + if (app_list_data->appExecPath == NULL || /* exePath is required. */ + !utf8_string_to_rail_string(app_list_data->appExecPath, &update_app_list.appExecPath)) goto Exit_updatePath; - if (app_list_data->appDesc == NULL || // desc is required. - !utf8_string_to_rail_string(app_list_data->appDesc, &updateAppList.appDesc)) + if (app_list_data->appDesc == NULL || /* desc is required. */ + !utf8_string_to_rail_string(app_list_data->appDesc, &update_app_list.appDesc)) goto Exit_updatePath; - if (app_list_data->appGroup && // group is optional. - utf8_string_to_rail_string(app_list_data->appGroup, &updateAppList.appGroup)) { - updateAppList.flags |= RDPAPPLIST_FIELD_GROUP; + if (app_list_data->appGroup && /* group is optional. */ + utf8_string_to_rail_string(app_list_data->appGroup, &update_app_list.appGroup)) { + update_app_list.flags |= RDPAPPLIST_FIELD_GROUP; } - if (app_list_data->appWorkingDir && // workingDir is optional. - utf8_string_to_rail_string(app_list_data->appWorkingDir, &updateAppList.appWorkingDir)) { - updateAppList.flags |= RDPAPPLIST_FIELD_WORKINGDIR; + if (app_list_data->appWorkingDir && /* workingDir is optional. */ + utf8_string_to_rail_string(app_list_data->appWorkingDir, &update_app_list.appWorkingDir)) { + update_app_list.flags |= RDPAPPLIST_FIELD_WORKINGDIR; } - if (app_list_data->appIcon) { // icon is optional. + if (app_list_data->appIcon) { /* icon is optional. */ iconData.flags = 0; iconData.iconWidth = pixman_image_get_width(app_list_data->appIcon); iconData.iconHeight = pixman_image_get_height(app_list_data->appIcon); @@ -4235,42 +4610,35 @@ rdp_rail_notify_app_list(void *rdp_backend, struct weston_rdprail_app_list_data goto Exit_updatePath; iconData.iconFormat = RDPAPPLIST_ICON_FORMAT_BMP; iconData.iconBitsLength = iconData.iconHeight * iconData.iconStride; - iconData.iconBits = malloc(iconData.iconBitsLength); - if (!iconData.iconBits) - goto Exit_updatePath; - char *src = (char *)pixman_image_get_data(app_list_data->appIcon); + iconData.iconBits = xmalloc(iconData.iconBitsLength); + char *src = pixman_image_get_data(app_list_data->appIcon); char *dst = (char *)iconData.iconBits + (iconData.iconHeight-1) * iconData.iconStride; - for (UINT32 i = 0; i < iconData.iconHeight; i++) { + + for (uint32_t i = 0; i < iconData.iconHeight; i++) { memcpy(dst, src, iconData.iconStride); src += iconData.iconStride; dst -= iconData.iconStride; } - updateAppList.appIcon = &iconData; - updateAppList.flags |= RDPAPPLIST_FIELD_ICON; + update_app_list.appIcon = &iconData; + update_app_list.flags |= RDPAPPLIST_FIELD_ICON; } - peerCtx->applist_server_context->UpdateApplicationList(peerCtx->applist_server_context, &updateAppList); + applist_ctx->UpdateApplicationList(applist_ctx, &update_app_list); Exit_updatePath: - if (iconData.iconBits) - free(iconData.iconBits); - if (updateAppList.appId.string) - free(updateAppList.appId.string); - if (updateAppList.appGroup.string) - free(updateAppList.appGroup.string); - if (updateAppList.appExecPath.string) - free(updateAppList.appExecPath.string); - if (updateAppList.appWorkingDir.string) - free(updateAppList.appWorkingDir.string); - if (updateAppList.appDesc.string) - free(updateAppList.appDesc.string); + free(iconData.iconBits); + free(update_app_list.appId.string); + free(update_app_list.appGroup.string); + free(update_app_list.appExecPath.string); + free(update_app_list.appWorkingDir.string); + free(update_app_list.appDesc.string); } return true; } -#endif // HAVE_FREERDP_RDPAPPLIST_H +#endif /* HAVE_FREERDP_RDPAPPLIST_H */ static struct weston_output * rdp_rail_get_primary_output(void *rdp_backend) { - struct rdp_backend *b = (struct rdp_backend*)rdp_backend; + struct rdp_backend *b = rdp_backend; struct weston_head *current; wl_list_for_each(current, &b->compositor->head_list, compositor_link) { @@ -4291,7 +4659,7 @@ struct weston_rdprail_api rdprail_api = { .notify_app_list = rdp_rail_notify_app_list, #else .notify_app_list = NULL, -#endif // HAVE_FREERDP_RDPAPPLIST_H +#endif /* HAVE_FREERDP_RDPAPPLIST_H */ .get_primary_output = rdp_rail_get_primary_output, .notify_window_zorder_change = rdp_rail_notify_window_zorder_change, .notify_window_proxy_surface = rdp_rail_notify_window_proxy_surface, @@ -4301,7 +4669,7 @@ int rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config *config) { int ret = weston_plugin_api_register(b->compositor, WESTON_RDPRAIL_API_NAME, - &rdprail_api, sizeof(rdprail_api)); + &rdprail_api, sizeof(rdprail_api)); if (ret < 0) { rdp_debug_error(b, "Failed to register rdprail API.\n"); return -1; @@ -4309,6 +4677,7 @@ rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config #ifdef HAVE_FREERDP_RDPAPPLIST_H bool use_rdpapplist = config->rail_config.use_rdpapplist; + if (use_rdpapplist) { use_rdpapplist = false; @@ -4317,16 +4686,20 @@ rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config dlerror(); /* clear error */ b->libRDPApplistServer = dlopen(RDPAPPLIST_MODULEDIR "/" "librdpapplist-server.so", RTLD_NOW); if (!b->libRDPApplistServer) { - rdp_debug_error(b, "dlopen(%s/librdpapplist-server.so) failed with %s\n", RDPAPPLIST_MODULEDIR, dlerror()); + rdp_debug_error(b, + "dlopen(%s/librdpapplist-server.so) failed with %s\n", + RDPAPPLIST_MODULEDIR, dlerror()); b->libRDPApplistServer = dlopen("librdpapplist-server.so", RTLD_NOW); if (!b->libRDPApplistServer) { - rdp_debug_error(b, "dlopen(librdpapplist-server.so) failed with %s\n", dlerror()); + rdp_debug_error(b, + "dlopen(librdpapplist-server.so) failed with %s\n", + dlerror()); } } if (b->libRDPApplistServer) { - *(void **)(&b->rdpapplist_server_context_new) = dlsym(b->libRDPApplistServer, "rdpapplist_server_context_new"); - *(void **)(&b->rdpapplist_server_context_free) = dlsym(b->libRDPApplistServer, "rdpapplist_server_context_free"); + b->rdpapplist_server_context_new = dlsym(b->libRDPApplistServer, "rdpapplist_server_context_new"); + b->rdpapplist_server_context_free = dlsym(b->libRDPApplistServer, "rdpapplist_server_context_free"); if (b->rdpapplist_server_context_new && b->rdpapplist_server_context_free) { use_rdpapplist = true; } else { @@ -4339,15 +4712,16 @@ rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config b->use_rdpapplist = use_rdpapplist; rdp_debug(b, "RDP backend: use_rdpapplist = %d\n", b->use_rdpapplist); -#endif // HAVE_FREERDP_RDPAPPLIST_H +#endif /* HAVE_FREERDP_RDPAPPLIST_H */ #ifdef HAVE_FREERDP_GFXREDIR_H bool use_gfxredir = config->rail_config.use_shared_memory; /* check if shared memory mount path is set */ if (use_gfxredir) { use_gfxredir = false; - /* shared memory mounth point path is always given as environment variable from WSL */ + /* shared memory mount point path is always given as environment variable from WSL */ char *s = getenv("WSL2_SHARED_MEMORY_MOUNT_POINT"); + if (s) { b->shared_memory_mount_path = s; b->shared_memory_mount_path_size = strlen(b->shared_memory_mount_path); @@ -4369,10 +4743,10 @@ rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config #endif if (!b->libFreeRDPServer) { rdp_debug_error(b, "dlopen(libfreerdp-server%d.so) failed with %s\n", - FREERDP_VERSION_MAJOR, dlerror()); + FREERDP_VERSION_MAJOR, dlerror()); } else { - *(void **)(&b->gfxredir_server_context_new) = dlsym(b->libFreeRDPServer, "gfxredir_server_context_new"); - *(void **)(&b->gfxredir_server_context_free) = dlsym(b->libFreeRDPServer, "gfxredir_server_context_free"); + b->gfxredir_server_context_new = dlsym(b->libFreeRDPServer, "gfxredir_server_context_new"); + b->gfxredir_server_context_free = dlsym(b->libFreeRDPServer, "gfxredir_server_context_free"); if (b->gfxredir_server_context_new && b->gfxredir_server_context_new) { use_gfxredir = true; } else { @@ -4386,12 +4760,13 @@ rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config /* Test virtfsio actually works */ if (use_gfxredir) { - use_gfxredir = false; struct weston_rdp_shared_memory shmem = {}; + + use_gfxredir = false; shmem.size = sysconf(_SC_PAGESIZE); if (rdp_allocate_shared_memory(b, &shmem)) { - *(UINT32*)shmem.addr = 0x12344321; - assert(*(UINT32*)shmem.addr == 0x12344321); + *(uint32_t *)shmem.addr = 0x12344321; + assert(*(uint32_t *)shmem.addr == 0x12344321); rdp_free_shared_memory(b, &shmem); use_gfxredir = true; } @@ -4399,52 +4774,67 @@ rdp_rail_backend_create(struct rdp_backend *b, struct weston_rdp_backend_config b->use_gfxredir = use_gfxredir; rdp_debug(b, "RDP backend: use_gfxredir = %d\n", b->use_gfxredir); -#endif // HAVE_FREERDP_GFXREDIR_H +#endif /* HAVE_FREERDP_GFXREDIR_H */ b->enable_hi_dpi_support = config->rail_config.enable_hi_dpi_support; - rdp_debug(b, "RDP backend: enable_hi_dpi_support = %d\n", b->enable_hi_dpi_support); + rdp_debug(b, "RDP backend: enable_hi_dpi_support = %d\n", + b->enable_hi_dpi_support); b->enable_fractional_hi_dpi_support = config->rail_config.enable_fractional_hi_dpi_support; - rdp_debug(b, "RDP backend: enable_fractional_hi_dpi_support = %d\n", b->enable_fractional_hi_dpi_support); + rdp_debug(b, "RDP backend: enable_fractional_hi_dpi_support = %d\n", + b->enable_fractional_hi_dpi_support); b->enable_fractional_hi_dpi_roundup = config->rail_config.enable_fractional_hi_dpi_roundup; - rdp_debug(b, "RDP backend: enable_fractional_hi_dpi_roundup = %d\n", b->enable_fractional_hi_dpi_roundup); + rdp_debug(b, "RDP backend: enable_fractional_hi_dpi_roundup = %d\n", + b->enable_fractional_hi_dpi_roundup); b->debug_desktop_scaling_factor = config->rail_config.debug_desktop_scaling_factor; - rdp_debug(b, "RDP backend: debug_desktop_scaling_factor = %d\n", b->debug_desktop_scaling_factor); + rdp_debug(b, "RDP backend: debug_desktop_scaling_factor = %d\n", + b->debug_desktop_scaling_factor); b->enable_window_zorder_sync = config->rail_config.enable_window_zorder_sync; - rdp_debug(b, "RDP backend: enable_window_zorder_sync = %d\n", b->enable_window_zorder_sync); + rdp_debug(b, "RDP backend: enable_window_zorder_sync = %d\n", + b->enable_window_zorder_sync); b->enable_window_snap_arrange = config->rail_config.enable_window_snap_arrange; - rdp_debug(b, "RDP backend: enable_window_snap_arrange = %d\n", b->enable_window_snap_arrange); + rdp_debug(b, "RDP backend: enable_window_snap_arrange = %d\n", + b->enable_window_snap_arrange); b->enable_window_shadow_remoting = config->rail_config.enable_window_shadow_remoting; - rdp_debug(b, "RDP backend: enable_window_shadow_remoting = %d\n", b->enable_window_shadow_remoting); + rdp_debug(b, "RDP backend: enable_window_shadow_remoting = %d\n", + b->enable_window_shadow_remoting); b->enable_display_power_by_screenupdate = config->rail_config.enable_display_power_by_screenupdate; - rdp_debug(b, "RDP backend: enable_display_power_by_screenupdate = %d\n", b->enable_display_power_by_screenupdate); + rdp_debug(b, "RDP backend: enable_display_power_by_screenupdate = %d\n", + b->enable_display_power_by_screenupdate); b->enable_distro_name_title = config->rail_config.enable_distro_name_title; - rdp_debug(b, "RDP backend: enable_distro_name_title = %d\n", b->enable_distro_name_title); + rdp_debug(b, "RDP backend: enable_distro_name_title = %d\n", + b->enable_distro_name_title); - b->enable_copy_warning_title = config->rail_config.enable_copy_warning_title; - rdp_debug(b, "RDP backend: enable_copy_warning_title = %d\n", b->enable_copy_warning_title); + b->enable_copy_warning_title = config->rail_config.enable_copy_warning_title; + rdp_debug(b, "RDP backend: enable_copy_warning_title = %d\n", + b->enable_copy_warning_title); b->rdprail_shell_name = NULL; /* M to dump all outstanding monitor info */ - b->debug_binding_M = weston_compositor_add_debug_binding(b->compositor, KEY_M, - rdp_rail_dump_monitor_binding, b); + b->debug_binding_M = weston_compositor_add_debug_binding(b->compositor, + KEY_M, + rdp_rail_dump_monitor_binding, + b); /* W to dump all outstanding window info */ - b->debug_binding_W = weston_compositor_add_debug_binding(b->compositor, KEY_W, - rdp_rail_dump_window_binding, b); + b->debug_binding_W = weston_compositor_add_debug_binding(b->compositor, + KEY_W, + rdp_rail_dump_window_binding, + b); /* Trigger to enter debug key : CTRL+SHIFT+SPACE */ weston_install_debug_key_binding(b->compositor, MODIFIER_CTRL); /* start listening surface creation */ b->create_window_listener.notify = rdp_rail_create_window; - wl_signal_add(&b->compositor->create_surface_signal, &b->create_window_listener); + wl_signal_add(&b->compositor->create_surface_signal, + &b->create_window_listener); return 0; } @@ -4457,8 +4847,7 @@ rdp_rail_destroy(struct rdp_backend *b) b->create_window_listener.notify = NULL; } - if (b->rdprail_shell_name) - free(b->rdprail_shell_name); + free(b->rdprail_shell_name); if (b->debug_binding_M) weston_binding_destroy(b->debug_binding_M); @@ -4469,10 +4858,10 @@ rdp_rail_destroy(struct rdp_backend *b) #if defined(HAVE_FREERDP_RDPAPPLIST_H) if (b->libRDPApplistServer) dlclose(b->libRDPApplistServer); -#endif // defined(HAVE_FREERDP_RDPAPPLIST_H) +#endif /* defined(HAVE_FREERDP_RDPAPPLIST_H) */ #if defined(HAVE_FREERDP_GFXREDIR_H) if (b->libFreeRDPServer) dlclose(b->libFreeRDPServer); -#endif // defined(HAVE_FREERDP_GFXREDIR_H) +#endif /* defined(HAVE_FREERDP_GFXREDIR_H) */ } From 22aec8e828055a8f35822b055dee56d31c94eb4e Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 3 Aug 2022 15:50:43 -0500 Subject: [PATCH 1595/1642] rdp: Fix build breaks in applist for sloppy style fixups --- libweston/backend-rdp/rdprail.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index e09d8de81..bcecf7d34 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -4549,7 +4549,7 @@ rdp_rail_notify_app_list(void *rdp_backend, if (app_list_data->appGroup && utf8_string_to_rail_string(app_list_data->appGroup, &delete_app_list.appGroup)) { - deleteAppList.flags |= RDPAPPLIST_FIELD_GROUP; + delete_app_list.flags |= RDPAPPLIST_FIELD_GROUP; } applist_ctx->DeleteApplicationList(applist_ctx, &delete_app_list); Exit_deletePath: @@ -4561,7 +4561,7 @@ rdp_rail_notify_app_list(void *rdp_backend, del_provider.flags = RDPAPPLIST_FIELD_PROVIDER; if (app_list_data->appProvider && utf8_string_to_rail_string(app_list_data->appProvider, &del_provider.appListProviderName)) - applist_ctx->DeleteApplicationListProvider(applistctx, &del_provider); + applist_ctx->DeleteApplicationListProvider(applist_ctx, &del_provider); free(del_provider.appListProviderName.string); } else { RDPAPPLIST_UPDATE_APPLIST_PDU update_app_list = {}; @@ -4611,7 +4611,7 @@ rdp_rail_notify_app_list(void *rdp_backend, iconData.iconFormat = RDPAPPLIST_ICON_FORMAT_BMP; iconData.iconBitsLength = iconData.iconHeight * iconData.iconStride; iconData.iconBits = xmalloc(iconData.iconBitsLength); - char *src = pixman_image_get_data(app_list_data->appIcon); + char *src = (char *)pixman_image_get_data(app_list_data->appIcon); char *dst = (char *)iconData.iconBits + (iconData.iconHeight-1) * iconData.iconStride; for (uint32_t i = 0; i < iconData.iconHeight; i++) { From dc5eb307aab7c0dd8309737dd175e50808ffeb0e Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Wed, 3 Aug 2022 16:20:24 -0500 Subject: [PATCH 1596/1642] rdp: Remove check for rail shell api I broke this check in a prior commit, but nothing this block uses it so instead of fixing the check, I'll remove the check. --- libweston/backend-rdp/rdprail.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index bcecf7d34..66d61791d 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -3332,8 +3332,7 @@ rdp_rail_peer_activate(freerdp_peer* client) #ifdef HAVE_FREERDP_RDPAPPLIST_H RdpAppListServerContext *applist_ctx; /* open Application List channel. */ - if (api && b->rdprail_shell_name && b->use_rdpapplist) { - + if (b->rdprail_shell_name && b->use_rdpapplist) { applist_ctx = b->rdpapplist_server_context_new(peer_ctx->vcm); if (!applist_ctx) goto error_exit; From 66e150e2b2211cfe01927119434d7644abad6945 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 5 Aug 2022 11:49:38 -0700 Subject: [PATCH 1597/1642] xwm: Don't send synthetic ConfigureNotify to windows that were mapped O-R (#102) Co-authored-by: Hideyuki Nagase --- xwayland/window-manager.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 06ec12cee..10f750635 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -767,6 +767,17 @@ weston_wm_window_send_configure_notify(struct weston_wm_window *window) const struct weston_desktop_xwayland_interface *xwayland_api = wm->server->compositor->xwayland_interface; + if (window->override_redirect) { + /* Some clever application has changed the override redirect + * flag on an existing window. We didn't see it at map time, + * so have no idea what to do with it now. Log and leave. + */ + wm_printf(wm, "XWM warning: Can't send XCB_CONFIGURE_NOTIFY to" + " window %d which was mapped override redirect\n", + window->id); + return; + } + weston_wm_window_get_child_position(window, &x, &y); /* Synthetic ConfigureNotify events must be relative to the root * window, so get our offset if we're mapped. */ @@ -882,6 +893,13 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev window->fullscreen ? ", fullscreen" : "", window->override_redirect ? ", override" : ""); + /* If we see this, a window's override_redirect state has changed + * after it was mapped, and we don't really know what to do about + * that. + */ + if (window->override_redirect) + return; + if (window->fullscreen) { weston_wm_window_send_configure_notify(window); return; @@ -900,7 +918,7 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev /* don't send x/y when frame (parent window) is not created yet, unless this is frame itself. Since only after frame is created, the app's window position will become relative to parent (frame). */ - if (window->frame || window->override_redirect) { + if (window->frame) { weston_wm_window_get_child_position(window, &x, &y); /* window is app's window has frame as parent, or override. */ values[i++] = x; // relative from frame. @@ -922,7 +940,7 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev } weston_wm_configure_window(wm, window->id, mask, values); - if (window->frame == NULL && !window->override_redirect) { + if (window->frame == NULL) { /* must not mapped yet */ assert(!window->shsurf); /* if frame is not created yet, or not override window, From 0d320f0a5c41da33f948b18b6bb65f794729a3a7 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 5 Aug 2022 11:49:59 -0700 Subject: [PATCH 1598/1642] fix default override window position back to (0,0) (#56) * fix default override window position * add debug msgs Co-authored-by: Hideyuki Nagase --- xwayland/window-manager.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 10f750635..0aae8c631 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -877,10 +877,10 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev int i = 0; bool is_our_resource = our_resource(wm, configure_request->window); - wm_printf(wm, "XCB_CONFIGURE_REQUEST (window %d) %d,%d @ %dx%d mask 0x%x%s\n", + wm_printf(wm, "XCB_CONFIGURE_REQUEST (window %d) %dx%d @ %d,%d mask 0x%x%s\n", configure_request->window, - configure_request->x, configure_request->y, configure_request->width, configure_request->height, + configure_request->x, configure_request->y, configure_request->value_mask, is_our_resource ? ", ours" : ""); @@ -977,10 +977,10 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve struct weston_wm_window *window; bool is_our_resource = our_resource(wm, configure_notify->window); - wm_printf(wm, "XCB_CONFIGURE_NOTIFY (window %d) %d,%d @ %dx%d%s%s\n", + wm_printf(wm, "XCB_CONFIGURE_NOTIFY (window %d) %dx%d @ %d,%d%s%s\n", configure_notify->window, - configure_notify->x, configure_notify->y, configure_notify->width, configure_notify->height, + configure_notify->x, configure_notify->y, configure_notify->override_redirect ? ", override" : "", is_our_resource ? ", ours" : ""); @@ -1299,7 +1299,8 @@ weston_wm_window_create_frame(struct weston_wm_window *window) 32, window->frame_id, wm->screen->root, - SHRT_MIN, SHRT_MIN, /* see XCB_CONFIGURE_NOTIFY */ + window->override_redirect ? 0 : SHRT_MIN, + window->override_redirect ? 0 : SHRT_MIN, /* see XCB_CONFIGURE_NOTIFY */ width, height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, @@ -3355,8 +3356,11 @@ xserver_map_shell_surface(struct weston_wm_window *window, window->surface, &shell_client); - wm_printf(wm, "XWM: map shell surface, win %d, weston_surface %p, xwayland surface %p\n", - window->id, window->surface, window->shsurf); + wm_printf(wm, "XWM: map shell surface, win %d, weston_surface %p, xwayland surface %p %dx%d @ %d,%d map_request %d,%d\n", + window->id, window->surface, window->shsurf, + window->width, window->height, + window->x, window->y, + window->map_request_x, window->map_request_y); if (window->name) xwayland_interface->set_title(window->shsurf, window->name); From 68ca072be0adf7c82771bb92d735587d4fa74569 Mon Sep 17 00:00:00 2001 From: "microsoft-github-policy-service[bot]" <77245923+microsoft-github-policy-service[bot]@users.noreply.github.com> Date: Fri, 5 Aug 2022 13:04:43 -0700 Subject: [PATCH 1599/1642] Microsoft mandatory file (#97) Co-authored-by: microsoft-github-policy-service[bot] <77245923+microsoft-github-policy-service[bot]@users.noreply.github.com> --- SECURITY.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..869fdfe2b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + From d57e0de202fd049f6cc0ce8c25b7120feea3468f Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 8 Aug 2022 11:41:48 -0700 Subject: [PATCH 1600/1642] xwayland: Fix some xwayland focus bugs (#103) Co-authored-by: Hideyuki Nagase --- xwayland/window-manager.c | 56 ++++++++++++++++++++++++++++++--------- xwayland/xwayland.h | 1 + 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 0aae8c631..e89c6e249 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -1069,8 +1069,6 @@ static void weston_wm_send_focus_window(struct weston_wm *wm, struct weston_wm_window *window) { - xcb_client_message_event_t client_message; - if (window) { uint32_t values[1]; @@ -1078,16 +1076,15 @@ weston_wm_send_focus_window(struct weston_wm *wm, return; if (window->take_focus) { - client_message.response_type = XCB_CLIENT_MESSAGE; - client_message.format = 32; - client_message.window = window->id; - client_message.type = wm->atom.wm_protocols; - client_message.data.data32[0] = wm->atom.wm_take_focus; - client_message.data.data32[1] = XCB_TIME_CURRENT_TIME; - - xcb_send_event(wm->conn, 0, window->id, - XCB_EVENT_MASK_NO_EVENT, - (char *) &client_message); + /* Set a property to get a roundtrip + * with a timestamp for WM_TAKE_FOCUS */ + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + window->id, + wm->atom.weston_focus_ping, + XCB_ATOM_STRING, + 8, /* format */ + 0, NULL); } xcb_set_input_focus (wm->conn, XCB_INPUT_FOCUS_POINTER_ROOT, @@ -1118,6 +1115,9 @@ weston_wm_window_activate(struct wl_listener *listener, void *data) window = get_wm_window(surface); } + if (wm->focus_window == window) + return; + if (window) { weston_wm_set_net_active_window(wm, window->id); } else { @@ -1715,6 +1715,35 @@ weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *even if (!wm_lookup_window(wm, property_notify->window, &window)) return; + /* We set the weston_focus_ping property on this window to + * get a timestamp to send a WM_TAKE_FOCUS... send it now, + * or just return if this is confirming we deleted the + * property. + */ + if (property_notify->atom == wm->atom.weston_focus_ping) { + xcb_client_message_event_t client_message; + + if (property_notify->state == XCB_PROPERTY_DELETE) + return; + + /* delete our ping property */ + xcb_delete_property(window->wm->conn, + window->id, + window->wm->atom.weston_focus_ping); + + client_message.response_type = XCB_CLIENT_MESSAGE; + client_message.format = 32; + client_message.window = window->id; + client_message.type = wm->atom.wm_protocols; + client_message.data.data32[0] = wm->atom.wm_take_focus; + client_message.data.data32[1] = property_notify->time; + xcb_send_event(wm->conn, 0, window->id, + XCB_EVENT_MASK_NO_EVENT, + (char *) &client_message); + + return; + } + window->properties_dirty = 1; if (wm_debug_is_enabled(wm)) @@ -2770,7 +2799,8 @@ weston_wm_get_resources(struct weston_wm *wm) { "XdndTypeList", F(atom.xdnd_type_list) }, { "XdndActionCopy", F(atom.xdnd_action_copy) }, { "_XWAYLAND_ALLOW_COMMITS", F(atom.allow_commits) }, - { "WL_SURFACE_ID", F(atom.wl_surface_id) } + { "WL_SURFACE_ID", F(atom.wl_surface_id) }, + { "_WESTON_FOCUS_PING", F(atom.weston_focus_ping) } }; #undef F diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index 0b0811721..91b96a6d8 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -162,6 +162,7 @@ struct weston_wm { xcb_atom_t xdnd_action_copy; xcb_atom_t wl_surface_id; xcb_atom_t allow_commits; + xcb_atom_t weston_focus_ping; } atom; }; From 56d759174e1bb06ae57fdf6bf6d8b53942782d54 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 17 Aug 2022 16:05:26 -0700 Subject: [PATCH 1601/1642] fix applist is not working regression (#104) --- libweston/backend-rdp/rdprail.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 66d61791d..ef748d23a 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -4519,7 +4519,7 @@ rdp_rail_notify_app_list(void *rdp_backend, peer_ctx = (RdpPeerContext *)b->rdp_peer->context; applist_ctx = peer_ctx->applist_server_context; - if (applist_ctx) + if (!applist_ctx) return false; rdp_debug(b, "rdp_rail_notify_app_list(): rdp_peer %p\n", peer_ctx); From 861260daebba0da578e1647d69364a08154660d3 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 6 Sep 2022 09:32:06 -0700 Subject: [PATCH 1602/1642] applist: verify file and directory explicitly (#105) Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index ec533aed5..3c7cef734 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -217,11 +217,18 @@ detach_app_list_namespace(struct desktop_shell *shell) } } +static bool +is_dir_exist(char *file) +{ + struct stat buffer; + return ((stat(file, &buffer) == 0) && S_ISDIR(buffer.st_mode)); +} + static bool is_file_exist(char *file) { struct stat buffer; - return (stat(file, &buffer) == 0); + return ((stat(file, &buffer) == 0) && S_ISREG(buffer.st_mode)); } static char * @@ -839,7 +846,7 @@ app_list_monitor_thread(LPVOID arg) folder = path; } - if (!is_file_exist(folder)) { + if (!is_dir_exist(folder)) { shell_rdp_debug(shell, "app_list_monitor_thread: %s doesn't exist, skipping.\n", folder); detach_app_list_namespace(shell); close(fd[num_watch]); From f99efbb62ec54b4255a1bef81e79ef06d0bcd10c Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 8 Sep 2022 15:29:03 -0700 Subject: [PATCH 1603/1642] rdp shell: locate icon for taskbar using window class name (#106) * rdp shell: locate icon for taskbar using window class name * adjust if brackets to avoid recycle id value without setting class name Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index b2ece61f4..2cffb6d42 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -407,6 +407,16 @@ shell_surface_set_window_icon(struct weston_desktop_surface *desktop_surface, if (image) shsurf->icon.is_default_icon_used = false; } + if (!image) { + /* If this is X app, try window class name as id for icon */ + if (api && api->is_xwayland_surface(surface)) { + id = api->get_class_name(surface); + if (id) + image = app_list_load_icon_file(shsurf->shell, id); + if (image) + shsurf->icon.is_default_icon_used = false; + } + } if (!image) { /* When caller doens't supply custom image, look for default images */ image = shsurf->shell->image_default_app_icon; From 196550720c801b9f0db193698756aa6186bf5d4e Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 12 Sep 2022 10:47:12 -0700 Subject: [PATCH 1604/1642] rdp shell: fix missing free (#107) Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 2cffb6d42..b08fec4f5 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -357,6 +357,7 @@ shell_surface_set_window_icon(struct weston_desktop_surface *desktop_surface, pixman_image_t *image = NULL; pixman_format_code_t format; const char *id; + char *class_name; shsurf = weston_desktop_surface_get_user_data(desktop_surface); if (!shsurf) @@ -410,11 +411,13 @@ shell_surface_set_window_icon(struct weston_desktop_surface *desktop_surface, if (!image) { /* If this is X app, try window class name as id for icon */ if (api && api->is_xwayland_surface(surface)) { - id = api->get_class_name(surface); - if (id) - image = app_list_load_icon_file(shsurf->shell, id); - if (image) - shsurf->icon.is_default_icon_used = false; + class_name = api->get_class_name(surface); + if (class_name) { + image = app_list_load_icon_file(shsurf->shell, class_name); + if (image) + shsurf->icon.is_default_icon_used = false; + free(class_name); + } } } if (!image) { From 98fd6d7d27c887ea4c570af9375fcc2429c9a555 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 12 Sep 2022 10:47:30 -0700 Subject: [PATCH 1605/1642] rdp shell: preblend default icon image with default overlay image (#108) Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 13 +++++++++++-- rdprail-shell/shell.c | 10 ++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index 3c7cef734..1ee61bc14 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -347,8 +347,9 @@ send_app_entry(struct desktop_shell *shell, char *key, struct app_entry *entry, if (app_list_data.appIcon) pixman_image_ref(app_list_data.appIcon); } - if (app_list_data.appIcon && - shell->is_blend_overlay_icon_app_list && + if (shell->is_blend_overlay_icon_app_list && + app_list_data.appIcon && + app_list_data.appIcon != context->default_icon && context->default_overlay_icon) shell_blend_overlay_icon(shell, app_list_data.appIcon, @@ -1291,6 +1292,14 @@ void app_list_init(struct desktop_shell *shell) if (iconpath && (strcmp(iconpath, "disabled") != 0)) context->default_overlay_icon = load_icon_image(shell, iconpath); + /* preblend default icon with overlay icon if requested */ + if (shell->is_blend_overlay_icon_app_list && + context->default_icon && + context->default_overlay_icon) + shell_blend_overlay_icon(shell, + context->default_icon, + context->default_overlay_icon); + /* set default language as "en_US". this will be updated once client connected */ strcpy(context->lang_info.requestedClientLanguageId, "en_US"); strcpy(context->lang_info.currentClientLanguageId, diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index b08fec4f5..d88a5c9ad 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -430,7 +430,9 @@ shell_surface_set_window_icon(struct weston_desktop_surface *desktop_surface, } if (!image) return; + /* no need to blend default icon as it's already pre-blended if requested. */ if (shsurf->shell->is_blend_overlay_icon_taskbar && + shsurf->shell->image_default_app_icon != image && shsurf->shell->image_default_app_overlay_icon) shell_blend_overlay_icon(shsurf->shell, image, @@ -764,6 +766,14 @@ shell_configuration(struct desktop_shell *shell) shell_rdp_debug(shell, "RDPRAIL-shell: WESTON_RDPRAIL_SHELL_BLEND_OVERLAY_ICON_TASKBAR:%d\n", shell->is_blend_overlay_icon_taskbar); + /* preblend overlay icon over app icon */ + if (shell->is_blend_overlay_icon_taskbar && + shell->image_default_app_icon && + shell->image_default_app_overlay_icon) + shell_blend_overlay_icon(shell, + shell->image_default_app_icon, + shell->image_default_app_overlay_icon); + shell->workspaces.num = 1; } From 3f3901bdf6542b27bcdc5a83e885dfa71a08a47f Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 14 Sep 2022 09:47:08 -0700 Subject: [PATCH 1606/1642] rdp shell: default window pos must be adjust by window geometry (#109) Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index d88a5c9ad..c4872b14a 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -1705,8 +1705,7 @@ shell_surface_set_output(struct shell_surface *shsurf, } static void -weston_view_set_initial_position(struct weston_view *view, - struct desktop_shell *shell); +weston_view_set_initial_position(struct shell_surface *shsurf); static void unset_fullscreen(struct shell_surface *shsurf) @@ -1732,7 +1731,7 @@ unset_fullscreen(struct shell_surface *shsurf) weston_view_set_position(shsurf->view, shsurf->saved_x, shsurf->saved_y); else - weston_view_set_initial_position(shsurf->view, shsurf->shell); + weston_view_set_initial_position(shsurf); shsurf->saved_position_valid = false; if (shsurf->saved_rotation_valid) { @@ -1770,7 +1769,7 @@ unset_maximized(struct shell_surface *shsurf) weston_view_set_position(shsurf->view, shsurf->saved_x, shsurf->saved_y); else - weston_view_set_initial_position(shsurf->view, shsurf->shell); + weston_view_set_initial_position(shsurf); shsurf->saved_position_valid = false; } @@ -2310,7 +2309,7 @@ set_position_from_xwayland(struct shell_surface *shsurf) } /* Otherwise, move to default initial position */ - weston_view_set_initial_position(shsurf->view, shsurf->shell); + weston_view_set_initial_position(shsurf); } static void @@ -2355,7 +2354,7 @@ map(struct desktop_shell *shell, struct shell_surface *shsurf, } else if (shsurf->parent) { set_default_position_from_parent(shsurf); } else { - weston_view_set_initial_position(shsurf->view, shell); + weston_view_set_initial_position(shsurf); } /* Surface stacking order, see also activate(). */ @@ -3713,14 +3712,16 @@ center_on_output(struct weston_view *view, struct weston_output *output) } static void -weston_view_set_initial_position(struct weston_view *view, - struct desktop_shell *shell) +weston_view_set_initial_position(struct shell_surface *shsurf) { + struct weston_view *view = shsurf->view; + struct desktop_shell *shell = shsurf->shell; struct weston_compositor *compositor = shell->compositor; int32_t range_x, range_y; int32_t x, y; struct weston_output *target_output = NULL; pixman_rectangle32_t area; + struct weston_geometry geometry; /* As a heuristic place the new window on the same output as the * pointer. Falling back to the output containing 0, 0. @@ -3774,9 +3775,9 @@ weston_view_set_initial_position(struct weston_view *view, * output. */ get_output_work_area(shell, target_output, &area); - - x = area.x; - y = area.y; + geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + x = area.x - geometry.x; + y = area.y - geometry.y; range_x = area.width - view->surface->width; range_y = area.height - view->surface->height; From 1085ac2e83a310befedf31919d601fa65293a38d Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 15 Sep 2022 09:10:20 -0700 Subject: [PATCH 1607/1642] xwayland: fix window maximizing (#110) Co-authored-by: Hideyuki Nagase --- libweston-desktop/xwayland.c | 13 +++++++++++-- xwayland/window-manager.c | 14 +++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index 4fbc318be..86cc330e1 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -64,6 +64,7 @@ struct weston_desktop_xwayland_surface { bool committed; bool added; enum weston_desktop_xwayland_surface_state state; + enum weston_desktop_xwayland_surface_state prev_state; }; static void @@ -150,8 +151,16 @@ weston_desktop_xwayland_surface_committed(struct weston_desktop_surface *dsurfac if (surface->has_next_geometry) { oldgeom = weston_desktop_surface_get_geometry(surface->surface); - sx -= surface->next_geometry.x - oldgeom.x; - sy -= surface->next_geometry.y - oldgeom.y; + /* If we're transitioning away from fullscreen or maximized + * we've moved to old saved co-ordinates that were saved + * with window geometry in place, so avoid adajusting by + * the geometry in those cases. + */ + if (surface->state == surface->prev_state) { + sx -= surface->next_geometry.x - oldgeom.x; + sy -= surface->next_geometry.y - oldgeom.y; + } + surface->prev_state = surface->state; surface->has_next_geometry = false; weston_desktop_surface_set_geometry(surface->surface, diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index e89c6e249..74a2223e2 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -1808,6 +1808,12 @@ weston_wm_window_create(struct weston_wm *wm, window->override_redirect = override; window->width = width; window->height = height; + /* Completely arbitrary defaults in case something starts + * maximized and we unmaximize it later - at which point 0 x 0 + * would not be the most useful size. + */ + window->saved_width = 512; + window->saved_height = 512; window->x = x; window->y = y; window->pos_dirty = false; @@ -2055,10 +2061,12 @@ weston_wm_window_set_toplevel(struct weston_wm_window *window) xwayland_interface->set_toplevel(window->shsurf); window->width = window->saved_width; window->height = window->saved_height; - if (window->frame) + if (window->frame) { + frame_unset_flag(window->frame, FRAME_FLAG_MAXIMIZED); frame_resize_inside(window->frame, window->width, window->height); + } weston_wm_window_configure(window); } @@ -2497,6 +2505,7 @@ weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) if (frame_status(window->frame) & FRAME_STATUS_MAXIMIZE) { window->maximized_horz = !window->maximized_horz; window->maximized_vert = !window->maximized_vert; + weston_wm_window_set_net_wm_state(window); if (weston_wm_window_is_maximized(window)) { window->saved_width = window->width; window->saved_height = window->height; @@ -3126,6 +3135,9 @@ send_configure(struct weston_surface *surface, int32_t width, int32_t height) window->height = new_height; if (window->frame) { + if (weston_wm_window_is_maximized(window)) + frame_set_flag(window->frame, FRAME_FLAG_MAXIMIZED); + frame_resize_inside(window->frame, window->width, window->height); } From ec8820aa72ecbb55124601f3c7817ffd38ed8d45 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 15 Sep 2022 16:09:34 -0700 Subject: [PATCH 1608/1642] xwayland: fix maximized window decoration (#112) Co-authored-by: Hideyuki Nagase --- xwayland/window-manager.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 74a2223e2..f03e94442 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2509,10 +2509,8 @@ weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) if (weston_wm_window_is_maximized(window)) { window->saved_width = window->width; window->saved_height = window->height; - frame_set_flag(window->frame, FRAME_FLAG_MAXIMIZED); xwayland_interface->set_maximized(window->shsurf); } else { - frame_unset_flag(window->frame, FRAME_FLAG_MAXIMIZED); weston_wm_window_set_toplevel(window); } frame_status_clear(window->frame, FRAME_STATUS_MAXIMIZE); From ee64e6666de5c5d35c9270b392c6241a4f8fbe7d Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 22 Sep 2022 11:19:45 -0700 Subject: [PATCH 1609/1642] rdp shell: fix window placement issues (#113) * rdp shell: fix window placement issues * xwayland: move window at request, not notify Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 81 ++++++++++++++++++++++++++++++++------- xwayland/window-manager.c | 13 +++++-- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index c4872b14a..f80c50719 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -236,6 +236,9 @@ shell_backend_request_window_activate(void *shell_context, struct weston_seat *s #define ICON_STRIDE( W, BPP ) ((((W) * (BPP) + 31) / 32) * 4) +#define TITLEBAR_GRAB_MARGIN_X (30) +#define TITLEBAR_GRAB_MARGIN_Y (10) + static int cached_tm_mday = -1; static char * @@ -788,8 +791,10 @@ get_default_output(struct weston_compositor *compositor) } static struct weston_output * -get_output_containing(struct weston_compositor *compositor, int x, int y) +get_output_containing(struct desktop_shell *shell, int x, int y, bool use_default) { + struct weston_compositor *compositor = shell->compositor; + if (wl_list_empty(&compositor->output_list)) return NULL; @@ -800,8 +805,14 @@ get_output_containing(struct weston_compositor *compositor, int x, int y) return output; } } - weston_log("Didn't find output containing (%d, %d), return default\n", x, y); - return get_default_output(compositor); + + if (use_default) { + shell_rdp_debug_verbose(shell, "%s: Didn't find output containing (%d, %d), return default\n", + __func__, x, y); + return get_default_output(compositor); + } else { + return NULL; + } } @@ -2289,14 +2300,14 @@ set_position_from_xwayland(struct shell_surface *shsurf) y = shsurf->xwayland.y - geometry.y; /* Make sure the position given from xwayland is a part of workarea */ - output = get_output_containing(shsurf->shell->compositor, x, y); + output = get_output_containing(shsurf->shell, shsurf->xwayland.x, shsurf->xwayland.y, false); if (output) { get_output_work_area(shsurf->shell, output, &area); /* Use xwayland position as this is the X app's origin of client area */ if (shsurf->xwayland.x >= area.x && shsurf->xwayland.y >= area.y && - shsurf->xwayland.x < (int32_t)(area.x + area.width - (area.width / 10)) && - shsurf->xwayland.y < (int32_t)(area.y + area.height - (area.height / 10))) { + shsurf->xwayland.x <= (int32_t)(area.x + area.width - TITLEBAR_GRAB_MARGIN_X) && + shsurf->xwayland.y <= (int32_t)(area.y + area.height - TITLEBAR_GRAB_MARGIN_Y)) { weston_view_set_position(shsurf->view, x, y); @@ -2806,9 +2817,10 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, * based on the last position of the mouse when the * grab event finished. */ - struct weston_output *output = get_output_containing(surface->compositor, + struct weston_output *output = get_output_containing(shsurf->shell, shsurf->snapped.last_grab_x, - shsurf->snapped.last_grab_y); + shsurf->snapped.last_grab_y, + true); weston_view_set_output(shsurf->view, output); shell_surface_set_output(shsurf, output); @@ -2992,6 +3004,15 @@ desktop_surface_get_position(struct weston_desktop_surface *surface, *y = shsurf->view->geometry.y; } +static bool +area_contain_point(pixman_rectangle32_t *area, int x, int y) +{ + return x >= area->x && + y >= area->y && + x < area->x + (int)area->width && + y < area->y + (int)area->height; +} + static void desktop_surface_move_xwayland_position(struct weston_desktop_surface *desktop_surface, int32_t x, int32_t y, void *shell_) @@ -3002,9 +3023,15 @@ desktop_surface_move_xwayland_position(struct weston_desktop_surface *desktop_su weston_desktop_surface_get_user_data(desktop_surface); struct desktop_shell *shell = shsurf->shell; const struct weston_xwayland_surface_api *api; + struct weston_geometry geometry; assert(shell == shell_); + geometry = weston_desktop_surface_get_geometry(desktop_surface); + if (shsurf->view->geometry.x == x - geometry.x && + shsurf->view->geometry.y == y - geometry.y) + return; + api = shell->xwayland_surface_api; if (!api) { api = weston_xwayland_surface_get_api(shell->compositor); @@ -3015,12 +3042,38 @@ desktop_surface_move_xwayland_position(struct weston_desktop_surface *desktop_su But this is not simple, for example, app can have accompanying window which move along with other main window, in such case, often, it's totally fine the accompanying goes out of workarea. */ - - weston_view_set_position(shsurf->view, x, y); - weston_compositor_schedule_repaint(shell->compositor); - - shell_rdp_debug_verbose(shell, "%s: surface:%p, position (%d,%d)\n", - __func__, surface, x, y); + /* Below code to make sure window title bar is grab-able */ + struct weston_output *output; + int left, right, top; + pixman_rectangle32_t area; + bool visible = false; + + left = x + TITLEBAR_GRAB_MARGIN_X; + right = x + (surface->width - TITLEBAR_GRAB_MARGIN_X); + top = y + TITLEBAR_GRAB_MARGIN_Y; + + /* check uppper left */ + output = get_output_containing(shell, left, top, false); + if (output) { + get_output_work_area(shell, output, &area); + visible = area_contain_point(&area, left, top); + } + if (!visible) { + /* check upper right */ + output = get_output_containing(shell, right, top, false); + if (output) { + get_output_work_area(shell, output, &area); + visible = area_contain_point(&area, right, top); + } + } + if (visible) { + x -= geometry.x; + y -= geometry.y; + weston_view_set_position(shsurf->view, x, y); + weston_compositor_schedule_repaint(shell->compositor); + shell_rdp_debug_verbose(shell, "%s: surface:%p, position (%d,%d)\n", + __func__, surface, x, y); + } } else { shell_rdp_debug_error(shell, "%s: surface:%p is not from xwayland\n", __func__, surface); diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index f03e94442..3210f5973 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -876,6 +876,8 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev int x, y; int i = 0; bool is_our_resource = our_resource(wm, configure_request->window); + const struct weston_desktop_xwayland_interface *xwayland_api = + wm->server->compositor->xwayland_interface; wm_printf(wm, "XCB_CONFIGURE_REQUEST (window %d) %dx%d @ %d,%d mask 0x%x%s\n", configure_request->window, @@ -951,6 +953,13 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev window->x = configure_request->x; if (configure_request->value_mask & XCB_CONFIG_WINDOW_Y) window->y = configure_request->y; + } else if (window->shsurf && + configure_request->value_mask & (XCB_CONFIG_WINDOW_X|XCB_CONFIG_WINDOW_Y)) { + xwayland_api->move_position(window->shsurf, + configure_request->value_mask & XCB_CONFIG_WINDOW_X ? + configure_request->x : window->x, + configure_request->value_mask & XCB_CONFIG_WINDOW_Y ? + configure_request->y : window->y); } weston_wm_window_configure_frame(window); weston_wm_window_send_configure_notify(window); @@ -1022,10 +1031,6 @@ weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *eve if (window->shsurf) xwayland_api->set_xwayland(window->shsurf, window->x, window->y); - } else if (is_our_resource) { - if (window->shsurf) - xwayland_api->move_position(window->shsurf, - window->x, window->y); } } From 86c7175dff9e95a55ab073cf1cf256c9f942d498 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 26 Sep 2022 11:05:44 -0700 Subject: [PATCH 1610/1642] xwm: Check size hints in weston_wm_window_is_positioned() (#114) Co-authored-by: Hideyuki Nagase --- xwayland/window-manager.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 3210f5973..0955b23a7 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -3337,6 +3337,9 @@ weston_wm_window_is_positioned(struct weston_wm_window *window) weston_log("XWM warning: win %d did not see map request\n", window->id); + if (window->size_hints.flags & (USPosition | PPosition)) + return true; + return window->map_request_x != 0 || window->map_request_y != 0; } From 5979eccb2cd76b7d0826c40bfe6c3021ab9ea767 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 28 Sep 2022 16:30:05 -0700 Subject: [PATCH 1611/1642] rdp shell: register snap application on start menu (#115) Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index 1ee61bc14..0df4196f7 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -116,6 +116,7 @@ struct app_entry { char *app_list_folder[] = { "/usr/share/applications", "/usr/local/share/applications", + "/var/lib/snapd/desktop/applications", }; /* list of folders to look for icon in specific orders */ @@ -248,6 +249,13 @@ find_icon_file(char *name) char buf[512]; int len; + /* if name is absolute path and file presents, use as-is */ + if (*name == '/') { + if (is_file_exist(name)) + return strdup(name); + return NULL; + } + /* TODO: follow icon search path desribed at "Icon Lookup" section at https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ for (int i = 0; i < (int)ARRAY_LENGTH(icon_folder); i++) { @@ -385,6 +393,7 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr char *lang_id = context->lang_info.currentClientLanguageId; char *s; GKeyFile *key_file; + GError *err = NULL; key_file = g_key_file_new(); if (!key_file) @@ -397,8 +406,9 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr goto exit; attach_app_list_namespace(shell); - if (!g_key_file_load_from_file(key_file, file, G_KEY_FILE_NONE, NULL)) { - shell_rdp_debug(shell, "desktop file: %s is failed to be loaded\n", entry->file); + if (!g_key_file_load_from_file(key_file, file, G_KEY_FILE_NONE, &err)) { + shell_rdp_debug(shell, "desktop file: %s is failed to be loaded: %s\n", + entry->file, err && err->message ? err->message : ""); detach_app_list_namespace(shell); goto exit; } @@ -491,6 +501,9 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr return true; exit: + if (err) + g_error_free(err); + g_key_file_free(key_file); /* caller will clean up partially filled entry upon returning false */ @@ -775,6 +788,7 @@ app_list_monitor_thread(LPVOID arg) struct app_entry *entry; int fd[ARRAY_LENGTH(app_list_folder)] = {}; int wd[ARRAY_LENGTH(app_list_folder)] = {}; + int app_list_folder_index[ARRAY_LENGTH(app_list_folder)] = {}; char *home; char *folder; int len, cur; @@ -875,6 +889,7 @@ app_list_monitor_thread(LPVOID arg) fd[num_watch] = 0; continue; } + app_list_folder_index[num_watch] = i; num_events++; num_watch++; } @@ -977,6 +992,8 @@ app_list_monitor_thread(LPVOID arg) if (shell->rdprail_api->notify_app_list && num_watch) { len = read(fd[status - WAIT_OBJECT_0 - NUM_CONTROL_EVENT], buf, sizeof buf); cur = 0; + if (len) + sleep(2); /* workaround to settle .desktop file and other resoures write */ while (cur < len) { event = (struct inotify_event *)&buf[cur]; if (event->len && @@ -984,7 +1001,7 @@ app_list_monitor_thread(LPVOID arg) is_desktop_file(event->name)) { if (event->mask & (IN_CREATE|IN_MODIFY|IN_MOVED_TO)) { shell_rdp_debug(shell, "app_list_monitor_thread: file created/updated (%s)\n", event->name); - app_list_desktop_file_changed(shell, app_list_folder[status - WAIT_OBJECT_0 - NUM_CONTROL_EVENT], event->name); + app_list_desktop_file_changed(shell, app_list_folder[app_list_folder_index[status - WAIT_OBJECT_0 - NUM_CONTROL_EVENT]], event->name); } else if (event->mask & (IN_DELETE|IN_MOVED_FROM)) { shell_rdp_debug(shell, "app_list_monitor_thread: file removed (%s)\n", event->name); From 32963ae4f093b3a5dc3462dc0f18b66c3fa97ae2 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 29 Sep 2022 12:52:54 -0700 Subject: [PATCH 1612/1642] rdp shell: use predefined constant for g_key_file api (#116) Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index 0df4196f7..c6c2b4f6a 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -64,8 +64,6 @@ #include #endif -#define GROUP_DESKTOP_ENTRY "Desktop Entry" - #if HAVE_GLIB && HAVE_WINPR #define NUM_CONTROL_EVENT 5 @@ -414,19 +412,19 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr } detach_app_list_namespace(shell); - if (!g_key_file_has_group(key_file, GROUP_DESKTOP_ENTRY)) { + if (!g_key_file_has_group(key_file, G_KEY_FILE_DESKTOP_GROUP)) { shell_rdp_debug(shell, "desktop file: %s is missing %s section\n", - entry->file, GROUP_DESKTOP_ENTRY); + entry->file, G_KEY_FILE_DESKTOP_GROUP); goto exit; } - if (g_key_file_get_boolean(key_file, GROUP_DESKTOP_ENTRY, "Hidden", NULL)) { + if (g_key_file_get_boolean(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL)) { shell_rdp_debug(shell, "desktop file: %s is hidden\n", entry->file); goto exit; } - s = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "Type", NULL); + s = g_key_file_get_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TYPE, NULL); if (s) { if (strcmp(s, "Application") != 0) { shell_rdp_debug(shell, "desktop file: %s is not app (%s)\n", entry->file, s); @@ -435,23 +433,23 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr } free(s); } - if (g_key_file_get_boolean(key_file, GROUP_DESKTOP_ENTRY, "NoDisplay", NULL)) { + if (g_key_file_get_boolean(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL)) { shell_rdp_debug(shell, "desktop file: %s has NoDisplay specified\n", entry->file); goto exit; // NoDisplay app is not included. } - if (g_key_file_get_boolean(key_file, GROUP_DESKTOP_ENTRY, "Terminal", NULL)) { + if (g_key_file_get_boolean(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TERMINAL, NULL)) { shell_rdp_debug(shell, "desktop file: %s is terminal based app\n", entry->file); goto exit; // terminal based app is not included. } /*TODO: OnlyShowIn/NotShowIn support for WSL environment. */ /* Need $XDG_CURRENT_DESKTOP keyword for WSL GUI environment */ - s = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "OnlyShowIn", NULL); + s = g_key_file_get_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, NULL); if (s) { shell_rdp_debug(shell, "desktop file: %s has OnlyShowIn %s\n", entry->file, s); free(s); goto exit; } - entry->name = g_key_file_get_locale_string(key_file, GROUP_DESKTOP_ENTRY, "Name", lang_id, NULL); + entry->name = g_key_file_get_locale_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, lang_id, NULL); if (!entry->name) { /* name is required */ shell_rdp_debug(shell, "desktop file: %s is missing Name key\n", entry->file); @@ -472,17 +470,17 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr entry->name = t; } } - entry->exec = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "Exec", NULL); + entry->exec = g_key_file_get_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL); if (!entry->exec) { shell_rdp_debug(shell, "desktop file: %s is missing Exec key\n", entry->file); goto exit; } trim_command_exec(entry->exec); - entry->try_exec = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "TryExec", NULL); + entry->try_exec = g_key_file_get_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, NULL); if (entry->try_exec) trim_command_exec(entry->try_exec); - entry->working_dir = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "Path", NULL); - entry->icon = g_key_file_get_string(key_file, GROUP_DESKTOP_ENTRY, "Icon", NULL); + entry->working_dir = g_key_file_get_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_PATH, NULL); + entry->icon = g_key_file_get_locale_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, lang_id, NULL); if (entry->icon) { attach_app_list_namespace(shell); entry->icon_file = find_icon_file(entry->icon); From 2270ceb3cf75a03e8b3f073eca2c5dc04b12e504 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 4 Oct 2022 11:01:55 -0700 Subject: [PATCH 1613/1642] rdp shell: add icon file search retry (#117) * rdp shell: add icon search retry * misc fixes * misc fixes * misc fixes Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 178 ++++++++++++++++++++++++++------------- 1 file changed, 121 insertions(+), 57 deletions(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index c6c2b4f6a..04f57f33f 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -68,6 +68,9 @@ #define NUM_CONTROL_EVENT 5 +#define EVENT_TIMEOUT_MS 2000 // 2 seconds +#define MAX_ICON_RETRY_COUNT 5 + struct app_list_context { wHashTable* table; HANDLE thread; @@ -81,6 +84,7 @@ struct app_list_context { bool isAppListNamespaceAttached; int app_list_pidfd; int weston_pidfd; + uint32_t icon_retry_count; pixman_image_t* default_icon; pixman_image_t* default_overlay_icon; struct { @@ -106,8 +110,10 @@ struct app_entry { char *exec; char *try_exec; char *working_dir; - char *icon; + char *icon_name; char *icon_file; + pixman_image_t* icon_image; + uint32_t icon_retry_count; }; /* TODO: obtain additional path from $XDG_DATA_DIRS, default path is defined here */ @@ -241,42 +247,62 @@ is_desktop_file(char *file) return NULL; } -static char * -find_icon_file(char *name) +static bool +find_icon_file(struct app_entry *entry) { + struct desktop_shell *shell = entry->shell; + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; char buf[512]; int len; + char *icon_file = buf; - /* if name is absolute path and file presents, use as-is */ - if (*name == '/') { - if (is_file_exist(name)) - return strdup(name); - return NULL; - } + assert(entry->icon_name); + assert(entry->icon_file == NULL); + assert(entry->icon_retry_count < MAX_ICON_RETRY_COUNT); - /* TODO: follow icon search path desribed at "Icon Lookup" section at + /* if name is absolute path and file presents, use as-is */ + if (*entry->icon_name == '/') { + if (is_file_exist(entry->icon_name)) { + icon_file = entry->icon_name; + goto Found; + } + } else { + /* TODO: follow icon search path desribed at "Icon Lookup" section at https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ - for (int i = 0; i < (int)ARRAY_LENGTH(icon_folder); i++) { - copy_string(buf, sizeof buf, icon_folder[i]); - append_string(buf, sizeof buf, name); - len = strlen(buf); - - /* first, try given file name as is */ - if (is_file_exist(buf)) - return strdup(buf); - - /* if not found, try again with .png extension appended */ - append_string(buf, sizeof buf, ".png"); - if (is_file_exist(buf)) - return strdup(buf); - - /* if not found, try again with .svg extension appended */ - copy_string(&buf[len], sizeof buf - len, ".svg"); - if (is_file_exist(buf)) - return strdup(buf); + for (int i = 0; i < (int)ARRAY_LENGTH(icon_folder); i++) { + copy_string(buf, sizeof buf, icon_folder[i]); + append_string(buf, sizeof buf, entry->icon_name); + len = strlen(buf); + + /* first, try given file name as is */ + if (is_file_exist(buf)) + goto Found; + + /* if not found, try again with .png extension appended */ + append_string(buf, sizeof buf, ".png"); + if (is_file_exist(buf)) + goto Found; + + /* if not found, try again with .svg extension appended */ + copy_string(&buf[len], sizeof buf - len, ".svg"); + if (is_file_exist(buf)) + goto Found; + } } - return NULL; + if (entry->icon_retry_count++ == 0) + context->icon_retry_count++; + else if (entry->icon_retry_count == MAX_ICON_RETRY_COUNT) + context->icon_retry_count--; + shell_rdp_debug(entry->shell, "%s: icon (%s) search retry:(%d) global:(%d)\n", + __func__, entry->icon_name, entry->icon_retry_count, context->icon_retry_count); + return false; + +Found: + if (entry->icon_retry_count) + context->icon_retry_count--; + entry->icon_file = strdup(icon_file); + return (entry->icon_file != NULL); } static void @@ -284,14 +310,21 @@ free_app_entry(void *arg) { struct app_entry *e = (struct app_entry *)arg; if (e) { - shell_rdp_debug(e->shell, "free_app_entry(): %s: %s\n", e->name, e->file); + struct desktop_shell *shell = e->shell; + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + + shell_rdp_debug(shell, "free_app_entry(): %s: %s\n", e->name, e->file); + if (e->file) free(e->file); if (e->name) free(e->name); if (e->exec) free(e->exec); if (e->try_exec) free(e->try_exec); if (e->working_dir) free(e->working_dir); - if (e->icon) free(e->icon); + if (e->icon_name) free(e->icon_name); if (e->icon_file) free(e->icon_file); + if (e->icon_image) pixman_image_unref(e->icon_image); + if (e->icon_retry_count) context->icon_retry_count--; + free(e); } } @@ -340,19 +373,11 @@ send_app_entry(struct desktop_shell *shell, char *key, struct app_entry *entry, app_list_data.appExecPath = entry->try_exec ? entry->try_exec : entry->exec; app_list_data.appWorkingDir = entry->working_dir; app_list_data.appDesc = entry->name; - if (entry->icon) { - attach_app_list_namespace(shell); - if (!entry->icon_file) - entry->icon_file = find_icon_file(entry->icon); - if (entry->icon_file) - app_list_data.appIcon = load_icon_image(shell, entry->icon_file); - detach_app_list_namespace(shell); - } - if (!app_list_data.appIcon) { + app_list_data.appIcon = entry->icon_image; + if (!app_list_data.appIcon) app_list_data.appIcon = context->default_icon; - if (app_list_data.appIcon) - pixman_image_ref(app_list_data.appIcon); - } + if (app_list_data.appIcon) + pixman_image_ref(app_list_data.appIcon); if (shell->is_blend_overlay_icon_app_list && app_list_data.appIcon && app_list_data.appIcon != context->default_icon && @@ -368,6 +393,42 @@ send_app_entry(struct desktop_shell *shell, char *key, struct app_entry *entry, pixman_image_unref(app_list_data.appIcon); } +static void +retry_find_icon_file(struct desktop_shell *shell) +{ + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + struct app_entry *entry; + char **keys; + char **cur; + int num_keys; + + keys = NULL; + num_keys = HashTable_GetKeys(context->table, (ULONG_PTR**)&keys); + if (num_keys < 0) + return; + + cur = keys; + for (int i = 0; i < num_keys; i++, cur++) { + entry = (struct app_entry *)HashTable_GetItemValue(context->table, (void *)*cur); + if (entry && + entry->icon_name && + entry->icon_file == NULL && + entry->icon_retry_count < MAX_ICON_RETRY_COUNT) { + shell_rdp_debug(entry->shell, "%s: icon (%s) retry count (%d)\n", + __func__, entry->icon_name, entry->icon_retry_count); + attach_app_list_namespace(shell); + if (find_icon_file(entry)) + entry->icon_image = load_icon_image(shell, entry->icon_file); + detach_app_list_namespace(shell); + if (entry->icon_image) + send_app_entry(shell, *cur, entry, false, false, false, false, false, false); + } + } + + if (keys) + free(keys); +} + static void trim_command_exec(char *s) { @@ -480,10 +541,11 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr if (entry->try_exec) trim_command_exec(entry->try_exec); entry->working_dir = g_key_file_get_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_PATH, NULL); - entry->icon = g_key_file_get_locale_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, lang_id, NULL); - if (entry->icon) { + entry->icon_name = g_key_file_get_locale_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, lang_id, NULL); + if (entry->icon_name) { attach_app_list_namespace(shell); - entry->icon_file = find_icon_file(entry->icon); + if (find_icon_file(entry)) + entry->icon_image = load_icon_image(shell, entry->icon_file); detach_app_list_namespace(shell); } g_key_file_free(key_file); @@ -493,8 +555,9 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr shell_rdp_debug(shell, " Exec:%s\n", entry->exec); shell_rdp_debug(shell, " TryExec:%s\n", entry->try_exec); shell_rdp_debug(shell, " WorkingDir:%s\n", entry->working_dir); - shell_rdp_debug(shell, " Icon name:%s\n", entry->icon); + shell_rdp_debug(shell, " Icon name:%s\n", entry->icon_name); shell_rdp_debug(shell, " Icon file:%s\n", entry->icon_file); + shell_rdp_debug(shell, " Icon image:%p\n", entry->icon_image); return true; @@ -900,7 +963,8 @@ app_list_monitor_thread(LPVOID arg) /* now loop as changes are made or stop event is signaled */ while (TRUE) { - status = WaitForMultipleObjects(num_events, events, FALSE, INFINITE); + status = WaitForMultipleObjects(num_events, events, FALSE, + context->icon_retry_count ? EVENT_TIMEOUT_MS : INFINITE); if (status == WAIT_FAILED) { error = GetLastError(); break; @@ -909,6 +973,12 @@ app_list_monitor_thread(LPVOID arg) /* winpr doesn't support auto-reset event */ ResetEvent(events[status - WAIT_OBJECT_0]); + /* Timeout */ + if (status == WAIT_TIMEOUT) { + retry_find_icon_file(shell); + continue; + } + /* Stop Event */ if (status == WAIT_OBJECT_0) { shell_rdp_debug(shell, "app_list_monitor_thread: stopEvent is signalled\n"); @@ -942,13 +1012,9 @@ app_list_monitor_thread(LPVOID arg) shell_rdp_debug(shell, "app_list_monitor_thread: loadIconEvent is signalled. %s\n", context->load_icon.key); if (context->load_icon.key) { entry = (struct app_entry *)HashTable_GetItemValue(context->table, (void*)context->load_icon.key); - if (entry && entry->icon) { - attach_app_list_namespace(shell); - if (!entry->icon_file) - entry->icon_file = find_icon_file(entry->icon); - if (entry->icon_file) - context->load_icon.image = load_icon_image(shell, entry->icon_file); - detach_app_list_namespace(shell); + if (entry && entry->icon_image) { + context->load_icon.image = entry->icon_image; + pixman_image_ref(context->load_icon.image); } shell_rdp_debug(shell, "app_list_monitor_thread: entry %p, image %p\n", entry, context->load_icon.image); } @@ -990,8 +1056,6 @@ app_list_monitor_thread(LPVOID arg) if (shell->rdprail_api->notify_app_list && num_watch) { len = read(fd[status - WAIT_OBJECT_0 - NUM_CONTROL_EVENT], buf, sizeof buf); cur = 0; - if (len) - sleep(2); /* workaround to settle .desktop file and other resoures write */ while (cur < len) { event = (struct inotify_event *)&buf[cur]; if (event->len && From 603601f2fe63e43f57a58cef6c880d054731dc6e Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 5 Oct 2022 14:09:12 -0700 Subject: [PATCH 1614/1642] rdp shell: make wslpath optional for obtaining windows path (#118) Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 2 +- rdprail-shell/shell.c | 5 +++++ rdprail-shell/shell.h | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index 04f57f33f..a2408a32d 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -755,7 +755,7 @@ translate_to_windows_path(struct desktop_shell *shell, char *image_name, size_t attach_app_list_namespace(shell); - if (is_file_exist("/usr/bin/wslpath")) { + if (shell->use_wslpath && is_file_exist("/usr/bin/wslpath")) { pid_t pid; int pipe[2] = {}; int imageNameLength, len; diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index f80c50719..c84b5ee0a 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -777,6 +777,11 @@ shell_configuration(struct desktop_shell *shell) shell->image_default_app_icon, shell->image_default_app_overlay_icon); + shell->use_wslpath = read_rdpshell_config_bool( + "WESTON_RDPRAIL_SHELL_USE_WSLPATH", false); + shell_rdp_debug(shell, "RDPRAIL-shell: WESTON_RDPRAIL_SHELL_USE_WSLPATH:%d\n", + shell->use_wslpath); + shell->workspaces.num = 1; } diff --git a/rdprail-shell/shell.h b/rdprail-shell/shell.h index 01c23b9fe..c9997ae77 100644 --- a/rdprail-shell/shell.h +++ b/rdprail-shell/shell.h @@ -149,6 +149,8 @@ struct desktop_shell { const struct weston_rdprail_api *rdprail_api; void *rdp_backend; + bool use_wslpath; + struct weston_log_scope *debug; uint32_t debugLevel; }; From c060b2a1efe0c2109dde4a2a87cf89d5830d6fa0 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 14 Oct 2022 10:36:23 -0700 Subject: [PATCH 1615/1642] rdp-backend: fix overactive assert (#119) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdputil.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c index 059879418..1c0d9eb3e 100644 --- a/libweston/backend-rdp/rdputil.c +++ b/libweston/backend-rdp/rdputil.c @@ -297,7 +297,8 @@ void * rdp_id_manager_lookup(struct rdp_id_manager *id_manager, UINT32 id) { /* lookup can be done under compositor thread or after mutex held by rdp_id_manager_lock */ - assert(id_manager->mutex_tid == rdp_get_tid()); + assert(id_manager->mutex_tid == rdp_get_tid() || + id_manager->rdp_backend->compositor_tid == rdp_get_tid()); assert(id_manager->hash_table); return hash_table_lookup(id_manager->hash_table, id); From 3ab02a3d9c5ec3975a543eda206682c2bc6621fd Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 18 Oct 2022 11:10:17 -0700 Subject: [PATCH 1616/1642] rdp shell: fix missing va_end() (#120) Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 1 + 1 file changed, 1 insertion(+) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index c84b5ee0a..bf7e77c49 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -296,6 +296,7 @@ shell_rdp_debug_print(struct weston_log_scope *scope, bool cont, char *fmt, ...) timestr, oom); } } + va_end(ap); } } From e7e91e17f370702f386c843cf9014cc4bcef4179 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 25 Oct 2022 05:45:27 -0700 Subject: [PATCH 1617/1642] rdp shell: workaround get_position crash (#121) Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index bf7e77c49..396961c34 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -3005,9 +3005,17 @@ desktop_surface_get_position(struct weston_desktop_surface *surface, void *shell_) { struct shell_surface *shsurf = weston_desktop_surface_get_user_data(surface); - - *x = shsurf->view->geometry.x; - *y = shsurf->view->geometry.y; + if (shsurf) { + *x = shsurf->view->geometry.x; + *y = shsurf->view->geometry.y; + } else { + /* Ideally libweston-desktop/xwayland.c must not call shell if + the surface is not reported to shell (surface.state == XWAYLAND), + but unfortunately this does happen, thus here workaround the crash + by returning (0,0) in such case. */ + *x = 0; + *y = 0; + } } static bool From 929ad1c610fafccfcc5fa9b89d20b851e3a9414f Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Fri, 2 Dec 2022 11:00:27 -0800 Subject: [PATCH 1618/1642] xwayland: revert max frame flag change which no longer needed (#122) Co-authored-by: Hideyuki Nagase --- xwayland/window-manager.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 0955b23a7..4bd90e28a 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2124,13 +2124,10 @@ weston_wm_window_handle_state(struct weston_wm_window *window, if (weston_wm_window_is_maximized(window)) { window->saved_width = window->width; window->saved_height = window->height; - if (window->frame) - frame_set_flag(window->frame, FRAME_FLAG_MAXIMIZED); + if (window->shsurf) xwayland_interface->set_maximized(window->shsurf); } else if (window->shsurf) { - if (window->frame) - frame_unset_flag(window->frame, FRAME_FLAG_MAXIMIZED); weston_wm_window_set_toplevel(window); } } @@ -3206,15 +3203,12 @@ set_maximized(struct weston_surface *surface, bool is_maximized) window->maximized_vert = 1; window->saved_width = window->width; window->saved_height = window->height; - frame_set_flag(window->frame, FRAME_FLAG_MAXIMIZED); wm->server->compositor->xwayland_interface->set_maximized(window->shsurf); } - } - else { + } else { if (weston_wm_window_is_maximized(window)) { window->maximized_horz = 0; window->maximized_vert = 0; - frame_unset_flag(window->frame, FRAME_FLAG_MAXIMIZED); weston_wm_window_set_toplevel(window); } } @@ -3444,7 +3438,6 @@ xserver_map_shell_surface(struct weston_wm_window *window, } else if (weston_wm_window_is_maximized(window)) { window->saved_width = window->width; window->saved_height = window->height; - frame_set_flag(window->frame, FRAME_FLAG_MAXIMIZED); xwayland_interface->set_maximized(window->shsurf); } else { if (weston_wm_window_type_inactive(window)) { From 103447134f004678488bb9d0a0141c636c0b823c Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 8 Dec 2022 08:18:23 -0800 Subject: [PATCH 1619/1642] xwm: Fix selection if no seat present at startup (#123) Co-authored-by: Hideyuki Nagase --- xwayland/selection.c | 70 ++++++++++++++++++++++++++++++++++----- xwayland/window-manager.c | 2 ++ xwayland/xwayland.h | 1 + 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/xwayland/selection.c b/xwayland/selection.c index a99717894..00f1547f0 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -25,6 +25,7 @@ #include "config.h" +#include #include #include #include @@ -203,6 +204,9 @@ weston_wm_get_selection_targets(struct weston_wm *wm) char *logstr; size_t logsize; + if (!seat) + return; + cookie = xcb_get_property(wm->conn, 1, /* delete */ wm->selection_window, @@ -602,6 +606,7 @@ weston_wm_handle_selection_request(struct weston_wm *wm, weston_log_continue("property %s\n", get_atom_name(wm->conn, selection_request->property)); + assert(selection_request->requestor != wm->selection_window); wm->selection_request = *selection_request; wm->incr = 0; wm->flush_property_on_delete = 0; @@ -646,6 +651,9 @@ weston_wm_handle_xfixes_selection_notify(struct weston_wm *wm, xfixes_selection_notify->owner); if (xfixes_selection_notify->owner == XCB_WINDOW_NONE) { + if (!seat) + return 1; + if (wm->selection_owner != wm->selection_window) { /* A real X client selection went away, not our * proxy selection. Clear the wayland selection. */ @@ -729,18 +737,55 @@ weston_wm_set_selection(struct wl_listener *listener, void *data) wm->selection_window, wm->atom.clipboard, XCB_TIME_CURRENT_TIME); + + xcb_flush(wm->conn); +} + +static void +maybe_reassign_selection_seat(struct weston_wm *wm) +{ + struct weston_seat *seat; + + /* If we already have a seat, keep it */ + if (!wl_list_empty(&wm->selection_listener.link)) + return; + + seat = weston_wm_pick_seat(wm); + if (!seat) + return; + + wl_list_remove(&wm->selection_listener.link); + wl_list_remove(&wm->seat_destroy_listener.link); + + wl_signal_add(&seat->selection_signal, &wm->selection_listener); + wl_signal_add(&seat->destroy_signal, &wm->seat_destroy_listener); + + weston_wm_set_selection(&wm->selection_listener, seat); } static void weston_wm_seat_created(struct wl_listener *listener, void *data) { - struct weston_seat *seat = data; struct weston_wm *wm = container_of(listener, struct weston_wm, seat_create_listener); - wl_signal_add(&seat->selection_signal, &wm->selection_listener); + maybe_reassign_selection_seat(wm); +} - weston_wm_set_selection(&wm->selection_listener, seat); +static void +weston_wm_seat_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_wm *wm = + container_of(listener, struct weston_wm, seat_destroy_listener); + + wl_list_remove(&wm->selection_listener.link); + wl_list_init(&wm->selection_listener.link); + + wl_list_remove(&wm->seat_destroy_listener.link); + wl_list_init(&wm->seat_destroy_listener.link); + + /* Try to pick another available seat to fall back to */ + maybe_reassign_selection_seat(wm); } void @@ -750,6 +795,8 @@ weston_wm_selection_init(struct weston_wm *wm) uint32_t values[1], mask; wl_list_init(&wm->selection_listener.link); + wl_list_init(&wm->seat_create_listener.link); + wl_list_init(&wm->seat_destroy_listener.link); wm->selection_request.requestor = XCB_NONE; @@ -778,13 +825,20 @@ weston_wm_selection_init(struct weston_wm *wm) xcb_xfixes_select_selection_input(wm->conn, wm->selection_window, wm->atom.clipboard, mask); + /* Try to set up a selection listener for any existing seat - we + * have a clipboard manager that can copy a subset of available + * selections so they don't disappear when the client owning + * them quits, but to make this work we need to have a seat + * to hang the selection off. + * + * If we have no seat or lose our seat we need to make sure we + * eventually assign a new one, so we listen for seat creation + * and destruction. + */ wm->selection_listener.notify = weston_wm_set_selection; - + wm->seat_destroy_listener.notify = weston_wm_seat_destroyed; wm->seat_create_listener.notify = weston_wm_seat_created; wl_signal_add(&wm->server->compositor->seat_created_signal, &wm->seat_create_listener); - - seat = weston_wm_pick_seat(wm); - if (seat) - weston_wm_seat_created(&wm->seat_create_listener, seat); + maybe_reassign_selection_seat(wm); } diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 4bd90e28a..7139c9347 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -3039,6 +3039,8 @@ weston_wm_destroy(struct weston_wm *wm) weston_wm_destroy_cursors(wm); xcb_disconnect(wm->conn); wl_event_source_remove(wm->source); + wl_list_remove(&wm->seat_create_listener.link); + wl_list_remove(&wm->seat_destroy_listener.link); wl_list_remove(&wm->selection_listener.link); wl_list_remove(&wm->activate_listener.link); wl_list_remove(&wm->kill_listener.link); diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index 91b96a6d8..6bc4e3a95 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -91,6 +91,7 @@ struct weston_wm { int flush_property_on_delete; struct wl_listener selection_listener; struct wl_listener seat_create_listener; + struct wl_listener seat_destroy_listener; xcb_window_t dnd_window; xcb_window_t dnd_owner; From 5f0c6a3e6003daefd9e5cf179d826e75cd10fb11 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 8 Dec 2022 12:16:25 -0800 Subject: [PATCH 1620/1642] rdp-backend: fix weston crash at RDP client disconnect (#124) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 765db51f9..b8c0a77ee 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -313,7 +313,8 @@ rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, b->rdp_peer->context->settings->HiDefRemoteApp) { /* RAIL mode, repaint RAIL window */ rdp_rail_output_repaint(output_base, damage); - } else if (output_base->renderer_state) { + } else if (output->shadow_surface && + output_base->renderer_state) { /* Add above 'output_base->renderer_state' check since this turns NULL when RDP connection is disconnected and hit fault at pixman_renderer_output_set_buffer() */ pixman_renderer_output_set_buffer(output_base, output->shadow_surface); From e0eee22a2a0500ccb1eae9d59da5e744a5af257f Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 9 Jan 2023 13:12:42 -0800 Subject: [PATCH 1621/1642] Build: use Ubuntu 20.04 for build verification (#126) Co-authored-by: Hideyuki Nagase --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 59bf9014b..e54bd1b90 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,7 +12,7 @@ trigger: - working pool: - vmImage: 'ubuntu-latest' + vmImage: 'ubuntu-20.04' variables: prefix: '/usr/local' From 4bdc5bccea4f1f4b035bf776d38b022ea611e74b Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 9 Jan 2023 13:13:02 -0800 Subject: [PATCH 1622/1642] rdp shell: allow alt+F4 to close app (#127) Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 30 ++++++++++++++++++++++++++++++ rdprail-shell/shell.h | 1 + 2 files changed, 31 insertions(+) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 396961c34..53c1c7431 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -234,6 +234,9 @@ shell_surface_update_child_surface_layers(struct shell_surface *shsurf); static void shell_backend_request_window_activate(void *shell_context, struct weston_seat *seat, struct weston_surface *surface); +static void +shell_backend_request_window_close(struct weston_surface *surface); + #define ICON_STRIDE( W, BPP ) ((((W) * (BPP) + 31) / 32) * 4) #define TITLEBAR_GRAB_MARGIN_X (30) @@ -699,6 +702,7 @@ shell_configuration(struct desktop_shell *shell) struct weston_config_section *section; char *s, *client; bool allow_zap; + bool allow_alt_f4_to_close_app; bool is_localmove_supported; section = weston_config_get_section(wet_get_config(shell->compositor), @@ -716,6 +720,13 @@ shell_configuration(struct desktop_shell *shell) shell->allow_zap = allow_zap; shell_rdp_debug(shell, "RDPRAIL-shell: allow-zap:%d\n", shell->allow_zap); + /* default to allow alt+F4 to close app */ + weston_config_section_get_bool(section, + "alt-f4-to-close-app", &allow_alt_f4_to_close_app, true); + allow_alt_f4_to_close_app = read_rdpshell_config_bool("WESTON_RDPRAIL_SHELL_ALLOW_ALT_F4_TO_CLOSE_APP", allow_alt_f4_to_close_app); + shell->allow_alt_f4_to_close_app = allow_alt_f4_to_close_app; + shell_rdp_debug(shell, "RDPRAIL-shell: allow-alt-f4-to-close-app:%d\n", shell->allow_alt_f4_to_close_app); + /* set "none" to default to disable optional key-bindings */ weston_config_section_get_string(section, "binding-modifier", &s, "none"); @@ -3315,6 +3326,20 @@ terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, weston_compositor_exit(compositor); } +static void +close_focused_app_binding(struct weston_keyboard *keyboard, const struct timespec *time, + uint32_t key, void *data) +{ + struct weston_surface *focus = keyboard->focus; + struct weston_surface *surface; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shell_backend_request_window_close(surface); +} + static void rotate_grab_motion(struct weston_pointer_grab *grab, const struct timespec *time, @@ -4270,6 +4295,11 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) MODIFIER_CTRL | MODIFIER_ALT, terminate_binding, ec); + if (shell->allow_alt_f4_to_close_app) + weston_compositor_add_key_binding(ec, KEY_F4, + MODIFIER_ALT, + close_focused_app_binding, ec); + /* fixed bindings */ weston_compositor_add_button_binding(ec, BTN_LEFT, 0, click_to_activate_binding, diff --git a/rdprail-shell/shell.h b/rdprail-shell/shell.h index c9997ae77..f70b4e272 100644 --- a/rdprail-shell/shell.h +++ b/rdprail-shell/shell.h @@ -117,6 +117,7 @@ struct desktop_shell { } input_panel; bool allow_zap; + bool allow_alt_f4_to_close_app; uint32_t binding_modifier; struct weston_layer minimized_layer; From 321f79960f79357f57a3556a4840254bf5ee7f77 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 9 Jan 2023 13:13:21 -0800 Subject: [PATCH 1623/1642] rdp shell: allow optional applist folder by .wslgconfig (#125) Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 108 ++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 36 deletions(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index a2408a32d..5cfe5555e 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -116,13 +116,6 @@ struct app_entry { uint32_t icon_retry_count; }; -/* TODO: obtain additional path from $XDG_DATA_DIRS, default path is defined here */ -char *app_list_folder[] = { - "/usr/share/applications", - "/usr/local/share/applications", - "/var/lib/snapd/desktop/applications", -}; - /* list of folders to look for icon in specific orders */ /* TODO: follow icon search path desribed at "Icon Lookup" section at https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ @@ -198,8 +191,8 @@ attach_app_list_namespace(struct desktop_shell *shell) { struct app_list_context *context = (struct app_list_context *)shell->app_list_context; assert(false == context->isAppListNamespaceAttached); - if (context && context->app_list_pidfd > 0) { - assert(context->weston_pidfd > 0); + if (context && context->app_list_pidfd >= 0) { + assert(context->weston_pidfd >= 0); if (setns(context->app_list_pidfd, 0) == -1) { shell_rdp_debug_error(shell, "attach_app_list_namespace failed %s\n", strerror(errno)); } else { @@ -212,7 +205,7 @@ static void detach_app_list_namespace(struct desktop_shell *shell) { struct app_list_context *context = (struct app_list_context *)shell->app_list_context; - if (context && context->weston_pidfd > 0 && context->isAppListNamespaceAttached) { + if (context && context->weston_pidfd >= 0 && context->isAppListNamespaceAttached) { if (setns(context->weston_pidfd, 0) == -1) { /* TODO: when failed to go back, this is fatal, should terminate weston and restart? */ shell_rdp_debug_error(shell, "detach_app_list_namespace failed %s\n", strerror(errno)); @@ -646,6 +639,7 @@ app_list_desktop_file_changed(struct desktop_shell *shell, char *folder, char *f if (entry_old) { if (HashTable_SetItemValue(context->table, key, (void*)entry) < 0) { /* failed to update with new entry, remove this desktop entry as data is stale */ + shell_rdp_debug(shell, "app list entry failed to update Key:%s\n", key); app_list_desktop_file_removed(shell, file); free_app_entry(entry); } else { @@ -655,13 +649,15 @@ app_list_desktop_file_changed(struct desktop_shell *shell, char *folder, char *f } } else { #if WINPR_VERSION_MAJOR >= 3 - if (HashTable_Insert(context->table, key, (void *)entry) < 0) + if (HashTable_Insert(context->table, key, (void *)entry) < 0) { #else - if (HashTable_Add(context->table, key, (void *)entry) < 0) + if (HashTable_Add(context->table, key, (void *)entry) < 0) { #endif + shell_rdp_debug(shell, "app list entry failed to insert to hash: Key:%s\n", key); free_app_entry(entry); - else if (context->isRdpNotifyStarted) + } else if (context->isRdpNotifyStarted) { send_app_entry(shell, key, entry, true, false, false, false, false, false); + } } } else if (entry) { shell_rdp_debug(shell, "app list entry failed to update: Key:%s\n", key); @@ -672,7 +668,7 @@ app_list_desktop_file_changed(struct desktop_shell *shell, char *folder, char *f } static void -app_list_update_all(struct desktop_shell *shell) +app_list_update_all(struct desktop_shell *shell, char *app_list_folder[]) { char path[512]; DIR *dir; @@ -680,7 +676,7 @@ app_list_update_all(struct desktop_shell *shell) char *folder; char *home; - for (int i = 0; i < (int)ARRAY_LENGTH(app_list_folder); i++) { + for (int i = 0; app_list_folder[i] != NULL; i++) { attach_app_list_namespace(shell); folder = app_list_folder[i]; if (*folder == '~') { @@ -705,7 +701,7 @@ app_list_update_all(struct desktop_shell *shell) } static void -app_list_start_rdp_notify(struct desktop_shell *shell) +app_list_start_rdp_notify(struct desktop_shell *shell, char *app_list_folder[]) { struct app_list_context *context = (struct app_list_context *)shell->app_list_context; struct app_entry *entry; @@ -724,7 +720,7 @@ app_list_start_rdp_notify(struct desktop_shell *shell) strcpy(context->lang_info.currentClientLanguageId, context->lang_info.requestedClientLanguageId); /* update with requested language */ - app_list_update_all(shell); + app_list_update_all(shell, app_list_folder); } keys = NULL; @@ -847,8 +843,20 @@ app_list_monitor_thread(LPVOID arg) struct desktop_shell *shell = (struct desktop_shell *)arg; struct app_list_context *context = (struct app_list_context *)shell->app_list_context; struct app_entry *entry; - int fd[ARRAY_LENGTH(app_list_folder)] = {}; - int wd[ARRAY_LENGTH(app_list_folder)] = {}; + /* TODO: obtain additional path from $XDG_DATA_DIRS, default path is defined here + but env variable at user distro is not accessible from system-distro, + thus, any additional path can be added by WESTON_RDPRAIL_SHELL_APP_LIST_PATH + using .wslgconfig */ + #define CUSTOM_APP_LIST_FOLDER_INDEX 3 + #define MAX_APP_LIST_FOLDER 128 + char *app_list_folder[MAX_APP_LIST_FOLDER] = { + "/usr/share/applications", + "/usr/local/share/applications", + "/var/lib/snapd/desktop/applications", + NULL, /* always terminated witn NULL entry */ + }; + int fd[ARRAY_LENGTH(app_list_folder)] = {-1, -1, -1, -1}; + int wd[ARRAY_LENGTH(app_list_folder)] = {-1, -1, -1, -1}; int app_list_folder_index[ARRAY_LENGTH(app_list_folder)] = {}; char *home; char *folder; @@ -899,8 +907,27 @@ app_list_monitor_thread(LPVOID arg) events[num_events++] = context->findImageNameEvent; assert(num_events == NUM_CONTROL_EVENT); + /* append optional folders */ + folder = getenv("WESTON_RDPRAIL_SHELL_APP_LIST_PATH"); + for (cur = CUSTOM_APP_LIST_FOLDER_INDEX; cur < MAX_APP_LIST_FOLDER-1; cur++) { + if (folder && *folder != '\0') { + char *s = strrchr(folder, ':'); + if (s) { + app_list_folder[cur] = strndup(folder, s-folder); + folder = s+1; + } else { + app_list_folder[cur] = strdup(folder); + folder = NULL; + } + } else { + break; + } + } + assert(cur < MAX_APP_LIST_FOLDER); + app_list_folder[cur] = NULL; /* terminated with NULL entry */ + if (shell->rdprail_api->notify_app_list) { - for (int i = 0; i < (int)ARRAY_LENGTH(app_list_folder); i++) { + for (int i = 0; app_list_folder[i] != NULL; i++) { fd[num_watch] = inotify_init(); if (fd[num_watch] < 0) { shell_rdp_debug_error(shell, "app_list_monitor_thread: inotify_init[%d] failed %s\n", i, strerror(errno)); @@ -914,7 +941,7 @@ app_list_monitor_thread(LPVOID arg) if (!home) { detach_app_list_namespace(shell); close(fd[num_watch]); - fd[num_watch] = 0; + fd[num_watch] = -1; continue; } copy_string(path, sizeof path, home); @@ -926,7 +953,7 @@ app_list_monitor_thread(LPVOID arg) shell_rdp_debug(shell, "app_list_monitor_thread: %s doesn't exist, skipping.\n", folder); detach_app_list_namespace(shell); close(fd[num_watch]); - fd[num_watch] = 0; + fd[num_watch] = -1; continue; } @@ -936,7 +963,7 @@ app_list_monitor_thread(LPVOID arg) shell_rdp_debug_error(shell, "app_list_monitor_thread: inotify_add_watch failed: %s\n", strerror(errno)); detach_app_list_namespace(shell); close(fd[num_watch]); - fd[num_watch] = 0; + fd[num_watch] = -1; continue; } detach_app_list_namespace(shell); @@ -945,11 +972,12 @@ app_list_monitor_thread(LPVOID arg) if (!events[num_events]) { shell_rdp_debug_error(shell, "app_list_monitor_thread: GetFileHandleForFileDescriptor failed\n"); inotify_rm_watch(fd[num_watch], wd[num_watch]); - wd[num_watch] = 0; + wd[num_watch] = -1; close(fd[num_watch]); - fd[num_watch] = 0; + fd[num_watch] = -1; continue; } + shell_rdp_debug(shell, "app_list_monitor_thread: monitor %s\n", folder); app_list_folder_index[num_watch] = i; num_events++; num_watch++; @@ -958,7 +986,7 @@ app_list_monitor_thread(LPVOID arg) /* first scan folders to update all existing .desktop files */ if (num_watch) - app_list_update_all(shell); + app_list_update_all(shell, app_list_folder); } /* now loop as changes are made or stop event is signaled */ @@ -990,7 +1018,7 @@ app_list_monitor_thread(LPVOID arg) shell_rdp_debug(shell, "app_list_monitor_thread: startRdpNotifyEvent is signalled. %d - %s\n", context->isRdpNotifyStarted, context->lang_info.requestedClientLanguageId); if (!context->isRdpNotifyStarted) { - app_list_start_rdp_notify(shell); + app_list_start_rdp_notify(shell, app_list_folder); context->isRdpNotifyStarted = true; } continue; @@ -1078,23 +1106,28 @@ app_list_monitor_thread(LPVOID arg) Exit: assert(false == context->isAppListNamespaceAttached); - for (int i = 0; i < (int)ARRAY_LENGTH(app_list_folder); i++) { + for (int i = 0; i < num_watch; i++) { if (events[i + NUM_CONTROL_EVENT]) CloseHandle(events[i + NUM_CONTROL_EVENT]); - if (fd[i] > 0) { - if (wd[i] > 0) + if (fd[i] != -1) { + if (wd[i] != -1) inotify_rm_watch(fd[i], wd[i]); close(fd[i]); } } - if (context->weston_pidfd > 0) { + for (int i = CUSTOM_APP_LIST_FOLDER_INDEX; app_list_folder[i] != NULL; i++) { + free(app_list_folder[i]); + app_list_folder[i] = NULL; + } + + if (context->weston_pidfd >= 0) { close(context->weston_pidfd); - context->weston_pidfd = 0; + context->weston_pidfd = -1; } - if (context->app_list_pidfd > 0) { + if (context->app_list_pidfd >= 0) { close(context->app_list_pidfd); - context->app_list_pidfd = 0; + context->app_list_pidfd = -1; } ExitThread(error); @@ -1108,6 +1141,9 @@ start_app_list_monitor(struct desktop_shell *shell) context->isRdpNotifyStarted = false; + context->weston_pidfd = -1; + context->app_list_pidfd = -1; + context->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!context->stopEvent) goto Error_Exit; @@ -1228,8 +1264,8 @@ stop_app_list_monitor(struct desktop_shell *shell) context->isRdpNotifyStarted = false; - assert(context->weston_pidfd <= 0); - assert(context->app_list_pidfd <= 0); + assert(context->weston_pidfd < 0); + assert(context->app_list_pidfd < 0); } #endif // HAVE_WINPR && HAVE_GLIB From f0c83cfe3401287f46b0d068f198e6b5756b8f77 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 10 Jan 2023 12:21:00 -0800 Subject: [PATCH 1624/1642] rdp fontend/backend/shell: keep compositor sleep until window is created (#128) Co-authored-by: Hideyuki Nagase --- compositor/main.c | 3 +- libweston/backend-rdp/rdp.c | 10 +++ libweston/backend-rdp/rdprail.c | 110 +++++++++++++++----------------- rdprail-shell/shell.c | 26 ++++++-- 4 files changed, 82 insertions(+), 67 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index c6d84c12e..929f57d3d 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -3607,7 +3607,8 @@ wet_main(int argc, char *argv[]) if (argc > 1) goto out; - weston_compositor_wake(wet.compositor); + /* Until RDP connection is established, keep compositor sleep state */ + weston_compositor_sleep(wet.compositor); wl_display_run(display); diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index b8c0a77ee..70f54c963 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -750,6 +750,9 @@ rdp_peer_context_free(freerdp_peer* client, RdpPeerContext* context) b = context->rdpBackend; + /* While RDP client is disconnected, keep compositor sleep state */ + weston_compositor_sleep(b->compositor); + wl_list_remove(&context->item.link); for (i = 0; i < ARRAY_LENGTH(context->events); i++) { @@ -1059,6 +1062,13 @@ xf_peer_activate(freerdp_peer* client) rdp_debug_error(b, "HiDef-RAIL is requested from client, but RAIL-shell is not used\n"); return FALSE; } + + /* do not wake up compositor yet, since in RAIL mode, there is no + need to paint 'desktop', thus defer until window is created */ + } else { + /* update RDP connection, wake up compositor to repaint 'desktop' */ + weston_compositor_wake(b->compositor); + weston_compositor_damage_all(b->compositor); } /* override settings by env variables */ diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index ef748d23a..32907c2cd 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -3191,6 +3191,44 @@ disp_client_monitor_layout_change(DispServerContext *context, const DISPLAY_CONT return CHANNEL_RC_OK; } +static void +rdp_rail_idle_handler(struct wl_listener *listener, void *data) +{ + RAIL_POWER_DISPLAY_REQUEST displayRequest; + RdpPeerContext *peer_ctx = container_of(listener, RdpPeerContext, + idle_listener); + struct rdp_backend *b = peer_ctx->rdpBackend; + RailServerContext *rail_ctx = peer_ctx->rail_server_context; + + assert_compositor_thread(b); + + rdp_debug(b, "%s is called on peer_ctx:%p\n", __func__, peer_ctx); + + if (peer_ctx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED) { + displayRequest.active = FALSE; + rail_ctx->ServerPowerDisplayRequest(rail_ctx, &displayRequest); + } +} + +static void +rdp_rail_wake_handler(struct wl_listener *listener, void *data) +{ + RAIL_POWER_DISPLAY_REQUEST displayRequest; + RdpPeerContext *peer_ctx = container_of(listener, RdpPeerContext, + wake_listener); + struct rdp_backend *b = peer_ctx->rdpBackend; + RailServerContext *rail_ctx = peer_ctx->rail_server_context; + + assert_compositor_thread(b); + + rdp_debug(b, "%s is called on peer_ctx:%p\n", __func__, peer_ctx); + + if (peer_ctx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED) { + displayRequest.active = TRUE; + rail_ctx->ServerPowerDisplayRequest(rail_ctx, &displayRequest); + } +} + bool rdp_rail_peer_activate(freerdp_peer* client) { @@ -3368,6 +3406,12 @@ rdp_rail_peer_activate(freerdp_peer* client) WTSVirtualChannelManagerCheckFileDescriptor(peer_ctx->vcm); } + /* subscribe idle/wake signal from compositor */ + peer_ctx->idle_listener.notify = rdp_rail_idle_handler; + wl_signal_add(&b->compositor->idle_signal, &peer_ctx->idle_listener); + peer_ctx->wake_listener.notify = rdp_rail_wake_handler; + wl_signal_add(&b->compositor->wake_signal, &peer_ctx->wake_listener); + return TRUE; error_exit: @@ -3420,40 +3464,6 @@ rdp_rail_peer_activate(freerdp_peer* client) return FALSE; } -static void -rdp_rail_idle_handler(struct wl_listener *listener, void *data) -{ - RAIL_POWER_DISPLAY_REQUEST displayRequest; - RdpPeerContext *peer_ctx = container_of(listener, RdpPeerContext, - idle_listener); - struct rdp_backend *b = peer_ctx->rdpBackend; - RailServerContext *rail_ctx = peer_ctx->rail_server_context; - - assert_compositor_thread(b); - - rdp_debug(b, "%s is called on peer_ctx:%p\n", __func__, peer_ctx); - - displayRequest.active = FALSE; - rail_ctx->ServerPowerDisplayRequest(rail_ctx, &displayRequest); -} - -static void -rdp_rail_wake_handler(struct wl_listener *listener, void *data) -{ - RAIL_POWER_DISPLAY_REQUEST displayRequest; - RdpPeerContext *peer_ctx = container_of(listener, RdpPeerContext, - wake_listener); - struct rdp_backend *b = peer_ctx->rdpBackend; - RailServerContext *rail_ctx = peer_ctx->rail_server_context; - - assert_compositor_thread(b); - - rdp_debug(b, "%s is called on peer_ctx:%p\n", __func__, peer_ctx); - - displayRequest.active = TRUE; - rail_ctx->ServerPowerDisplayRequest(rail_ctx, &displayRequest); -} - static void rdp_rail_notify_window_proxy_surface(struct weston_surface *proxy_surface) { @@ -3577,36 +3587,19 @@ rdp_rail_sync_window_status(freerdp_peer *client) if (rail_state && rail_state->window_id) { if (api && api->request_window_icon) api->request_window_icon(surface); - } - wl_list_for_each(sub, &surface->subsurface_list, parent_link) { - struct weston_surface_rail_state *sub_rail_state = sub->surface->backend_state; - - if (sub->surface == surface) - continue; - if (!sub_rail_state || sub_rail_state->window_id == 0) - rdp_rail_create_window(NULL, sub->surface); + wl_list_for_each(sub, &surface->subsurface_list, parent_link) { + struct weston_surface_rail_state *sub_rail_state = sub->surface->backend_state; + if (sub->surface == surface) + continue; + if (!sub_rail_state || sub_rail_state->window_id == 0) + rdp_rail_create_window(NULL, sub->surface); + } } } } /* this assume repaint to be scheduled on idle loop, not directly from here */ weston_compositor_damage_all(b->compositor); - - if (peer_ctx->clientStatusFlags & TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED) { - RAIL_POWER_DISPLAY_REQUEST displayRequest; - - /* subscribe idle/wake signal from compositor */ - peer_ctx->idle_listener.notify = rdp_rail_idle_handler; - wl_signal_add(&b->compositor->idle_signal, &peer_ctx->idle_listener); - peer_ctx->wake_listener.notify = rdp_rail_wake_handler; - wl_signal_add(&b->compositor->wake_signal, &peer_ctx->wake_listener); - - displayRequest.active = TRUE; - rail_ctx->ServerPowerDisplayRequest(rail_ctx, &displayRequest); - - /* Upon client connection, make sure compositor is in wake state */ - weston_compositor_wake(b->compositor); - } } void @@ -3831,7 +3824,6 @@ rdp_rail_peer_context_free(freerdp_peer *client, RdpPeerContext *context) #ifdef HAVE_FREERDP_RDPAPPLIST_H if (context->applist_server_context) { struct rdp_backend *b = context->rdpBackend; - if (context->isAppListEnabled) context->rdpBackend->rdprail_shell_api->stop_app_list_update(context->rdpBackend->rdprail_shell_context); context->applist_server_context->Close(context->applist_server_context); diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 53c1c7431..93662c5d7 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -237,6 +237,8 @@ shell_backend_request_window_activate(void *shell_context, struct weston_seat *s static void shell_backend_request_window_close(struct weston_surface *surface); +static void launch_desktop_shell_process(void *data); + #define ICON_STRIDE( W, BPP ) ((((W) * (BPP) + 31) / 32) * 4) #define TITLEBAR_GRAB_MARGIN_X (30) @@ -2172,7 +2174,7 @@ handle_metadata_change(struct wl_listener *listener, void *data) static void desktop_surface_added(struct weston_desktop_surface *desktop_surface, - void *shell) + void *data) { struct weston_desktop_client *client = weston_desktop_surface_get_client(desktop_surface); @@ -2182,6 +2184,10 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, struct shell_surface *shsurf; struct weston_surface *surface = weston_desktop_surface_get_surface(desktop_surface); + struct desktop_shell *shell = + (struct desktop_shell *)data; + struct weston_compositor *ec = shell->compositor; + struct wl_event_loop *loop; view = weston_desktop_surface_create_view(desktop_surface); if (!view) @@ -2199,7 +2205,7 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, weston_surface_set_label_func(surface, shell_surface_get_label); - shsurf->shell = (struct desktop_shell *) shell; + shsurf->shell = shell; shsurf->unresponsive = 0; shsurf->saved_position_valid = false; shsurf->saved_rotation_valid = false; @@ -2208,8 +2214,7 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, shsurf->fullscreen.black_view = NULL; wl_list_init(&shsurf->fullscreen.transform.link); - shell_surface_set_output( - shsurf, get_default_output(shsurf->shell->compositor)); + shell_surface_set_output(shsurf, get_default_output(ec)); wl_signal_init(&shsurf->destroy_signal); @@ -2231,6 +2236,14 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, shsurf->metadata_listener.notify = handle_metadata_change; weston_desktop_surface_add_metadata_listener(desktop_surface, &shsurf->metadata_listener); + + /* when surface is added, compositor is in wake state */ + weston_compositor_wake(ec); + /* and, shell process (= focus_proxy) is running */ + if (!shell->child.client) { + loop = wl_display_get_event_loop(ec->wl_display); + wl_event_loop_add_idle(loop, launch_desktop_shell_process, shell); + } } static void @@ -3967,6 +3980,7 @@ launch_desktop_shell_process(void *data) { struct desktop_shell *shell = data; + assert(!shell->child.client); shell->child.client = weston_client_start(shell->compositor, shell->client); @@ -4618,7 +4632,6 @@ wet_shell_init(struct weston_compositor *ec, struct desktop_shell *shell; struct workspace **pws; unsigned int i; - struct wl_event_loop *loop; char *debug_level; shell = zalloc(sizeof *shell); @@ -4701,8 +4714,7 @@ wet_shell_init(struct weston_compositor *ec, setup_output_destroy_handler(ec, shell); - loop = wl_display_get_event_loop(ec->wl_display); - wl_event_loop_add_idle(loop, launch_desktop_shell_process, shell); + shell->child.client = NULL; wl_list_for_each(seat, &ec->seat_list, link) handle_seat_created(NULL, seat); From 3ff4250b28aefbcfefdd869cb997d0591c06b6d8 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 23 Jan 2023 15:43:36 -0800 Subject: [PATCH 1625/1642] libweston-desktop: xwayland window is not visible upon commit until mouse is moved (#130) * libweston-desktop: xwayland window is not visible upon commit * check XWAYLAND state instead of relying on added Co-authored-by: Hideyuki Nagase Co-authored-by: Hideyuki Nagase --- libweston-desktop/xwayland.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index 86cc330e1..232388f57 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -170,6 +170,9 @@ weston_desktop_xwayland_surface_committed(struct weston_desktop_surface *dsurfac if (surface->added) weston_desktop_api_committed(surface->desktop, surface->surface, sx, sy); + + if (surface->state == XWAYLAND) + weston_view_update_transform(surface->view); } static void From 31169e0a6a3e7bace6fb31f8770ce3895aad44f3 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 31 Jan 2023 09:05:25 -0800 Subject: [PATCH 1626/1642] rdp backend/rdp shell: sync window state with RDP client (#131) * sync window state with client * state transition between fullscreen and maximized * clear local move pending if mouse move is reported --------- Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 15 ++-- libweston-desktop/xwayland.c | 7 ++ libweston/backend-rdp/rdprail.c | 137 ++++++++++++++++++++++---------- rdprail-shell/shell.c | 123 ++++++++++++++++++++++++---- xwayland/window-manager.c | 65 ++++++++++----- 5 files changed, 261 insertions(+), 86 deletions(-) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index 90960a9e5..cb4213cb4 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -200,6 +200,13 @@ struct weston_rdp_shared_memory { char name[RDP_SHARED_MEMORY_NAME_SIZE + 1]; // +1 for NULL }; +/* weston_surface_rail_state.showState_requested */ +#define RDP_WINDOW_HIDE 0x00 +#define RDP_WINDOW_SHOW_MINIMIZED 0x02 +#define RDP_WINDOW_SHOW_MAXIMIZED 0x03 +#define RDP_WINDOW_SHOW_FULLSCREEN 0x04 +#define RDP_WINDOW_SHOW 0x05 + struct weston_surface_rail_state { struct wl_listener destroy_listener; struct wl_listener repaint_listener; @@ -217,12 +224,8 @@ struct weston_surface_rail_state { uint32_t parent_window_id; bool isCursor; bool isWindowCreated; - bool is_minimized; - bool is_minimized_requested; - bool is_maximized; - bool is_maximized_requested; - bool is_fullscreen; - bool is_fullscreen_requested; + uint32_t showState_requested; + uint32_t showState; bool forceRecreateSurface; bool forceUpdateWindowState; bool error; diff --git a/libweston-desktop/xwayland.c b/libweston-desktop/xwayland.c index 232388f57..df860c2f8 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston-desktop/xwayland.c @@ -80,6 +80,13 @@ weston_desktop_xwayland_surface_change_state(struct weston_desktop_xwayland_surf assert(!parent || state == TRANSIENT); if (to_add && surface->added) { + /* at restoring from maximized/fullscreen, let shell knows */ + if (state == TOPLEVEL) { + if (surface->state == MAXIMIZED) + weston_desktop_api_maximized_requested(surface->desktop, surface->surface, false); + else if (surface->state == FULLSCREEN) + weston_desktop_api_fullscreen_requested(surface->desktop, surface->surface, false, NULL); + } surface->state = state; return; } diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 32907c2cd..6db6ca113 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -79,6 +79,7 @@ struct rdp_rail_dispatch_data { union { RAIL_SYSPARAM_ORDER sys_param; RAIL_SYSCOMMAND_ORDER sys_command; + RAIL_SYSMENU_ORDER sys_menu; RAIL_ACTIVATE_ORDER activate; RAIL_EXEC_ORDER exec; RAIL_WINDOW_MOVE_ORDER window_move; @@ -541,7 +542,7 @@ rail_client_Syscommand_callback(bool freeOnly, void *arg) break; } - rdp_debug(b, + rdp_debug_verbose(b, "Client: ClientSyscommand: WindowId:0x%x, surface:0x%p, command:%s (0x%x)\n", syscommand->windowId, surface, commandString, syscommand->command); @@ -558,6 +559,30 @@ rail_client_Syscommand(RailServerContext *context, const RAIL_SYSCOMMAND_ORDER * return CHANNEL_RC_OK; } +static void +rail_client_Sysmenu_callback(bool freeOnly, void *arg) +{ + struct rdp_rail_dispatch_data *data = wl_container_of(arg, data, task_base); + const RAIL_SYSMENU_ORDER *sysmenu = &data->sys_menu; + freerdp_peer *client = data->client; + RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peer_ctx->rdpBackend; + + rdp_debug_verbose(b, + "Client: ClientSyscommand: WindowId:0x%x, left:%d, top:%d\n", + sysmenu->windowId, sysmenu->left, sysmenu->top); + + free(data); +} + +static UINT +rail_client_Sysmenu(RailServerContext* context, const RAIL_SYSMENU_ORDER* arg) +{ + RDP_DISPATCH_TO_DISPLAY_LOOP(context, sys_menu, arg, + rail_client_Sysmenu_callback); + return CHANNEL_RC_OK; +} + static void rail_client_ClientSysparam_callback(bool freeOnly, void *arg) { @@ -1386,6 +1411,25 @@ rdp_rail_update_cursor(struct weston_surface *surface) return 0; } +static char * +rdp_showstate_to_string(uint32_t showstate) +{ + switch (showstate) { + case RDP_WINDOW_HIDE: + return "Hide"; + case RDP_WINDOW_SHOW_MINIMIZED: + return "Minimized"; + case RDP_WINDOW_SHOW_MAXIMIZED: + return "Maximized"; + case RDP_WINDOW_SHOW_FULLSCREEN: + return "Fullscreen"; + case RDP_WINDOW_SHOW: + return "Normal"; + default: + return "Unknown"; + } +} + static void rdp_rail_create_window(struct wl_listener *listener, void *data) { @@ -1648,6 +1692,9 @@ rdp_rail_create_window(struct wl_listener *listener, void *data) rail_state->isWindowCreated = TRUE; rail_state->get_label = (void *)-1; /* label to be re-checked at update. */ rail_state->taskbarButton = window_state_order.TaskbarButton; + assert(window_state_order.showState == WINDOW_HIDE); + rail_state->showState = RDP_WINDOW_HIDE; + rail_state->showState_requested = RDP_WINDOW_SHOW; // show window at following update. pixman_region32_init_rect(&rail_state->damage, 0, 0, surface->width_from_buffer, surface->height_from_buffer); @@ -2100,7 +2147,7 @@ rdp_rail_update_window(struct weston_surface *surface, rail_state->clientPos.y != newClientPos.y || rail_state->clientPos.width != newClientPos.width || rail_state->clientPos.height != newClientPos.height || - rail_state->is_minimized != rail_state->is_minimized_requested || + rail_state->showState != rail_state->showState_requested || rail_state->get_label != surface->get_label || rail_state->forceUpdateWindowState) { window_order_info.windowId = window_id; @@ -2124,40 +2171,46 @@ rdp_rail_update_window(struct weston_surface *surface, } } - if (rail_state->forceUpdateWindowState || - rail_state->is_minimized != rail_state->is_minimized_requested) { - window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_SHOW; - - window_state_order.showState = rail_state->is_minimized_requested ? WINDOW_SHOW_MINIMIZED : WINDOW_SHOW; - - rail_state->is_minimized = rail_state->is_minimized_requested; - - rdp_debug_verbose(b, "WindowUpdate(0x%x - is_minimized:%d)\n", - window_id, - rail_state->is_minimized_requested); - } - - if (rail_state->is_maximized != rail_state->is_maximized_requested) { - rdp_debug_verbose(b, "WindowUpdate(0x%x - is_maximized:%d)\n", - window_id, - rail_state->is_maximized_requested); - rail_state->is_maximized = rail_state->is_maximized_requested; - } - - if (rail_state->is_fullscreen != rail_state->is_fullscreen_requested) { - rdp_debug_verbose(b, "WindowUpdate(0x%x - is_fullscreen:%d)\n", + if (rail_state->showState != rail_state->showState_requested) { + rdp_debug_verbose(b, "WindowUpdate(0x%x - showState:%s -> %s)\n", window_id, - rail_state->is_fullscreen_requested); - rail_state->is_fullscreen = rail_state->is_fullscreen_requested; - - window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_STYLE; - if (rail_state->is_fullscreen) - window_state_order.style = RAIL_WINDOW_FULLSCREEN_STYLE; - else + rdp_showstate_to_string(rail_state->showState), + rdp_showstate_to_string(rail_state->showState_requested)); + /* if exiting fullscreen, restore window style to normal style */ + if (rail_state->showState == RDP_WINDOW_SHOW_FULLSCREEN) { + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_STYLE; window_state_order.style = RAIL_WINDOW_NORMAL_STYLE; - window_state_order.extendedStyle = WS_EX_LAYERED; - /* force update window geometry */ - rail_state->forceUpdateWindowState = true; + window_state_order.extendedStyle = WS_EX_LAYERED; + /* force update window geometry */ + rail_state->forceUpdateWindowState = true; + } + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_SHOW; + switch (rail_state->showState_requested) { + case RDP_WINDOW_HIDE: + window_state_order.showState = WINDOW_HIDE; + break; + case RDP_WINDOW_SHOW: + window_state_order.showState = WINDOW_SHOW; + break; + case RDP_WINDOW_SHOW_MINIMIZED: + window_state_order.showState = WINDOW_SHOW_MINIMIZED; + break; + case RDP_WINDOW_SHOW_MAXIMIZED: + window_state_order.showState = WINDOW_SHOW_MAXIMIZED; + break; + case RDP_WINDOW_SHOW_FULLSCREEN: + /* fullscreen is treat as normal window at Window's client */ + window_state_order.showState = WINDOW_SHOW; + /* entering fullscreen mode, change window style */ + window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_STYLE; + window_state_order.style = RAIL_WINDOW_FULLSCREEN_STYLE; + /* force update window geometry */ + rail_state->forceUpdateWindowState = true; + break; + default: + assert(false); + } + rail_state->showState = rail_state->showState_requested; } if (rail_state->forceUpdateWindowState || @@ -2286,7 +2339,7 @@ rdp_rail_update_window(struct weston_surface *surface, window_state_order.visibilityRects = &window_vis; window_state_order.clientAreaWidth = newClientPos.width; window_state_order.clientAreaHeight = newClientPos.height; - if (!rail_state->is_fullscreen) { + if (rail_state->showState != RDP_WINDOW_SHOW_FULLSCREEN) { /* when window is not in fullscreen, there should be 'some' area for title bar, thus substracting 32 pixels out from window size for client area, this value does not need to be accurate at all, all here need to tell RDP client is that @@ -2942,8 +2995,8 @@ rdp_insert_window_zorder_array(struct weston_view *view, minimized), those won't included in z order list. */ if (rail_state && rail_state->isWindowCreated && - !rail_state->is_minimized && - !rail_state->is_minimized_requested) { + rail_state->showState != RDP_WINDOW_SHOW_MINIMIZED && + rail_state->showState_requested != RDP_WINDOW_SHOW_MINIMIZED) { if (iCurrent >= WindowIdArraySize) { rdp_debug_error(b, "%s: more windows in tree than ID manager tracking (%d vs %d)\n", __func__, iCurrent, WindowIdArraySize); @@ -3276,6 +3329,7 @@ rdp_rail_peer_activate(freerdp_peer* client) rail_ctx->ClientExec = rail_client_Exec; rail_ctx->ClientActivate = rail_client_Activate; rail_ctx->ClientSyscommand = rail_client_Syscommand; + rail_ctx->ClientSysmenu = rail_client_Sysmenu; rail_ctx->ClientSysparam = rail_client_ClientSysparam; rail_ctx->ClientGetAppidReq = rail_client_ClientGetAppidReq; rail_ctx->ClientWindowMove = rail_client_WindowMove; @@ -4178,12 +4232,9 @@ rdp_rail_dump_window_iter(void *element, void *data) } fprintf(fp, " parent_surface:%p, isCursor:%d, isWindowCreated:%d\n", rail_state->parent_surface, rail_state->isCursor, rail_state->isWindowCreated); - fprintf(fp, " isWindowMinimized:%d, isWindowMinimizedRequested:%d\n", - rail_state->is_minimized, rail_state->is_minimized_requested); - fprintf(fp, " isWindowMaximized:%d, isWindowMaximizedRequested:%d\n", - rail_state->is_maximized, rail_state->is_maximized_requested); - fprintf(fp, " isWindowFullscreen:%d, isWindowFullscreenRequested:%d\n", - rail_state->is_fullscreen, rail_state->is_fullscreen_requested); + fprintf(fp, " showState:%s, showState_requested:%s\n", + rdp_showstate_to_string(rail_state->showState), + rdp_showstate_to_string(rail_state->showState_requested)); fprintf(fp, " forceRecreateSurface:%d, error:%d\n", rail_state->forceRecreateSurface, rail_state->error); fprintf(fp, " isUdatePending:%d, isFirstUpdateDone:%d\n", diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 93662c5d7..ab14271ff 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -108,6 +108,8 @@ struct shell_surface { int32_t saved_x, saved_y; bool saved_position_valid; + uint32_t saved_showstate; + bool saved_showstate_valid; bool saved_rotation_valid; int unresponsive, grabbed; uint32_t resize_edges; @@ -1213,6 +1215,13 @@ move_grab_motion(struct weston_pointer_grab *grab, if (!shsurf) return; + /* if local move is expected, but recieved the mouse move, + then cacenl local move. */ + if (shsurf->shell->is_localmove_pending) { + shell_rdp_debug(shsurf->shell, "%s: mouse move is detected while attempting local move\n", __func__); + shsurf->shell->is_localmove_pending = false; + } + surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); constrain_position(move, &cx, &cy); @@ -1747,7 +1756,6 @@ unset_fullscreen(struct shell_surface *shsurf) if (!rail_state) return; - rail_state->is_fullscreen_requested = false; /* Unset the fullscreen output, driver configuration and transforms. */ wl_list_remove(&shsurf->fullscreen.transform.link); @@ -1757,6 +1765,12 @@ unset_fullscreen(struct shell_surface *shsurf) weston_surface_destroy(shsurf->fullscreen.black_view->surface); shsurf->fullscreen.black_view = NULL; + if (shsurf->saved_showstate_valid) + rail_state->showState_requested = shsurf->saved_showstate; + else + rail_state->showState_requested = RDP_WINDOW_SHOW; + shsurf->saved_showstate_valid = false; + if (shsurf->saved_position_valid) weston_view_set_position(shsurf->view, shsurf->saved_x, shsurf->saved_y); @@ -1769,6 +1783,7 @@ unset_fullscreen(struct shell_surface *shsurf) &shsurf->rotation.transform.link); shsurf->saved_rotation_valid = false; } + } static void @@ -1781,12 +1796,17 @@ unset_maximized(struct shell_surface *shsurf) if (!rail_state) return; - rail_state->is_maximized_requested = false; /* if shell surface has already output assigned, leave where it is. (don't move to primary). */ if (!shsurf->output) shell_surface_set_output(shsurf, get_default_output(surface->compositor)); + if (shsurf->saved_showstate_valid) + rail_state->showState_requested = shsurf->saved_showstate; + else + rail_state->showState_requested = RDP_WINDOW_SHOW; + shsurf->saved_showstate_valid = false; + if (shsurf->snapped.is_snapped) { /* Restore to snap state. */ @@ -1825,11 +1845,15 @@ set_minimized(struct weston_surface *surface) if (!rail_state) return; - rail_state->is_minimized_requested = true; assert(weston_surface_get_main_surface(view->surface) == view->surface); shsurf = get_shell_surface(surface); + + shsurf->saved_showstate = rail_state->showState; + shsurf->saved_showstate_valid = true; + rail_state->showState_requested = RDP_WINDOW_SHOW_MINIMIZED; + current_ws = get_current_workspace(shsurf->shell); weston_layer_entry_remove(&view->layer_link); @@ -1857,11 +1881,17 @@ set_unminimized(struct weston_surface *surface) if (!rail_state) return; - rail_state->is_minimized_requested = false; assert(weston_surface_get_main_surface(view->surface) == view->surface); shsurf = get_shell_surface(surface); + + if (shsurf->saved_showstate_valid) + rail_state->showState_requested = shsurf->saved_showstate; + else + rail_state->showState_requested = RDP_WINDOW_SHOW; + shsurf->saved_showstate_valid = false; + current_ws = get_current_workspace(shsurf->shell); weston_layer_entry_remove(&view->layer_link); @@ -1874,9 +1904,17 @@ set_unminimized(struct weston_surface *surface) static void set_unsnap(struct shell_surface *shsurf, int grabX, int grabY) { + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; + if (!shsurf->snapped.is_snapped) return; + if (!rail_state) + return; + /* * Reposition the window such that the mouse remain within the * new bound of the window after resize. @@ -1887,6 +1925,8 @@ set_unsnap(struct shell_surface *shsurf, int grabX, int grabY) } weston_desktop_surface_set_size(shsurf->desktop_surface, shsurf->snapped.saved_width, shsurf->snapped.saved_height);*/ + rail_state->showState_requested = RDP_WINDOW_SHOW; + shsurf->saved_showstate_valid = false; shsurf->snapped.is_snapped = false; } @@ -2207,6 +2247,7 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, shsurf->shell = shell; shsurf->unresponsive = 0; + shsurf->saved_showstate_valid = false; shsurf->saved_position_valid = false; shsurf->saved_rotation_valid = false; shsurf->desktop_surface = desktop_surface; @@ -2421,6 +2462,8 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, weston_desktop_surface_get_user_data(desktop_surface); struct weston_surface *surface = weston_desktop_surface_get_surface(desktop_surface); + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; struct weston_view *view = shsurf->view; struct desktop_shell *shell = data; bool was_fullscreen; @@ -2450,16 +2493,26 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, was_maximized == shsurf->state.maximized) return; - if (was_fullscreen) + if (was_fullscreen && !shsurf->state.fullscreen) unset_fullscreen(shsurf); - if (was_maximized) + if (was_maximized && !shsurf->state.maximized) unset_maximized(shsurf); - if ((shsurf->state.fullscreen || shsurf->state.maximized) && - !shsurf->saved_position_valid) { - shsurf->saved_x = shsurf->view->geometry.x; - shsurf->saved_y = shsurf->view->geometry.y; - shsurf->saved_position_valid = true; + if ((shsurf->state.fullscreen || shsurf->state.maximized)) { + if (!shsurf->saved_position_valid) { + shsurf->saved_x = shsurf->view->geometry.x; + shsurf->saved_y = shsurf->view->geometry.y; + shsurf->saved_position_valid = true; + } + + if (!shsurf->saved_showstate_valid) { + if (shsurf->state.fullscreen) + rail_state->showState_requested = RDP_WINDOW_SHOW_FULLSCREEN; + else + rail_state->showState_requested = RDP_WINDOW_SHOW_MAXIMIZED; + shsurf->saved_showstate = rail_state ? rail_state->showState : RDP_WINDOW_SHOW; + shsurf->saved_showstate_valid = true; + } if (!wl_list_empty(&shsurf->rotation.transform.link)) { wl_list_remove(&shsurf->rotation.transform.link); @@ -2541,9 +2594,14 @@ set_fullscreen(struct shell_surface *shsurf, bool fullscreen, if (!rail_state) return; - rail_state->is_fullscreen_requested = fullscreen; if (fullscreen) { + /* if window is created as fullscreen, always set previous state as normal */ + shsurf->saved_showstate = weston_surface_is_mapped(surface) ? \ + rail_state->showState : RDP_WINDOW_SHOW; + shsurf->saved_showstate_valid = true; + rail_state->showState_requested = RDP_WINDOW_SHOW_FULLSCREEN; + /* handle clients launching in fullscreen */ if (output == NULL && !weston_surface_is_mapped(surface)) { /* Set the output to the one that has focus currently. */ @@ -2556,8 +2614,18 @@ set_fullscreen(struct shell_surface *shsurf, bool fullscreen, width = shsurf->output->width; height = shsurf->output->height; } else if (weston_desktop_surface_get_maximized(desktop_surface)) { + shsurf->saved_showstate = rail_state->showState; + shsurf->saved_showstate_valid = true; + rail_state->showState_requested = RDP_WINDOW_SHOW_MAXIMIZED; get_maximized_size(shsurf, &width, &height); + } else { + if (shsurf->saved_showstate_valid) + rail_state->showState_requested = shsurf->saved_showstate; + else + rail_state->showState_requested = RDP_WINDOW_SHOW; + shsurf->saved_showstate_valid = false; } + weston_desktop_surface_set_fullscreen(desktop_surface, fullscreen); weston_desktop_surface_set_size(desktop_surface, width, height); } @@ -2677,12 +2745,17 @@ set_maximized(struct shell_surface *shsurf, bool maximized) if (!rail_state) return; - rail_state->is_maximized_requested = maximized; if (maximized) { struct weston_output *output; - if (!weston_surface_is_mapped(surface)) + /* if window is created as maximized, always set previous state as normal */ + shsurf->saved_showstate = weston_surface_is_mapped(surface) ? \ + rail_state->showState : RDP_WINDOW_SHOW; + shsurf->saved_showstate_valid = true; + rail_state->showState_requested = RDP_WINDOW_SHOW_MAXIMIZED; + + if (!weston_surface_is_mapped(surface)) output = get_focused_output(surface->compositor); else /* TODO: Need to revisit here for local move. */ @@ -2691,6 +2764,12 @@ set_maximized(struct shell_surface *shsurf, bool maximized) shell_surface_set_output(shsurf, output); get_maximized_size(shsurf, &width, &height); + } else { + if (shsurf->saved_showstate_valid) + rail_state->showState_requested = shsurf->saved_showstate; + else + rail_state->showState_requested = RDP_WINDOW_SHOW; + shsurf->saved_showstate_valid = false; } weston_desktop_surface_set_maximized(desktop_surface, maximized); weston_desktop_surface_set_size(desktop_surface, width, height); @@ -2728,6 +2807,11 @@ desktop_surface_set_window_icon(struct weston_desktop_surface *desktop_surface, static void shell_backend_request_window_minimize(struct weston_surface *surface) { + struct shell_surface *shsurf = get_shell_surface(surface); + + if (!shsurf) + return; + set_minimized(surface); } @@ -2776,9 +2860,16 @@ shell_backend_request_window_restore(struct weston_surface *surface) if (!rail_state) return; - if (rail_state->is_minimized_requested) { + if (rail_state->showState == RDP_WINDOW_SHOW_MINIMIZED) { set_unminimized(surface); - } else if (shsurf->state.maximized) { + } else if (shsurf->state.fullscreen) { + /* fullscreen is treated as normal (aka restored) state in + Windows client, thus there should be not be 'restore' + request to be made while in fullscreen state. */ + shell_rdp_debug(shsurf->shell, + "%s: surface:%p is requested to be restored while in fullscreen\n", + __func__, surface); + } else if (shsurf->state.maximized) { api = shsurf->shell->xwayland_surface_api; if (!api) { api = weston_xwayland_surface_get_api(shsurf->shell->compositor); diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index 7139c9347..a7549df50 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2100,14 +2100,17 @@ weston_wm_window_handle_state(struct weston_wm_window *window, update_state(action, &window->fullscreen)) { weston_wm_window_set_net_wm_state(window); if (window->fullscreen) { - window->saved_width = window->width; - window->saved_height = window->height; - + if (!weston_wm_window_is_maximized(window)) { + window->saved_width = window->width; + window->saved_height = window->height; + } if (window->shsurf) xwayland_interface->set_fullscreen(window->shsurf, NULL); - } else { - if (window->shsurf) + } else if (window->shsurf) { + if (weston_wm_window_is_maximized(window)) + xwayland_interface->set_maximized(window->shsurf); + else weston_wm_window_set_toplevel(window); } } else { @@ -2122,13 +2125,18 @@ weston_wm_window_handle_state(struct weston_wm_window *window, if (maximized != weston_wm_window_is_maximized(window)) { if (weston_wm_window_is_maximized(window)) { - window->saved_width = window->width; - window->saved_height = window->height; - + if (!window->fullscreen) { + window->saved_width = window->width; + window->saved_height = window->height; + } if (window->shsurf) xwayland_interface->set_maximized(window->shsurf); } else if (window->shsurf) { - weston_wm_window_set_toplevel(window); + if (window->fullscreen) + xwayland_interface->set_fullscreen(window->shsurf, + NULL); + else + weston_wm_window_set_toplevel(window); } } } @@ -2149,8 +2157,10 @@ weston_wm_window_handle_iconic_state(struct weston_wm_window *window, iconic_state = client_message->data.data32[0]; if (iconic_state == ICCCM_ICONIC_STATE) { - window->saved_height = window->height; - window->saved_width = window->width; + if (!weston_wm_window_is_maximized(window) && !window->fullscreen) { + window->saved_height = window->height; + window->saved_width = window->width; + } xwayland_interface->set_minimized(window->shsurf); } } @@ -2509,9 +2519,14 @@ weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) window->maximized_vert = !window->maximized_vert; weston_wm_window_set_net_wm_state(window); if (weston_wm_window_is_maximized(window)) { - window->saved_width = window->width; - window->saved_height = window->height; + if (!window->fullscreen) { + window->saved_width = window->width; + window->saved_height = window->height; + } xwayland_interface->set_maximized(window->shsurf); + } else if (window->fullscreen) { + xwayland_interface->set_fullscreen(window->shsurf, + NULL); } else { weston_wm_window_set_toplevel(window); } @@ -2519,8 +2534,10 @@ weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) } if (frame_status(window->frame) & FRAME_STATUS_MINIMIZE) { - window->saved_width = window->width; - window->saved_height = window->height; + if (!weston_wm_window_is_maximized(window) && !window->fullscreen) { + window->saved_width = window->width; + window->saved_height = window->height; + } xwayland_interface->set_minimized(window->shsurf); frame_status_clear(window->frame, FRAME_STATUS_MINIMIZE); } @@ -3192,26 +3209,32 @@ static void set_maximized(struct weston_surface *surface, bool is_maximized) { struct weston_wm_window *window = get_wm_window(surface); - struct weston_wm *wm; + const struct weston_desktop_xwayland_interface *xwayland_interface; if (!window || !window->wm) return; - wm = window->wm; + xwayland_interface = window->wm->server->compositor->xwayland_interface; if (is_maximized) { if (!weston_wm_window_is_maximized(window)) { window->maximized_horz = 1; window->maximized_vert = 1; - window->saved_width = window->width; - window->saved_height = window->height; - wm->server->compositor->xwayland_interface->set_maximized(window->shsurf); + if (!window->fullscreen) { + window->saved_width = window->width; + window->saved_height = window->height; + } + xwayland_interface->set_maximized(window->shsurf); } } else { if (weston_wm_window_is_maximized(window)) { window->maximized_horz = 0; window->maximized_vert = 0; - weston_wm_window_set_toplevel(window); + if (window->fullscreen) + xwayland_interface->set_fullscreen(window->shsurf, + NULL); + else + weston_wm_window_set_toplevel(window); } } weston_wm_window_set_net_wm_state(window); From 3a235a89fa9247939b377fea438c70754eb3782c Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 2 Feb 2023 10:40:56 -0800 Subject: [PATCH 1627/1642] rdp backend/rdp shell: send window minmax info when window become visible (#132) * send minmax info when window become visible * apply coordinate translation to min/max info * make minmax debug message verbose --------- Co-authored-by: Hideyuki Nagase Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 28 ++++--- libweston/backend-rdp/rdp.h | 2 - libweston/backend-rdp/rdprail.c | 130 ++++++++++++++++++++------------ rdprail-shell/shell.c | 54 ++++++++++--- 4 files changed, 145 insertions(+), 69 deletions(-) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index cb4213cb4..65a10d311 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -114,6 +114,10 @@ struct weston_rdprail_shell_api { /** Query window geometry */ void (*get_window_geometry)(struct weston_surface *surface, struct weston_geometry *geometry); + + /** Request to send window minmax info + */ + void (*request_window_minmax_info)(struct weston_surface *surface); }; #define WESTON_RDPRAIL_API_NAME "weston_rdprail_api_v1" @@ -134,6 +138,13 @@ struct weston_rdprail_app_list_data { pixman_image_t *appIcon; }; +struct weston_rdp_rail_window_pos { + int32_t x; + int32_t y; + uint32_t width; + uint32_t height; +}; + struct weston_rdprail_api { /** Initialize */ @@ -144,13 +155,19 @@ struct weston_rdprail_api { /** Start a local window move operation */ void (*start_window_move)(struct weston_surface *surface, - int pointerGrabX, int pointerGrabY, - struct weston_size minSize, struct weston_size maxSize); + int pointerGrabX, int pointerGrabY); /** End local window move operation */ void (*end_window_move)(struct weston_surface *surface); + /** Send window min/max information. + */ + void (*send_window_minmax_info)(struct weston_surface* surface, + struct weston_rdp_rail_window_pos* maxPosSize, + struct weston_size* minTrackSize, + struct weston_size* maxTrackSize); + /** Set window icon */ void (*set_window_icon)(struct weston_surface *surface, @@ -184,13 +201,6 @@ weston_rdprail_get_api(struct weston_compositor *compositor) return (const struct weston_rdprail_api *)api; } -struct weston_rdp_rail_window_pos { - int32_t x; - int32_t y; - uint32_t width; - uint32_t height; -}; - #define RDP_SHARED_MEMORY_NAME_SIZE (32 + 4 + 2) struct weston_rdp_shared_memory { diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index fb3ee90b7..d3d14c4e7 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -384,8 +384,6 @@ void rdp_rail_peer_context_free(freerdp_peer *client, RdpPeerContext *context); void rdp_rail_output_repaint(struct weston_output *output, pixman_region32_t *damage); bool rdp_drdynvc_init(freerdp_peer *client); void rdp_drdynvc_destroy(RdpPeerContext *context); -void rdp_rail_start_window_move(struct weston_surface *surface, int pointerGrabX, int pointerGrabY, struct weston_size minSize, struct weston_size maxSize); -void rdp_rail_end_window_move(struct weston_surface *surface); // rdpdisp.c bool diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 6db6ca113..007f89691 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2190,6 +2190,10 @@ rdp_rail_update_window(struct weston_surface *surface, window_state_order.showState = WINDOW_HIDE; break; case RDP_WINDOW_SHOW: + /* if previoulsy hidden, send minmax info */ + if (rail_state->showState == WINDOW_HIDE && + api && api->request_window_minmax_info) + api->request_window_minmax_info(surface); window_state_order.showState = WINDOW_SHOW; break; case RDP_WINDOW_SHOW_MINIMIZED: @@ -3165,7 +3169,7 @@ disp_monitor_layout_change_callback(bool freeOnly, void *dataIn) RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; struct rdp_backend *b = peerCtx->rdpBackend; RDPGFX_RESET_GRAPHICS_PDU reset_graphics = {}; - MONITOR_DEF *reset_monitor_def; + MONITOR_DEF *reset_monitor_def = NULL; assert_compositor_thread(b); @@ -3288,16 +3292,18 @@ rdp_rail_peer_activate(freerdp_peer* client) RdpPeerContext *peer_ctx = (RdpPeerContext *)client->context; struct rdp_backend *b = peer_ctx->rdpBackend; rdpSettings *settings = client->context->settings; - RailServerContext *rail_ctx; - RdpgfxServerContext *gfx_ctx; - DispServerContext *disp_ctx; + RailServerContext *rail_ctx = NULL; + RdpgfxServerContext *gfx_ctx = NULL; + DispServerContext *disp_ctx = NULL; bool rail_server_started = false; bool disp_server_opened = false; bool rail_grfx_server_opened = false; #ifdef HAVE_FREERDP_GFXREDIR_H + GfxRedirServerContext *redir_ctx = NULL; bool gfxredir_server_opened = false; #endif /* HAVE_FREERDP_GFXREDIR_H */ #ifdef HAVE_FREERDP_RDPAPPLIST_H + RdpAppListServerContext *applist_ctx = NULL; bool applist_server_opened = false; RDPAPPLIST_SERVER_CAPS_PDU app_list_caps = {}; #endif /* HAVE_FREERDP_RDPAPPLIST_H */ @@ -3403,7 +3409,6 @@ rdp_rail_peer_activate(freerdp_peer* client) rail_grfx_server_opened = TRUE; #ifdef HAVE_FREERDP_GFXREDIR_H - GfxRedirServerContext *redir_ctx; /* open Graphics Redirection channel. */ if (b->use_gfxredir) { @@ -3422,7 +3427,6 @@ rdp_rail_peer_activate(freerdp_peer* client) #endif /* HAVE_FREERDP_GFXREDIR_H */ #ifdef HAVE_FREERDP_RDPAPPLIST_H - RdpAppListServerContext *applist_ctx; /* open Application List channel. */ if (b->rdprail_shell_name && b->use_rdpapplist) { applist_ctx = b->rdpapplist_server_context_new(peer_ctx->vcm); @@ -3656,13 +3660,75 @@ rdp_rail_sync_window_status(freerdp_peer *client) weston_compositor_damage_all(b->compositor); } -void +static void +rdp_rail_send_window_minmax_info( + struct weston_surface* surface, + struct weston_rdp_rail_window_pos* maxPosSize, + struct weston_size* minTrackSize, + struct weston_size* maxTrackSize) +{ + struct weston_compositor *compositor = surface->compositor; + struct weston_surface_rail_state *rail_state = surface->backend_state; + struct rdp_backend *b = to_rdp_backend(compositor); + RdpPeerContext *peer_ctx; + RailServerContext *rail_ctx; + RAIL_MINMAXINFO_ORDER minmax_order; + int dummyX = 0, dummyY = 0; + + if (!b->rdp_peer || !b->rdp_peer->context->settings->HiDefRemoteApp) { + return; + } + + peer_ctx = (RdpPeerContext *)b->rdp_peer->context; + + /* apply global to output transform, and translate to client coordinate */ + if (surface->output) { + to_client_coordinate(peer_ctx, surface->output, + &maxPosSize->x, &maxPosSize->y, + &maxPosSize->width, &maxPosSize->height); + to_client_coordinate(peer_ctx, surface->output, + &dummyX, &dummyY, + &minTrackSize->width, &minTrackSize->height); + to_client_coordinate(peer_ctx, surface->output, + &dummyX, &dummyY, + &maxTrackSize->width, &maxTrackSize->height); + } + + /* Inform the RDP client about the minimum/maximum width and height allowed + * on this window. + */ + minmax_order.windowId = rail_state->window_id; + minmax_order.maxPosX = maxPosSize->x; + minmax_order.maxPosY = maxPosSize->y; + minmax_order.maxWidth = maxPosSize->width; + minmax_order.maxHeight = maxPosSize->height; + minmax_order.minTrackWidth = minTrackSize->width; + minmax_order.minTrackHeight = minTrackSize->height; + minmax_order.maxTrackWidth = maxTrackSize->width; + minmax_order.maxTrackHeight = maxTrackSize->height; + + rdp_debug_verbose(b, + "Minmax order: maxPosX:%d, maxPosY:%d, maxWidth:%d, maxHeight:%d\n", + minmax_order.maxPosX, + minmax_order.maxPosY, + minmax_order.maxWidth, + minmax_order.maxHeight); + rdp_debug_verbose(b, + "Minmax order: minTrackWidth:%d, minTrackHeight:%d, maxTrackWidth:%d, maxTrackHeight:%d\n", + minmax_order.minTrackWidth, + minmax_order.minTrackHeight, + minmax_order.maxTrackWidth, + minmax_order.maxTrackHeight); + + rail_ctx = peer_ctx->rail_server_context; + rail_ctx->ServerMinMaxInfo(rail_ctx, &minmax_order); +} + +static void rdp_rail_start_window_move( struct weston_surface* surface, int pointerGrabX, - int pointerGrabY, - struct weston_size minSize, - struct weston_size maxSize) + int pointerGrabY) { struct weston_compositor *compositor = surface->compositor; struct weston_surface_rail_state *rail_state = surface->backend_state; @@ -3675,7 +3741,6 @@ rdp_rail_start_window_move( struct rdp_backend *b = to_rdp_backend(compositor); const struct weston_rdprail_shell_api *api = b->rdprail_shell_api; RdpPeerContext *peer_ctx; - RAIL_MINMAXINFO_ORDER minmax_order; RAIL_LOCALMOVESIZE_ORDER move_order; int posX = 0, posY = 0; int numViews = 0; @@ -3713,47 +3778,14 @@ rdp_rail_start_window_move( /* apply global to output transform, and translate to client coordinate */ if (surface->output) { to_client_coordinate(peer_ctx, surface->output, - &posX, &posY, - &minSize.width, &minSize.height); + &posX, &posY, NULL, NULL); to_client_coordinate(peer_ctx, surface->output, - &pointerGrabX, &pointerGrabY, - &maxSize.width, &maxSize.height); + &pointerGrabX, &pointerGrabY, NULL, NULL); } rdp_debug(b, "============== StartWindowMove ==============\n"); rdp_debug(b, "WindowsPosition: Pre-move (%d,%d) at client.\n", posX, posY); rdp_debug(b, "pointerGrab: (%d,%d)\n", pointerGrabX, pointerGrabY); - rdp_debug(b, "minSize: (%dx%d)\n", minSize.width, minSize.height); - rdp_debug(b, "maxSize: (%dx%d)\n", maxSize.width, maxSize.height); - - /* Inform the RDP client about the minimum/maximum width and height allowed - * on this window. - */ - minmax_order.windowId = rail_state->window_id; - minmax_order.maxPosX = 0; - minmax_order.maxPosY = 0; - minmax_order.maxWidth = 0; - minmax_order.maxHeight = 0; - minmax_order.minTrackWidth = minSize.width; - minmax_order.minTrackHeight = minSize.height; - minmax_order.maxTrackWidth = maxSize.width; - minmax_order.maxTrackHeight = maxSize.height; - - rdp_debug(b, - "Minmax order: maxPosX:%d, maxPosY:%d, maxWidth:%d, maxHeight:%d\n", - minmax_order.maxPosX, - minmax_order.maxPosY, - minmax_order.maxWidth, - minmax_order.maxHeight); - rdp_debug(b, - "Minmax order: minTrackWidth:%d, minTrackHeight:%d, maxTrackWidth:%d, maxTrackHeight:%d\n", - minmax_order.minTrackWidth, - minmax_order.minTrackHeight, - minmax_order.maxTrackWidth, - minmax_order.maxTrackHeight); - - rail_ctx = peer_ctx->rail_server_context; - rail_ctx->ServerMinMaxInfo(rail_ctx, &minmax_order); /* Start the local Window move. */ @@ -3771,12 +3803,13 @@ rdp_rail_start_window_move( move_order.posX, move_order.posY); + rail_ctx = peer_ctx->rail_server_context; rail_ctx->ServerLocalMoveSize(rail_ctx, &move_order); - rdp_debug(b, "=============== StartWindowMove ===============\n"); + rdp_debug(b, "============== StartWindowMove ==============\n"); } -void +static void rdp_rail_end_window_move(struct weston_surface *surface) { struct weston_compositor *compositor = surface->compositor; @@ -4696,6 +4729,7 @@ struct weston_rdprail_api rdprail_api = { .shell_initialize_notify = rdp_rail_shell_initialize_notify, .start_window_move = rdp_rail_start_window_move, .end_window_move = rdp_rail_end_window_move, + .send_window_minmax_info = rdp_rail_send_window_minmax_info, .set_window_icon = rdp_rail_set_window_icon, #ifdef HAVE_FREERDP_RDPAPPLIST_H .notify_app_list = rdp_rail_notify_app_list, diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index ab14271ff..c702742a0 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -502,6 +502,45 @@ get_default_view(struct weston_surface *surface) return container_of(surface->views.next, struct weston_view, surface_link); } +static void +shell_send_minmax_info(struct weston_surface *surface) +{ + struct shell_surface *shsurf = get_shell_surface(surface); + struct desktop_shell *shell; + struct weston_output *output; + struct weston_rdp_rail_window_pos maxPosSize; + struct weston_size min_size; + struct weston_size max_size; + + if (!shsurf) + return; + + shell = shsurf->shell; + + if (shell->rdprail_api->send_window_minmax_info) { + /* minmax info is based on primary monitor */ + /* https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-minmaxinfo */ + output = get_default_output(shell->compositor); + assert(output); + + maxPosSize.x = 0; + maxPosSize.y = 0; + maxPosSize.width = output->width; + maxPosSize.height = output->height; + + min_size = weston_desktop_surface_get_min_size(shsurf->desktop_surface); + max_size = weston_desktop_surface_get_max_size(shsurf->desktop_surface); + if (max_size.width == 0) + max_size.width = output->width; + if (max_size.height == 0) + max_size.height = output->height; + + shell->rdprail_api->send_window_minmax_info( + weston_desktop_surface_get_surface(shsurf->desktop_surface), + &maxPosSize, &min_size, &max_size); + } +} + static void shell_grab_start(struct shell_grab *grab, const struct weston_pointer_grab_interface *interface, @@ -526,24 +565,18 @@ shell_grab_start(struct shell_grab *grab, (interface == &move_grab_interface) && shell->rdprail_api->start_window_move) { - struct weston_size min_size; - struct weston_size max_size; - if (grab->shsurf->snapped.is_snapped) { set_unsnap(grab->shsurf, wl_fixed_to_int(pointer->grab_x), wl_fixed_to_int(pointer->grab_y)); } - - min_size = weston_desktop_surface_get_min_size(shsurf->desktop_surface); - max_size = weston_desktop_surface_get_max_size(shsurf->desktop_surface); - shell->is_localmove_pending = true; + shell_send_minmax_info( + weston_desktop_surface_get_surface(shsurf->desktop_surface)); + shell->rdprail_api->start_window_move( weston_desktop_surface_get_surface(shsurf->desktop_surface), wl_fixed_to_int(pointer->grab_x), - wl_fixed_to_int(pointer->grab_y), - min_size, - max_size); + wl_fixed_to_int(pointer->grab_y)); } else if (grab->shsurf->snapped.is_snapped) { /** Cancel snap state on anything but a move grab */ @@ -4713,6 +4746,7 @@ static const struct weston_rdprail_shell_api rdprail_shell_api = { .request_window_icon = shell_backend_request_window_icon, .request_launch_shell_process = shell_backend_launch_shell_process, .get_window_geometry = shell_backend_get_window_geometry, + .request_window_minmax_info = shell_send_minmax_info, }; WL_EXPORT int From 23cf3a56ef3155a688e7cf48a33d1705446b6854 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 6 Feb 2023 11:25:41 -0800 Subject: [PATCH 1628/1642] rdp shell: fix window unsnap by keyboard shortcut and mouse drag. (#133) * support window unsnap by keyboard shortcut and mouse * resize either from left or right at unsnap * report snap support to client --------- Co-authored-by: Hideyuki Nagase --- compositor/main.c | 2 +- libweston/backend-rdp/rdprail.c | 2 +- rdprail-shell/shell.c | 96 ++++++++++++++++++++++----------- 3 files changed, 67 insertions(+), 33 deletions(-) diff --git a/compositor/main.c b/compositor/main.c index 929f57d3d..2cca62e46 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -2882,7 +2882,7 @@ load_rdp_backend(struct weston_compositor *c, wet->rdp_params.default_width = parsed_options->width; wet->rdp_params.default_height = parsed_options->height; config.rail_config.enable_window_zorder_sync = read_rdp_config_bool("WESTON_RDP_WINDOW_ZORDER_SYNC", true); - config.rail_config.enable_window_snap_arrange = read_rdp_config_bool("WESTON_RDP_WINDOW_SNAP_ARRANGE", false); + config.rail_config.enable_window_snap_arrange = read_rdp_config_bool("WESTON_RDP_WINDOW_SNAP_ARRANGE", true); config.rail_config.enable_window_shadow_remoting = read_rdp_config_bool("WESTON_RDP_WINDOW_SHADOW_REMOTING", true); config.rail_config.enable_display_power_by_screenupdate = diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 007f89691..72ccd4dd4 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -333,7 +333,7 @@ rail_client_SnapArrange_callback(bool freeOnly, void *arg) pixman_rectangle32_t snap_rect; struct weston_geometry geometry; - rdp_debug(b, "Client: SnapArrange: WindowId:0x%x at (%d, %d) %dx%d\n", + rdp_debug_verbose(b, "Client: SnapArrange: WindowId:0x%x at (%d, %d) %dx%d\n", snap->windowId, snap->left, snap->top, diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index c702742a0..694f5a461 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -151,8 +151,11 @@ struct shell_surface { int y; int width; int height; - int saved_width; - int saved_height; + int saved_x; + int saved_y; + int saved_surface_width; + int saved_width; // based on window geometry + int saved_height; // based on window geometry int last_grab_x; int last_grab_y; } snapped; @@ -212,7 +215,7 @@ struct shell_seat { static const struct weston_pointer_grab_interface move_grab_interface; -static void set_unsnap(struct shell_surface *shsurf, int grabX, int grabY); +static void grab_unsnap_motion(struct weston_pointer_grab *grab); static struct desktop_shell * shell_surface_get_shell(struct shell_surface *shsurf); @@ -565,9 +568,6 @@ shell_grab_start(struct shell_grab *grab, (interface == &move_grab_interface) && shell->rdprail_api->start_window_move) { - if (grab->shsurf->snapped.is_snapped) { - set_unsnap(grab->shsurf, wl_fixed_to_int(pointer->grab_x), wl_fixed_to_int(pointer->grab_y)); - } shell->is_localmove_pending = true; shell_send_minmax_info( @@ -577,10 +577,6 @@ shell_grab_start(struct shell_grab *grab, weston_desktop_surface_get_surface(shsurf->desktop_surface), wl_fixed_to_int(pointer->grab_x), wl_fixed_to_int(pointer->grab_y)); - } else if (grab->shsurf->snapped.is_snapped) { - /** Cancel snap state on anything but a move grab - */ - grab->shsurf->snapped.is_snapped = false; } } @@ -1251,15 +1247,18 @@ move_grab_motion(struct weston_pointer_grab *grab, /* if local move is expected, but recieved the mouse move, then cacenl local move. */ if (shsurf->shell->is_localmove_pending) { - shell_rdp_debug(shsurf->shell, "%s: mouse move is detected while attempting local move\n", __func__); + shell_rdp_debug_verbose(shsurf->shell, "%s: mouse move is detected while attempting local move\n", __func__); shsurf->shell->is_localmove_pending = false; } surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); - constrain_position(move, &cx, &cy); - - weston_view_set_position(shsurf->view, cx, cy); + if (shsurf->snapped.is_snapped) { + grab_unsnap_motion(grab); + } else { + constrain_position(move, &cx, &cy); + weston_view_set_position(shsurf->view, cx, cy); + } weston_compositor_schedule_repaint(surface->compositor); } @@ -1935,12 +1934,16 @@ set_unminimized(struct weston_surface *surface) } static void -set_unsnap(struct shell_surface *shsurf, int grabX, int grabY) +grab_unsnap_motion(struct weston_pointer_grab *grab) { + struct weston_pointer *pointer = grab->pointer; + struct weston_move_grab *move = (struct weston_move_grab *) grab; + struct shell_surface *shsurf = move->base.shsurf; struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + int cx, cy, dx; if (!shsurf->snapped.is_snapped) return; @@ -1948,19 +1951,35 @@ set_unsnap(struct shell_surface *shsurf, int grabX, int grabY) if (!rail_state) return; - /* - * Reposition the window such that the mouse remain within the - * new bound of the window after resize. - */ - /* Need to fix RDP event processing while doing a local move first otherwise this undo the move! - if (grabX - shsurf->view->geometry.x > shsurf->snapped.saved_width) { - weston_view_set_position(shsurf->view, grabX - shsurf->snapped.saved_width/2, shsurf->view->geometry.y); - } - - weston_desktop_surface_set_size(shsurf->desktop_surface, shsurf->snapped.saved_width, shsurf->snapped.saved_height);*/ + /* window is no longer in snap state */ + shsurf->snapped.is_snapped = false; rail_state->showState_requested = RDP_WINDOW_SHOW; shsurf->saved_showstate_valid = false; - shsurf->snapped.is_snapped = false; + + /* restore original size */ + weston_desktop_surface_set_size(shsurf->desktop_surface, + shsurf->snapped.saved_width, + shsurf->snapped.saved_height); + + /* Reposition the window such that the mouse remain within the + * new bound of the window after resize. */ + dx = wl_fixed_to_int(move->dx); + if (abs(dx) < surface->width / 2) { + /* keep left edge pos, resize from right edge */ + cx = shsurf->view->geometry.x; + } else { + /* keep right edge pos, resize from left edge */ + cx = (shsurf->view->geometry.x + surface->width) - shsurf->snapped.saved_surface_width; + } + cy = shsurf->view->geometry.y + wl_fixed_to_int(move->dy); + weston_view_set_position(shsurf->view, cx, cy); + move->dx = wl_fixed_from_int(cx - wl_fixed_to_int(pointer->x)); + + shell_rdp_debug_verbose(shsurf->shell, "%s: restore surface:%p at (%d,%d) (%dx%d), new move_dx:%d\n", + __func__, surface, cx, cy, + shsurf->snapped.saved_width, + shsurf->snapped.saved_height, + wl_fixed_to_int(move->dx)); } static struct desktop_shell * @@ -2895,6 +2914,15 @@ shell_backend_request_window_restore(struct weston_surface *surface) if (rail_state->showState == RDP_WINDOW_SHOW_MINIMIZED) { set_unminimized(surface); + } else if (shsurf->snapped.is_snapped) { + /* weston_desktop_surface_set_size() expects the size in window geometry coordinates */ + /* saved_width and saved_height is already based on window geometry. */ + weston_desktop_surface_set_size( + shsurf->desktop_surface, + shsurf->snapped.saved_width, shsurf->snapped.saved_height); + weston_view_set_position(shsurf->view, + shsurf->snapped.saved_x, shsurf->snapped.saved_y); + shsurf->snapped.is_snapped = false; } else if (shsurf->state.fullscreen) { /* fullscreen is treated as normal (aka restored) state in Windows client, thus there should be not be 'restore' @@ -2949,6 +2977,7 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, { struct weston_view *view; struct shell_surface *shsurf = get_shell_surface(surface); + struct weston_geometry geometry; view = get_default_view(surface); if (!view || !shsurf) @@ -2983,9 +3012,15 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, return; } + geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + if (!shsurf->snapped.is_snapped) { - shsurf->snapped.saved_width = surface->width; - shsurf->snapped.saved_height = surface->height; + shsurf->snapped.saved_x = shsurf->view->geometry.x; + shsurf->snapped.saved_y = shsurf->view->geometry.y; + /* saved_width and height is based on window geometry */ + shsurf->snapped.saved_surface_width = surface->width; + shsurf->snapped.saved_width = geometry.width; + shsurf->snapped.saved_height = geometry.height; } shsurf->snapped.is_snapped = true; @@ -2995,7 +3030,6 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, struct weston_size max_size = weston_desktop_surface_get_max_size(desktop_surface); struct weston_size min_size = weston_desktop_surface_get_min_size(desktop_surface); - struct weston_geometry geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); /* weston_desktop_surface_set_size() expects the size in window geometry coordinates */ width -= (surface->width - geometry.width); height -= (surface->height - geometry.height); @@ -3012,7 +3046,7 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, else if (max_size.height > 0 && height > max_size.height) height = max_size.height; - shell_rdp_debug(shsurf->shell, "%s: surface:%p is resized (%dx%d) -> (%d,%d)\n", + shell_rdp_debug_verbose(shsurf->shell, "%s: surface:%p is resized (%dx%d) -> (%d,%d)\n", __func__, surface, surface->width, surface->height, width, height); weston_desktop_surface_set_size(desktop_surface, width, height); } @@ -3024,7 +3058,7 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, shsurf->snapped.width = width; // save width in window geometry coordinates. shsurf->snapped.height = height; // save height in window geometry coordinates. - shell_rdp_debug(shsurf->shell, "%s: surface:%p is snapped at (%d,%d) %dx%d\n", + shell_rdp_debug_verbose(shsurf->shell, "%s: surface:%p is snapped at (%d,%d) %dx%d\n", __func__, surface, x, y, width, height); } From 3126b248975fbfd2d255503ee9b43000f5985fd4 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 8 Feb 2023 08:58:46 -0800 Subject: [PATCH 1629/1642] publish flatpak applications to start menu (#135) Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index 5cfe5555e..3600da400 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -128,6 +128,9 @@ char *icon_folder[] = { "/usr/share/icons/hicolor/24x24/apps/", "/usr/share/icons/hicolor/22x22/apps/", "/usr/share/icons/hicolor/16x16/apps/", + "/var/lib/flatpak/exports/share/icons/hicolor/96x96/apps/", + "/var/lib/flatpak/exports/share/icons/hicolor/128x128/apps/", + "/var/lib/flatpak/exports/share/icons/hicolor/48x48/apps/", "/usr/share/icons/HighContrast/96x96/apps/", "/usr/share/icons/HighContrast/128x128/apps/", "/usr/share/icons/HighContrast/48x48/apps/", @@ -847,12 +850,13 @@ app_list_monitor_thread(LPVOID arg) but env variable at user distro is not accessible from system-distro, thus, any additional path can be added by WESTON_RDPRAIL_SHELL_APP_LIST_PATH using .wslgconfig */ - #define CUSTOM_APP_LIST_FOLDER_INDEX 3 + #define CUSTOM_APP_LIST_FOLDER_INDEX 4 #define MAX_APP_LIST_FOLDER 128 char *app_list_folder[MAX_APP_LIST_FOLDER] = { "/usr/share/applications", "/usr/local/share/applications", "/var/lib/snapd/desktop/applications", + "/var/lib/flatpak/exports/share/applications", NULL, /* always terminated witn NULL entry */ }; int fd[ARRAY_LENGTH(app_list_folder)] = {-1, -1, -1, -1}; From 7fd9ba2f35b6171ddaedc5990432d6de10695008 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 8 Feb 2023 09:30:58 -0800 Subject: [PATCH 1630/1642] rdp backend: East Asian keyboard fixes (#134) * default to us keyboard when no layout * add Chinese Traditional Bopomofo language profile * add workaround for Windows 10 setting extended bit for right shift key --------- Co-authored-by: Hideyuki Nagase Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 18 ++++++++++++++++-- libweston/backend-rdp/rdprail.c | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 70f54c963..d579f4739 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1007,6 +1007,11 @@ convert_rdp_keyboard_to_xkb_rule_names( xkbRuleNames->layout = "us"; xkbRuleNames->variant = 0; } + /* when no layout, default to "us" */ + if (!xkbRuleNames->layout) { + xkbRuleNames->layout = "us"; + xkbRuleNames->variant = 0; + } weston_log("%s: matching model=%s layout=%s variant=%s options=%s\n", __FUNCTION__, xkbRuleNames->model, xkbRuleNames->layout, xkbRuleNames->variant, xkbRuleNames->options); @@ -1535,7 +1540,7 @@ xf_input_synchronize_event(rdpInput *input, UINT32 flags) static BOOL xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) { - uint32_t scan_code, vk_code, full_code; + uint32_t scan_code, vk_code, full_code, keyboard_locale; enum wl_keyboard_key_state keyState; freerdp_peer *client = input->context->peer; RdpPeerContext *peerContext = (RdpPeerContext *)input->context; @@ -1560,8 +1565,17 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) if (keyboard && notify) { full_code = code; - if (flags & KBD_FLAGS_EXTENDED) + /* On Windows 10 client, certain locale's keyboard layout reports extended + bit for right shift key (scancode 0x36) due to bug, so drop the bit here. */ + keyboard_locale = client->context->settings->KeyboardLayout & 0xFFFF; + if (code == 0x36 && /* Right shift key */ + (keyboard_locale == KBD_CHINESE_TRADITIONAL_US || + keyboard_locale == KBD_CHINESE_SIMPLIFIED_US || + keyboard_locale == KBD_JAPANESE)) { + flags &= ~KBD_FLAGS_EXTENDED; + } else if (flags & KBD_FLAGS_EXTENDED) { full_code |= KBD_FLAGS_EXTENDED; + } /* Korean keyboard support */ /* WinPR's GetVirtualKeyCodeFromVirtualScanCode() can't handle hangul/hanja keys */ diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 72ccd4dd4..6fe1fe3f1 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -859,6 +859,12 @@ rail_client_LangbarInfo(RailServerContext *context, return CHANNEL_RC_OK; } +/* GUID_CHTIME_BOPOMOFO is not defined in FreeRDP */ +#define GUID_CHTIME_BOPOMOFO \ +{ \ + 0xB115690A, 0xEA02, 0x48D5, 0xA2, 0x31, 0xE3, 0x57, 0x8D, 0x2F, 0xDF, 0x80 \ +} + static char * languageGuid_to_string(const GUID *guid) { @@ -868,6 +874,7 @@ languageGuid_to_string(const GUID *guid) static const struct lang_GUID c_GUID_KORIME = GUID_MSIME_KOR; static const struct lang_GUID c_GUID_CHSIME = GUID_CHSIME; static const struct lang_GUID c_GUID_CHTIME = GUID_CHTIME; + static const struct lang_GUID c_GUID_CHTIME_BOPOMOFO = GUID_CHTIME_BOPOMOFO; static const struct lang_GUID c_GUID_PROFILE_NEWPHONETIC = GUID_PROFILE_NEWPHONETIC; static const struct lang_GUID c_GUID_PROFILE_CHANGJIE = GUID_PROFILE_CHANGJIE; static const struct lang_GUID c_GUID_PROFILE_QUICK = GUID_PROFILE_QUICK; @@ -888,6 +895,8 @@ languageGuid_to_string(const GUID *guid) return "GUID_CHSIME"; else if (UuidEqual(guid, (GUID *)&c_GUID_CHTIME, &rpc_status)) return "GUID_CHTIME"; + else if (UuidEqual(guid, (GUID *)&c_GUID_CHTIME_BOPOMOFO, &rpc_status)) + return "GUID_CHTIME_BOPOMOFO"; else if (UuidEqual(guid, (GUID *)&c_GUID_PROFILE_NEWPHONETIC, &rpc_status)) return "GUID_PROFILE_NEWPHONETIC"; else if (UuidEqual(guid, (GUID *)&c_GUID_PROFILE_CHANGJIE, &rpc_status)) @@ -920,7 +929,7 @@ rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) struct rdp_backend *b = peer_ctx->rdpBackend; uint32_t new_keyboard_layout = 0; struct xkb_keymap *keymap = NULL; - struct xkb_rule_names xkbRuleNames; + struct xkb_rule_names xkbRuleNames = {}; char *s; assert_compositor_thread(b); @@ -957,6 +966,7 @@ rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) static const struct lang_GUID c_GUID_KORIME = GUID_MSIME_KOR; static const struct lang_GUID c_GUID_CHSIME = GUID_CHSIME; static const struct lang_GUID c_GUID_CHTIME = GUID_CHTIME; + static const struct lang_GUID c_GUID_CHTIME_BOPOMOFO = GUID_CHTIME_BOPOMOFO; RPC_STATUS rpc_status; if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, @@ -971,6 +981,9 @@ rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, (GUID *)&c_GUID_CHTIME, &rpc_status)) new_keyboard_layout = KBD_CHINESE_TRADITIONAL_US; + else if (UuidEqual(&languageImeInfo->LanguageProfileCLSID, + (GUID *)&c_GUID_CHTIME_BOPOMOFO, &rpc_status)) + new_keyboard_layout = KBD_CHINESE_TRADITIONAL_US; else new_keyboard_layout = KBD_US; } @@ -988,6 +1001,8 @@ rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) weston_seat_update_keymap(peer_ctx->item.seat, keymap); xkb_keymap_unref(keymap); settings->KeyboardLayout = new_keyboard_layout; + rdp_debug(b, "%s: new keyboard layout: 0x%x\n", + __func__, new_keyboard_layout); } } if (!keymap) { From 30f7ff8f84d9c824e5c4d80f1b7e0417b32f9eef Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 7 Mar 2023 18:40:26 -0800 Subject: [PATCH 1631/1642] rdp backend: handle the case nothing to send for window zorder to client (#137) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdprail.c | 36 ++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 6fe1fe3f1..555f2eff0 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -3054,8 +3054,11 @@ rdp_rail_sync_window_zorder(struct weston_compositor *compositor) if (!b->enable_window_zorder_sync) return; + numWindowId = peer_ctx->windowId.id_used; + if (numWindowId == 0) + return; /* +1 for marker window (aka proxy_surface) */ - numWindowId = peer_ctx->windowId.id_used + 1; + numWindowId++; windowIdArray = xzalloc(numWindowId * sizeof(uint32_t)); rdp_debug_verbose(b, "Dump Window Z order\n"); @@ -3083,21 +3086,22 @@ rdp_rail_sync_window_zorder(struct weston_compositor *compositor) } } assert(iCurrent <= numWindowId); - assert(iCurrent > 0); - rdp_debug_verbose(b, " send Window Z order: numWindowIds:%d\n", - iCurrent); - - window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | - WINDOW_ORDER_FIELD_DESKTOP_ZORDER | - WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND; - monitored_desktop_order.activeWindowId = windowIdArray[0]; - monitored_desktop_order.numWindowIds = iCurrent; - monitored_desktop_order.windowIds = windowIdArray; - - client->context->update->window->MonitoredDesktop(client->context, - &window_order_info, - &monitored_desktop_order); - client->DrainOutputBuffer(client); + if (iCurrent > 0) { + rdp_debug_verbose(b, " send Window Z order: numWindowIds:%d\n", + iCurrent); + + window_order_info.fieldFlags = WINDOW_ORDER_TYPE_DESKTOP | + WINDOW_ORDER_FIELD_DESKTOP_ZORDER | + WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND; + monitored_desktop_order.activeWindowId = windowIdArray[0]; + monitored_desktop_order.numWindowIds = iCurrent; + monitored_desktop_order.windowIds = windowIdArray; + + client->context->update->window->MonitoredDesktop(client->context, + &window_order_info, + &monitored_desktop_order); + client->DrainOutputBuffer(client); + } Exit: free(windowIdArray); From b4f672d1b090f7ccab6ad8fa84bbcc8823cb2fc7 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 8 Mar 2023 13:32:35 -0800 Subject: [PATCH 1632/1642] rdp backend: use rdp_dispatch_task_to_display_loop for dispatching failure response to display loop (#138) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdpclip.c | 40 +++++++++++++++++---------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c index f14cd1681..2f0b69034 100644 --- a/libweston/backend-rdp/rdpclip.c +++ b/libweston/backend-rdp/rdpclip.c @@ -898,25 +898,25 @@ clipboard_data_source_read(int fd, uint32_t mask, void *arg) } /* client's reply with error for data request, clean up */ -static int -clipboard_data_source_fail(int fd, uint32_t mask, void *arg) +static void +clipboard_data_source_fail(bool freeOnly, void *arg) { - struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; + struct rdp_clipboard_data_source *source = wl_container_of(arg, source, task_base); freerdp_peer *client = (freerdp_peer *)source->context; RdpPeerContext *ctx = (RdpPeerContext *)client->context; struct rdp_backend *b = ctx->rdpBackend; - rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", __func__, - source, clipboard_data_source_state_to_string(source), fd); + rdp_debug_clipboard(b, "RDP %s (%p:%s) fd:%d\n", __func__, + source, + clipboard_data_source_state_to_string(source), + source->data_source_fd); assert_compositor_thread(b); - assert(source->data_source_fd == fd); /* this data source must be tracked as inflight */ assert(source == ctx->clipboard_inflight_client_data_source); - - wl_event_source_remove(source->transfer_event_source); - source->transfer_event_source = NULL; + /* transfer event source must not be set */ + assert(source->transfer_event_source == NULL); /* if data was received, but failed for another reason then keep data * and format index for future request, otherwise data is purged at @@ -942,8 +942,6 @@ clipboard_data_source_fail(int fd, uint32_t mask, void *arg) /* clear inflight data source from client to server. */ ctx->clipboard_inflight_client_data_source = NULL; clipboard_data_source_unref(source); - - return 0; } /* Send client's clipboard data to the requesting application at server side */ @@ -1668,14 +1666,18 @@ clipboard_client_format_data_response(CliprdrServerContext *context, const CLIPR source->data_response_fail_count); assert(source->transfer_event_source == NULL); - ret = rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, - success ? clipboard_data_source_write : clipboard_data_source_fail, - source, &source->transfer_event_source); - if (!ret) { - source->state = RDP_CLIPBOARD_SOURCE_FAILED; - weston_log("Client: %s (%p:%s) rdp_event_loop_add_fd failed\n", - __func__, source, clipboard_data_source_state_to_string(source)); - return -1; + if (success) { + ret = rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + clipboard_data_source_write, + source, &source->transfer_event_source); + if (!ret) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("Client: %s (%p:%s) rdp_event_loop_add_fd failed\n", + __func__, source, clipboard_data_source_state_to_string(source)); + return -1; + } + } else { + rdp_dispatch_task_to_display_loop(ctx, clipboard_data_source_fail, &source->task_base); } return 0; From 28553ce729a1a4aeef3b1b9c08be45d66f132702 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 8 Mar 2023 15:32:13 -0800 Subject: [PATCH 1633/1642] rdp shell: fix overactive assert at launch_desktop_shell_process (#139) Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 694f5a461..0f843c5cc 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -4138,7 +4138,10 @@ launch_desktop_shell_process(void *data) { struct desktop_shell *shell = data; - assert(!shell->child.client); + /* check if it's already running */ + if (shell->child.client) + return; + shell->child.client = weston_client_start(shell->compositor, shell->client); From f09791f3314de7b414bf21a46940ff684d936eaf Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Thu, 9 Mar 2023 10:00:43 -0800 Subject: [PATCH 1634/1642] rdp shell/rdp backend: support associate window id (#140) Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 2 + libweston/backend-rdp/meson.build | 2 +- libweston/backend-rdp/rdprail.c | 45 ++++++++++++- rdprail-shell/app-list.c | 102 +++++++++++++++++++++++++++++- rdprail-shell/shell.c | 78 ++++++++++++++--------- rdprail-shell/shell.h | 1 + 6 files changed, 194 insertions(+), 36 deletions(-) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index 65a10d311..0ef44a2a3 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -129,6 +129,7 @@ struct weston_rdprail_app_list_data { bool newAppId; bool deleteAppId; bool deleteAppProvider; + bool associateWindowId; char *appId; char *appGroup; char *appExecPath; @@ -136,6 +137,7 @@ struct weston_rdprail_app_list_data { char *appDesc; char *appProvider; pixman_image_t *appIcon; + uint32_t appWindowId; }; struct weston_rdp_rail_window_pos { diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index 1443a671b..7d5d227db 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -28,7 +28,7 @@ if not dep_wpr.found() endif endif -dep_rdpapplist = dependency('rdpapplist', version: '>= 1.0.0', required: false) +dep_rdpapplist = dependency('rdpapplist', version: '>= 2.0.0', required: false) if dep_rdpapplist.found() config_h.set('HAVE_FREERDP_RDPAPPLIST_H', '1') endif diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 555f2eff0..6edf5eab9 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -770,8 +770,8 @@ rail_client_ClientGetAppidReq_callback(bool freeOnly, void *arg) goto Exit; } - rdp_debug(b, "Client: ClientGetAppidReq: pid:%d appId:%s\n", - (uint32_t)pid, appId); + rdp_debug(b, "Client: ClientGetAppidReq: pid:%d appId:%s WindowId:0x%x\n", + (uint32_t)pid, appId, getAppidReq->windowId); rdp_debug_verbose(b, "Client: ClientGetAppidReq: pid:%d imageName:%s\n", (uint32_t)pid, imageName); @@ -3460,9 +3460,20 @@ rdp_rail_peer_activate(freerdp_peer* client) rdp_debug(b, "Server AppList caps version:%d\n", RDPAPPLIST_CHANNEL_VERSION); app_list_caps.version = RDPAPPLIST_CHANNEL_VERSION; + rdp_debug(b, " appListProviderName:%s\n", b->rdprail_shell_name); if (!utf8_string_to_rail_string(b->rdprail_shell_name, &app_list_caps.appListProviderName)) goto error_exit; +#if RDPAPPLIST_CHANNEL_VERSION >= 4 + /* assign unique id */ + char *s = getenv("WSLG_SERVICE_ID"); + if (!s) + s = b->rdprail_shell_name; + rdp_debug(b, " appListProviderUniqueId:%s\n", s); + if (!utf8_string_to_rail_string(s, + &app_list_caps.appListProviderUniqueId)) + goto error_exit; +#endif /* RDPAPPLIST_CHANNEL_VERSION >= 4 */ if (applist_ctx->ApplicationListCaps(applist_ctx, &app_list_caps) != CHANNEL_RC_OK) goto error_exit; free(app_list_caps.appListProviderName.string); @@ -4624,6 +4635,7 @@ rdp_rail_notify_app_list(void *rdp_backend, rdp_debug(b, " newAppId: %d\n", app_list_data->newAppId); rdp_debug(b, " deleteAppId: %d\n", app_list_data->deleteAppId); rdp_debug(b, " deleteAppProvider: %d\n", app_list_data->deleteAppProvider); + rdp_debug(b, " associateWindowId: %d\n", app_list_data->associateWindowId); rdp_debug(b, " appId: %s\n", app_list_data->appId); rdp_debug(b, " appGroup: %s\n", app_list_data->appGroup); rdp_debug(b, " appExecPath: %s\n", app_list_data->appExecPath); @@ -4631,8 +4643,35 @@ rdp_rail_notify_app_list(void *rdp_backend, rdp_debug(b, " appDesc: %s\n", app_list_data->appDesc); rdp_debug(b, " appIcon: %p\n", app_list_data->appIcon); rdp_debug(b, " appProvider: %s\n", app_list_data->appProvider); + rdp_debug(b, " appWindowId: 0x%x\n", app_list_data->appWindowId); + + if (app_list_data->associateWindowId) { + RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU associate_window_id = {}; + + assert(app_list_data->appProvider == NULL); + associate_window_id.flags = RDPAPPLIST_FIELD_ID | RDPAPPLIST_FIELD_WINDOW_ID; + associate_window_id.appWindowId = app_list_data->appWindowId; + if (app_list_data->appId == NULL || + !utf8_string_to_rail_string(app_list_data->appId, &associate_window_id.appId)) + goto Exit_associateWindowId; - if (app_list_data->deleteAppId) { + if (app_list_data->appGroup && + utf8_string_to_rail_string(app_list_data->appGroup, &associate_window_id.appGroup)) { + associate_window_id.flags |= RDPAPPLIST_FIELD_GROUP; + } + if (app_list_data->appExecPath && + utf8_string_to_rail_string(app_list_data->appExecPath, &associate_window_id.appExecPath)) { + associate_window_id.flags |= RDPAPPLIST_FIELD_EXECPATH; + } + if (app_list_data->appDesc && + utf8_string_to_rail_string(app_list_data->appDesc, &associate_window_id.appDesc)) { + associate_window_id.flags |= RDPAPPLIST_FIELD_DESC; + } + applist_ctx->AssociateWindowId(applist_ctx, &associate_window_id); + Exit_associateWindowId: + free(associate_window_id.appId.string); + free(associate_window_id.appGroup.string); + } else if (app_list_data->deleteAppId) { RDPAPPLIST_DELETE_APPLIST_PDU delete_app_list = {}; assert(app_list_data->appProvider == NULL); diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index 3600da400..22c008a2f 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -66,7 +66,7 @@ #if HAVE_GLIB && HAVE_WINPR -#define NUM_CONTROL_EVENT 5 +#define NUM_CONTROL_EVENT 6 #define EVENT_TIMEOUT_MS 2000 // 2 seconds #define MAX_ICON_RETRY_COUNT 5 @@ -79,6 +79,7 @@ struct app_list_context { HANDLE stopRdpNotifyEvent; // control event: wait index 2 HANDLE loadIconEvent; // control event: wait index 3 HANDLE findImageNameEvent; // control event: wait index 4 + HANDLE associateWindowAppIdEvent;// control event: wait index 5 HANDLE replyEvent; bool isRdpNotifyStarted; bool isAppListNamespaceAttached; @@ -97,6 +98,11 @@ struct app_list_context { char *image_name; size_t image_name_size; } find_image_name; + struct { + pid_t pid; + char *app_id; + uint32_t window_id; + } associate_window_app_id; struct { char requestedClientLanguageId[32]; // 32 = RDPAPPLIST_LANG_SIZE. char currentClientLanguageId[32]; @@ -389,6 +395,41 @@ send_app_entry(struct desktop_shell *shell, char *key, struct app_entry *entry, pixman_image_unref(app_list_data.appIcon); } +static void +send_associate_window_app_id(struct desktop_shell *shell, pid_t pid, char *app_id, uint32_t window_id) +{ + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + struct weston_rdprail_app_list_data app_list_data = {}; + struct app_entry *entry; + char *app_exec = NULL; + char *app_desc = NULL; + + if (!shell->rdprail_api->notify_app_list) + return; + + entry = (struct app_entry *)HashTable_GetItemValue(context->table, app_id); + if (entry) { + app_exec = entry->try_exec ? entry->try_exec : entry->exec; + app_desc = entry->name; + } + + if (!app_exec) { + /*TODO: obtain from /proc/[pid]/cmdline */ + } + + if (!app_desc) + app_desc = app_id; + + app_list_data.associateWindowId = true; + app_list_data.appId = app_id; + app_list_data.appGroup = NULL; + app_list_data.appExecPath = app_exec; + app_list_data.appDesc = app_desc; + app_list_data.appWindowId = window_id; + + shell->rdprail_api->notify_app_list(shell->rdp_backend, &app_list_data); +} + static void retry_find_icon_file(struct desktop_shell *shell) { @@ -909,6 +950,7 @@ app_list_monitor_thread(LPVOID arg) events[num_events++] = context->stopRdpNotifyEvent; events[num_events++] = context->loadIconEvent; events[num_events++] = context->findImageNameEvent; + events[num_events++] = context->associateWindowAppIdEvent; assert(num_events == NUM_CONTROL_EVENT); /* append optional folders */ @@ -1084,6 +1126,22 @@ app_list_monitor_thread(LPVOID arg) continue; } + /* Associate Window/AppId event */ + if (status == WAIT_OBJECT_0 + 5) { + shell_rdp_debug_verbose(shell, "app_list_monitor_thread: associateWindowAppIdEvent is signalled. pid:%d, app_id:%s, window_id:0x%x\n", + context->associate_window_app_id.pid, + context->associate_window_app_id.app_id, + context->associate_window_app_id.window_id); + + send_associate_window_app_id(shell, + context->associate_window_app_id.pid, + context->associate_window_app_id.app_id, + context->associate_window_app_id.window_id); + + SetEvent(context->replyEvent); + continue; + } + /* Somethings are changed in watch folders */ if (shell->rdprail_api->notify_app_list && num_watch) { len = read(fd[status - WAIT_OBJECT_0 - NUM_CONTROL_EVENT], buf, sizeof buf); @@ -1172,6 +1230,11 @@ start_app_list_monitor(struct desktop_shell *shell) if (!context->findImageNameEvent) goto Error_Exit; + /* bManualReset = TRUE, ideally here needs FALSE, but winpr doesn't support it */ + context->associateWindowAppIdEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!context->associateWindowAppIdEvent) + goto Error_Exit; + /* bManualReset = TRUE, ideally here needs FALSE, but winpr doesn't support it */ context->replyEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!context->replyEvent) @@ -1190,6 +1253,11 @@ start_app_list_monitor(struct desktop_shell *shell) context->replyEvent = NULL; } + if (context->associateWindowAppIdEvent) { + CloseHandle(context->associateWindowAppIdEvent); + context->associateWindowAppIdEvent = NULL; + } + if (context->findImageNameEvent) { CloseHandle(context->findImageNameEvent); context->findImageNameEvent = NULL; @@ -1246,6 +1314,11 @@ stop_app_list_monitor(struct desktop_shell *shell) context->replyEvent = NULL; } + if (context->associateWindowAppIdEvent) { + CloseHandle(context->associateWindowAppIdEvent); + context->associateWindowAppIdEvent = NULL; + } + if (context->findImageNameEvent) { CloseHandle(context->findImageNameEvent); context->findImageNameEvent = NULL; @@ -1330,6 +1403,33 @@ void app_list_find_image_name(struct desktop_shell *shell, pid_t pid, char *imag return; } +void app_list_associate_window_app_id(struct desktop_shell *shell, pid_t pid, char *app_id, uint32_t window_id) +{ +#if HAVE_WINPR && HAVE_GLIB + struct app_list_context *context = (struct app_list_context *)shell->app_list_context; + + if (context) { + assert(context->associate_window_app_id.pid == (pid_t) 0); + assert(context->associate_window_app_id.app_id == NULL); + assert(context->associate_window_app_id.window_id == 0); + context->associate_window_app_id.pid = pid; + context->associate_window_app_id.app_id = app_id; + context->associate_window_app_id.window_id = window_id; + + /* signal worker thread to load icon at worker thread */ + SetEvent(context->associateWindowAppIdEvent); + WaitForSingleObject(context->replyEvent, INFINITE); + /* here must reset since winpr doesn't support auto reset event */ + ResetEvent(context->replyEvent); + + context->associate_window_app_id.pid = (pid_t) 0; + context->associate_window_app_id.app_id = NULL; + context->associate_window_app_id.window_id = 0; + } +#endif + return; +} + bool app_list_start_backend_update(struct desktop_shell *shell, char *clientLanguageId) { #if HAVE_WINPR && HAVE_GLIB diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 0f843c5cc..c9869d296 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -165,6 +165,10 @@ struct shell_surface { bool is_icon_set; } icon; + struct { + bool is_window_app_id_associated; + } app_id; + struct wl_listener metadata_listener; }; @@ -2612,12 +2616,6 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, wl_list_for_each(view, &surface->views, surface_link) weston_view_update_transform(view); } - - if (!shsurf->icon.is_icon_set) { - /* TODO hook to meta data change notification */ - shell_surface_set_window_icon(desktop_surface, 0, 0, 0, NULL, NULL); - shsurf->icon.is_icon_set = true; - } } static void @@ -4635,6 +4633,7 @@ shell_backend_get_app_id(void *shell_context, struct weston_surface *surface, ch { struct desktop_shell *shell = (struct desktop_shell *)shell_context; struct weston_desktop_surface *desktop_surface; + struct weston_surface_rail_state *rail_state; struct shell_surface *shsurf; const struct weston_xwayland_surface_api *api; pid_t pid; @@ -4655,31 +4654,35 @@ shell_backend_get_app_id(void *shell_context, struct weston_surface *surface, ch if (!desktop_surface) return -1; - /* obtain application id specified via wayland interface */ - id = weston_desktop_surface_get_app_id(desktop_surface); - if (id) { - strncpy(app_id, id, app_id_size); - } else { - /* if app_id is not specified via wayland interface, - obtain class name from X server for X app, and use as app_id */ - shsurf = weston_desktop_surface_get_user_data(desktop_surface); - if (shsurf) { - api = shsurf->shell->xwayland_surface_api; - if (!api) { - api = weston_xwayland_surface_get_api(shsurf->shell->compositor); - shsurf->shell->xwayland_surface_api = api; - } - if (api && api->is_xwayland_surface(surface)) { - class_name = api->get_class_name(surface); - if (class_name) { - strncpy(app_id, class_name, app_id_size); - free(class_name); - /* app_id is from Xwayland */ - is_wayland = false; - } - } + shsurf = weston_desktop_surface_get_user_data(desktop_surface); + if (!shsurf) + return -1; + + rail_state = (struct weston_surface_rail_state *)surface->backend_state; + if (!rail_state) + return -1; + + /* first obtain class name from X server for X app, and use as app_id */ + api = shsurf->shell->xwayland_surface_api; + if (!api) { + api = weston_xwayland_surface_get_api(shsurf->shell->compositor); + shsurf->shell->xwayland_surface_api = api; + } + if (api && api->is_xwayland_surface(surface)) { + class_name = api->get_class_name(surface); + if (class_name) { + strncpy(app_id, class_name, app_id_size); + free(class_name); + /* app_id is from Xwayland */ + is_wayland = false; } } + /* if not, obtain application id specified via wayland interface */ + if (app_id[0] == '\0') { + id = weston_desktop_surface_get_app_id(desktop_surface); + if (id) + strncpy(app_id, id, app_id_size); + } /* obtain pid for execuable path */ pid = weston_desktop_surface_get_pid(desktop_surface); @@ -4700,8 +4703,21 @@ shell_backend_get_app_id(void *shell_context, struct weston_surface *surface, ch strncpy(image_name, app_id, image_name_size); } - shell_rdp_debug_verbose(shell, "shell_backend_get_app_id: 0x%p: pid:%d, app_id:%s, image_name:%s\n", - surface, pid, app_id, image_name); + shell_rdp_debug_verbose(shell, "shell_backend_get_app_id: 0x%p: pid:%d, app_id:%s, windowId:0x%x, image_name:%s\n", + surface, pid, app_id, rail_state->window_id, image_name); + + /* obtain window icon for app */ + if (!shsurf->icon.is_icon_set) { + /* TODO hook to meta data change notification */ + shell_surface_set_window_icon(desktop_surface, 0, 0, 0, NULL, NULL); + shsurf->icon.is_icon_set = true; + } + + /* associate window and app_id at client side */ + if (!shsurf->app_id.is_window_app_id_associated && app_id[0] != '\0') { + app_list_associate_window_app_id(shsurf->shell, pid, app_id, rail_state->window_id); + shsurf->app_id.is_window_app_id_associated = true; + } return pid; } diff --git a/rdprail-shell/shell.h b/rdprail-shell/shell.h index f70b4e272..aa98f126e 100644 --- a/rdprail-shell/shell.h +++ b/rdprail-shell/shell.h @@ -204,5 +204,6 @@ pixman_image_t *app_list_load_icon_file(struct desktop_shell *shell, const char bool app_list_start_backend_update(struct desktop_shell *shell, char *clientLanguageId); void app_list_stop_backend_update(struct desktop_shell *shell); void app_list_find_image_name(struct desktop_shell *shell, pid_t pid, char *image_name, size_t image_name_size, bool is_wayland); +void app_list_associate_window_app_id(struct desktop_shell *shell, pid_t pid, char *app_id, uint32_t window_id); // img-load.c pixman_image_t *load_icon_image(struct desktop_shell *shell, const char *filename); From b5c27cb69e4a13dfcca4d68cf7e6f4d5c31a6e2e Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 5 Apr 2023 14:53:44 -0700 Subject: [PATCH 1635/1642] rdp shell/rdp backend: drop window shadow when window is snapped (#141) * draft: support window snap with no gap * no gap snap with window shadow enabled * adjust window unsnap position by pointer grab * unsnap only if when pointer is actually moved * adjust window unsnap position with window geometry * restore window (not back to snap) after snap->maximized->unmaximized * adjust debug message --------- Co-authored-by: Hideyuki Nagase Co-authored-by: Hideyuki Nagase --- include/libweston/backend-rdp.h | 1 + libweston/backend-rdp/rdprail.c | 108 +++++++++++++++----------------- rdprail-shell/shell.c | 83 ++++++++++++++++-------- 3 files changed, 111 insertions(+), 81 deletions(-) diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index 0ef44a2a3..767672a5c 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -236,6 +236,7 @@ struct weston_surface_rail_state { uint32_t parent_window_id; bool isCursor; bool isWindowCreated; + bool isWindowSnapped; uint32_t showState_requested; uint32_t showState; bool forceRecreateSurface; diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 6edf5eab9..58ad28d8f 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -353,31 +353,19 @@ rail_client_SnapArrange_callback(bool freeOnly, void *arg) snap_rect.y = snap->top; snap_rect.width = snap->right - snap->left; snap_rect.height = snap->bottom - snap->top; - /* SnapArrange PDU include window resize margin */ - /* [MS-RDPERP] - v20200304 - 3.2.5.1.6 Processing Window Information Orders - However, the Client Window Move PDU (section 2.2.2.7.4) and Client Window Snap PDU - (section 2.2.2.7.5) do include resize margins in the window boundaries. */ - snap_rect.x += rail_state->window_margin_left; - snap_rect.y += rail_state->window_margin_top; - snap_rect.width -= rail_state->window_margin_left + - rail_state->window_margin_right; - snap_rect.height -= rail_state->window_margin_top + - rail_state->window_margin_bottom; to_weston_coordinate(peer_ctx, &snap_rect.x, &snap_rect.y, &snap_rect.width, &snap_rect.height); - if (is_window_shadow_remoting_disabled(peer_ctx)) { - /* offset window shadow area */ - /* window_geometry here is last commited geometry */ - api->get_window_geometry(surface, - &geometry); - snap_rect.x -= geometry.x; - snap_rect.y -= geometry.y; - snap_rect.width += (surface->width - geometry.width); - snap_rect.height += (surface->height - geometry.height); - } + /* offset window shadow area as there is no shadow when snapped */ + /* window_geometry here is last commited geometry */ + api->get_window_geometry(surface, + &geometry); + snap_rect.x -= geometry.x; + snap_rect.y -= geometry.y; + snap_rect.width += (surface->width - geometry.width); + snap_rect.height += (surface->height - geometry.height); api->request_window_snap(surface, snap_rect.x, snap_rect.y, @@ -433,22 +421,25 @@ rail_client_WindowMove_callback(bool freeOnly, void *arg) windowMoveRect.y = windowMove->top; windowMoveRect.width = windowMove->right - windowMove->left; windowMoveRect.height = windowMove->bottom - windowMove->top; - /* WindowMove PDU include window resize margin */ - /* [MS-RDPERP] - v20200304 - 3.2.5.1.6 Processing Window Information Orders - However, the Client Window Move PDU (section 2.2.2.7.4) and Client Window Snap PDU - (section 2.2.2.7.5) do include resize margins in the window boundaries. */ - windowMoveRect.x += rail_state->window_margin_left; - windowMoveRect.y += rail_state->window_margin_top; - windowMoveRect.width -= rail_state->window_margin_left + - rail_state->window_margin_right; - windowMoveRect.height -= rail_state->window_margin_top + - rail_state->window_margin_bottom; + if (!rail_state->isWindowSnapped) { + /* WindowMove PDU include window resize margin */ + /* [MS-RDPERP] - v20200304 - 3.2.5.1.6 Processing Window Information Orders + However, the Client Window Move PDU (section 2.2.2.7.4) and Client Window Snap PDU + (section 2.2.2.7.5) do include resize margins in the window boundaries. */ + windowMoveRect.x += rail_state->window_margin_left; + windowMoveRect.y += rail_state->window_margin_top; + windowMoveRect.width -= rail_state->window_margin_left + + rail_state->window_margin_right; + windowMoveRect.height -= rail_state->window_margin_top + + rail_state->window_margin_bottom; + } to_weston_coordinate(peer_ctx, &windowMoveRect.x, &windowMoveRect.y, &windowMoveRect.width, &windowMoveRect.height); - if (is_window_shadow_remoting_disabled(peer_ctx)) { + if (is_window_shadow_remoting_disabled(peer_ctx) || + rail_state->isWindowSnapped) { /* offset window shadow area */ /* window_geometry here is last commited geometry */ api->get_window_geometry(surface, @@ -2094,34 +2085,37 @@ rdp_rail_update_window(struct weston_surface *surface, __func__, rail_state->window_id); } - if (is_window_shadow_remoting_disabled(peer_ctx)) { + if (is_window_shadow_remoting_disabled(peer_ctx) || + rail_state->isWindowSnapped) { /* drop window shadow area */ api->get_window_geometry(surface, &geometry); - /* calculate window margin from input extents */ - if (geometry.x > max(0, surface->input.extents.x1)) - window_margin_left = geometry.x - + if (!rail_state->isWindowSnapped) { + /* calculate window margin from input extents */ + if (geometry.x > max(0, surface->input.extents.x1)) + window_margin_left = geometry.x - max(0, surface->input.extents.x1); - window_margin_left = max(window_margin_left, - RDP_RAIL_WINDOW_RESIZE_MARGIN); - - if (geometry.y > max(0, surface->input.extents.y1)) - window_margin_top = geometry.y - - max(0, surface->input.extents.y1); - window_margin_top = max(window_margin_top, - RDP_RAIL_WINDOW_RESIZE_MARGIN); - - if (min(surface->input.extents.x2, surface->width) > (geometry.x + geometry.width)) - window_margin_right = min(surface->input.extents.x2, surface->width) - - (geometry.x + geometry.width); - window_margin_right = max(window_margin_right, - RDP_RAIL_WINDOW_RESIZE_MARGIN); - - if (min(surface->input.extents.y2, surface->height) > (geometry.y + geometry.height)) - window_margin_bottom = min(surface->input.extents.y2, surface->height) - + window_margin_left = max(window_margin_left, + RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (geometry.y > max(0, surface->input.extents.y1)) + window_margin_top = geometry.y - + max(0, surface->input.extents.y1); + window_margin_top = max(window_margin_top, + RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (min(surface->input.extents.x2, surface->width) > (geometry.x + geometry.width)) + window_margin_right = min(surface->input.extents.x2, surface->width) - + (geometry.x + geometry.width); + window_margin_right = max(window_margin_right, + RDP_RAIL_WINDOW_RESIZE_MARGIN); + + if (min(surface->input.extents.y2, surface->height) > (geometry.y + geometry.height)) + window_margin_bottom = min(surface->input.extents.y2, surface->height) - (geometry.y + geometry.height); - window_margin_bottom = max(window_margin_bottom, - RDP_RAIL_WINDOW_RESIZE_MARGIN); + window_margin_bottom = max(window_margin_bottom, + RDP_RAIL_WINDOW_RESIZE_MARGIN); + } /* offset window origin by window geometry */ newClientPos.x += geometry.x; @@ -2138,7 +2132,8 @@ rdp_rail_update_window(struct weston_surface *surface, &newClientPos.width, &newClientPos.height); - if (is_window_shadow_remoting_disabled(peer_ctx)) { + if (is_window_shadow_remoting_disabled(peer_ctx) || + rail_state->isWindowSnapped) { to_client_coordinate(peer_ctx, surface->output, &window_margin_left, &window_margin_top, @@ -2626,7 +2621,8 @@ rdp_rail_update_window(struct weston_surface *surface, } /* damage_box represents damaged area in contentBuffer */ /* if it's not remoting window shadow, exclude the area from damage_box */ - if (is_window_shadow_remoting_disabled(peer_ctx)) { + if (is_window_shadow_remoting_disabled(peer_ctx) || + rail_state->isWindowSnapped) { if (damage_box.x1 < content_buffer_window_geometry.x) damage_box.x1 = content_buffer_window_geometry.x; if (damage_box.x2 > content_buffer_window_geometry.x + content_buffer_window_geometry.width) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index c9869d296..6672e8809 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -1250,14 +1250,18 @@ move_grab_motion(struct weston_pointer_grab *grab, /* if local move is expected, but recieved the mouse move, then cacenl local move. */ - if (shsurf->shell->is_localmove_pending) { + if (shsurf->shell->is_localmove_pending && + (pointer->grab_x != pointer->x || + pointer->grab_y != pointer->y)) { shell_rdp_debug_verbose(shsurf->shell, "%s: mouse move is detected while attempting local move\n", __func__); shsurf->shell->is_localmove_pending = false; } surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); - if (shsurf->snapped.is_snapped) { + if (shsurf->snapped.is_snapped && + (pointer->grab_x != pointer->x || + pointer->grab_y != pointer->y)) { grab_unsnap_motion(grab); } else { constrain_position(move, &cx, &cy); @@ -1844,10 +1848,16 @@ unset_maximized(struct shell_surface *shsurf) shsurf->saved_showstate_valid = false; if (shsurf->snapped.is_snapped) { - /* Restore to snap state. - */ - weston_desktop_surface_set_size(shsurf->desktop_surface, shsurf->snapped.width, shsurf->snapped.height); - weston_view_set_position(shsurf->view, shsurf->snapped.x, shsurf->snapped.y); + /* If previously snapped state, don't go back to snap, but restore */ + /* weston_desktop_surface_set_size() expects the size in window geometry coordinates */ + /* saved_width and saved_height is already based on window geometry. */ + weston_desktop_surface_set_size( + shsurf->desktop_surface, + shsurf->snapped.saved_width, shsurf->snapped.saved_height); + weston_view_set_position(shsurf->view, + shsurf->snapped.saved_x, shsurf->snapped.saved_y); + shsurf->snapped.is_snapped = false; + rail_state->isWindowSnapped = false; } else { /* Restore to previous size or make up one if the window started maximized. */ @@ -1947,7 +1957,8 @@ grab_unsnap_motion(struct weston_pointer_grab *grab) weston_desktop_surface_get_surface(shsurf->desktop_surface); struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; - int cx, cy, dx; + int cx, cy, geometry_offset; + float dx, move_dx; if (!shsurf->snapped.is_snapped) return; @@ -1957,33 +1968,51 @@ grab_unsnap_motion(struct weston_pointer_grab *grab) /* window is no longer in snap state */ shsurf->snapped.is_snapped = false; + rail_state->isWindowSnapped = false; rail_state->showState_requested = RDP_WINDOW_SHOW; shsurf->saved_showstate_valid = false; + geometry_offset = shsurf->snapped.saved_surface_width - + shsurf->snapped.saved_width; + geometry_offset /= 2; + + /* Reposition the window such that the mouse remain within the + * new bound of the window after resize. */ + /* calc based on pointer position based on current (snapped) window size */ + /* move->dx is offset of pointer position from window origin */ + dx = wl_fixed_to_double(move->dx) / surface->width; + dx = fabsf(dx); + /* calc the distance based on unsnapped window size */ + cx = shsurf->snapped.saved_width * dx; + /* add window geometry offset */ + cx += geometry_offset; + /* obtain new window offset from current pointer position */ + cx = wl_fixed_to_int(pointer->x) - cx; + cy = shsurf->view->geometry.y; /* unchanged */ + + /* adjust move->dx based on new position */ + move_dx = wl_fixed_from_int(cx) - pointer->grab_x; + + shell_rdp_debug(shsurf->shell, "%s: restore surface:%p at (%d,%d) size:%dx%d from (%d,%d) size:%dx%d\n", + __func__, surface, + cx, cy, + shsurf->snapped.saved_width, shsurf->snapped.saved_height, + (int)shsurf->view->geometry.x, (int)shsurf->view->geometry.y, + surface->width, surface->height); + shell_rdp_debug(shsurf->shell, "%s: restore surface:%p, geometry_offset:%d, grab_ratio:%f, move_dx:%d->%d\n", + __func__, surface, geometry_offset, dx, + wl_fixed_to_int(move->dx), wl_fixed_to_int(move_dx)); + /* restore original size */ weston_desktop_surface_set_size(shsurf->desktop_surface, shsurf->snapped.saved_width, shsurf->snapped.saved_height); - /* Reposition the window such that the mouse remain within the - * new bound of the window after resize. */ - dx = wl_fixed_to_int(move->dx); - if (abs(dx) < surface->width / 2) { - /* keep left edge pos, resize from right edge */ - cx = shsurf->view->geometry.x; - } else { - /* keep right edge pos, resize from left edge */ - cx = (shsurf->view->geometry.x + surface->width) - shsurf->snapped.saved_surface_width; - } - cy = shsurf->view->geometry.y + wl_fixed_to_int(move->dy); + /* and move to new position reletive to pointer */ weston_view_set_position(shsurf->view, cx, cy); - move->dx = wl_fixed_from_int(cx - wl_fixed_to_int(pointer->x)); - shell_rdp_debug_verbose(shsurf->shell, "%s: restore surface:%p at (%d,%d) (%dx%d), new move_dx:%d\n", - __func__, surface, cx, cy, - shsurf->snapped.saved_width, - shsurf->snapped.saved_height, - wl_fixed_to_int(move->dx)); + /* update move_dx based on new position */ + move->dx = move_dx; } static struct desktop_shell * @@ -2921,6 +2950,7 @@ shell_backend_request_window_restore(struct weston_surface *surface) weston_view_set_position(shsurf->view, shsurf->snapped.saved_x, shsurf->snapped.saved_y); shsurf->snapped.is_snapped = false; + rail_state->isWindowSnapped = false; } else if (shsurf->state.fullscreen) { /* fullscreen is treated as normal (aka restored) state in Windows client, thus there should be not be 'restore' @@ -2976,9 +3006,11 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, struct weston_view *view; struct shell_surface *shsurf = get_shell_surface(surface); struct weston_geometry geometry; + struct weston_surface_rail_state *rail_state = + (struct weston_surface_rail_state *)surface->backend_state; view = get_default_view(surface); - if (!view || !shsurf) + if (!view || !shsurf || !rail_state) return; if (shsurf->shell->is_localmove_pending) { @@ -3021,6 +3053,7 @@ shell_backend_request_window_snap(struct weston_surface *surface, int x, int y, shsurf->snapped.saved_height = geometry.height; } shsurf->snapped.is_snapped = true; + rail_state->isWindowSnapped = true; if (surface->width != width || surface->height != height) { struct weston_desktop_surface *desktop_surface = From 5d53896d1c2d63025a50c19bec49aca6e757ce94 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 10 Apr 2023 09:58:04 -0700 Subject: [PATCH 1636/1642] rdp shell: support restore from maximized by grabbing title bar (#142) * support restore from maximized by grabbing titlebar * adjust debug message --------- Co-authored-by: Hideyuki Nagase --- rdprail-shell/shell.c | 109 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 9 deletions(-) diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 6672e8809..26bd783ca 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -124,6 +124,13 @@ struct shell_surface { struct weston_view *black_view; } fullscreen; + struct { + bool grab_unmaximized; + int32_t saved_surface_width; + int32_t saved_width; + int32_t saved_height; + } maximized; + struct weston_output *fullscreen_output; struct weston_output *output; struct wl_listener output_destroy_listener; @@ -219,7 +226,10 @@ struct shell_seat { static const struct weston_pointer_grab_interface move_grab_interface; +static void set_maximized(struct shell_surface *shsurf, bool maximized); + static void grab_unsnap_motion(struct weston_pointer_grab *grab); +static void grab_unmaximized_motion(struct weston_pointer_grab *grab); static struct desktop_shell * shell_surface_get_shell(struct shell_surface *shsurf); @@ -1263,6 +1273,10 @@ move_grab_motion(struct weston_pointer_grab *grab, (pointer->grab_x != pointer->x || pointer->grab_y != pointer->y)) { grab_unsnap_motion(grab); + } else if (shsurf->state.maximized && + (pointer->grab_x != pointer->x || + pointer->grab_y != pointer->y)) { + grab_unmaximized_motion(grab); } else { constrain_position(move, &cx, &cy); weston_view_set_position(shsurf->view, cx, cy); @@ -1317,8 +1331,9 @@ surface_move(struct shell_surface *shsurf, struct weston_pointer *pointer, return -1; if (shsurf->grabbed || - weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || - weston_desktop_surface_get_maximized(shsurf->desktop_surface)) + weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)/* || + // RAIL shell to allow unmaximize by dragging title bar + weston_desktop_surface_get_maximized(shsurf->desktop_surface) */) return 0; move = malloc(sizeof *move); @@ -1858,16 +1873,17 @@ unset_maximized(struct shell_surface *shsurf) shsurf->snapped.saved_x, shsurf->snapped.saved_y); shsurf->snapped.is_snapped = false; rail_state->isWindowSnapped = false; - } else { - /* Restore to previous size or make up one if the window started maximized. - */ + } else if (!shsurf->maximized.grab_unmaximized) { + /* Restore to previous position or make up one if the window started maximized. + if window is not being grabbed */ if (shsurf->saved_position_valid) weston_view_set_position(shsurf->view, shsurf->saved_x, shsurf->saved_y); else weston_view_set_initial_position(shsurf); - shsurf->saved_position_valid = false; } + shsurf->saved_position_valid = false; + shsurf->maximized.grab_unmaximized = false; if (shsurf->saved_rotation_valid) { wl_list_insert(&shsurf->view->geometry.transformation_list, @@ -1988,18 +2004,17 @@ grab_unsnap_motion(struct weston_pointer_grab *grab) cx += geometry_offset; /* obtain new window offset from current pointer position */ cx = wl_fixed_to_int(pointer->x) - cx; - cy = shsurf->view->geometry.y; /* unchanged */ /* adjust move->dx based on new position */ move_dx = wl_fixed_from_int(cx) - pointer->grab_x; - shell_rdp_debug(shsurf->shell, "%s: restore surface:%p at (%d,%d) size:%dx%d from (%d,%d) size:%dx%d\n", + shell_rdp_debug_verbose(shsurf->shell, "%s: restore surface:%p at (%d,%d) size:%dx%d from (%d,%d) size:%dx%d\n", __func__, surface, cx, cy, shsurf->snapped.saved_width, shsurf->snapped.saved_height, (int)shsurf->view->geometry.x, (int)shsurf->view->geometry.y, surface->width, surface->height); - shell_rdp_debug(shsurf->shell, "%s: restore surface:%p, geometry_offset:%d, grab_ratio:%f, move_dx:%d->%d\n", + shell_rdp_debug_verbose(shsurf->shell, "%s: restore surface:%p, geometry_offset:%d, grab_ratio:%f, move_dx:%d->%d\n", __func__, surface, geometry_offset, dx, wl_fixed_to_int(move->dx), wl_fixed_to_int(move_dx)); @@ -2008,11 +2023,80 @@ grab_unsnap_motion(struct weston_pointer_grab *grab) shsurf->snapped.saved_width, shsurf->snapped.saved_height); + /* update move_dx based on new position */ + move->dx = move_dx; + /* and move to new position reletive to pointer */ + constrain_position(move, &cx, &cy); weston_view_set_position(shsurf->view, cx, cy); +} + +static void +grab_unmaximized_motion(struct weston_pointer_grab *grab) +{ + struct weston_pointer *pointer = grab->pointer; + struct weston_move_grab *move = (struct weston_move_grab *) grab; + struct shell_surface *shsurf = move->base.shsurf; + struct weston_surface *surface = + weston_desktop_surface_get_surface(shsurf->desktop_surface); + const struct weston_xwayland_surface_api *api; + int cx, cy, geometry_offset; + float dx, move_dx; + + if (shsurf->maximized.grab_unmaximized) + return; + + geometry_offset = shsurf->maximized.saved_surface_width - + shsurf->maximized.saved_width; + geometry_offset /= 2; + + /* Reposition the window such that the mouse remain within the + * new bound of the window after resize. */ + /* calc based on pointer position based on current (maximized) window size */ + /* move->dx is offset of pointer position from window origin */ + dx = wl_fixed_to_double(move->dx) / surface->width; + dx = fabsf(dx); + /* calc the distance based on restored window size */ + cx = shsurf->maximized.saved_width * dx; + /* add window geometry offset */ + cx += geometry_offset; + /* obtain new window offset from current pointer position */ + cx = wl_fixed_to_int(pointer->x) - cx; + + /* adjust move->dx based on new position */ + move_dx = wl_fixed_from_int(cx) - pointer->grab_x; + + shell_rdp_debug_verbose(shsurf->shell, "%s: restore surface:%p at (%d,%d) size:%dx%d from (%d,%d) size:%dx%d\n", + __func__, surface, + cx, cy, + shsurf->maximized.saved_width, shsurf->maximized.saved_height, + (int)shsurf->view->geometry.x, (int)shsurf->view->geometry.y, + surface->width, surface->height); + shell_rdp_debug_verbose(shsurf->shell, "%s: restore surface:%p, geometry_offset:%d, grab_ratio:%f, move_dx:%d->%d\n", + __func__, surface, geometry_offset, dx, + wl_fixed_to_int(move->dx), wl_fixed_to_int(move_dx)); + + /* restore from maximized */ + api = shsurf->shell->xwayland_surface_api; + if (!api) { + api = weston_xwayland_surface_get_api(shsurf->shell->compositor); + shsurf->shell->xwayland_surface_api = api; + } + if (api && api->is_xwayland_surface(surface)) { + api->set_maximized(surface, false); + } else { + set_maximized(shsurf, false); + } /* update move_dx based on new position */ move->dx = move_dx; + + /* and move to new position reletive to pointer */ + constrain_position(move, &cx, &cy); + weston_view_set_position(shsurf->view, cx, cy); + + /* don't reset position at next commit */ + shsurf->maximized.grab_unmaximized = true; } static struct desktop_shell * @@ -2820,6 +2904,7 @@ set_maximized(struct shell_surface *shsurf, bool maximized) weston_desktop_surface_get_surface(shsurf->desktop_surface); struct weston_surface_rail_state *rail_state = (struct weston_surface_rail_state *)surface->backend_state; + struct weston_geometry geometry; int32_t width = 0, height = 0; if (!rail_state) @@ -2843,6 +2928,12 @@ set_maximized(struct shell_surface *shsurf, bool maximized) shell_surface_set_output(shsurf, output); get_maximized_size(shsurf, &width, &height); + + /* save current window size */ + geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + shsurf->maximized.saved_surface_width = surface->width; + shsurf->maximized.saved_width = geometry.width; + shsurf->maximized.saved_height = geometry.height; } else { if (shsurf->saved_showstate_valid) rail_state->showState_requested = shsurf->saved_showstate; From f784b4ae3391aa866b759c288113ea12f9b8bc82 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Wed, 12 Apr 2023 10:10:29 -0700 Subject: [PATCH 1637/1642] rdp backend: do not set window margin unless changed (#143) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdprail.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 58ad28d8f..5da545e87 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -2090,7 +2090,7 @@ rdp_rail_update_window(struct weston_surface *surface, /* drop window shadow area */ api->get_window_geometry(surface, &geometry); - if (!rail_state->isWindowSnapped) { + if (is_window_shadow_remoting_disabled(peer_ctx)) { /* calculate window margin from input extents */ if (geometry.x > max(0, surface->input.extents.x1)) window_margin_left = geometry.x - @@ -2301,11 +2301,14 @@ rdp_rail_update_window(struct weston_surface *surface, } if (is_window_shadow_remoting_disabled(peer_ctx)) { - if (rail_state->forceUpdateWindowState || - rail_state->window_margin_left != window_margin_left || - rail_state->window_margin_top != window_margin_top || - rail_state->window_margin_right != window_margin_right || - rail_state->window_margin_bottom != window_margin_bottom) { + /* Due to how mstsc/msrdc works, window margin must not be set + while window is snapped unless they are changed. */ + if ((rail_state->forceUpdateWindowState && + !rail_state->isWindowSnapped) || + rail_state->window_margin_left != window_margin_left || + rail_state->window_margin_top != window_margin_top || + rail_state->window_margin_right != window_margin_right || + rail_state->window_margin_bottom != window_margin_bottom) { /* add resize margin area */ window_order_info.fieldFlags |= WINDOW_ORDER_FIELD_RESIZE_MARGIN_X | WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y; From b5e22bab8dd9a5dd0ef638bbe630bccd64933a09 Mon Sep 17 00:00:00 2001 From: Filippo Argiolas Date: Tue, 2 May 2023 21:02:35 +0200 Subject: [PATCH 1638/1642] Fall back to weston.ini keymap if no mapping is found (#144) * rdp backend: do not force us layout Do not force "us" layout when no xkb mapping is found. Weston will this way fallback to the keymap defined in compositor xkb_names which falls back to "us" if no layout is defined in weston.ini. * rdp backend: reset default keymap if mapping fails Reload default global compositor keymap when no xkb mapping is found for current keyboard layout. This will default to the keymap in weston.ini or to "us" if no keymap is defined there. Should allow to use custom KLC layouts for keymaps that exist in Linux but have no correspondence in Windows (e.g. altgr-intl). This should also partially fix #173. Still need a way to export weston.ini to the user. --- libweston/backend-rdp/rdp.c | 5 ----- libweston/backend-rdp/rdprail.c | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index d579f4739..02a3d763d 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -1007,11 +1007,6 @@ convert_rdp_keyboard_to_xkb_rule_names( xkbRuleNames->layout = "us"; xkbRuleNames->variant = 0; } - /* when no layout, default to "us" */ - if (!xkbRuleNames->layout) { - xkbRuleNames->layout = "us"; - xkbRuleNames->variant = 0; - } weston_log("%s: matching model=%s layout=%s variant=%s options=%s\n", __FUNCTION__, xkbRuleNames->model, xkbRuleNames->layout, xkbRuleNames->variant, xkbRuleNames->options); diff --git a/libweston/backend-rdp/rdprail.c b/libweston/backend-rdp/rdprail.c index 5da545e87..39e002938 100644 --- a/libweston/backend-rdp/rdprail.c +++ b/libweston/backend-rdp/rdprail.c @@ -1001,6 +1001,14 @@ rail_client_LanguageImeInfo_callback(bool freeOnly, void *arg) __func__, new_keyboard_layout, settings->KeyboardType, settings->KeyboardSubType); + + rdp_debug_error(b, "%s: Resetting default keymap\n", __func__); + keymap = xkb_keymap_new_from_names(peer_ctx->item.seat->compositor->xkb_context, + &peer_ctx->item.seat->compositor->xkb_names, + 0); + weston_seat_update_keymap(peer_ctx->item.seat, keymap); + xkb_keymap_unref(keymap); + settings->KeyboardLayout = new_keyboard_layout; } } } From 75aea1a4ab2c4a312adb9014c8caeadd08fddbf4 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 8 May 2023 15:08:59 -0700 Subject: [PATCH 1639/1642] rdp shell: SVG rendering at system-distro name space (#145) * SVG rendering at system-distro name space * move fclose call --------- Co-authored-by: Hideyuki Nagase --- rdprail-shell/app-list.c | 45 ++++++++++++++++--- rdprail-shell/img-load.c | 96 +++++++++++++++++++++++++++++++++------- rdprail-shell/shell.c | 5 ++- rdprail-shell/shell.h | 3 +- 4 files changed, 124 insertions(+), 25 deletions(-) diff --git a/rdprail-shell/app-list.c b/rdprail-shell/app-list.c index 22c008a2f..7058ba29e 100644 --- a/rdprail-shell/app-list.c +++ b/rdprail-shell/app-list.c @@ -49,6 +49,7 @@ #include "shell.h" #include "shared/helpers.h" +#include "shared/image-loader.h" #if HAVE_GLIB #include @@ -118,6 +119,7 @@ struct app_entry { char *working_dir; char *icon_name; char *icon_file; + bool is_icon_file_svg; pixman_image_t* icon_image; uint32_t icon_retry_count; }; @@ -285,10 +287,12 @@ find_icon_file(struct app_entry *entry) if (is_file_exist(buf)) goto Found; +#ifdef HAVE_LIBRSVG2 /* if not found, try again with .svg extension appended */ copy_string(&buf[len], sizeof buf - len, ".svg"); if (is_file_exist(buf)) goto Found; +#endif // HAVE_LIBRSVG2 } } @@ -301,8 +305,16 @@ find_icon_file(struct app_entry *entry) return false; Found: + len = strlen(icon_file); + if (len > 4) { + /* TODO: file contents should be verified */ + char *ext = &icon_file[len - 4]; + entry->is_icon_file_svg = strcasecmp(ext, ".svg") == 0; + } + if (entry->icon_retry_count) context->icon_retry_count--; + entry->icon_file = strdup(icon_file); return (entry->icon_file != NULL); } @@ -451,12 +463,22 @@ retry_find_icon_file(struct desktop_shell *shell) entry->icon_name && entry->icon_file == NULL && entry->icon_retry_count < MAX_ICON_RETRY_COUNT) { + void *data = NULL; + uint32_t data_len = 0; shell_rdp_debug(entry->shell, "%s: icon (%s) retry count (%d)\n", __func__, entry->icon_name, entry->icon_retry_count); attach_app_list_namespace(shell); - if (find_icon_file(entry)) - entry->icon_image = load_icon_image(shell, entry->icon_file); + if (find_icon_file(entry)) { + if (entry->is_icon_file_svg) + data = load_file_svg(shell, entry->icon_file, &data_len); + else + entry->icon_image = load_image(entry->icon_file); + } detach_app_list_namespace(shell); + if (entry->is_icon_file_svg && data) + entry->icon_image = load_image_svg(shell, data, data_len, entry->icon_file); + if (data) + free(data); if (entry->icon_image) send_app_entry(shell, *cur, entry, false, false, false, false, false, false); } @@ -580,10 +602,20 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr entry->working_dir = g_key_file_get_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_PATH, NULL); entry->icon_name = g_key_file_get_locale_string(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, lang_id, NULL); if (entry->icon_name) { + void *data = NULL; + uint32_t data_len = 0; attach_app_list_namespace(shell); - if (find_icon_file(entry)) - entry->icon_image = load_icon_image(shell, entry->icon_file); + if (find_icon_file(entry)) { + if (entry->is_icon_file_svg) + data = load_file_svg(shell, entry->icon_file, &data_len); + else + entry->icon_image = load_image(entry->icon_file); + } detach_app_list_namespace(shell); + if (entry->is_icon_file_svg && data) + entry->icon_image = load_image_svg(shell, data, data_len, entry->icon_file); + if (data) + free(data); } g_key_file_free(key_file); @@ -593,6 +625,7 @@ update_app_entry(struct desktop_shell *shell, char *file, struct app_entry *entr shell_rdp_debug(shell, " TryExec:%s\n", entry->try_exec); shell_rdp_debug(shell, " WorkingDir:%s\n", entry->working_dir); shell_rdp_debug(shell, " Icon name:%s\n", entry->icon_name); + shell_rdp_debug(shell, " Icon SVG :%d\n", entry->is_icon_file_svg); shell_rdp_debug(shell, " Icon file:%s\n", entry->icon_file); shell_rdp_debug(shell, " Icon image:%p\n", entry->icon_image); @@ -1505,11 +1538,11 @@ void app_list_init(struct desktop_shell *shell) /* load default icon */ iconpath = getenv("WSL2_DEFAULT_APP_ICON"); if (iconpath && (strcmp(iconpath, "disabled") != 0)) - context->default_icon = load_icon_image(shell, iconpath); + context->default_icon = load_image(iconpath); iconpath = getenv("WSL2_DEFAULT_APP_OVERLAY_ICON"); if (iconpath && (strcmp(iconpath, "disabled") != 0)) - context->default_overlay_icon = load_icon_image(shell, iconpath); + context->default_overlay_icon = load_image(iconpath); /* preblend default icon with overlay icon if requested */ if (shell->is_blend_overlay_icon_app_list && diff --git a/rdprail-shell/img-load.c b/rdprail-shell/img-load.c index 359779667..14c3efec2 100644 --- a/rdprail-shell/img-load.c +++ b/rdprail-shell/img-load.c @@ -29,6 +29,7 @@ #include #include #include +#include #ifdef HAVE_LIBRSVG2 #include @@ -36,27 +37,26 @@ #include "shell.h" -#include "shared/image-loader.h" - #ifdef HAVE_LIBRSVG2 -static pixman_image_t * -load_svg(struct desktop_shell *shell, const char *filename) +pixman_image_t * +load_image_svg(struct desktop_shell *shell, const void *data, uint32_t data_len, const char *filename) { GError *error = NULL; RsvgHandle *rsvg = NULL; cairo_surface_t *surface = NULL; cairo_t *cr = NULL; pixman_image_t *image = NULL; + cairo_status_t status; /* DEPRECATED: g_type_init(); rsvg_init(); */ /* rsvg_init has been deprecated since version 2.36 and should not be used in newly-written code. Use g_type_init() */ /* g_type_init has been deprecated since version 2.36 and should not be used in newly-written code. the type system is now initialised automatically. */ - rsvg = rsvg_handle_new_from_file(filename, &error); + rsvg = rsvg_handle_new_from_data(data, data_len, &error); if (!rsvg) { - shell_rdp_debug(shell, "%s: rsvg_handle_new_from_file failed %s\n", - __func__, filename); + shell_rdp_debug(shell, "%s: rsvg_handle_new_from_file failed %s %s\n", + __func__, filename, error ? error->message : "(no error message)"); goto Exit; } @@ -102,6 +102,12 @@ load_svg(struct desktop_shell *shell, const char *filename) pixman_image_ref(image); Exit: + status = cairo_status(cr); + if (status != CAIRO_STATUS_SUCCESS) { + shell_rdp_debug(shell, "%s: cairo status error %s\n", + __func__, cairo_status_to_string(status)); + } + if (cr) cairo_destroy(cr); @@ -123,18 +129,76 @@ load_svg(struct desktop_shell *shell, const char *filename) /* rsvg_term has been deprecated since version 2.36 and should not be used in newly-written code. There is no need to de-initialize librsvg. */ + if (error) + g_error_free(error); + return image; } -#endif // HAVE_LIBRSVG2 +void * +load_file_svg(struct desktop_shell *shell, const char *filename, uint32_t *data_len) +{ + FILE *fp; + void *data = NULL; + int len, ret; + + fp = fopen(filename, "rb"); + if (!fp) { + shell_rdp_debug(shell, "%s: fopen failed %s %s\n", + __func__, filename, strerror(errno)); + goto Fail; + } + + if (fseek(fp, 0, SEEK_END) != 0) { + shell_rdp_debug(shell, "%s: fseek failed %s %s\n", + __func__, filename, strerror(errno)); + goto Fail; + } + len = ftell(fp); + rewind(fp); + + data = malloc(len); + if (!data) { + shell_rdp_debug(shell, "%s: malloc(%d) failed %s %s\n", + __func__, len, filename, strerror(errno)); + goto Fail; + } + + ret = fread(data, 1, len, fp); + if (ret != len) { + shell_rdp_debug(shell, "%s: fread failed, expect %d but returned %d %s %s\n", + __func__, len, ret, filename, strerror(errno)); + goto Fail; + } + + goto Exit; + +Fail: + if (data) + free(data); + + data = NULL; + len = 0; + +Exit: + if (fp) + fclose(fp); + + *data_len = len; + + return data; +} +#else pixman_image_t * -load_icon_image(struct desktop_shell *shell, const char *filename) +load_image_svg(struct desktop_shell *, const void *, uint32_t) { - pixman_image_t *image; - image = load_image(filename); -#ifdef HAVE_LIBRSVG2 - if (!image) - image = load_svg(shell, filename); -#endif // HAVE_LIBRSVG2 - return image; + return NULL; } + +void * +load_file_svg(struct desktop_shell *, const char *, uint32_t *data_len) +{ + *data_len = 0; + return NULL; +} +#endif // HAVE_LIBRSVG2 diff --git a/rdprail-shell/shell.c b/rdprail-shell/shell.c index 26bd783ca..b8410cdef 100644 --- a/rdprail-shell/shell.c +++ b/rdprail-shell/shell.c @@ -44,6 +44,7 @@ #include #include "shared/helpers.h" #include "shared/timespec-util.h" +#include "shared/image-loader.h" #include #include #include @@ -802,14 +803,14 @@ shell_configuration(struct desktop_shell *shell) /* default icon path is provided from WSL via enviromment variable */ s = getenv("WSL2_DEFAULT_APP_ICON"); if (s && (strcmp(s, "disabled") != 0)) - shell->image_default_app_icon = load_icon_image(shell, s); + shell->image_default_app_icon = load_image(s); shell_rdp_debug(shell, "RDPRAIL-shell: WSL2_DEFAULT_APP_ICON:%s (loaded:%s)\n", s, shell->image_default_app_icon ? "yes" : "no"); /* default overlay icon path is provided from WSL via enviromment variable */ s = getenv("WSL2_DEFAULT_APP_OVERLAY_ICON"); if (s && (strcmp(s, "disabled") != 0)) - shell->image_default_app_overlay_icon = load_icon_image(shell, s); + shell->image_default_app_overlay_icon = load_image(s); shell_rdp_debug(shell, "RDPRAIL-shell: WSL2_DEFAULT_APP_OVERLAY_ICON:%s (loaded:%s)\n", s, shell->image_default_app_overlay_icon ? "yes" : "no"); diff --git a/rdprail-shell/shell.h b/rdprail-shell/shell.h index aa98f126e..7df23c0de 100644 --- a/rdprail-shell/shell.h +++ b/rdprail-shell/shell.h @@ -206,4 +206,5 @@ void app_list_stop_backend_update(struct desktop_shell *shell); void app_list_find_image_name(struct desktop_shell *shell, pid_t pid, char *image_name, size_t image_name_size, bool is_wayland); void app_list_associate_window_app_id(struct desktop_shell *shell, pid_t pid, char *app_id, uint32_t window_id); // img-load.c -pixman_image_t *load_icon_image(struct desktop_shell *shell, const char *filename); +pixman_image_t *load_image_svg(struct desktop_shell *shell, const void *data, uint32_t data_len, const char *filename); +void *load_file_svg(struct desktop_shell *shell, const char *filename, uint32_t *data_len); From 3525068264858668bc9162c318c173c5d95c21bd Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Mon, 12 Jun 2023 12:17:33 -0700 Subject: [PATCH 1640/1642] support Lithuanian input layout (#146) Co-authored-by: Hideyuki Nagase --- libweston/backend-rdp/rdp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 02a3d763d..c14be2f27 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -904,6 +904,7 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_SLOVENIAN, "si", 0}, {KBD_ESTONIAN, "ee", 0}, {KBD_LATVIAN, "lv", 0}, + {KBD_LITHUANIAN, "lt", 0}, {KBD_LITHUANIAN_IBM, "lt", "ibm"}, // 0x429 (KBD_FARSI) is for Persian(Iran) // TODO: define exact match with Windows layout in Xkb. From 00104c4555269953b9e531ce8a097802842950bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Boudreault?= Date: Fri, 23 Jun 2023 09:41:02 -0400 Subject: [PATCH 1641/1642] rdp-backend: Fix incorrect keyboard variant for Canadian French (#147) * Fix incorrect keyboard variant for Canadian French * Revert "Fix incorrect keyboard variant for Canadian French" This reverts commit 6c70afdfccb4764cc835acc7ca9352db3c85941a. * Fixes the correct entry --- libweston/backend-rdp/rdp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index c14be2f27..de9408655 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -957,7 +957,7 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_INUKTITUT_LATIN, "ca", "ike"}, {KBD_CANADIAN_FRENCH_LEGACY, "ca", "fr-legacy"}, {KBD_SERBIAN_CYRILLIC, "rs", 0}, - {KBD_CANADIAN_FRENCH, "ca", "fr-legacy"}, + {KBD_CANADIAN_FRENCH, "ca", 0}, {KBD_SWISS_FRENCH, "ch", "fr"}, {KBD_BOSNIAN, "ba", 0}, {KBD_IRISH, 0, 0}, From 70cbfad51df2b4cd62fd5a6f98f596a787250c66 Mon Sep 17 00:00:00 2001 From: Hideyuki Nagase Date: Tue, 25 Jul 2023 13:36:20 -0700 Subject: [PATCH 1642/1642] support french standard azert and bepo keyboard layout --- libweston/backend-rdp/rdp.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index de9408655..8c3c0170c 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -847,6 +847,14 @@ struct rdp_to_xkb_keyboard_layout { /* 0x50429 is for Dari (Afghanistan) */ #define KBD_PERSIAN 0x50429 #endif +#ifndef KBD_FRENCH_STANDARD_BEPO +/* 0x2040c is for French (Standard, Bepo) */ +#define KBD_FRENCH_STANDARD_BEPO 0x2040c +#endif +#ifndef KBD_FRENCH_STANDARD_AZERTY +/* 0x1040c is for French (Standard, AZERTY) */ +#define KBD_FRENCH_STANDARD_AZERTY 0x1040c +#endif static const struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { @@ -871,6 +879,8 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_SPANISH_VARIATION, "es", "nodeadkeys"}, {KBD_FINNISH, "fi", 0}, {KBD_FRENCH, "fr", 0}, + {KBD_FRENCH_STANDARD_BEPO, "fr", "bepo"}, + {KBD_FRENCH_STANDARD_AZERTY, "fr", "afnor"}, {KBD_HEBREW, "il", 0}, {KBD_HEBREW_STANDARD, "il", "basic"}, {KBD_HUNGARIAN, "hu", 0},

    =!T6s8P@0B0>UQedjg3egSyQ@1BB z_X$V#8Om%}1tCm=iRktgvO`WErPtA5k~11Kr_Lx+=5_j#9Z?Tn8|k^O)nJqH;CUy1 zX3>le3>U=a!IQ&qIp2xjx!$0|!}7u(!KlLVr3r4CZbeYAv!Lfe1G4&PXm>v@{FH^}8%#~Z+hE5qsh+;cr!z0pt|AoW&iJU3? zFNAWXn-xOv?eTB?VDjM%yZV&ERN^bD{w;5Plv5{h$7fEui4)lllt`RJ9b@Oa^#&A} z2hKIPC$D)RN1;`>b9mL@MTu2hnt5i`(~h4@*Lho)5Fb>A?EVk8@%MVkNhY>G1T!)n zuw|26_S)yQ=VUFrVrVrXd645&v+=;rAhtjqnHtj^COFPKGC*H2C+hDV$eqJ)%$GJcs>c1X;sG+#@HtdV|? z4va8jLLb374ua(p=NHPVJYM|Xwc95slH-xd9(Sa3nHeS%m=9we@(9lDZcwvQpI(k( zr5Zmy%dQJqlulR6(2ApiH_A051{HpjnWP=9SNQat*(o1|Cydq5t35m&9$ECQno~zR zw$W>=Aj&WgJ4&h0KfS>VbV*1#H)YQ>QNTP#6A4s65w55$D=_C01xq0<-^1_}#dLbV zs*Eb^=C*~zDmI^-zlC!KV*h3yC5qga`T8xR_e}G-(H=p|NtL34B|!*66Y&9R>Hs;(kk1n6NA!66iA*6)}fzMzQc6|5?boGKlDFIe9G*qoGt9m(#X8Y~2N zdfkH5AmvpKjrJv~BG5tGA9mXwB~~Bl#WbsW#9xN>olB6b-G^A=HCUW>NBtALTqzY>Z-z?_n8ljg^a4kBX~)<{D=@ z`-{fLKTRKX3(-Qpw|JW)2FZ|P33)wvq!n{-!&_lGfFkzP`j<+N?rC!@{Ab7bGd(#u zkuDwyC|7S;Nmt~&$|t8kdv9~YdxPY^QL~?t)mA*EMk7;XuvyC0Ci73*@ayb-*UPdb zL^1*MgA-Vi9Lth4JUJo2!n+l2T!0$NIu85rXhP^puz2v+s3KKKxHU@T=c)3D^6~QV zZhxJ977YEiwZaP_kO9ha>u}wv2V)BEfBKn6l8w?EA-e(j}Lkpx(^9=lPud3E~FIW zydWmlLezi1!JZ2iaG9WD>bq;VWDv~8AH{x%b1G6>T$;d$WM06=+3m>qX-MH6XDOL3 zs;qUFWQ8%}%NUIJg#!_;bW9kl{?G*lrPNbtZu#a#HypsTAw!aTPv%YL&vC&A^0EHP zf+AAwd-t&NF-Fmdku+U|sJXq~)@w8wNF1f3)4NwgcSb!{aO(Hg5YB&VS3!{)?x?~s z3@cF0D!odi5W5hjVB{gvH9)h$1C>H)QldXpt-f z%S-wg0yK143?ZUOch}Gbj7{=*5(?G{I90A?ehH&6gpcoYFhmoQ_*P2UhruLVC&SAXPy>Wk7`H$Lp*&cY1B5+NE6mL?~CMv%NCwF+zKAJxkoKhzZGh z%V8Rc-_*GaFEFW)*+P)O6v8n+=)LRS{oL!uMf(kM^Uksrw##NMiQJR^%enT#Qu^^R z7Q*wGFFmBQRhD_03J*>egr2vkUDd**ry;-0y~M{#Z_Qsk8ZJ@so<4`%Pg$WI<8=T!)32Xb_^P_!aD;Q2cOE0>E`*{V}g8h&ia+;y1nI!@D@*@=pYedknH<%rAtE0-c(V8Ky{1sD2o8cWyCV1>ghhr$L zqREK^{ZstdR=TipI%hYBj0&m_<{MD1PoB>i8qHBqPLaQI#mm@ygH`uLDRazF?FeRu z{UY&*Z9Sq(6;12C`u%RT{vR8SvP$|WWmrw9B{MyXImqFJ%u8g|EI-441puA2Alb7e zCngYWpADNF6Mf!S=95G8A+1!hcJ2wRh4pDXo;Atq%Cm^KZ57MJ6GkbwqC;nrQ?uUFey$Ufv3%5CTsqph-{ z+L4jSFzm};uT{7uzOh#NXboe>SNrs7H5Zv_EE6Fpm+azBbQ(GpL^4JV$@GYayDK&R z>#Em+=RBxLNHMV(sZo%((2o7Z-OjW2SAbIoztwYKBSBaH3%HVR6BNk@zPytPX5wG>GWX}Ju$mGpKB=r7N z4Y8VvrZUti^ymIfTm5Wx9CPupHak=`vf)aGAp{SP@JMr z?v%i&4=(JuHE8YO(|~WYE`RcWkTfH*{VWa*VVZJdptiNb;d~3)zPiw#_`$3_X&!Sz z3l2LP?=s|?51Xk9In|g!k^U~`#G2Z#h0ELTE5Y^r;SFWn!#yt=*^zna5LuY`eoUNBdw0copW_sNmw_wzuEGgc zADVjBtevUIVRp4NUWtFnI3?=PTDpQkjp~=xB>Pjkml4`)YNSz3yeinl3c6}x`o1S# zS>6qlm=&-X&?gzcx?>^PWPUT~)&`#9)Qlk~^3-HkY6Pbh$Ct?0lg8pooC#9GOcBQ6 ze|PEm;GtLiQ9bI*F%0wuB9(YcP-CrM#a54axn-4-t7JLXZpt~yGs-ouJri_w7obn~ zZ!(Ko_>@ejBx7QAry^yUxrFue&Z`)!zeHsYH&m$gf(FDY{fQNFdi<*I#fml3gA2jk z{>>03VOdKpJ3?aeTkuMdw>xzJMPRXE&JNzJGR_VAx^_jI0Q=H^VT0IvG$mr6;t~9M z=B!?n=ozsFtGPVY;+GZZx>}FdA1^k>U#%*%Vw0LK#6-WwuSZP%EC2wT&kl!0XDdPi z-^vf$7-S1LE@TA^XsqU4v&_6lX7O?d>1E4* zpK-{={g3Z?85BJdPg4FT(~{tTca$9W=3Aa~O-@!^^phQdt=bO$M;Xei`8HadWD7Z9 zH?5eMSX-3`ct^^IPjTR+=*YPcP&U>b*F&vtaCJ9E>cUT-2Cb4uO|_VdtEh{NvH88* zGwJ20TU!3F@-{${$C$@sLlWY literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-90-00.png b/tests/reference/output_2-90_buffer_1-90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..a26f923841a132dc7ef4408e8502bf5ef8839929 GIT binary patch literal 14482 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED-00.png new file mode 100644 index 0000000000000000000000000000000000000000..95730615ed7bf8cf10b6f48af9118e230e4c50c6 GIT binary patch literal 14336 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 sdmK4#=Ln+Px# literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED_180-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED_180-00.png new file mode 100644 index 0000000000000000000000000000000000000000..ce944d91e5b695476675ea370b295d64b8ede337 GIT binary patch literal 14762 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED_270-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED_270-00.png new file mode 100644 index 0000000000000000000000000000000000000000..6ade0281ebb056d1db7095c9feba961d0796e85b GIT binary patch literal 14394 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_1-FLIPPED_90-00.png b/tests/reference/output_2-90_buffer_1-FLIPPED_90-00.png new file mode 100644 index 0000000000000000000000000000000000000000..f40570dc51ff377260e279da8d4a3e1651a80cd3 GIT binary patch literal 14446 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 literal 0 HcmV?d00001 diff --git a/tests/reference/output_2-90_buffer_2-NORMAL-00.png b/tests/reference/output_2-90_buffer_2-NORMAL-00.png new file mode 100644 index 0000000000000000000000000000000000000000..53fdb7b5e6b81927fbf8248f229a97f667a8e54e GIT binary patch literal 5288 zcmcgwXH-*Lw@m_w7o;di5k!&#HRZ11V2w&$8#0gmLH=%-& zutpeq84o>ZPj8sHxc=j-Tsja(T7UTGUfs@eJ@QVG6;RH-8=x0`E@>loU|;~i$RPH z9ZmE@(lN3EVzu^_Kfrd#*EH%lK_ZxNq1WBkw`>+VZ27GOwA-J=DBg7?rBWuZYjr&~{2o-#D!F%YD*Z8acW$*SI#SkWhX>p*yBfKl)>lsG78+(m^6kseKbHg{ z?#|R4NKU)ZssCIauSb544OsE9ww$oWlP>h)7`4cZ?V?b-$!tB`B zpj*}`Uu#n*_jo@E? zn%=$vKPK$(3%N&gVTg*O6SQFfami9L49)%QmcEPRTmyo$b~W|Xv) ze8)}}->XfS&}-=7MPd54svBu=e$YfUiZ_pHMX~+9+08FYf>mIZCIX+4t@}jLq|dv$ zj>KS8{HkYvK4sCbclp=1Tn~6^=HW)T{0>=*3pn?<9ht@wfmABicelbEG%Tbzz1#&{nWLN%6#MOh7 zap9C6fSLte!%QP_u{p8zf?vlr zB(1JLztl~XX#MV9Z+)*!|9TLK7*C-{KpDCRzxHhNfZ1s@`WxN-tfiPaY2FtD%qSfE_QDDb$-jsweO{^LV(6&aA}SesLo4e5a22keZR^0MFEa6_93 z&Cw1~fUw78^%K5CbqcOcu}ZJ(Ytf|U{zhJpO>`+3 zlHzGnzv69b)bwvf&}IOo(ip%7PO0B=qpU9)pad5M$zzx9wqSB{K0%7CweWZLA#$mmrk5sf(zvy`deL%E<39R|AF`8b;uTZiKOJl;1)9 z4rFQ5*2KtewC>`1|5unzTe-sQr@kO=qGLg4p{jFmmUM2Qa{)&)YDJMQ1)lJSQt3b9 z2Tv_jUXnP#kg(ueCy$#a8X7BtgvNeGG?xHAC#t{MxXl!fO~6%0%)F$vdyFu&ZpI1m zr4=sM2##o<))xN05h3Za{bzo}N~C_J8}H^^8pU~tEWaVqXAt1+<#h=PHqTT|BA-1I z?1=*my*Rg6l@GoONr|Wh-}-udEXJ0uxU@8;n-o`If=VcH=N3G`$lcXD(wHGJXn;2Y z7604Tw>Gc6BvI7#oP{lO`T@RH_2tT$<0>u=0fI?AN=T~4Zhm}36~{f#91$ui_jr%g zkV<2G36MMn#R0pLr57F)6+Wv9Sh5*=pLM_YE4DM`{|3`tJhHml%9a@PxsWD